From 2101a607ef5a3256e709f76c385b670dc4271cf9 Mon Sep 17 00:00:00 2001 From: Dusan Morhac <55763425+dudo50@users.noreply.github.com> Date: Mon, 7 Oct 2024 00:05:26 +0200 Subject: [PATCH] new stuff --- templates/parachain/pallets/xcnft/src/lib.rs | 224 ++++++++++++++++-- .../parachain/runtime/src/configs/mod.rs | 3 +- 2 files changed, 203 insertions(+), 24 deletions(-) diff --git a/templates/parachain/pallets/xcnft/src/lib.rs b/templates/parachain/pallets/xcnft/src/lib.rs index 412eece6e918..807e7bbe1d4b 100644 --- a/templates/parachain/pallets/xcnft/src/lib.rs +++ b/templates/parachain/pallets/xcnft/src/lib.rs @@ -72,15 +72,14 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_runtime::traits::{CheckedAdd, One}; - use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*, DefaultNoBound, sp_runtime::traits::Hash, - traits::{Currency, LockableCurrency, ReservableCurrency}}; + use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*, sp_runtime::traits::Hash, traits::{Currency, LockableCurrency, ReservableCurrency}, DefaultNoBound}; use frame_support::traits::PalletInfo; use codec::Encode; use cumulus_primitives_core::{ParaId}; use scale_info::prelude::vec; use sp_std::prelude::*; use xcm::latest::prelude::*; - use pallet_nfts::{AttributeNamespace, Call as NftsCall, ItemDetails}; + use pallet_nfts::{AttributeNamespace, Call as NftsCall, ItemDetails, ItemMetadata, CollectionDetails}; use core::marker::PhantomData; pub type BalanceOf = @@ -93,6 +92,7 @@ pub mod pallet { >::CollectionId, >; + pub type ParachainID = ParaId; /// Configure the pallet by specifying the parameters and types on which it depends. @@ -117,8 +117,6 @@ pub mod pallet { /// Specifies how much different owners can be in a collection - used in voting process type MaxOwners: Get; - /// Max amount of aye and nay - type MaxVotes: Get; } #[pallet::pallet] @@ -148,6 +146,9 @@ pub mod pallet { pub struct Proposal, I: 'static = ()> { proposal_id: u64, collection_id: T::CollectionId, + proposed_collection_owner: T::AccountId, + proposed_destination_para: ParachainID, + proposed_destination_config: CollectionConfigFor, owners: BoundedVec, number_of_votes: Votes, end_time: BlockNumberFor, @@ -232,14 +233,16 @@ pub mod pallet { destination: ParaId, }, - /// Event emitted when cross-chain transfer fails + /// Event emitted when there are different NFT owners in the collection CollectionTransferProposalCreated { proposal_id: u64, collection_id: T::CollectionId, - owner: T::AccountId, + proposer: T::AccountId, + proposed_collection_owner: AccountIdLookupOf, destination: ParaId, }, + /// Event emitted when a collection and its NFTs are transferred cross-chain CollectionAndNFTsTransferred { origin_collection_id: T::CollectionId, nft_ids: Vec, @@ -247,16 +250,12 @@ pub mod pallet { to_address: AccountIdLookupOf, }, + /// Event emitted when a vote is registered CrossChainPropoposalVoteRegistered{ proposal_id: u64, voter: T::AccountId, vote: Vote, }, - - TestEvent{ - proposal: T::CollectionId, - collection: T::CollectionId, - } } /// @@ -267,6 +266,9 @@ pub mod pallet { ProposalAlreadyExists, NotCollectionOwner, ProposalExpired, + ProposalStillActive, + ProposalDoesNotExist, + ProposalDidNotPass, AlreadyVotedThis, MaxOwnersReached, NotNFTOwner, @@ -275,10 +277,7 @@ pub mod pallet { //#[pallet::hooks] //impl Hooks> for Pallet {} - /// Dispatchable functions allows users to interact with the pallet and invoke state changes. - /// These functions materialize as "extrinsics", which are often compared to transactions. - /// Dispatchable functions must be annotated with a weight and must return a DispatchResult. - /// + #[pallet::call] impl, I: 'static> Pallet { @@ -332,6 +331,17 @@ pub mod pallet { //Burning the collection pallet_nfts::Collection::::remove(&origin_collection); + pallet_nfts::CollectionMetadataOf::::remove(&origin_collection); + + pallet_nfts::CollectionConfigOf::::remove(&origin_collection); + + pallet_nfts::CollectionRoleOf::::remove(&origin_collection, who.clone()); + + pallet_nfts::CollectionAccount::::remove(who.clone(), &origin_collection); + + // Unreserve the funds + T::Currency::unreserve(&who.clone(), >::CollectionDeposit::get()); + // Emit an event. Self::deposit_event(Event::CollectionTransferred { origin_collection_id: origin_collection, @@ -398,6 +408,9 @@ pub mod pallet { let proposal = Proposal:: { proposal_id: proposal_id, collection_id: origin_collection, + proposed_collection_owner: T::Lookup::lookup(destination_account.clone())?, //Converting back to AccountId, if the proposal gets accepted, then converting it back to AccountIdLookup + proposed_destination_config: config, + proposed_destination_para: destination_para, owners: different_owners, number_of_votes: Votes { aye: BoundedVec::new(), @@ -411,9 +424,12 @@ pub mod pallet { Self::deposit_event(Event::CollectionTransferProposalCreated { proposal_id: proposal_id, collection_id: origin_collection, - owner: who.clone(), + proposer: who.clone(), + proposed_collection_owner: destination_account.clone(), destination: destination_para, }); + + return Ok(().into()); } } } @@ -462,6 +478,17 @@ pub mod pallet { //Burning the collection pallet_nfts::Collection::::remove(&origin_collection); + pallet_nfts::CollectionMetadataOf::::remove(&origin_collection); + + pallet_nfts::CollectionConfigOf::::remove(&origin_collection); + + pallet_nfts::CollectionRoleOf::::remove(&origin_collection, who.clone()); + + pallet_nfts::CollectionAccount::::remove(who.clone(), &origin_collection); + + // Unreserve the funds + T::Currency::unreserve(&who.clone(), >::CollectionDeposit::get()); + // Emit an event. Self::deposit_event(Event::CollectionAndNFTsTransferred { origin_collection_id: origin_collection, @@ -482,15 +509,14 @@ pub mod pallet { Ok(().into()) } - /// An example dispatchable that takes a singles value as a parameter, writes the value to - /// storage and emits an event. This function must be dispatched by a signed extrinsic. + #[pallet::call_index(1)] #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn collectionXtransferVote(origin: OriginFor, proposal_id: u64, actual_vote: Vote) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let proposal = CrossChainProposals::::get(proposal_id); - ensure!(proposal.is_some(), Error::::ProposalAlreadyExists); // Check before unwrapping + ensure!(proposal.is_some(), Error::::ProposalDoesNotExist); // Check before unwrapping // Safely unwrap the proposal once let mut unwrapped_proposal = proposal.unwrap(); @@ -505,8 +531,7 @@ pub mod pallet { // If proposal did not pass (Less than 50% of votes are aye) // Remove proposal from proposals to free up storage space let number_of_votes = &unwrapped_proposal.number_of_votes.aye.len() + &unwrapped_proposal.number_of_votes.nay.len(); - if unwrapped_proposal.number_of_votes.aye.len() < number_of_votes / 2 { - CrossChainProposals::::remove(proposal_id); + if (unwrapped_proposal.number_of_votes.aye.len() < number_of_votes / 2 || unwrapped_proposal.number_of_votes.aye.len() == 0 && unwrapped_proposal.number_of_votes.nay.len() == 0) { return Err(Error::::ProposalExpired.into()); } return Err(Error::::ProposalExpired.into()); @@ -547,9 +572,164 @@ pub mod pallet { voter: who.clone(), vote: actual_vote, }); + + //Convert accountid to accountidlookup + Ok(().into()) } + #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + pub fn collectionXtransferInitiate(origin: OriginFor, proposal_id: u64) -> DispatchResultWithPostInfo{ + let who = ensure_signed(origin.clone())?; + + let proposal = CrossChainProposals::::get(proposal_id); + + // Check if proposal exists + ensure!(proposal.is_some(), Error::::ProposalDoesNotExist); + //Check if owner of the collection is the one who initiated the transfer + + let proposal = proposal.as_ref().unwrap(); // Borrow the proposal + + let owner = pallet_nfts::Pallet::::collection_owner(proposal.collection_id.clone()).ok_or(Error::::CollectionDoesNotExist)?; + ensure!(owner == who.clone(), Error::::NotCollectionOwner); + + // Check if the proposal is active or not + let block_n: BlockNumberFor = frame_system::Pallet::::block_number(); + if block_n < proposal.end_time { + return Err(Error::::ProposalStillActive.into()); + } + + // Check if the proposal passed + let number_of_votes = proposal.number_of_votes.aye.len() + proposal.number_of_votes.nay.len(); + if (proposal.number_of_votes.aye.len() < number_of_votes / 2 || proposal.number_of_votes.aye.len() == 0 && proposal.number_of_votes.nay.len() == 0) { + // If proposal did not pass (Less than 50% of votes are aye) + return Err(Error::::ProposalDidNotPass.into()); + } + else if proposal.number_of_votes.aye.len() >= number_of_votes / 2 { + // Transfer the collection to the destination parachain + // (Implementation for transfer goes here) + + //Get the collection metadata + let mut collection_metadata; + if pallet_nfts::CollectionMetadataOf::::contains_key(proposal.collection_id.clone()){ + collection_metadata = pallet_nfts::CollectionMetadataOf::::get(proposal.collection_id.clone()).unwrap(); + } + //Get NFT metadata + let mut nft_metadata = Vec::new(); + + // Iterate through all items in the collection to get item ids + let mut items = Vec::new(); + + for (item_id, _item_details) in pallet_nfts::Item::::iter_prefix(proposal.collection_id.clone()) { + // Process or collect the item details + // item_id is the ItemId, and item_details contains the item's details + //Store items in an array + items.push(item_id); + } + + if items.is_empty() { + //If we have no items meanwhile we might as well just transfer the collection through regular function + //remove proposal + CrossChainProposals::::remove(proposal_id); + Self::collectionXtransfer(origin, proposal.collection_id, proposal.proposed_destination_para, T::Lookup::unlookup(proposal.proposed_collection_owner.clone()), proposal.proposed_destination_config.clone())?; + } + + for item_id in items.clone() { + if pallet_nfts::ItemMetadataOf::::contains_key(proposal.collection_id.clone(), item_id) { + if let Some(nft_owner) = pallet_nfts::Pallet::::owner(proposal.collection_id, item_id) { + let item_details = pallet_nfts::ItemMetadataOf::::get(proposal.collection_id.clone(), item_id).unwrap(); + let unlookup_owner = T::Lookup::unlookup(nft_owner.clone()); + nft_metadata.push((item_id, unlookup_owner.clone(), item_details)); + } + } + } + + let destination = proposal.proposed_destination_para.clone(); + let recipient = proposal.proposed_collection_owner.clone(); + let unlooked_recipient = T::Lookup::unlookup(recipient.clone()); + let config = proposal.proposed_destination_config.clone(); + + //Send xcm, if successful, burn the collection and NFTs + match send_xcm::( + (Parent, Junction::Parachain(destination.into())).into(), + Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(1_000_000_000, 64 * 1024), + call: >::RuntimeCall::from(pallet_nfts::Call::< + T, + I, + >::create { + admin: unlooked_recipient.clone(), + config, + }) + .encode() + .into(), + }, + ]), + ) { + Ok((_hash, _cost)) => { + //Burning the NFTs + for item_id in items.clone() { + + // Remove the NFT item from storage + pallet_nfts::Item::::remove(proposal.collection_id.clone(), item_id); + + // Remove associated metadata + pallet_nfts::ItemMetadataOf::::remove(proposal.collection_id.clone(), item_id); + + // Remove associated attributes + pallet_nfts::ItemAttributesApprovalsOf::::remove(proposal.collection_id.clone(), item_id); + + // Clear ownership records + pallet_nfts::ItemPriceOf::::remove(proposal.collection_id.clone(), item_id); + + // Remove any pending approvals + pallet_nfts::ItemConfigOf::::remove(proposal.collection_id.clone(), item_id); + + } + + //Burning the collection + //Retrieve the collection details + + pallet_nfts::Collection::::remove(proposal.collection_id.clone()); + + pallet_nfts::CollectionMetadataOf::::remove(proposal.collection_id.clone()); + + pallet_nfts::CollectionConfigOf::::remove(proposal.collection_id.clone()); + + pallet_nfts::CollectionRoleOf::::remove(proposal.collection_id.clone(), who.clone()); + + pallet_nfts::CollectionAccount::::remove(who.clone(), proposal.collection_id.clone()); + + // Unreserve the funds + T::Currency::unreserve(&who.clone(), >::CollectionDeposit::get()); + + // Remove proposal from proposals to free up storage space + CrossChainProposals::::remove(proposal_id); + + // Emit an event. + Self::deposit_event(Event::CollectionAndNFTsTransferred { + origin_collection_id: proposal.collection_id.clone(), + nft_ids: items.clone(), + destination_para_id: proposal.proposed_destination_para.clone(), + to_address: unlooked_recipient.clone(), + }); + }, + Err(e) => Self::deposit_event(Event::CollectionFailedToXCM { + e, + collection_id: proposal.collection_id.clone(), + owner: who.clone(), + destination: proposal.proposed_destination_para.clone(), + }), + } + } + + Ok(().into()) + } + } } diff --git a/templates/parachain/runtime/src/configs/mod.rs b/templates/parachain/runtime/src/configs/mod.rs index d40e91e5c7f5..7ec55d29b0c0 100644 --- a/templates/parachain/runtime/src/configs/mod.rs +++ b/templates/parachain/runtime/src/configs/mod.rs @@ -330,7 +330,6 @@ impl pallet_parachain_xcnft::Config for Runtime { type RuntimeCall = RuntimeCall; type ProposalTimeInBlocks = proposal_time_in_blocks_parameter; type MaxOwners = max_owners_parameter; - type MaxVotes = max_votes; } pub const UNIT: Balance = 1; parameter_types! { @@ -348,7 +347,7 @@ parameter_types! { pub const MaxDeadlineDuration: u32 = 1; pub const MaxAttributesPerCall: u32 = 10; pub NftFeatures: pallet_nfts::PalletFeatures = pallet_nfts::PalletFeatures::all_enabled(); - pub const proposal_time_in_blocks_parameter: u32 = 200000; + pub const proposal_time_in_blocks_parameter: u32 = 100; pub const max_owners_parameter: u32 = 1000000; pub const max_votes: u32 = 1000000; }