From 4c921a6f02c7ee68887396adc3cce66781d6c1c0 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 29 Oct 2023 11:52:45 +1300 Subject: [PATCH 001/114] Create land minting infrastructure --- pallets/land-minting/src/lib.rs | 2327 +++++++++++++++++++++ pallets/land-minting/src/mock.rs | 637 ++++++ pallets/land-minting/src/rate.rs | 167 ++ pallets/land-minting/src/tests.rs | 2908 +++++++++++++++++++++++++++ pallets/land-minting/src/weights.rs | 699 +++++++ pallets/reward/src/tests.rs | 2 +- 6 files changed, 6739 insertions(+), 1 deletion(-) create mode 100644 pallets/land-minting/src/lib.rs create mode 100644 pallets/land-minting/src/mock.rs create mode 100644 pallets/land-minting/src/rate.rs create mode 100644 pallets/land-minting/src/tests.rs create mode 100644 pallets/land-minting/src/weights.rs diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs new file mode 100644 index 000000000..3c124e779 --- /dev/null +++ b/pallets/land-minting/src/lib.rs @@ -0,0 +1,2327 @@ +// This file is part of Metaverse.Network & Bit.Country. + +// Copyright (C) 2020-2022 Metaverse.Network & Bit.Country . +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_prelude::*; +use frame_support::{ + dispatch::DispatchResult, + ensure, log, + traits::{Currency, ExistenceRequirement, Get}, + transactional, PalletId, +}; +use frame_system::pallet_prelude::*; +use frame_system::{ensure_root, ensure_signed}; +use scale_info::TypeInfo; + +use sp_runtime::{ + traits::{AccountIdConversion, Convert, One, Saturating, Zero}, + ArithmeticError, DispatchError, Perbill, SaturatedConversion, +}; +use sp_std::vec::Vec; + +use auction_manager::{Auction, CheckAuctionItemHandler}; +use core_primitives::*; +pub use pallet::*; +use primitives::estate::EstateInfo; +use primitives::{ + estate::{Estate, LandUnitStatus, LeaseContract, OwnerId}, + Attributes, ClassId, EstateId, FungibleTokenId, ItemId, MetaverseId, NftMetadata, TokenId, UndeployedLandBlock, + UndeployedLandBlockId, UndeployedLandBlockType, +}; +pub use rate::{MintingRateInfo, Range}; +pub use weights::WeightInfo; + +//#[cfg(feature = "runtime-benchmarks")] +//pub mod benchmarking; + +#[cfg(test)] +mod mock; +mod rate; + +#[cfg(test)] +mod tests; + +pub mod weights; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::traits::{Currency, Imbalance, ReservableCurrency}; + use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero}; + + use primitives::estate::EstateInfo; + use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; + use primitives::{Balance, RoundIndex, UndeployedLandBlockId}; + + use crate::rate::{round_issuance_range, MintingRateInfo}; + + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Land treasury source + #[pallet::constant] + type LandTreasury: Get; + + /// Source of metaverse info + type MetaverseInfoSource: MetaverseTrait; + + /// Currency type + type Currency: Currency + ReservableCurrency; + + /// Minimum land price + type MinimumLandPrice: Get>; + + /// Council origin which allows to update max bound + type CouncilOrigin: EnsureOrigin; + + /// Auction handler + type AuctionHandler: Auction + CheckAuctionItemHandler>; + + /// Minimum number of blocks per round + #[pallet::constant] + type MinBlocksPerRound: Get; + + /// Weight implementation for estate extrinsics + type WeightInfo: WeightInfo; + + /// Minimum staking balance + #[pallet::constant] + type MinimumStake: Get>; + + /// Delay of staking reward payment (in number of rounds) + #[pallet::constant] + type RewardPaymentDelay: Get; + + /// NFT trait required for land and estate tokenization + type NFTTokenizationSource: NFTTrait, ClassId = ClassId, TokenId = TokenId>; + + /// Default max bound for each metaverse mapping system, this could change through proposal + type DefaultMaxBound: Get<(i32, i32)>; + + /// Network fee charged when deploying a land block or creating an estate + #[pallet::constant] + type NetworkFee: Get>; + + /// Maximum lease offers for an estate + #[pallet::constant] + type MaxOffersPerEstate: Get; + + /// Minimum lease price per block + #[pallet::constant] + type MinLeasePricePerBlock: Get>; + + /// Maximum lease period duration (in number of blocks) + #[pallet::constant] + type MaxLeasePeriod: Get; + + /// The period for each lease offer will be available for acceptance (in number of blocks) + #[pallet::constant] + type LeaseOfferExpiryPeriod: Get; + + /// Storage deposit free charged when saving data into the blockchain. + /// The fee will be unreserved after the storage is freed. + #[pallet::constant] + type StorageDepositFee: Get>; + + /// Allows converting block numbers into balance + type BlockNumberToBalance: Convert>; + } + + pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; + + #[pallet::storage] + #[pallet::getter(fn all_land_units_count)] + /// Track the total number of land units + pub(super) type AllLandUnitsCount = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn all_undeployed_land_unit)] + /// Track the total of undeployed land units + pub(super) type TotalUndeployedLandUnit = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_land_units)] + /// Index land owners by metaverse ID and coordinate + pub type LandUnits = StorageDoubleMap< + _, + Twox64Concat, + MetaverseId, + Twox64Concat, + (i32, i32), + OwnerId, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn next_estate_id)] + /// Track the next estate ID + pub type NextEstateId = StorageValue<_, EstateId, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn all_estates_count)] + /// Track the total of estates + pub(super) type AllEstatesCount = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_estates)] + /// Store estate information + pub(super) type Estates = StorageMap<_, Twox64Concat, EstateId, EstateInfo, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_estate_owner)] + /// Track estate owners + pub type EstateOwner = + StorageMap<_, Twox64Concat, EstateId, OwnerId, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn next_undeployed_land_block_id)] + /// Track the next undeployed land ID + pub(super) type NextUndeployedLandBlockId = StorageValue<_, UndeployedLandBlockId, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_undeployed_land_block)] + /// Store undeployed land blocks + pub(super) type UndeployedLandBlocks = + StorageMap<_, Blake2_128Concat, UndeployedLandBlockId, UndeployedLandBlock, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_undeployed_land_block_owner)] + /// Index undeployed land blocks by account ID + pub type UndeployedLandBlocksOwner = + StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, UndeployedLandBlockId, (), OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn round)] + /// Current round index and next round scheduled transition + pub type Round = StorageValue<_, RoundInfo, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn minting_rate_config)] + /// Minting rate configuration + pub type MintingRateConfig = StorageValue<_, MintingRateInfo, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn leases)] + /// Current active estate leases + pub type EstateLeases = + StorageMap<_, Twox64Concat, EstateId, LeaseContract, T::BlockNumber>, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn leasors)] + /// Current estate leasors + pub type EstateLeasors = + StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Twox64Concat, EstateId, (), OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn lease_offers)] + /// Current estate lease offers + pub type EstateLeaseOffers = StorageDoubleMap< + _, + Twox64Concat, + EstateId, + Blake2_128Concat, + T::AccountId, + LeaseContract, T::BlockNumber>, + OptionQuery, + >; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub minting_rate_config: MintingRateInfo, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + minting_rate_config: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + >::put(self.minting_rate_config.clone()); + + // Start Round 1 at Block 0 + let round: RoundInfo = RoundInfo::new(1u32, 0u32.into(), T::MinBlocksPerRound::get()); + + let round_issuance_per_round = round_issuance_range::(self.minting_rate_config.clone()); + + >::put(round); + >::deposit_event(Event::NewRound( + T::BlockNumber::zero(), + 1u32, + round_issuance_per_round.max, + )); + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub (crate) fn deposit_event)] + pub enum Event { + /// New lands are minted [Beneficial Account Id, Metaverse Id, Coordinates] + NewLandsMinted(T::AccountId, MetaverseId, Vec<(i32, i32)>), + /// Land unit is transferred [Metaverse Id, Coordinates, From Account Id, To Account Id] + TransferredLandUnit(MetaverseId, (i32, i32), T::AccountId, T::AccountId), + /// Estate unit is transferred [Estate Id, From Account Id, To Account Id] + TransferredEstate(EstateId, T::AccountId, T::AccountId), + /// New land is minted [Beneficial Account Id, Metaverse Id, Coordinates] + NewLandUnitMinted(OwnerId, MetaverseId, (i32, i32)), + /// New estate is minted [Estate Id, OwnerId, Metaverse Id, Coordinates] + NewEstateMinted( + EstateId, + OwnerId, + MetaverseId, + Vec<(i32, i32)>, + ), + /// Max bound is set for a metaverse [Metaverse Id, Min and Max Coordinate] + MaxBoundSet(MetaverseId, (i32, i32)), + /// Land block is deployed [From Account Id, Metaverse Id, Undeployed Land Block Id, + /// Coordinates] + LandBlockDeployed(T::AccountId, MetaverseId, UndeployedLandBlockId, Vec<(i32, i32)>), + /// Undeployed land block is issued [Beneficial Account Id, Undeployed Land + /// Block Id] + UndeployedLandBlockIssued(T::AccountId, UndeployedLandBlockId), + /// Undeployed land block is transferred [From Account Id, To Account Id, Undeployed + /// Land Block Id] + UndeployedLandBlockTransferred(T::AccountId, T::AccountId, UndeployedLandBlockId), + /// Undeployed land block is approved [Owner Account Id, Approved Account Id, Undeployed + /// Land Block Id] + UndeployedLandBlockApproved(T::AccountId, T::AccountId, UndeployedLandBlockId), + /// Estate is destroyed [Estate Id, Owner Id] + EstateDestroyed(EstateId, OwnerId), + /// Estate is updated [Estate Id, Owner Id, Coordinates] + EstateUpdated(EstateId, OwnerId, Vec<(i32, i32)>), + /// Land unit is added to an estate [Estate Id, Owner Id, Coordinates] + LandUnitAdded(EstateId, OwnerId, Vec<(i32, i32)>), + /// Land unit is removed from an estate [Estate Id, Owner Id, Coordinates] + LandUnitsRemoved(EstateId, OwnerId, Vec<(i32, i32)>), + /// Undeployed land block is unapproved [Undeployed Land Block Id] + UndeployedLandBlockUnapproved(UndeployedLandBlockId), + /// Undeployed land block is freezed [Undeployed Land Block Id] + UndeployedLandBlockFreezed(UndeployedLandBlockId), + /// Undeployed land block is unfreezed [Undeployed Land Block Id] + UndeployedLandBlockUnfreezed(UndeployedLandBlockId), + /// Undeployed land block is burnt [Undeployed Land Block Id] + UndeployedLandBlockBurnt(UndeployedLandBlockId), + /// Estate lease offer is created [AccountId, Estate Id, Total rent] + EstateLeaseOfferCreated(T::AccountId, EstateId, BalanceOf), + /// Estate lease offer is accepted [Estate Id, Leasor account Id, Lease End Block] + EstateLeaseOfferAccepted(EstateId, T::AccountId, T::BlockNumber), + /// Estate lease offer is removed [AccountId, Estate Id] + EstateLeaseOfferRemoved(T::AccountId, EstateId), + /// Estate lease contract ended [Estate Id] + EstateLeaseContractEnded(EstateId), + /// Estate lease contract was cancelled [Estate Id] + EstateLeaseContractCancelled(EstateId), + /// Estate rent collected [EstateId, Balance] + EstateRentCollected(EstateId, BalanceOf), + /// New staking round started [Starting Block, Round, Total Land Unit] + NewRound(T::BlockNumber, RoundIndex, u64), + } + + #[pallet::error] + pub enum Error { + /// No permission + NoPermission, + /// No available estate ID + NoAvailableEstateId, + /// Insufficient fund + InsufficientFund, + /// Estate ID already exist + EstateIdAlreadyExist, + /// Land unit is not available + LandUnitIsNotAvailable, + /// Land unit is out of bound + LandUnitIsOutOfBound, + /// Undeployed land block is not found + UndeployedLandBlockNotFound, + /// Undeployed land block is not transferable + UndeployedLandBlockIsNotTransferable, + /// Undeployed land block does not hae enough land units + UndeployedLandBlockDoesNotHaveEnoughLandUnits, + /// Number of land block credit and land unit does not match + UndeployedLandBlockUnitAndInputDoesNotMatch, + /// Account is not the owner of a given undeployed land block + UndeployedLandBlockNotOwned, + /// Already own the undeployed land block + AlreadyOwnTheUndeployedLandBlock, + /// Undeployed land block is freezed + UndeployedLandBlockFreezed, + /// Undeployed land block is already freezed + UndeployedLandBlockAlreadyFreezed, + /// Undeployed land block is not frozen + UndeployedLandBlockNotFrozen, + /// Already owning the estate + AlreadyOwnTheEstate, + /// Already owning the land unit + AlreadyOwnTheLandUnit, + /// Estate is not in auction + EstateNotInAuction, + /// Land unit is not in auction + LandUnitNotInAuction, + /// Estate is already in auction + EstateAlreadyInAuction, + /// Land unit is already in auction + LandUnitAlreadyInAuction, + /// Undeployed land block is already in auction + UndeployedLandBlockAlreadyInAuction, + /// Estate is does not exist + EstateDoesNotExist, + /// Land unit does not exist + LandUnitDoesNotExist, + /// Only frozen undeployed land block can be destroyed + OnlyFrozenUndeployedLandBlockCanBeDestroyed, + /// Below minimum staking amount + BelowMinimumStake, + /// Value overflow + Overflow, + /// Estate stake is already left + EstateStakeAlreadyLeft, + /// Account has not staked anything + AccountHasNoStake, + /// Invalid owner value + InvalidOwnerValue, + /// Coordinate for estate is not valid + CoordinatesForEstateIsNotValid, + /// Insufficient balance for deploying land blocks or creating estates + InsufficientBalanceForDeployingLandOrCreatingEstate, + // Land Unit already formed in Estate + LandUnitAlreadyInEstate, + /// Estate is already leased + EstateIsAlreadyLeased, + /// Estate lease offer limit is reached + EstateLeaseOffersQueueLimitIsReached, + /// Lease offer price per block is below the minimum + LeaseOfferPriceBelowMinimum, + /// Lease offer does not exist + LeaseOfferDoesNotExist, + /// Lease offer already exists + LeaseOfferAlreadyExists, + /// Lease offer is not expired + LeaseOfferIsNotExpired, + /// Lease does not exist + LeaseDoesNotExist, + /// Lease is not expired + LeaseIsNotExpired, + /// Lease is expired + LeaseIsExpired, + /// Lease duration beyond max duration + LeaseOfferDurationAboveMaximum, + /// No unclaimed rent balance + NoUnclaimedRentLeft, + } + + // TO DO: Implement offchain removal of expired lease offers + //#[pallet::hooks] + //impl Hooks> for Pallet { + // fn offchain_worker(block_number: T::BlockNumber) { + // } + //} + + #[pallet::call] + impl Pallet { + /// Minting of a land unit, only used by council to manually mint single land for + /// beneficiary + /// + /// The dispatch origin for this call must be _Root_. + /// - `beneficiary`: the account which will be the owner of the land unit + /// - `metaverse_id`: the metaverse id that the land united will be minted on + /// - `coordinate`: coordinate of the land unit + /// + /// Emits `NewLandsMinted` if successful. + #[pallet::weight(T::WeightInfo::mint_land())] + pub fn mint_land( + origin: OriginFor, + beneficiary: T::AccountId, + metaverse_id: MetaverseId, + coordinate: (i32, i32), + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + // Mint land unit + let owner = Self::mint_land_unit( + metaverse_id, + OwnerId::Account(beneficiary.clone()), + beneficiary, + coordinate, + LandUnitStatus::NonExisting, + )?; + + // Update total land count + Self::set_total_land_unit(One::one(), false)?; + + Self::deposit_event(Event::::NewLandUnitMinted(owner, metaverse_id, coordinate)); + + Ok(().into()) + } + + /// Minting of a land units, only used by council to manually mint number of lands for + /// beneficiary + /// + /// The dispatch origin for this call must be _Root_. + /// - `beneficiary`: the account which will be the owner of the land units + /// - `metaverse_id`: the metaverse id that the land units will be minted on + /// - `coordinates`: list of land units coordinates + /// + /// Emits `NewLandsMinted` if successful. + #[pallet::weight(T::WeightInfo::mint_lands())] + pub fn mint_lands( + origin: OriginFor, + beneficiary: T::AccountId, + metaverse_id: MetaverseId, + coordinates: Vec<(i32, i32)>, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + // Mint land units + for coordinate in coordinates.clone() { + Self::mint_land_unit( + metaverse_id, + OwnerId::Account(beneficiary.clone()), + beneficiary.clone(), + coordinate, + LandUnitStatus::NonExisting, + )?; + } + + // Update total land count + Self::set_total_land_unit(coordinates.len() as u64, false)?; + + Self::deposit_event(Event::::NewLandsMinted( + beneficiary.clone(), + metaverse_id.clone(), + coordinates.clone(), + )); + + Ok(().into()) + } + + /// Mint new estate with no existing land units, only used for council to manually mint + /// estate for beneficiary + /// + /// The dispatch origin for this call must be _Root_. + /// - `beneficiary`: the account which will be the owner of the land units + /// - `metaverse_id`: the metaverse id that the land units will be minted on + /// - `coordinates`: list of land units coordinates + /// + /// Emits `NewEstateMinted` if successful. + #[pallet::weight(T::WeightInfo::mint_estate())] + pub fn mint_estate( + origin: OriginFor, + beneficiary: T::AccountId, + metaverse_id: MetaverseId, + coordinates: Vec<(i32, i32)>, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + // Generate new estate id + let new_estate_id = Self::get_new_estate_id()?; + + // Generate sub account from estate + let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(new_estate_id); + + // Estate as owner + let token_properties = Self::get_estate_token_properties(metaverse_id, new_estate_id); + let class_id = T::MetaverseInfoSource::get_metaverse_estate_class(metaverse_id)?; + let token_id: TokenId = + T::NFTTokenizationSource::mint_token(&beneficiary, class_id, token_properties.0, token_properties.1)?; + let token_owner = OwnerId::Token(class_id, token_id); + + // Mint land units + for coordinate in coordinates.clone() { + Self::mint_land_unit( + metaverse_id, + token_owner.clone(), + estate_account_id.clone(), + coordinate, + LandUnitStatus::NonExistingWithEstate, + )?; + } + // Update total land count + Self::set_total_land_unit(coordinates.len() as u64, false)?; + + // Update estate information + Self::update_estate_information(new_estate_id, metaverse_id, token_owner, coordinates)?; + Ok(().into()) + } + + /// Transferring a land unit if it is not already in auction + /// + /// The dispatch origin for this call must be _Signed_. + /// Only the owner of a land can make this call. + /// - `to`: the account which will be the owner of the land units + /// - `metaverse_id`: the metaverse id of the land unit + /// - `coordinate`: the coordinate of the land unit + /// + /// Emits `TransferredLandUnit` if successful. + #[pallet::weight(T::WeightInfo::transfer_land())] + pub fn transfer_land( + origin: OriginFor, + to: T::AccountId, + metaverse_id: MetaverseId, + coordinate: (i32, i32), + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + Self::do_transfer_landunit(coordinate, &who, &to, metaverse_id)?; + Ok(().into()) + } + + /// Create new estate from existing land units + /// + /// The dispatch origin for this call must be _Signed_. + /// - `metaverse_id`: the metaverse id that the land units will be minted on + /// - `coordinates`: list of land units coordinates + /// + /// Emits `NewEstateMinted` if successful. + #[pallet::weight(T::WeightInfo::create_estate().saturating_mul(coordinates.len() as u64))] + #[transactional] + pub fn create_estate( + origin: OriginFor, + metaverse_id: MetaverseId, + coordinates: Vec<(i32, i32)>, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + ensure!( + Self::verify_land_unit_for_estate(coordinates.clone()), + Error::::CoordinatesForEstateIsNotValid + ); + // Collect network fee + Self::collect_network_fee(&who)?; + // Generate new estate id + let new_estate_id = Self::get_new_estate_id()?; + // Generate sub account from estate + let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(new_estate_id); + + let token_properties = Self::get_estate_token_properties(metaverse_id, new_estate_id); + let class_id = T::MetaverseInfoSource::get_metaverse_estate_class(metaverse_id)?; + let token_id: TokenId = + T::NFTTokenizationSource::mint_token(&who, class_id, token_properties.0, token_properties.1)?; + let beneficiary = OwnerId::Token(class_id, token_id); + + let storage_fee: BalanceOf = + Perbill::from_percent(100u32.saturating_mul(coordinates.len() as u32)) * T::StorageDepositFee::get(); + T::Currency::transfer( + &who, + &T::MetaverseInfoSource::get_network_treasury(), + storage_fee.saturated_into(), + ExistenceRequirement::KeepAlive, + )?; + + // Mint land units + for coordinate in coordinates.clone() { + Self::mint_land_unit( + metaverse_id, + beneficiary.clone(), + estate_account_id.clone(), + coordinate, + LandUnitStatus::Existing(who.clone()), + )?; + } + + // Update estate information + Self::update_estate_information(new_estate_id, metaverse_id, beneficiary, coordinates.clone())?; + + Ok(().into()) + } + + /// Transfer estate ownership if it is not in auction. + /// + /// The dispatch origin for this call must be _Signed_. + /// Only the owner of an estate can make this call. + /// - `to`: the account which will be the owner of the estate + /// - `estate_id`: the estate ID of the the estate that will be transferred + /// + /// Emits `TransferredEstate` if successful. + #[pallet::weight(T::WeightInfo::transfer_estate())] + #[transactional] + pub fn transfer_estate( + origin: OriginFor, + to: T::AccountId, + estate_id: EstateId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + Self::do_transfer_estate(estate_id, &who, &to)?; + Ok(().into()) + } + + /// Deploy raw land block to metaverse and turn raw land block to land unit with given + /// coordinates + /// + /// The dispatch origin for this call must be _Signed_. + /// Only the undeployed land block owner can make this call. + /// - `undeployed_land_block_id`: the undeployed land block ID + /// - `metaverse_id`: the metaverse ID that the land block will be deployed on + /// - `land_block_coordinates`: the coordinates of the land block + /// - `coordinates`: list of land units coordinates + /// + /// Emits `LandBlockDeployed` if successful. + #[pallet::weight(T::WeightInfo::deploy_land_block() * coordinates.len() as u64)] + #[transactional] + pub fn deploy_land_block( + origin: OriginFor, + undeployed_land_block_id: UndeployedLandBlockId, + metaverse_id: MetaverseId, + land_block_coordinate: (i32, i32), + coordinates: Vec<(i32, i32)>, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), + Error::::UndeployedLandBlockAlreadyInAuction + ); + + ensure!( + T::MetaverseInfoSource::check_ownership(&who, &metaverse_id), + Error::::NoPermission + ); + + // Ensure the max bound is set for the metaverse + let max_bound = T::DefaultMaxBound::get(); + + // Check whether the coordinate is within the bound + ensure!( + (land_block_coordinate.0 >= max_bound.0 && max_bound.1 >= land_block_coordinate.0) + && (land_block_coordinate.1 >= max_bound.0 && max_bound.1 >= land_block_coordinate.1), + Error::::LandUnitIsOutOfBound + ); + + ensure!( + Self::verify_land_unit_in_bound(&land_block_coordinate, &coordinates), + Error::::LandUnitIsOutOfBound + ); + + let undeployed_land_block_record = UndeployedLandBlocks::::get(undeployed_land_block_id) + .ok_or(Error::::UndeployedLandBlockNotFound)?; + + ensure!( + undeployed_land_block_record.owner == who.clone(), + Error::::NoPermission + ); + + ensure!( + undeployed_land_block_record.is_locked == false, + Error::::UndeployedLandBlockFreezed + ); + + let land_units_to_mint = coordinates.len() as u32; + + // Ensure undeployed land block only deployed once + ensure!( + undeployed_land_block_record.number_land_units == land_units_to_mint, + Error::::UndeployedLandBlockUnitAndInputDoesNotMatch + ); + + // Collect network fee + Self::collect_network_fee(&who)?; + + // Mint land units + for coordinate in coordinates.clone() { + Self::mint_land_unit( + metaverse_id, + OwnerId::Account(who.clone()), + who.clone(), + coordinate, + LandUnitStatus::NonExisting, + )?; + } + + // Update total land count + Self::set_total_land_unit(coordinates.len() as u64, false)?; + + // Burn undeployed land block + Self::do_burn_undeployed_land_block(undeployed_land_block_id)?; + + Self::deposit_event(Event::::LandBlockDeployed( + who.clone(), + metaverse_id, + undeployed_land_block_id, + coordinates, + )); + + Ok(().into()) + } + + /// Issues new undeployed land block(s) + /// + /// The dispatch origin for this call must be _Root_. + /// - `beneficiary`: the account which will be the owner of the undeployed land block(s) + /// - `number_of_land_block`: the number of undeployed land block(s) that will be created + /// - `number_land_units_per_land_block`: the number of land units in each undeployed land + /// block + /// - `land_block_coordinates`: the coordinates of the undeployed land block + /// + /// Emits `UndeployedLandBlockIssued` if successful. + #[pallet::weight(T::WeightInfo::issue_undeployed_land_blocks())] + #[transactional] + pub fn issue_undeployed_land_blocks( + who: OriginFor, + beneficiary: T::AccountId, + number_of_land_block: u32, + number_land_units_per_land_block: u32, + undeployed_land_block_type: UndeployedLandBlockType, + ) -> DispatchResultWithPostInfo { + ensure_root(who)?; + + Self::do_issue_undeployed_land_blocks( + &beneficiary, + number_of_land_block, + number_land_units_per_land_block, + undeployed_land_block_type, + )?; + + Ok(().into()) + } + + /// Freezes undeployed land block which is not already frozen + /// + /// The dispatch origin for this call must be _Root_. + /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be freezed + /// + /// Emits `UndeployedLandBlockFreezed` if successful. + #[pallet::weight(T::WeightInfo::freeze_undeployed_land_blocks())] + #[transactional] + pub fn freeze_undeployed_land_blocks( + origin: OriginFor, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + Self::do_freeze_undeployed_land_block(undeployed_land_block_id)?; + + Ok(().into()) + } + + /// Unfreezes undeployed land block which is frozen. + /// + /// The dispatch origin for this call must be _Root_. + /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be unfreezed + /// + /// Emits `UndeployedLandBlockUnfreezed` if successful. + #[pallet::weight(T::WeightInfo::unfreeze_undeployed_land_blocks())] + #[transactional] + pub fn unfreeze_undeployed_land_blocks( + origin: OriginFor, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + UndeployedLandBlocks::::try_mutate_exists( + &undeployed_land_block_id, + |undeployed_land_block| -> DispatchResultWithPostInfo { + let undeployed_land_block_record = undeployed_land_block + .as_mut() + .ok_or(Error::::UndeployedLandBlockNotFound)?; + + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock( + undeployed_land_block_id + )), + Error::::UndeployedLandBlockAlreadyInAuction + ); + + ensure!( + undeployed_land_block_record.is_locked == true, + Error::::UndeployedLandBlockNotFrozen + ); + + undeployed_land_block_record.is_locked = false; + + Self::deposit_event(Event::::UndeployedLandBlockUnfreezed(undeployed_land_block_id)); + + Ok(().into()) + }, + ) + } + + /// Transfer undeployed land block owner if it is not in auction. + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the undeployed land block owner can make this call. + /// - `to`: the account that will receive the undeployed land block + /// - `undeployed_land_block_id`: the ID of the land block that will be transferred + /// + /// Emits `UndeployedLandBlockTransferred` if successful. + #[pallet::weight(T::WeightInfo::transfer_undeployed_land_blocks())] + #[transactional] + pub fn transfer_undeployed_land_blocks( + origin: OriginFor, + to: T::AccountId, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), + Error::::UndeployedLandBlockAlreadyInAuction + ); + + Self::do_transfer_undeployed_land_block(&who, &to, undeployed_land_block_id)?; + + Ok(().into()) + } + + /// Burn raw land block that will reduce total supply + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the undeployed land block owner can make this call. + /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be burned + /// + /// Emits `UndeployedLandBlockBurnt` if successful. + #[pallet::weight(T::WeightInfo::burn_undeployed_land_blocks())] + #[transactional] + pub fn burn_undeployed_land_blocks( + origin: OriginFor, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + Self::do_burn_undeployed_land_block(undeployed_land_block_id)?; + + Ok(().into()) + } + + /// Approve existing undeployed land block which is not frozen. + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the undeployed land block owner can make this call. + /// - `to`: the account for which the undeployed land block will be approved + /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be burned + /// + /// Emits `UndeployedLandBlockApproved` if successful + #[pallet::weight(T::WeightInfo::approve_undeployed_land_blocks())] + #[transactional] + pub fn approve_undeployed_land_blocks( + origin: OriginFor, + to: T::AccountId, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + UndeployedLandBlocks::::try_mutate_exists( + &undeployed_land_block_id, + |undeployed_land_block| -> DispatchResultWithPostInfo { + let mut undeployed_land_block_record = undeployed_land_block + .as_mut() + .ok_or(Error::::UndeployedLandBlockNotFound)?; + + ensure!( + undeployed_land_block_record.owner == who.clone(), + Error::::NoPermission + ); + + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock( + undeployed_land_block_id + )), + Error::::UndeployedLandBlockAlreadyInAuction + ); + + ensure!( + undeployed_land_block_record.is_locked == false, + Error::::UndeployedLandBlockAlreadyFreezed + ); + + undeployed_land_block_record.approved = Some(to.clone()); + + Self::deposit_event(Event::::UndeployedLandBlockApproved( + who.clone(), + to.clone(), + undeployed_land_block_id.clone(), + )); + + Ok(().into()) + }, + ) + } + + /// Unapprove existing undeployed land block which is not frozen. + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the undeployed land block owner can make this call. + /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be + /// unapproved + /// + /// Emits `UndeployedLandBlockUnapproved` if successful + #[pallet::weight(T::WeightInfo::unapprove_undeployed_land_blocks())] + #[transactional] + pub fn unapprove_undeployed_land_blocks( + origin: OriginFor, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + UndeployedLandBlocks::::try_mutate_exists( + &undeployed_land_block_id, + |undeployed_land_block| -> DispatchResultWithPostInfo { + let mut undeployed_land_block_record = undeployed_land_block + .as_mut() + .ok_or(Error::::UndeployedLandBlockNotFound)?; + + ensure!( + undeployed_land_block_record.owner == who.clone(), + Error::::NoPermission + ); + + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock( + undeployed_land_block_id + )), + Error::::UndeployedLandBlockAlreadyInAuction + ); + + ensure!( + undeployed_land_block_record.is_locked == false, + Error::::UndeployedLandBlockAlreadyFreezed + ); + + undeployed_land_block_record.approved = None; + + Self::deposit_event(Event::::UndeployedLandBlockUnapproved( + undeployed_land_block_id.clone(), + )); + + Ok(().into()) + }, + ) + } + + /// Dissolve estate to land units if it is not in auction. + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the estate owner can make this call. + /// - `estate_id`: the ID of the estate that will be dissolved + /// + /// Emits `EstateDestroyed` if successful + #[pallet::weight(T::WeightInfo::dissolve_estate())] + #[transactional] + pub fn dissolve_estate(origin: OriginFor, estate_id: EstateId) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; + match estate_owner_value { + OwnerId::Token(c, t) => { + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::NFT(c, t)), + Error::::EstateAlreadyInAuction + ); + //ensure there is record of the estate owner with estate id and account id + ensure!( + Self::check_if_land_or_estate_owner(&who, &estate_owner_value), + Error::::NoPermission + ); + let estate_info = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; + EstateOwner::::try_mutate_exists(&estate_id, |estate_owner| { + // Reset estate ownership + match estate_owner_value { + OwnerId::Token(class_id, token_id) => { + T::NFTTokenizationSource::burn_nft(&who, &(class_id, token_id)); + *estate_owner = None; + } + OwnerId::Account(ref a) => { + *estate_owner = None; + } + } + + // Remove estate + Estates::::remove(&estate_id); + + // Update total estates + let total_estates_count = Self::all_estates_count(); + let new_total_estates_count = total_estates_count + .checked_sub(One::one()) + .ok_or("Overflow adding new count to total estates")?; + AllEstatesCount::::put(new_total_estates_count); + + // Mint new land tokens to replace the lands in the dissolved estate + let estate_account_id: T::AccountId = + T::LandTreasury::get().into_sub_account_truncating(estate_id); + let storage_fee: BalanceOf = + Perbill::from_percent(100u32.saturating_mul(estate_info.land_units.len() as u32)) + * T::StorageDepositFee::get(); + T::Currency::transfer( + &who, + &T::MetaverseInfoSource::get_network_treasury(), + storage_fee.saturated_into(), + ExistenceRequirement::KeepAlive, + )?; + for land_unit in estate_info.land_units { + // Transfer land unit from treasury to estate owner + Self::mint_land_unit( + estate_info.metaverse_id, + estate_owner_value.clone(), + who.clone(), + land_unit, + LandUnitStatus::RemovedFromEstate, + )?; + } + + Self::deposit_event(Event::::EstateDestroyed( + estate_id.clone(), + estate_owner_value.clone(), + )); + + Ok(().into()) + }) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + + /// Add more land units to existing estate that is not in auction + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the estate owner can make this call. + /// They must also own the land units. + /// - `estate_id`: the ID of the estate that the land units will be added to + /// - `land_units`: list of land unit coordinates that will be added to estate + /// + /// Emits `LandUnitAdded` if successful + #[pallet::weight(T::WeightInfo::add_land_unit_to_estate())] + #[transactional] + pub fn add_land_unit_to_estate( + origin: OriginFor, + estate_id: EstateId, + land_units: Vec<(i32, i32)>, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; + match estate_owner_value { + OwnerId::Token(c, t) => { + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::NFT(c, t)), + Error::::EstateAlreadyInAuction + ); + ensure!( + Self::check_if_land_or_estate_owner(&who, &estate_owner_value), + Error::::NoPermission + ); + + let estate_info: EstateInfo = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; + let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(estate_id); + + let storage_fee: BalanceOf = + Perbill::from_percent(100u32.saturating_mul(land_units.len() as u32)) + * T::StorageDepositFee::get(); + T::Currency::transfer( + &who, + &T::MetaverseInfoSource::get_network_treasury(), + storage_fee.saturated_into(), + ExistenceRequirement::KeepAlive, + )?; + + // Check land unit ownership + for land_unit in land_units.clone() { + let metaverse_land_unit = Self::get_land_units(estate_info.metaverse_id, land_unit) + .ok_or(Error::::UndeployedLandBlockNotFound)?; + ensure!( + Self::check_if_land_or_estate_owner(&who, &metaverse_land_unit,), + Error::::LandUnitDoesNotExist + ); + + // Mint land unit + Self::mint_land_unit( + estate_info.metaverse_id, + estate_owner_value.clone(), + estate_account_id.clone(), + land_unit, + LandUnitStatus::Existing(who.clone()), + )?; + } + + // Mutate estates + Estates::::try_mutate_exists(&estate_id, |maybe_estate_info| { + // Append new coordinates to estate + let mut_estate_info = maybe_estate_info.as_mut().ok_or(Error::::EstateDoesNotExist)?; + mut_estate_info.land_units.append(&mut land_units.clone()); + + Self::deposit_event(Event::::LandUnitAdded( + estate_id.clone(), + estate_owner_value.clone(), + land_units.clone(), + )); + + Ok(().into()) + }) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + + /// Remove land units from existing estate if it is not in auction. + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the estate owner can make this call. + /// - `estate_id`: the ID of the estate that the land units will be removed from + /// - `land_units`: list of land unit coordinates that will be added to estate + /// + /// Emits `LandUnitsRemoved` if successful + #[pallet::weight(T::WeightInfo::remove_land_unit_from_estate())] + #[transactional] + pub fn remove_land_unit_from_estate( + origin: OriginFor, + estate_id: EstateId, + land_units: Vec<(i32, i32)>, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; + match estate_owner_value { + OwnerId::Token(c, t) => { + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::NFT(c, t)), + Error::::EstateAlreadyInAuction + ); + ensure!( + Self::check_if_land_or_estate_owner(&who, &estate_owner_value), + Error::::NoPermission + ); + let estate_info: EstateInfo = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; + let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(estate_id); + + // Mutate estates + Estates::::try_mutate_exists(&estate_id, |maybe_estate_info| { + let mut mut_estate_info = maybe_estate_info.as_mut().ok_or(Error::::EstateDoesNotExist)?; + + let storage_fee: BalanceOf = + Perbill::from_percent(100u32.saturating_mul(land_units.len() as u32)) + * T::StorageDepositFee::get(); + T::Currency::transfer( + &who, + &T::MetaverseInfoSource::get_network_treasury(), + storage_fee.saturated_into(), + ExistenceRequirement::KeepAlive, + )?; + // Mutate land unit ownership + for land_unit in land_units.clone() { + // Transfer land unit from treasury to estate owner + Self::mint_land_unit( + estate_info.metaverse_id, + estate_owner_value.clone(), + who.clone(), + land_unit, + LandUnitStatus::RemovedFromEstate, + )?; + // Remove coordinates from estate + let index = mut_estate_info + .land_units + .iter() + .position(|x| *x == land_unit) + .ok_or(Error::::LandUnitIsNotAvailable)?; + mut_estate_info.land_units.remove(index); + } + + Self::deposit_event(Event::::LandUnitsRemoved( + estate_id.clone(), + estate_owner_value.clone(), + land_units.clone(), + )); + + Ok(().into()) + }) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + + /// Create a lease offer for estate that is not leased + /// + /// The dispatch origin for this call must be _Singed_. + /// Only origin that is not the estate owner can make this call. + /// - `estate_id`: the ID of the estate that will be leased + /// - `price_per_block`: lease price per block + /// - `duration`: lease duration (in number of blocks) + /// + /// Emits `EstateLeaseOfferCreated` if successful + #[pallet::weight(T::WeightInfo::create_lease_offer())] + #[transactional] + pub fn create_lease_offer( + origin: OriginFor, + estate_id: EstateId, + price_per_block: BalanceOf, + duration: u32, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; + ensure!( + !Self::check_if_land_or_estate_owner(&who, &estate_owner_value), + Error::::NoPermission + ); + ensure!( + !EstateLeaseOffers::::contains_key(estate_id, who.clone()), + Error::::LeaseOfferAlreadyExists + ); + ensure!( + !EstateLeases::::contains_key(estate_id), + Error::::EstateIsAlreadyLeased + ); + ensure!( + price_per_block >= T::MinLeasePricePerBlock::get(), + Error::::LeaseOfferPriceBelowMinimum + ); + ensure!( + duration <= T::MaxLeasePeriod::get(), + Error::::LeaseOfferDurationAboveMaximum + ); + + match estate_owner_value { + OwnerId::Token(class_id, token_id) => { + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::NFT(class_id, token_id)), + Error::::EstateAlreadyInAuction + ); + ensure!( + EstateLeaseOffers::::iter_key_prefix(estate_id).count() as u32 + <= T::MaxOffersPerEstate::get(), + Error::::EstateLeaseOffersQueueLimitIsReached + ); + + let current_block_number = >::block_number(); + let end_block = current_block_number + T::LeaseOfferExpiryPeriod::get().into(); + let unclaimed_rent: BalanceOf = price_per_block * duration.into(); + + let lease_offer = LeaseContract { + price_per_block, + duration, + end_block, + start_block: end_block + 1u32.into(), + unclaimed_rent, + }; + + EstateLeaseOffers::::insert(estate_id, who.clone(), lease_offer); + T::Currency::reserve(&who, unclaimed_rent); + + Self::deposit_event(Event::::EstateLeaseOfferCreated(who, estate_id, unclaimed_rent)); + + Ok(().into()) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + + /// Accept lease offer for estate that is not leased + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the estate owner can make this call. + /// - `estate_id`: the ID of the estate that will be leased + /// - `recipient`: the account that made the lease offer + /// + /// Emits `EstateLeaseOfferAccepted` if successful + #[pallet::weight(T::WeightInfo::accept_lease_offer())] + #[transactional] + pub fn accept_lease_offer( + origin: OriginFor, + estate_id: EstateId, + recipient: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!( + !EstateLeases::::contains_key(estate_id), + Error::::EstateIsAlreadyLeased + ); + + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; + match estate_owner_value { + OwnerId::Token(class_id, token_id) => { + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::NFT(class_id, token_id)), + Error::::EstateAlreadyInAuction + ); + ensure!( + Self::check_if_land_or_estate_owner(&who, &estate_owner_value), + Error::::NoPermission + ); + + let mut lease = + Self::lease_offers(estate_id, recipient.clone()).ok_or(Error::::LeaseOfferDoesNotExist)?; + + lease.start_block = >::block_number(); + lease.end_block = lease.start_block + lease.duration.into(); + // 200% storage fee since there are 2 storage inserts + let storage_fee: BalanceOf = Perbill::from_percent(200) * T::StorageDepositFee::get(); + T::Currency::transfer( + &who, + &T::MetaverseInfoSource::get_network_treasury(), + storage_fee.saturated_into(), + ExistenceRequirement::KeepAlive, + )?; + + EstateLeaseOffers::::remove_prefix(estate_id, None); + EstateLeases::::insert(estate_id, lease.clone()); + EstateLeasors::::insert(recipient.clone(), estate_id, ()); + T::NFTTokenizationSource::set_lock_nft((class_id, token_id), true)?; + + Self::deposit_event(Event::::EstateLeaseOfferAccepted( + estate_id, + recipient.clone(), + lease.end_block, + )); + + Ok(().into()) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + + /// Cancels existing lease + /// + /// The dispatch origin for this call must be _Root_. + /// - `estate_id`: the ID of the estate that will be leased + /// - `leasor`: the account that is leasing the estate + /// + /// Emits `EstateLeaseContractCancelled` if successful + #[pallet::weight(T::WeightInfo::cancel_lease())] + #[transactional] + pub fn cancel_lease( + origin: OriginFor, + estate_owner: T::AccountId, + estate_id: EstateId, + leasor: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + let lease = Self::leases(estate_id).ok_or(Error::::LeaseDoesNotExist)?; + ensure!( + EstateLeasors::::contains_key(leasor.clone(), estate_id), + Error::::LeaseDoesNotExist + ); + ensure!( + lease.end_block > >::block_number(), + Error::::LeaseIsExpired + ); + + let total_rent: BalanceOf = lease.price_per_block * lease.duration.into(); + let rent_period = >::block_number() - lease.start_block; + let rent_claim_amount = lease.price_per_block * T::BlockNumberToBalance::convert(rent_period) + + lease.unclaimed_rent + - total_rent; + + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; + match estate_owner_value { + OwnerId::Token(class_id, token_id) => { + //let estate_owner = T::NFTTokenizationSource::get_asset_owner(&(class_id, token_id))?; + ensure!( + Self::check_if_land_or_estate_owner(&estate_owner, &estate_owner_value), + Error::::NoPermission + ); + T::Currency::unreserve(&leasor, lease.unclaimed_rent.into()); + ::Currency::transfer( + &leasor, + &estate_owner, + rent_claim_amount, + ExistenceRequirement::KeepAlive, + )?; + + EstateLeasors::::remove(leasor.clone(), estate_id); + EstateLeases::::remove(estate_id); + T::NFTTokenizationSource::set_lock_nft((class_id, token_id), false)?; + + Self::deposit_event(Event::::EstateLeaseContractCancelled(estate_id)); + Ok(().into()) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + + /// Removes expired lease + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the estate owner can make this call. + /// - `estate_id`: the ID of the estate that will be leased + /// - `leasor`: the account that is leasing the estate + /// + /// Emits `EstateLeaseContractEnded` if successful + #[pallet::weight(T::WeightInfo::remove_expired_lease())] + #[transactional] + pub fn remove_expired_lease( + origin: OriginFor, + estate_id: EstateId, + leasor: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let lease = Self::leases(estate_id).ok_or(Error::::LeaseDoesNotExist)?; + ensure!( + EstateLeasors::::contains_key(leasor.clone(), estate_id), + Error::::LeaseDoesNotExist + ); + ensure!( + lease.end_block <= >::block_number(), + Error::::LeaseIsNotExpired + ); + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; + match estate_owner_value { + OwnerId::Token(class_id, token_id) => { + ensure!( + Self::check_if_land_or_estate_owner(&who, &estate_owner_value), + Error::::NoPermission + ); + + T::Currency::unreserve(&leasor, lease.unclaimed_rent.into()); + ::Currency::transfer( + &leasor, + &who, + lease.unclaimed_rent.into(), + ExistenceRequirement::KeepAlive, + )?; + + EstateLeasors::::remove(leasor, estate_id); + EstateLeases::::remove(estate_id); + T::NFTTokenizationSource::set_lock_nft((class_id, token_id), false)?; + + Self::deposit_event(Event::::EstateLeaseContractEnded(estate_id)); + Ok(().into()) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + + /// Removes lease offer + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the account made the lease offer can make this call. + /// - `estate_id`: the ID of the estate that will be leased + /// + /// Emits `EstateLeaseOfferRemoved` if successful + #[pallet::weight(T::WeightInfo::remove_lease_offer())] + #[transactional] + pub fn remove_lease_offer(origin: OriginFor, estate_id: EstateId) -> DispatchResultWithPostInfo { + let leasor = ensure_signed(origin)?; + let lease_offer = + Self::lease_offers(estate_id, leasor.clone()).ok_or(Error::::LeaseOfferDoesNotExist)?; + EstateLeaseOffers::::remove(estate_id, leasor.clone()); + T::Currency::unreserve(&leasor, lease_offer.unclaimed_rent.into()); + Self::deposit_event(Event::::EstateLeaseOfferRemoved(leasor, estate_id)); + Ok(().into()) + } + + /// Collect rent for a leased estate + /// + /// The dispatch origin for this call must be _Singed_. + /// Only the estate owner can make this call. + /// - `estate_id`: the ID of the estate that will be leased + /// + /// Emits `EstateRentCollected` if successful + #[pallet::weight(T::WeightInfo::collect_rent())] + #[transactional] + pub fn collect_rent( + origin: OriginFor, + estate_id: EstateId, + leasor: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!( + Self::check_estate_ownership(who.clone(), estate_id)?, + Error::::NoPermission + ); + ensure!( + EstateLeasors::::contains_key(leasor.clone(), estate_id), + Error::::LeaseDoesNotExist + ); + let current_block = >::block_number(); + EstateLeases::::try_mutate_exists(&estate_id, |estate_lease_value| { + let mut lease = estate_lease_value.as_mut().ok_or(Error::::LeaseDoesNotExist)?; + + ensure!(lease.end_block > current_block, Error::::LeaseIsExpired); + + let total_rent: BalanceOf = lease.price_per_block * lease.duration.into(); + let rent_period = >::block_number() - lease.start_block; + let rent_claim_amount = lease.price_per_block * T::BlockNumberToBalance::convert(rent_period) + + lease.unclaimed_rent + - total_rent; + + ::Currency::unreserve(&leasor, rent_claim_amount); + ::Currency::transfer( + &leasor, + &who, + rent_claim_amount.into(), + ExistenceRequirement::KeepAlive, + )?; + + lease.unclaimed_rent -= rent_claim_amount; + + Self::deposit_event(Event::::EstateRentCollected(estate_id, rent_claim_amount.into())); + Ok(().into()) + }) + } + } +} + +impl Pallet { + /// Internal getter for new estate ID + fn get_new_estate_id() -> Result { + let estate_id = NextEstateId::::try_mutate(|id| -> Result { + let current_id = *id; + *id = id.checked_add(One::one()).ok_or(Error::::NoAvailableEstateId)?; + Ok(current_id) + })?; + Ok(estate_id) + } + + /// Internal minting of land unit + fn mint_land_unit( + metaverse_id: MetaverseId, + token_owner: OwnerId, + to: T::AccountId, + coordinate: (i32, i32), + land_unit_status: LandUnitStatus, + ) -> Result, DispatchError> { + let mut owner = token_owner.clone(); + + match land_unit_status { + // Use case - create new estate. + LandUnitStatus::Existing(a) => { + ensure!( + LandUnits::::contains_key(metaverse_id, coordinate), + Error::::LandUnitIsNotAvailable + ); + + let existing_owner_value = Self::get_land_units(metaverse_id, coordinate); + match existing_owner_value { + Some(owner_value) => match owner_value { + OwnerId::Token(class_id, token_id) => { + // Implement check if user own nft + ensure!( + T::NFTTokenizationSource::check_ownership(&a, &(class_id, token_id))?, + Error::::NoPermission + ); + + if let OwnerId::Token(owner_class_id, owner_token_id) = token_owner { + ensure!(owner_class_id != class_id, Error::::LandUnitAlreadyInEstate) + } + + // Ensure not locked + T::NFTTokenizationSource::set_lock_nft((class_id, token_id), false)?; + T::NFTTokenizationSource::burn_nft(&a, &(class_id, token_id)); + + LandUnits::::insert(metaverse_id, coordinate, token_owner.clone()); + } + _ => (), + }, + /* It doesn't make sense to mint existing land unit when ownership doesn't exists */ + _ => (), + } + } + LandUnitStatus::NonExisting => { + ensure!( + !LandUnits::::contains_key(metaverse_id, coordinate), + Error::::LandUnitIsNotAvailable + ); + + let token_properties = Self::get_land_token_properties(metaverse_id, coordinate); + let class_id = T::MetaverseInfoSource::get_metaverse_land_class(metaverse_id)?; + let token_id = + T::NFTTokenizationSource::mint_token(&to, class_id, token_properties.0, token_properties.1)?; + owner = OwnerId::Token(class_id, token_id); + LandUnits::::insert(metaverse_id, coordinate, OwnerId::Token(class_id, token_id.clone())); + } + LandUnitStatus::NonExistingWithEstate => { + ensure!( + !LandUnits::::contains_key(metaverse_id, coordinate), + Error::::LandUnitIsNotAvailable + ); + + owner = token_owner.clone(); + + LandUnits::::insert(metaverse_id, coordinate, token_owner.clone()); + } + LandUnitStatus::RemovedFromEstate => { + ensure!( + LandUnits::::contains_key(metaverse_id, coordinate), + Error::::LandUnitIsNotAvailable + ); + + let token_properties = Self::get_land_token_properties(metaverse_id, coordinate); + let class_id = T::MetaverseInfoSource::get_metaverse_land_class(metaverse_id)?; + let token_id = + T::NFTTokenizationSource::mint_token(&to, class_id, token_properties.0, token_properties.1)?; + owner = OwnerId::Token(class_id, token_id); + LandUnits::::remove(metaverse_id, coordinate); + LandUnits::::insert(metaverse_id, coordinate, OwnerId::Token(class_id, token_id)); + } + } + Ok(owner) + } + + /// Internal updating information about an estate + fn update_estate_information( + new_estate_id: EstateId, + metaverse_id: MetaverseId, + estate_owner: OwnerId, + coordinates: Vec<(i32, i32)>, + ) -> DispatchResult { + // Update total estates + let total_estates_count = Self::all_estates_count(); + let new_total_estates_count = total_estates_count + .checked_add(One::one()) + .ok_or("Overflow adding new count to total estates")?; + AllEstatesCount::::put(new_total_estates_count); + + // Update estates + let estate_info = EstateInfo { + metaverse_id, + land_units: coordinates.clone(), + }; + + Estates::::insert(new_estate_id, estate_info); + EstateOwner::::insert(new_estate_id, estate_owner.clone()); + + Self::deposit_event(Event::::NewEstateMinted( + new_estate_id.clone(), + estate_owner, + metaverse_id, + coordinates.clone(), + )); + + Ok(()) + } + + /// Internal getter of new undeployed land block ID + fn get_new_undeployed_land_block_id() -> Result { + let undeployed_land_block_id = + NextUndeployedLandBlockId::::try_mutate(|id| -> Result { + let current_id = *id; + *id = id.checked_add(One::one()).ok_or(Error::::NoAvailableEstateId)?; + Ok(current_id) + })?; + Ok(undeployed_land_block_id) + } + + /// Internal transfer of undeployed land block + fn do_transfer_undeployed_land_block( + who: &T::AccountId, + to: &T::AccountId, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + // 200% storage fee since there are 2 storage inserts + let storage_fee: BalanceOf = Perbill::from_percent(200) * T::StorageDepositFee::get(); + T::Currency::transfer( + &who, + &T::MetaverseInfoSource::get_network_treasury(), + storage_fee.saturated_into(), + ExistenceRequirement::KeepAlive, + )?; + + UndeployedLandBlocks::::try_mutate( + &undeployed_land_block_id, + |undeployed_land_block| -> Result { + let undeployed_land_block_record = undeployed_land_block + .as_mut() + .ok_or(Error::::UndeployedLandBlockNotFound)?; + + ensure!( + undeployed_land_block_record.owner == who.clone(), + Error::::NoPermission + ); + + ensure!( + undeployed_land_block_record.is_locked == false, + Error::::UndeployedLandBlockAlreadyFreezed + ); + + ensure!( + undeployed_land_block_record.undeployed_land_block_type == UndeployedLandBlockType::Transferable, + Error::::UndeployedLandBlockIsNotTransferable + ); + + undeployed_land_block_record.owner = to.clone(); + + UndeployedLandBlocksOwner::::remove(who.clone(), &undeployed_land_block_id); + UndeployedLandBlocksOwner::::insert(to.clone(), &undeployed_land_block_id, ()); + + Self::deposit_event(Event::::UndeployedLandBlockTransferred( + who.clone(), + to.clone(), + undeployed_land_block_id.clone(), + )); + + Ok(undeployed_land_block_id) + }, + ) + } + + /// Internal burn of undeployed land block + fn do_burn_undeployed_land_block( + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + let undeployed_land_block_info = + UndeployedLandBlocks::::get(undeployed_land_block_id).ok_or(Error::::UndeployedLandBlockNotFound)?; + + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), + Error::::UndeployedLandBlockAlreadyInAuction + ); + + ensure!( + !undeployed_land_block_info.is_locked, + Error::::OnlyFrozenUndeployedLandBlockCanBeDestroyed + ); + + Self::set_total_undeployed_land_unit(undeployed_land_block_info.number_land_units as u64, true)?; + UndeployedLandBlocksOwner::::remove(undeployed_land_block_info.owner, &undeployed_land_block_id); + UndeployedLandBlocks::::remove(&undeployed_land_block_id); + + Self::deposit_event(Event::::UndeployedLandBlockBurnt(undeployed_land_block_id.clone())); + + Ok(undeployed_land_block_id) + } + + /// Internal freeze of undeployed land block + fn do_freeze_undeployed_land_block( + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + UndeployedLandBlocks::::try_mutate_exists( + &undeployed_land_block_id, + |undeployed_land_block| -> Result { + let undeployed_land_block_record = undeployed_land_block + .as_mut() + .ok_or(Error::::UndeployedLandBlockNotFound)?; + + ensure!( + undeployed_land_block_record.is_locked == false, + Error::::UndeployedLandBlockAlreadyFreezed + ); + + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), + Error::::UndeployedLandBlockAlreadyInAuction + ); + + undeployed_land_block_record.is_locked = true; + + Self::deposit_event(Event::::UndeployedLandBlockFreezed(undeployed_land_block_id)); + + Ok(undeployed_land_block_id) + }, + ) + } + + /// Internal issue of undeployed land block + fn do_issue_undeployed_land_blocks( + beneficiary: &T::AccountId, + number_of_land_block: u32, + number_land_units_per_land_block: u32, + undeployed_land_block_type: UndeployedLandBlockType, + ) -> Result, DispatchError> { + let mut undeployed_land_block_ids: Vec = Vec::new(); + + // 2 inserts per land blocks + let storage_fee: BalanceOf = + Perbill::from_percent(number_of_land_block.saturating_mul(2).saturating_mul(100)) + * T::StorageDepositFee::get(); + + T::Currency::transfer( + beneficiary, + &T::MetaverseInfoSource::get_network_treasury(), + storage_fee.saturated_into(), + ExistenceRequirement::KeepAlive, + )?; + + for _ in 0..number_of_land_block { + let new_undeployed_land_block_id = Self::get_new_undeployed_land_block_id()?; + + let undeployed_land_block = UndeployedLandBlock { + id: new_undeployed_land_block_id, + number_land_units: number_land_units_per_land_block, + undeployed_land_block_type, + approved: None, + is_locked: false, + owner: beneficiary.clone(), + }; + + UndeployedLandBlocks::::insert(new_undeployed_land_block_id, undeployed_land_block); + UndeployedLandBlocksOwner::::insert(beneficiary.clone(), new_undeployed_land_block_id, ()); + + // Update total undeployed land count + Self::set_total_undeployed_land_unit(number_land_units_per_land_block as u64, false)?; + + Self::deposit_event(Event::::UndeployedLandBlockIssued( + beneficiary.clone(), + new_undeployed_land_block_id.clone(), + )); + + undeployed_land_block_ids.push(new_undeployed_land_block_id); + } + + Ok(undeployed_land_block_ids) + } + + /// Internal transfer of estate + fn do_transfer_estate( + estate_id: EstateId, + from: &T::AccountId, + to: &T::AccountId, + ) -> Result { + EstateOwner::::try_mutate_exists(&estate_id, |estate_owner| -> Result { + //ensure there is record of the estate owner with estate id and account id + ensure!(from != to, Error::::AlreadyOwnTheEstate); + let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; + let estate_info = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; + ensure!( + !EstateLeases::::contains_key(estate_id), + Error::::EstateIsAlreadyLeased + ); + + match estate_owner_value { + OwnerId::Token(class_id, token_id) => { + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::NFT(class_id, token_id)), + Error::::EstateAlreadyInAuction + ); + ensure!( + Self::check_if_land_or_estate_owner(from, &estate_owner_value), + Error::::NoPermission + ); + T::NFTTokenizationSource::transfer_nft(from, to, &(class_id, token_id)); + + Self::deposit_event(Event::::TransferredEstate( + estate_id.clone(), + from.clone(), + to.clone(), + )); + + Ok(estate_id) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + }) + } + + /// Internal transfer of land unit + fn do_transfer_landunit( + coordinate: (i32, i32), + from: &T::AccountId, + to: &T::AccountId, + metaverse_id: MetaverseId, + ) -> Result<(i32, i32), DispatchError> { + LandUnits::::try_mutate_exists( + &metaverse_id, + &coordinate, + |land_unit_owner| -> Result<(i32, i32), DispatchError> { + // ensure there is record of the land unit with bit country id and coordinate + ensure!(land_unit_owner.is_some(), Error::::NoPermission); + ensure!(from != to, Error::::AlreadyOwnTheLandUnit); + match land_unit_owner { + Some(owner) => { + ensure!( + Self::check_if_land_or_estate_owner(from, owner), + Error::::NoPermission + ); + match owner { + OwnerId::Token(class_id, token_id) => { + ensure!( + !T::AuctionHandler::check_item_in_auction(ItemId::NFT(*class_id, *token_id)), + Error::::LandUnitAlreadyInAuction + ); + + T::NFTTokenizationSource::transfer_nft(from, to, &(*class_id, *token_id)); + // Update + Self::deposit_event(Event::::TransferredLandUnit( + metaverse_id.clone(), + coordinate.clone(), + from.clone(), + to.clone(), + )); + + Ok(coordinate) + } + _ => Err(Error::::InvalidOwnerValue.into()), + } + } + None => Err(DispatchError::Other("No Permissson")), + } + }, + ) + } + + /// Internal setting of total undeployed land units + fn set_total_undeployed_land_unit(total: u64, deduct: bool) -> Result<(), DispatchError> { + let total_undeployed_land_units = Self::all_undeployed_land_unit(); + + if deduct { + let new_total_undeployed_land_unit_count = total_undeployed_land_units + .checked_sub(total) + .ok_or("Overflow deducting new count to total undeployed lands")?; + TotalUndeployedLandUnit::::put(new_total_undeployed_land_unit_count); + } else { + let new_total_undeployed_land_unit_count = total_undeployed_land_units + .checked_add(total) + .ok_or("Overflow adding new count to total undeployed lands")?; + TotalUndeployedLandUnit::::put(new_total_undeployed_land_unit_count); + } + + Ok(()) + } + + /// Internal setting of total land units + fn set_total_land_unit(total: u64, deduct: bool) -> Result<(), DispatchError> { + let total_land_units_count = Self::all_land_units_count(); + + if deduct { + let new_total_land_units_count = total_land_units_count + .checked_sub(total) + .ok_or("Overflow deducting new count to total lands")?; + AllLandUnitsCount::::put(new_total_land_units_count); + } else { + let new_total_land_units_count = total_land_units_count + .checked_add(total) + .ok_or("Overflow adding new count to total lands")?; + AllLandUnitsCount::::put(new_total_land_units_count); + } + Ok(()) + } + + /// Internal getter of land token properties + fn get_land_token_properties(metaverse_id: MetaverseId, coordinate: (i32, i32)) -> (NftMetadata, Attributes) { + let mut land_coordinate_attribute = Vec::::new(); + land_coordinate_attribute.append(&mut coordinate.0.to_be_bytes().to_vec()); + land_coordinate_attribute.append(&mut coordinate.1.to_be_bytes().to_vec()); + + let mut nft_metadata: NftMetadata = NftMetadata::new(); + nft_metadata.append(&mut land_coordinate_attribute.clone()); + + let mut nft_attributes: Attributes = Attributes::new(); + nft_attributes.insert("MetaverseId:".as_bytes().to_vec(), metaverse_id.to_be_bytes().to_vec()); + nft_attributes.insert("Coordinate:".as_bytes().to_vec(), land_coordinate_attribute); + + return (nft_metadata, nft_attributes); + } + + /// Internal getter of estate token properties + fn get_estate_token_properties(metaverse_id: MetaverseId, estate_id: EstateId) -> (NftMetadata, Attributes) { + let mut nft_metadata: NftMetadata = NftMetadata::new(); + nft_metadata.append(&mut metaverse_id.to_be_bytes().to_vec()); + nft_metadata.append(&mut estate_id.to_be_bytes().to_vec()); + + let mut nft_attributes: Attributes = Attributes::new(); + nft_attributes.insert("MetaverseId:".as_bytes().to_vec(), metaverse_id.to_be_bytes().to_vec()); + nft_attributes.insert("Estate Id:".as_bytes().to_vec(), estate_id.to_be_bytes().to_vec()); + + return (nft_metadata, nft_attributes); + } + + fn check_if_land_or_estate_owner(who: &T::AccountId, owner_id: &OwnerId) -> bool { + match owner_id { + OwnerId::Token(class_id, token_id) => { + return T::NFTTokenizationSource::check_ownership(who, &(*class_id, *token_id)).unwrap_or(false); + } + _ => return false, + } + } + + fn verify_land_unit_for_estate(land_units: Vec<(i32, i32)>) -> bool { + if land_units.len() == 1 { + return false; + } + + let mut vec_axis = land_units.iter().map(|lu| lu.0).collect::>(); + let mut vec_yaxis = land_units.iter().map(|lu| lu.1).collect::>(); + + // Sort by ascending and dedup + vec_axis.sort(); + vec_axis.dedup(); + vec_yaxis.sort(); + vec_yaxis.dedup(); + + let mut is_axis_valid = true; + let mut is_yaxis_valid = true; + + // Ensure axis is next to each other + for (i, axis) in vec_axis.iter().enumerate() { + if axis != &vec_axis[i] { + let valid = axis.saturating_sub(vec_axis[i + 1]); + if valid != 1 { + is_axis_valid = false; + break; + } + } + } + + // Ensure yaxis is next to each other + for (i, yaxis) in vec_yaxis.iter().enumerate() { + if yaxis != &vec_yaxis[i] { + let valid = yaxis.saturating_sub(vec_yaxis[i + 1]); + if valid != 1 { + is_yaxis_valid = false; + break; + } + } + } + + is_axis_valid && is_yaxis_valid + } + + fn verify_land_unit_in_bound(block_coordinate: &(i32, i32), land_unit_coordinates: &Vec<(i32, i32)>) -> bool { + let mut vec_axis = land_unit_coordinates.iter().map(|lu| lu.0).collect::>(); + let mut vec_yaxis = land_unit_coordinates.iter().map(|lu| lu.1).collect::>(); + + let max_axis = vec_axis.iter().max().unwrap_or(&i32::MAX); + let max_yaxis = vec_yaxis.iter().max().unwrap_or(&i32::MAX); + let min_axis = vec_axis.iter().min().unwrap_or(&i32::MIN); + let min_yaxis = vec_yaxis.iter().min().unwrap_or(&i32::MIN); + + let top_left_axis = block_coordinate + .0 + .saturating_mul(100i32) + .saturating_sub(50i32) + .saturating_div(10i32) + .saturating_add(1i32); + let top_right_axis = block_coordinate + .0 + .saturating_mul(100i32) + .saturating_add(50i32) + .saturating_div(10i32); + let top_left_yaxis = block_coordinate + .1 + .saturating_mul(100i32) + .saturating_add(50i32) + .saturating_div(10i32); + let top_right_yaxis = block_coordinate + .1 + .saturating_mul(100i32) + .saturating_sub(50i32) + .saturating_div(10i32) + .saturating_add(1i32); + + top_left_axis <= *min_axis + && top_right_axis >= *max_axis + && top_left_yaxis >= *max_yaxis + && top_right_yaxis <= *min_yaxis + } + + /// Remove all land unit and estate + pub fn remove_all_estate_storage() -> Weight { + log::info!("Start removing all land unit and estates"); + LandUnits::::remove_all(None); + Estates::::remove_all(None); + EstateOwner::::remove_all(None); + NextEstateId::::put(1); + AllLandUnitsCount::::put(0); + AllEstatesCount::::put(0); + Weight::from_ref_time(0) + } + + fn collect_network_fee( + recipient: &T::AccountId, + // social_currency_id: FungibleTokenId, + ) -> DispatchResult { + let network_fund = T::MetaverseInfoSource::get_network_treasury(); + //if social_currency_id == FungibleTokenId::NativeToken(0) { + ::Currency::transfer( + &recipient, + &network_fund, + T::NetworkFee::get(), + ExistenceRequirement::KeepAlive, + )?; + // } else { + // T::FungibleTokenCurrency::transfer( + // social_currency_id.clone(), + // &recipient, + // &network_fund, + // T::NetworkFee::get(), + //)?; + //} + Ok(()) + } +} + +impl MetaverseLandTrait for Pallet { + fn get_user_land_units(who: &T::AccountId, metaverse_id: &MetaverseId) -> Vec<(i32, i32)> { + // Check land units owner. + let mut total_land_units: Vec<(i32, i32)> = Vec::default(); + + let land_in_metaverse = LandUnits::::iter_prefix(metaverse_id) + .filter(|(_, owner)| Self::check_if_land_or_estate_owner(who, owner)) + .collect::>(); + + for land_unit in land_in_metaverse { + let land = land_unit.0; + total_land_units.push(land); + } + + total_land_units + } + + fn is_user_own_metaverse_land(who: &T::AccountId, metaverse_id: &MetaverseId) -> bool { + Self::get_user_land_units(&who, metaverse_id).len() > 0 + } + + fn check_landunit(metaverse_id: MetaverseId, coordinate: (i32, i32)) -> Result { + Ok(LandUnits::::contains_key(metaverse_id, coordinate)) + } +} + +impl UndeployedLandBlocksTrait for Pallet { + fn issue_undeployed_land_blocks( + beneficiary: &T::AccountId, + number_of_land_block: u32, + number_land_units_per_land_block: u32, + undeployed_land_block_type: UndeployedLandBlockType, + ) -> Result, DispatchError> { + let new_undeployed_land_block_id = Self::do_issue_undeployed_land_blocks( + &beneficiary, + number_of_land_block, + number_land_units_per_land_block, + undeployed_land_block_type, + )?; + + Ok(new_undeployed_land_block_id) + } + + fn transfer_undeployed_land_block( + who: &T::AccountId, + to: &T::AccountId, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + Self::do_transfer_undeployed_land_block(who, to, undeployed_land_block_id)?; + + Ok(undeployed_land_block_id) + } + + fn burn_undeployed_land_block( + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + let undeployed_land_block_id = Self::do_burn_undeployed_land_block(undeployed_land_block_id)?; + + Ok(undeployed_land_block_id) + } + + fn freeze_undeployed_land_block( + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + let undeployed_land_block_id = Self::do_freeze_undeployed_land_block(undeployed_land_block_id)?; + + Ok(undeployed_land_block_id) + } + + fn check_undeployed_land_block( + owner: &T::AccountId, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + let undeployed_land_block = + Self::get_undeployed_land_block(undeployed_land_block_id).ok_or(Error::::UndeployedLandBlockNotFound)?; + + if undeployed_land_block.is_locked + || undeployed_land_block.undeployed_land_block_type == UndeployedLandBlockType::BoundToAddress + || undeployed_land_block.owner != *owner + { + return Ok(false); + } + return Ok(true); + } +} + +impl Estate for Pallet { + fn transfer_estate(estate_id: EstateId, from: &T::AccountId, to: &T::AccountId) -> Result { + let estate_id = Self::do_transfer_estate(estate_id, from, to)?; + Ok(estate_id) + } + + fn transfer_landunit( + coordinate: (i32, i32), + from: &T::AccountId, + to: &(T::AccountId, MetaverseId), + ) -> Result<(i32, i32), DispatchError> { + let coordinate = Self::do_transfer_landunit(coordinate, from, &(to).0, to.1)?; + Ok(coordinate) + } + + fn transfer_undeployed_land_block( + who: &T::AccountId, + to: &T::AccountId, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + let undeployed_land_block_id = Self::do_transfer_undeployed_land_block(who, to, undeployed_land_block_id)?; + + Ok(undeployed_land_block_id) + } + + fn check_estate(estate_id: EstateId) -> Result { + Ok(Estates::::contains_key(estate_id)) + } + + fn check_landunit(metaverse_id: MetaverseId, coordinate: (i32, i32)) -> Result { + Ok(LandUnits::::contains_key(metaverse_id, coordinate)) + } + + fn check_undeployed_land_block( + owner: &T::AccountId, + undeployed_land_block_id: UndeployedLandBlockId, + ) -> Result { + let undeployed_land_block = + Self::get_undeployed_land_block(undeployed_land_block_id).ok_or(Error::::UndeployedLandBlockNotFound)?; + + if undeployed_land_block.is_locked + || undeployed_land_block.undeployed_land_block_type == UndeployedLandBlockType::BoundToAddress + || undeployed_land_block.owner != *owner + { + return Ok(false); + } + return Ok(true); + } + + fn get_total_land_units(estate_id: Option) -> u64 { + match estate_id { + Some(id) => { + if let Some(estate_info) = Estates::::get(id) { + estate_info.land_units.len() as u64 + } else { + 0 + } + } + None => AllLandUnitsCount::::get(), + } + } + + fn get_total_undeploy_land_units() -> u64 { + TotalUndeployedLandUnit::::get() + } + + fn check_estate_ownership(owner: T::AccountId, estate_id: EstateId) -> Result { + let owner_value = Self::get_estate_owner(estate_id); + match owner_value { + Some(token_value) => match token_value { + OwnerId::Token(c, t) => T::NFTTokenizationSource::check_ownership(&owner, &(c, t)), + OwnerId::Account(_) => Err(Error::::InvalidOwnerValue.into()), + }, + None => Ok(false), + } + } + + fn is_estate_leasor(leasor: T::AccountId, estate_id: EstateId) -> Result { + Ok(EstateLeasors::::contains_key(leasor, estate_id)) + } + + fn is_estate_leased(estate_id: EstateId) -> Result { + Ok(EstateLeases::::contains_key(estate_id)) + } +} diff --git a/pallets/land-minting/src/mock.rs b/pallets/land-minting/src/mock.rs new file mode 100644 index 000000000..3856a489b --- /dev/null +++ b/pallets/land-minting/src/mock.rs @@ -0,0 +1,637 @@ +#![cfg(test)] + +use frame_support::{construct_runtime, ord_parameter_types, parameter_types, PalletId}; +use frame_system::EnsureSignedBy; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{ConvertInto, IdentityLookup}, + DispatchError, Perbill, +}; +use sp_std::collections::btree_map::BTreeMap; +use sp_std::default::Default; +use sp_std::vec::Vec; + +use auction_manager::{Auction, AuctionInfo, AuctionItem, AuctionType, CheckAuctionItemHandler, ListingLevel}; +use core_primitives::{CollectionType, NftClassData, TokenType}; +use primitives::{ + AssetId, Attributes, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, NftMetadata, TokenId, LAND_CLASS_ID, +}; + +use crate as estate; + +use super::*; + +pub type AccountId = u128; +pub type Balance = u128; +pub type MetaverseId = u64; +pub type BlockNumber = u64; +pub type EstateId = u64; + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 5; +pub const CHARLIE: AccountId = 6; +pub const DOM: AccountId = 7; +pub const BENEFICIARY_ID: AccountId = 99; +pub const AUCTION_BENEFICIARY_ID: AccountId = 100; +pub const CLASS_FUND_ID: AccountId = 123; +pub const METAVERSE_ID: MetaverseId = 0; +pub const DOLLARS: Balance = 1_000_000_000_000_000_000; +pub const ALICE_METAVERSE_ID: MetaverseId = 1; +pub const BOB_METAVERSE_ID: MetaverseId = 2; +pub const MAX_BOUND: (i32, i32) = (-100, 100); +pub const LANDBLOCK_COORDINATE: (i32, i32) = (0, 0); +pub const COORDINATE_IN_1: (i32, i32) = (-4, 4); +pub const COORDINATE_IN_2: (i32, i32) = (-4, 5); +pub const COORDINATE_IN_3: (i32, i32) = (-4, 6); +pub const COORDINATE_IN_4: (i32, i32) = (-4, 8); +pub const COORDINATE_OUT: (i32, i32) = (0, 101); +pub const COORDINATE_IN_AUCTION: (i32, i32) = (-4, 7); +pub const ESTATE_IN_AUCTION: EstateId = 3; +pub const UNDEPLOYED_LAND_BLOCK_IN_AUCTION: UndeployedLandBlockId = 1; + +pub const BOND_AMOUNT_1: Balance = 1000; +pub const BOND_AMOUNT_2: Balance = 2000; +pub const BOND_AMOUNT_BELOW_MINIMUM: Balance = 100; +pub const BOND_LESS_AMOUNT_1: Balance = 100; + +pub const ESTATE_ID: EstateId = 0; + +pub const ASSET_ID_1: TokenId = 101; +pub const ASSET_ID_2: TokenId = 100; +pub const ASSET_CLASS_ID: ClassId = 5; +pub const ASSET_TOKEN_ID: TokenId = 6; +pub const ASSET_COLLECTION_ID: GroupCollectionId = 7; +pub const METAVERSE_LAND_CLASS: ClassId = 15; +pub const METAVERSE_LAND_IN_AUCTION_TOKEN: TokenId = 4; +pub const METAVERSE_ESTATE_CLASS: ClassId = 16; +pub const METAVERSE_ESTATE_IN_AUCTION_TOKEN: TokenId = 3; + +pub const OWNER_ACCOUNT_ID: OwnerId = OwnerId::Account(BENEFICIARY_ID); +pub const OWNER_ID_ALICE: OwnerId = OwnerId::Account(ALICE); +pub const OWNER_LAND_ASSET_ID: OwnerId = OwnerId::Token(METAVERSE_LAND_CLASS, ASSET_ID_1); +pub const OWNER_ESTATE_ASSET_ID: OwnerId = + OwnerId::Token(METAVERSE_ESTATE_CLASS, ASSET_ID_2); + +pub const GENERAL_METAVERSE_FUND: AccountId = 102; + +ord_parameter_types! { + pub const One: AccountId = ALICE; +} + +// Configure a mock runtime to test the pallet. + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxLocks = (); + type WeightInfo = (); + type MaxReserves = (); + type ReserveIdentifier = (); +} + +// pub type AdaptedBasicCurrency = +// currencies::BasicCurrencyAdapter; + +parameter_types! { + pub const GetNativeCurrencyId: FungibleTokenId = FungibleTokenId::NativeToken(0); + pub const MiningCurrencyId: FungibleTokenId = FungibleTokenId::MiningResource(0); + pub const LandTreasuryPalletId: PalletId = PalletId(*b"bit/land"); + pub const MinimumLandPrice: Balance = 10 * DOLLARS; +} + +pub struct MetaverseInfoSource {} + +impl MetaverseTrait for MetaverseInfoSource { + fn create_metaverse(who: &AccountId, metadata: MetaverseMetadata) -> MetaverseId { + 1u64 + } + + fn check_ownership(who: &AccountId, metaverse_id: &MetaverseId) -> bool { + match *who { + ALICE => *metaverse_id == ALICE_METAVERSE_ID, + BOB => *metaverse_id == BOB_METAVERSE_ID, + _ => false, + } + } + + fn get_metaverse(_metaverse_id: u64) -> Option> { + None + } + + fn get_metaverse_token(_metaverse_id: u64) -> Option { + None + } + + fn update_metaverse_token(_metaverse_id: u64, _currency_id: FungibleTokenId) -> Result<(), DispatchError> { + Ok(()) + } + + fn get_metaverse_land_class(metaverse_id: MetaverseId) -> Result { + Ok(METAVERSE_LAND_CLASS) + } + + fn get_metaverse_estate_class(metaverse_id: MetaverseId) -> Result { + Ok(METAVERSE_ESTATE_CLASS) + } + + fn get_metaverse_marketplace_listing_fee(metaverse_id: MetaverseId) -> Result { + Ok(Perbill::from_percent(1u32)) + } + + fn get_metaverse_treasury(metaverse_id: MetaverseId) -> AccountId { + GENERAL_METAVERSE_FUND + } + + fn get_network_treasury() -> AccountId { + GENERAL_METAVERSE_FUND + } + + fn check_if_metaverse_estate( + metaverse_id: primitives::MetaverseId, + class_id: &ClassId, + ) -> Result { + if class_id == &METAVERSE_LAND_CLASS || class_id == &METAVERSE_ESTATE_CLASS { + return Ok(true); + } + return Ok(false); + } + + fn check_if_metaverse_has_any_land(_metaverse_id: primitives::MetaverseId) -> Result { + Ok(true) + } + + fn is_metaverse_owner(who: &AccountId) -> bool { + true + } +} + +pub struct MockAuctionManager; + +impl Auction for MockAuctionManager { + type Balance = Balance; + + fn auction_info(_id: u64) -> Option> { + None + } + + fn auction_item(id: AuctionId) -> Option> { + None + } + + fn update_auction(_id: u64, _info: AuctionInfo) -> DispatchResult { + Ok(()) + } + + fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { + Ok(()) + } + + fn new_auction( + _recipient: u128, + _initial_amount: Self::Balance, + _start: u64, + _end: Option, + ) -> Result { + Ok(1) + } + + fn create_auction( + _auction_type: AuctionType, + _item_id: ItemId, + _end: Option, + _recipient: u128, + _initial_amount: Self::Balance, + _start: u64, + _listing_level: ListingLevel, + _listing_fee: Perbill, + _currency_id: FungibleTokenId, + ) -> Result { + Ok(1) + } + + fn remove_auction(_id: u64, _item_id: ItemId) {} + + fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { + Ok(()) + } + + fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { + Ok(()) + } + + fn local_auction_bid_handler( + _now: u64, + _id: u64, + _new_bid: (u128, Self::Balance), + _last_bid: Option<(u128, Self::Balance)>, + _social_currency_id: FungibleTokenId, + ) -> DispatchResult { + Ok(()) + } + + fn collect_royalty_fee( + _high_bid_price: &Self::Balance, + _high_bidder: &u128, + _asset_id: &(u32, u64), + _social_currency_id: FungibleTokenId, + ) -> DispatchResult { + Ok(()) + } +} + +impl CheckAuctionItemHandler for MockAuctionManager { + fn check_item_in_auction(item_id: ItemId) -> bool { + match item_id { + ItemId::NFT(METAVERSE_LAND_CLASS, METAVERSE_LAND_IN_AUCTION_TOKEN) => { + return true; + } + ItemId::NFT(METAVERSE_ESTATE_CLASS, METAVERSE_ESTATE_IN_AUCTION_TOKEN) => { + return true; + } + ItemId::UndeployedLandBlock(UNDEPLOYED_LAND_BLOCK_IN_AUCTION) => { + return true; + } + _ => { + return false; + } + } + } +} + +fn test_attributes(x: u8) -> Attributes { + let mut attr: Attributes = BTreeMap::new(); + attr.insert(vec![x, x + 5], vec![x, x + 10]); + attr +} + +pub struct MockNFTHandler; + +impl NFTTrait for MockNFTHandler { + type TokenId = TokenId; + type ClassId = ClassId; + + fn check_ownership(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Result { + let nft_value = *asset_id; + if (*who == ALICE && (nft_value.1 == 1 || nft_value.1 == 3)) + || (*who == BOB && (nft_value.1 == 2 || nft_value.1 == 4)) + || (*who == BENEFICIARY_ID && (nft_value.1 == 100 || nft_value.1 == 101)) + | (*who == AUCTION_BENEFICIARY_ID + && (nft_value.1 == METAVERSE_ESTATE_IN_AUCTION_TOKEN + || nft_value.1 == METAVERSE_LAND_IN_AUCTION_TOKEN)) + { + return Ok(true); + } + Ok(false) + } + + fn check_collection_and_class( + collection_id: GroupCollectionId, + class_id: Self::ClassId, + ) -> Result { + if class_id == ASSET_CLASS_ID && collection_id == ASSET_COLLECTION_ID { + return Ok(true); + } + Ok(false) + } + fn get_nft_group_collection(nft_collection: &Self::ClassId) -> Result { + Ok(ASSET_COLLECTION_ID) + } + + fn is_stackable(asset_id: (Self::ClassId, Self::TokenId)) -> Result { + Ok(false) + } + + fn create_token_class( + sender: &AccountId, + metadata: NftMetadata, + attributes: Attributes, + collection_id: GroupCollectionId, + token_type: TokenType, + collection_type: CollectionType, + royalty_fee: Perbill, + mint_limit: Option, + ) -> Result { + match *sender { + ALICE => { + if collection_id == 0 { + Ok(0) + } else if collection_id == 1 { + Ok(1) + } else { + Ok(2) + } + } + BOB => Ok(3), + BENEFICIARY_ID => Ok(ASSET_CLASS_ID), + _ => Ok(100), + } + } + + fn mint_token( + sender: &AccountId, + class_id: ClassId, + metadata: NftMetadata, + attributes: Attributes, + ) -> Result { + match *sender { + ALICE => Ok(1), + BOB => Ok(2), + BENEFICIARY_ID => { + if class_id == METAVERSE_LAND_CLASS { + return Ok(ASSET_ID_1); + } else if class_id == METAVERSE_ESTATE_CLASS { + return Ok(ASSET_ID_2); + } else { + return Ok(200); + } + } + AUCTION_BENEFICIARY_ID => { + if class_id == METAVERSE_LAND_CLASS { + return Ok(METAVERSE_LAND_IN_AUCTION_TOKEN); + } else if class_id == METAVERSE_ESTATE_CLASS { + return Ok(METAVERSE_ESTATE_IN_AUCTION_TOKEN); + } else { + return Ok(201); + } + } + _ => { + if class_id == 0 { + return Ok(1000); + } else { + return Ok(1001); + } + } + } + } + + fn transfer_nft(from: &AccountId, to: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + Ok(()) + } + + fn check_item_on_listing(class_id: Self::ClassId, token_id: Self::TokenId) -> Result { + Ok(true) + } + + fn burn_nft(account: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + Ok(()) + } + fn is_transferable(nft: &(Self::ClassId, Self::TokenId)) -> Result { + Ok(true) + } + + fn get_class_fund(class_id: &Self::ClassId) -> AccountId { + CLASS_FUND_ID + } + + fn get_nft_detail(asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { + let new_data = NftClassData { + deposit: 0, + attributes: test_attributes(1), + token_type: TokenType::Transferable, + collection_type: CollectionType::Collectable, + is_locked: false, + royalty_fee: Perbill::from_percent(0u32), + mint_limit: None, + total_minted_tokens: 0u32, + }; + Ok(new_data) + } + + fn set_lock_collection(class_id: Self::ClassId, is_locked: bool) -> sp_runtime::DispatchResult { + Ok(()) + } + + fn set_lock_nft(token_id: (Self::ClassId, Self::TokenId), is_locked: bool) -> sp_runtime::DispatchResult { + Ok(()) + } + + fn get_nft_class_detail(_class_id: Self::ClassId) -> Result, DispatchError> { + let new_data = NftClassData { + deposit: 0, + attributes: test_attributes(1), + token_type: TokenType::Transferable, + collection_type: CollectionType::Collectable, + is_locked: false, + royalty_fee: Perbill::from_percent(0u32), + mint_limit: None, + total_minted_tokens: 0u32, + }; + Ok(new_data) + } + + fn get_total_issuance(class_id: Self::ClassId) -> Result { + Ok(10u64) + } + + fn get_asset_owner(asset_id: &(Self::ClassId, Self::TokenId)) -> Result { + Ok(ALICE) + } + + fn mint_token_with_id( + sender: &AccountId, + class_id: Self::ClassId, + token_id: Self::TokenId, + metadata: core_primitives::NftMetadata, + attributes: core_primitives::Attributes, + ) -> Result { + match *sender { + ALICE => Ok(1), + BOB => Ok(2), + BENEFICIARY_ID => { + if class_id == METAVERSE_LAND_CLASS { + return Ok(ASSET_ID_1); + } else if class_id == METAVERSE_ESTATE_CLASS { + return Ok(ASSET_ID_2); + } else { + return Ok(200); + } + } + AUCTION_BENEFICIARY_ID => { + if class_id == METAVERSE_LAND_CLASS { + return Ok(METAVERSE_LAND_IN_AUCTION_TOKEN); + } else if class_id == METAVERSE_ESTATE_CLASS { + return Ok(METAVERSE_ESTATE_IN_AUCTION_TOKEN); + } else { + return Ok(201); + } + } + _ => { + if class_id == 0 { + return Ok(1000); + } else { + return Ok(1001); + } + } + } + } + + fn get_free_stackable_nft_balance(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { + 1000 + } + + fn reserve_stackable_nft_balance( + who: &AccountId, + asset_id: &(Self::ClassId, Self::TokenId), + amount: Balance, + ) -> DispatchResult { + Ok(()) + } + + fn unreserve_stackable_nft_balance( + who: &AccountId, + asset_id: &(Self::ClassId, Self::TokenId), + amount: Balance, + ) -> sp_runtime::DispatchResult { + Ok(()) + } + + fn transfer_stackable_nft( + sender: &AccountId, + to: &AccountId, + nft: &(Self::ClassId, Self::TokenId), + amount: Balance, + ) -> sp_runtime::DispatchResult { + Ok(()) + } +} + +parameter_types! { + pub const MinBlocksPerRound: u32 = 10; + pub const MinimumStake: Balance = 200; + /// Reward payments are delayed by 2 hours (2 * 300 * block_time) + pub const RewardPaymentDelay: u32 = 2; + pub const DefaultMaxBound: (i32,i32) = MAX_BOUND; + pub const NetworkFee: Balance = 1; // Network fee + pub const MaxOffersPerEstate: u32 = 2; + pub const MinLeasePricePerBlock: Balance = 1u128; + pub const MaxLeasePeriod: u32 = 9; + pub const LeaseOfferExpiryPeriod: u32 = 6; + pub StorageDepositFee: Balance = 1; +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type LandTreasury = LandTreasuryPalletId; + type MetaverseInfoSource = MetaverseInfoSource; + type Currency = Balances; + type MinimumLandPrice = MinimumLandPrice; + type CouncilOrigin = EnsureSignedBy; + type AuctionHandler = MockAuctionManager; + type MinBlocksPerRound = MinBlocksPerRound; + type WeightInfo = (); + type MinimumStake = MinimumStake; + type RewardPaymentDelay = RewardPaymentDelay; + type NFTTokenizationSource = MockNFTHandler; + type DefaultMaxBound = DefaultMaxBound; + type NetworkFee = NetworkFee; + type MaxOffersPerEstate = MaxOffersPerEstate; + type MinLeasePricePerBlock = MinLeasePricePerBlock; + type MaxLeasePeriod = MaxLeasePeriod; + type LeaseOfferExpiryPeriod = LeaseOfferExpiryPeriod; + type BlockNumberToBalance = ConvertInto; + type StorageDepositFee = StorageDepositFee; +} + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Estate: estate:: {Pallet, Call, Storage, Event}, + } +); + +pub type EstateModule = Pallet; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub struct ExtBuilder; + +impl Default for ExtBuilder { + fn default() -> Self { + ExtBuilder + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, 100000), (BOB, 100000), (BENEFICIARY_ID, 1000000)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +pub fn last_event() -> RuntimeEvent { + frame_system::Pallet::::events() + .pop() + .expect("Event expected") + .event +} + +fn next_block() { + EstateModule::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + EstateModule::on_initialize(System::block_number()); +} + +pub fn run_to_block(n: u64) { + while System::block_number() < n { + next_block(); + } +} diff --git a/pallets/land-minting/src/rate.rs b/pallets/land-minting/src/rate.rs new file mode 100644 index 000000000..747a72029 --- /dev/null +++ b/pallets/land-minting/src/rate.rs @@ -0,0 +1,167 @@ +// This file is part of Metaverse.Network & Bit.Country. + +// Copyright (C) 2020-2022 Metaverse.Network & Bit.Country . +// 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. + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_runtime::{Perbill, RuntimeDebug}; + +use crate::{AllLandUnitsCount, TotalUndeployedLandUnit}; +// Helper methods to compute the issuance rate for undeployed land. +use crate::pallet::{Config, Pallet}; + +const SECONDS_PER_YEAR: u32 = 31557600; +const SECONDS_PER_BLOCK: u32 = 12; +const BLOCKS_PER_YEAR: u32 = SECONDS_PER_YEAR / SECONDS_PER_BLOCK; + +fn rounds_per_year() -> u32 { + let blocks_per_round = >::round().length; + BLOCKS_PER_YEAR / blocks_per_round +} + +fn get_annual_max_issuance(max_supply: u64, annual_percentage: u64) -> u64 { + let total_land_unit_circulating = >::get(); + let total_undeployed_land_unit_circulating = >::get(); + let circulating = total_land_unit_circulating.saturating_add(total_undeployed_land_unit_circulating); + max_supply.saturating_sub(circulating).saturating_mul(annual_percentage) +} + +/// Compute round issuance range from round inflation range and current total issuance +pub fn round_issuance_range(config: MintingRateInfo) -> Range { + // Get total round per year + let total_round_per_year = rounds_per_year::(); + + // Get total land unit circulating + let total_land_unit_circulating = >::get(); + + // Get total undeployed land unit circulating + let total_undeployed_land_unit_circulating = >::get(); + + // Total circulating + let circulating = total_land_unit_circulating.saturating_add(total_undeployed_land_unit_circulating); + + // Total annual minting percent + let annual_percentage = Perbill::from_percent(config.annual as u32).deconstruct(); + + // Round percentage minting rate + let round_percentage = annual_percentage.checked_div(total_round_per_year).unwrap(); + + // Convert to percentage + let round_percentage_per_bill = Perbill::from_parts(round_percentage); + + // Return range - could implement more cases in the future. + Range { + min: round_percentage_per_bill * circulating, + ideal: round_percentage_per_bill * circulating, + max: round_percentage_per_bill * circulating, + } +} + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +pub struct Range { + pub min: T, + pub ideal: T, + pub max: T, +} + +impl Range { + pub fn is_valid(&self) -> bool { + self.max >= self.ideal && self.ideal >= self.min + } +} + +impl From for Range { + fn from(other: T) -> Range { + Range { + min: other, + ideal: other, + max: other, + } + } +} + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +pub struct MintingRateInfo { + /// Number of metaverse expectations + pub expect: Range, + /// Annual minting range + pub annual: u64, + /// Max total supply + pub max: u64, +} + +impl MintingRateInfo { + pub fn new(annual: u64, expect: Range, max: u64) -> MintingRateInfo { + MintingRateInfo { expect, annual, max } + } + + /// Set minting rate expectations + pub fn set_expectations(&mut self, expect: Range) { + self.expect = expect; + } + + /// Set minting rate expectations + pub fn set_max(&mut self, max: u64) { + self.max = max; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Compute round issuance range from round inflation range and current total issuance + pub fn mock_round_issuance_per_year( + config: MintingRateInfo, + circulation: u64, + total_round_per_year: u32, + ) -> Range { + let annual_percentage = Perbill::from_percent(config.annual as u32).deconstruct(); + let round_percentage = annual_percentage.checked_div(total_round_per_year).unwrap(); + + let round_percentage_per_bill = Perbill::from_parts(round_percentage); + + Range { + min: round_percentage_per_bill * circulation, + ideal: round_percentage_per_bill * circulation, + max: round_percentage_per_bill * circulation, + } + } + + #[test] + fn simple_round_issuance() { + // 5% minting rate for 100_000 land unit = 100 minted over the year + // let's assume there are 10 periods in a year + // => mint 100 over 10 periods => 10 minted per period + + let mock_config: MintingRateInfo = MintingRateInfo { + expect: Default::default(), + annual: 5, + max: 100_000, + }; + + let round_issuance = mock_round_issuance_per_year(mock_config, 2_000, 10); + + // make sure 10 land unit deploy per period + assert_eq!(round_issuance.min, 10); + assert_eq!(round_issuance.ideal, 10); + assert_eq!(round_issuance.max, 10); + } +} diff --git a/pallets/land-minting/src/tests.rs b/pallets/land-minting/src/tests.rs new file mode 100644 index 000000000..d597baa73 --- /dev/null +++ b/pallets/land-minting/src/tests.rs @@ -0,0 +1,2908 @@ +// This file is part of Metaverse.Network & Bit.Country. + +// Copyright (C) 2020-2022 Metaverse.Network & Bit.Country . +// 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. + +#![cfg(test)] + +use frame_support::{assert_err, assert_noop, assert_ok}; +use sp_runtime::traits::BadOrigin; + +use mock::{RuntimeEvent, *}; + +use super::*; + +fn estate_sub_account(estate_id: mock::EstateId) -> AccountId { + ::LandTreasury::get().into_sub_account_truncating(estate_id) +} + +#[test] +fn mint_land_should_reject_non_root() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::mint_land( + RuntimeOrigin::signed(ALICE), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + ), + BadOrigin + ); + }); +} + +#[test] +fn mint_land_should_work_with_one_coordinate() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( + OWNER_LAND_ASSET_ID, + METAVERSE_ID, + COORDINATE_IN_1, + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 1); + }); +} + +#[test] +fn mint_land_token_should_work_have_correct_owner() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), None); + + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( + OWNER_LAND_ASSET_ID, + METAVERSE_ID, + COORDINATE_IN_1, + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 1); + + assert_eq!( + EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), + Some(OWNER_LAND_ASSET_ID) + ); + }); +} + +#[test] +fn mint_land_should_reject_with_duplicate_coordinates() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( + OWNER_LAND_ASSET_ID, + METAVERSE_ID, + COORDINATE_IN_1, + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 1); + assert_noop!( + EstateModule::mint_land(RuntimeOrigin::root(), BENEFICIARY_ID, METAVERSE_ID, COORDINATE_IN_1), + Error::::LandUnitIsNotAvailable + ); + }); +} + +#[test] +fn mint_lands_should_reject_with_duplicate_coordinates() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_lands( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandsMinted( + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2], + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 2); + assert_noop!( + EstateModule::mint_lands( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + ), + Error::::LandUnitIsNotAvailable + ); + }); +} + +#[test] +fn mint_land_should_work_with_different_coordinate() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( + OWNER_LAND_ASSET_ID, + METAVERSE_ID, + COORDINATE_IN_1, + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 1); + + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_2 + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( + OWNER_LAND_ASSET_ID, + METAVERSE_ID, + COORDINATE_IN_2, + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 2); + }); +} + +#[test] +fn mint_lands_should_reject_non_root() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::mint_lands( + RuntimeOrigin::signed(ALICE), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + ), + BadOrigin + ); + }); +} + +#[test] +fn mint_lands_should_work_with_one_coordinate() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_lands( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + )); + + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 1 + ); + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandsMinted( + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1], + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 1); + }); +} + +#[test] +fn mint_lands_should_work_with_more_than_one_coordinate() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_lands( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::NewLandsMinted( + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2], + )) + ); + + assert_eq!(EstateModule::all_land_units_count(), 2); + }); +} + +#[test] +fn transfer_land_token_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + assert_eq!( + EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), + Some(OWNER_LAND_ASSET_ID) + ); + + assert_ok!(EstateModule::transfer_land( + RuntimeOrigin::signed(BENEFICIARY_ID), + ALICE, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_eq!( + EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), + Some(OWNER_LAND_ASSET_ID) + ); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::TransferredLandUnit( + METAVERSE_ID, + COORDINATE_IN_1, + BENEFICIARY_ID, + ALICE, + )) + ); + }); +} + +#[test] +fn transfer_land_should_reject_no_permission() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_eq!( + EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), + Some(OWNER_LAND_ASSET_ID) + ); + + assert_noop!( + EstateModule::transfer_land(RuntimeOrigin::signed(BOB), ALICE, METAVERSE_ID, COORDINATE_IN_1), + Error::::NoPermission + ); + }); +} + +#[test] +fn transfer_land_should_do_fail_for_same_account() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_eq!( + EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), + Some(OWNER_LAND_ASSET_ID) + ); + + assert_noop!( + EstateModule::transfer_land( + RuntimeOrigin::signed(BENEFICIARY_ID), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + ), + Error::::AlreadyOwnTheLandUnit + ); + + assert_eq!( + EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), + Some(OWNER_LAND_ASSET_ID) + ); + }); +} + +#[test] +fn transfer_land_should_do_fail_for_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + AUCTION_BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_AUCTION + )); + assert_eq!( + EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_AUCTION), + Some(OwnerId::Token(METAVERSE_LAND_CLASS, METAVERSE_LAND_IN_AUCTION_TOKEN)) + ); + + assert_noop!( + EstateModule::transfer_land( + RuntimeOrigin::signed(AUCTION_BENEFICIARY_ID), + BOB, + METAVERSE_ID, + COORDINATE_IN_AUCTION + ), + Error::::LandUnitAlreadyInAuction + ); + }); +} + +#[test] +fn mint_estate_should_reject_non_root() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::mint_estate( + RuntimeOrigin::signed(ALICE), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + ), + BadOrigin + ); + }); +} + +#[test] +fn mint_estate_should_fail_for_minted_land() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_err!( + EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + ), + Error::::LandUnitIsNotAvailable + ); + }); +} + +#[test] +fn dissolve_estate_should_work() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 2 + ); + + // Destroy estate + assert_ok!(EstateModule::dissolve_estate( + RuntimeOrigin::signed(BENEFICIARY_ID), + estate_id, + )); + + assert_eq!(EstateModule::all_estates_count(), 0); + assert_eq!(EstateModule::get_estates(estate_id), None); + assert_eq!(EstateModule::get_estate_owner(estate_id), None); + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 2 + ); + }); +} + +#[test] +fn dissolve_estate_should_reject_non_owner() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_err!( + EstateModule::dissolve_estate(RuntimeOrigin::signed(ALICE), 0), + Error::::NoPermission + ); + }); +} + +#[test] +fn add_land_unit_to_estate_should_reject_non_owner() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_2] + )); + + assert_err!( + EstateModule::add_land_unit_to_estate(RuntimeOrigin::signed(ALICE), 0, vec![COORDINATE_IN_2]), + Error::::NoPermission + ); + }); +} + +#[test] +fn add_land_unit_to_estate_should_work() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1] + }) + ); + + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 1 + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + assert_eq!(EstateModule::all_land_units_count(), 1); + + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_2 + )); + // Update estate + assert_ok!(EstateModule::add_land_unit_to_estate( + RuntimeOrigin::signed(BENEFICIARY_ID), + estate_id, + vec![COORDINATE_IN_2] + )); + + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 2 + ); + assert_eq!(EstateModule::all_land_units_count(), 2); + }); +} + +#[test] +fn remove_land_unit_from_estate_should_reject_non_owner() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_err!( + EstateModule::remove_land_unit_from_estate(RuntimeOrigin::signed(ALICE), 0, vec![COORDINATE_IN_2]), + Error::::NoPermission + ); + }); +} + +#[test] +fn remove_land_unit_from_estate_should_work() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 2 + ); + assert_eq!(EstateModule::all_land_units_count(), 2); + + // Update estate + assert_ok!(EstateModule::remove_land_unit_from_estate( + RuntimeOrigin::signed(BENEFICIARY_ID), + estate_id, + vec![COORDINATE_IN_2] + )); + + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1] + }) + ); + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 2 + ); + assert_eq!(EstateModule::all_land_units_count(), 2); + }); +} + +#[test] +fn mint_estate_and_land_should_return_correct_total_land_unit() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 2 + ); + + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + (-6, 6) + )); + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 3 + ); + }); +} + +#[test] +fn mint_estate_should_return_none_for_non_exist_estate() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + let estate_id_non_exists: u64 = 999; + assert_eq!(EstateModule::get_estates(estate_id_non_exists), None); + assert_eq!(EstateModule::get_estate_owner(estate_id_non_exists), None); + }); +} + +#[test] +fn transfer_estate_token_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + assert_ok!(EstateModule::transfer_estate( + RuntimeOrigin::signed(BENEFICIARY_ID), + ALICE, + estate_id + )); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::TransferredEstate(estate_id, BENEFICIARY_ID, ALICE)) + ); + }); +} + +#[test] +fn transfer_estate_should_reject_no_permission() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + assert_noop!( + EstateModule::transfer_estate(RuntimeOrigin::signed(BOB), ALICE, estate_id), + Error::::NoPermission + ); + }); +} + +#[test] +fn transfer_estate_should_reject_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + )); + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_2] + )); + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_3] + )); + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + AUCTION_BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_AUCTION] + )); + assert_noop!( + EstateModule::transfer_estate(RuntimeOrigin::signed(AUCTION_BENEFICIARY_ID), ALICE, ESTATE_IN_AUCTION), + Error::::EstateAlreadyInAuction + ); + }); +} + +#[test] +fn transfer_estate_should_fail_with_same_account() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + assert_noop!( + EstateModule::transfer_estate(RuntimeOrigin::signed(BENEFICIARY_ID), BENEFICIARY_ID, estate_id), + Error::::AlreadyOwnTheEstate + ); + + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + }); +} + +#[test] +fn create_estate_token_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_lands( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_estate( + RuntimeOrigin::signed(BENEFICIARY_ID), + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + assert_eq!(Balances::free_balance(BENEFICIARY_ID), 999998); + }); +} + +#[test] +fn create_estate_token_after_minting_account_and_token_based_lands_should_give_correct_total_user_land_units() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_1 + )); + + assert_ok!(EstateModule::mint_land( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + COORDINATE_IN_2 + )); + + assert_ok!(EstateModule::create_estate( + RuntimeOrigin::signed(BENEFICIARY_ID), + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + assert_eq!( + EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), + 2 + ); + assert_eq!(EstateModule::all_land_units_count(), 2); + assert_eq!(Balances::free_balance(BENEFICIARY_ID), 999998); + }); +} + +#[test] +fn create_estate_should_return_none_for_non_exist_estate() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_lands( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_estate( + RuntimeOrigin::signed(BENEFICIARY_ID), + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + assert_eq!(Balances::free_balance(BENEFICIARY_ID), 999998); + + let estate_id: u64 = 0; + assert_eq!(EstateModule::all_estates_count(), 1); + assert_eq!(EstateModule::next_estate_id(), 1); + assert_eq!( + EstateModule::get_estates(estate_id), + Some(EstateInfo { + metaverse_id: METAVERSE_ID, + land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] + }) + ); + assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); + + let estate_id_non_exists: u64 = 999; + assert_eq!(EstateModule::get_estates(estate_id_non_exists), None); + assert_eq!(EstateModule::get_estate_owner(estate_id_non_exists), None); + }); +} + +#[test] +fn issue_land_block_should_fail_if_not_root() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::signed(ALICE), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + ), + BadOrigin + ); + }); +} + +#[test] +fn issue_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(BOB, 0)) + ); + + assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 20); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + }); +} + +#[test] +fn issue_two_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(BOB, 0)) + ); + + assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); + + let first_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); + match first_issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 20); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + ALICE, + 1, + 30, + UndeployedLandBlockType::Transferable + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(ALICE, 1)) + ); + + assert_eq!(EstateModule::get_undeployed_land_block_owner(ALICE, 1), Some(())); + + let second_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); + match second_issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, ALICE); + assert_eq!(a.number_land_units, 30); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + }); +} + +#[test] +fn freeze_undeployed_land_block_should_fail_if_not_root() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), + BadOrigin + ); + }); +} + +#[test] +fn freeze_undeployed_land_block_should_fail_not_found() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), + Error::::UndeployedLandBlockNotFound + ); + }); +} + +#[test] +fn freeze_undeployed_land_block_should_fail_if_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 1, + UndeployedLandBlockType::Transferable, + )); + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 21, + UndeployedLandBlockType::Transferable, + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(UNDEPLOYED_LAND_BLOCK_IN_AUCTION); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 21); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + assert_noop!( + EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), UNDEPLOYED_LAND_BLOCK_IN_AUCTION), + Error::::UndeployedLandBlockAlreadyInAuction + ); + }); +} + +#[test] +fn freeze_undeployed_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 20); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_ok!(EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockFreezed(0)) + ); + + assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); + + let frozen_undeployed_land_block = EstateModule::get_undeployed_land_block(0); + match frozen_undeployed_land_block { + Some(a) => { + assert_eq!(a.is_locked, true); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + }); +} + +#[test] +fn freeze_undeployed_land_block_should_fail_already_freezed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + assert_ok!(EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockFreezed(0)) + ); + + assert_noop!( + EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), + Error::::UndeployedLandBlockAlreadyFreezed + ); + }); +} + +#[test] +fn unfreeze_undeployed_land_block_should_fail_if_not_root() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), + BadOrigin + ); + }); +} + +#[test] +fn unfreeze_undeployed_land_block_should_fail_not_found() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), + Error::::UndeployedLandBlockNotFound + ); + }); +} + +#[test] +fn unfreeze_undeployed_land_block_should_fail_if_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 1, + UndeployedLandBlockType::Transferable, + )); + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 21, + UndeployedLandBlockType::Transferable, + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 21); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_noop!( + EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), UNDEPLOYED_LAND_BLOCK_IN_AUCTION), + Error::::UndeployedLandBlockAlreadyInAuction + ); + }); +} + +#[test] +fn unfreeze_undeployed_land_block_should_fail_not_frozen() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + assert_noop!( + EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), + Error::::UndeployedLandBlockNotFrozen + ); + }); +} + +#[test] +fn unfreeze_undeployed_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + assert_ok!(EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); + + let freezed_undeployed_land_block = EstateModule::get_undeployed_land_block(0); + match freezed_undeployed_land_block { + Some(a) => { + assert_eq!(a.is_locked, true); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_ok!(EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockUnfreezed(0)) + ); + + let unfreezed_undeployed_land_block = EstateModule::get_undeployed_land_block(0); + match unfreezed_undeployed_land_block { + Some(a) => { + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + }); +} + +#[test] +fn transfer_undeployed_land_block_should_fail_if_not_found() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, 0), + Error::::UndeployedLandBlockNotFound + ); + }); +} + +#[test] +fn transfer_undeployed_land_block_should_fail_if_not_owner() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_noop!( + EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, undeployed_land_block_id), + Error::::NoPermission + ); + }); +} + +#[test] +fn transfer_undeployed_land_block_should_fail_if_freezed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_ok!(EstateModule::freeze_undeployed_land_blocks( + RuntimeOrigin::root(), + undeployed_land_block_id + )); + + assert_noop!( + EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(BOB), ALICE, undeployed_land_block_id), + Error::::UndeployedLandBlockAlreadyFreezed + ); + }); +} + +#[test] +fn transfer_undeployed_land_block_should_fail_if_not_transferable() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_noop!( + EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(BOB), ALICE, undeployed_land_block_id), + Error::::UndeployedLandBlockIsNotTransferable + ); + }); +} + +#[test] +fn transfer_undeployed_land_block_should_fail_if_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 1, + UndeployedLandBlockType::Transferable, + )); + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 21, + UndeployedLandBlockType::Transferable, + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 21); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_noop!( + EstateModule::transfer_undeployed_land_blocks( + RuntimeOrigin::signed(BOB), + ALICE, + UNDEPLOYED_LAND_BLOCK_IN_AUCTION + ), + Error::::UndeployedLandBlockAlreadyInAuction + ); + }); +} + +#[test] +fn transfer_undeployed_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::Transferable + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, BOB); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_eq!( + EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), + Some(()) + ); + + assert_ok!(EstateModule::transfer_undeployed_land_blocks( + RuntimeOrigin::signed(BOB), + ALICE, + undeployed_land_block_id + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockTransferred( + BOB, + ALICE, + undeployed_land_block_id, + )) + ); + + let transferred_issued_undeployed_land_block = + EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match transferred_issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, ALICE); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_eq!( + EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), + None + ); + assert_eq!( + EstateModule::get_undeployed_land_block_owner(ALICE, undeployed_land_block_id), + Some(()) + ); + }); +} + +#[test] +fn deploy_undeployed_land_block_should_fail_if_not_found() { + ExtBuilder::default().build().execute_with(|| { + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_noop!( + EstateModule::deploy_land_block( + RuntimeOrigin::signed(ALICE), + undeployed_land_block_id, + ALICE_METAVERSE_ID, + LANDBLOCK_COORDINATE, + vec![COORDINATE_IN_1] + ), + Error::::UndeployedLandBlockNotFound + ); + assert_eq!(Balances::free_balance(BOB), 100000); + }); +} + +#[test] +fn deploy_undeployed_land_block_should_fail_if_not_owner() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_noop!( + EstateModule::deploy_land_block( + RuntimeOrigin::signed(ALICE), + undeployed_land_block_id, + METAVERSE_ID, + LANDBLOCK_COORDINATE, + vec![COORDINATE_IN_1] + ), + Error::::NoPermission + ); + assert_eq!(Balances::free_balance(ALICE), 100000); + }); +} + +#[test] +fn deploy_undeployed_land_block_should_fail_if_freezed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_ok!(EstateModule::freeze_undeployed_land_blocks( + RuntimeOrigin::root(), + undeployed_land_block_id + )); + + assert_noop!( + EstateModule::deploy_land_block( + RuntimeOrigin::signed(BOB), + undeployed_land_block_id, + BOB_METAVERSE_ID, + LANDBLOCK_COORDINATE, + vec![COORDINATE_IN_1] + ), + Error::::UndeployedLandBlockFreezed + ); + assert_eq!(Balances::free_balance(BOB), 99999); + }); +} + +#[test] +fn deploy_undeployed_land_block_should_fail_if_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 1, + UndeployedLandBlockType::Transferable, + )); + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 21, + UndeployedLandBlockType::Transferable, + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 21); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_noop!( + EstateModule::deploy_land_block( + RuntimeOrigin::signed(BOB), + UNDEPLOYED_LAND_BLOCK_IN_AUCTION, + METAVERSE_ID, + LANDBLOCK_COORDINATE, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + ), + Error::::UndeployedLandBlockAlreadyInAuction + ); + assert_eq!(Balances::free_balance(BOB), 99998); + }); +} + +#[test] +fn deploy_undeployed_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 2, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + let undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match undeployed_land_block { + Some(a) => { + assert_eq!(a.number_land_units, 2); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_ok!(EstateModule::deploy_land_block( + RuntimeOrigin::signed(BOB), + undeployed_land_block_id, + BOB_METAVERSE_ID, + LANDBLOCK_COORDINATE, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::LandBlockDeployed( + BOB, + BOB_METAVERSE_ID, + undeployed_land_block_id, + vec![COORDINATE_IN_1, COORDINATE_IN_2], + )) + ); + + let updated_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + + assert_eq!(updated_undeployed_land_block, None); + + assert_eq!(EstateModule::all_land_units_count(), 2); + assert_eq!(Balances::free_balance(BOB), 99998); + }); +} + +#[test] +fn approve_undeployed_land_block_should_fail_if_not_found() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::approve_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, 0), + Error::::UndeployedLandBlockNotFound + ); + }); +} + +#[test] +fn approve_undeployed_land_block_should_fail_if_not_owner() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_noop!( + EstateModule::approve_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, undeployed_land_block_id), + Error::::NoPermission + ); + }); +} + +#[test] +fn approve_undeployed_land_block_should_fail_if_freezed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_ok!(EstateModule::freeze_undeployed_land_blocks( + RuntimeOrigin::root(), + undeployed_land_block_id + )); + + assert_noop!( + EstateModule::approve_undeployed_land_blocks(RuntimeOrigin::signed(BOB), ALICE, undeployed_land_block_id), + Error::::UndeployedLandBlockAlreadyFreezed + ); + }); +} + +#[test] +fn approve_undeployed_land_block_should_fail_if_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 1, + UndeployedLandBlockType::Transferable, + )); + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 21, + UndeployedLandBlockType::Transferable, + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 21); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_noop!( + EstateModule::approve_undeployed_land_blocks( + RuntimeOrigin::signed(BOB), + ALICE, + UNDEPLOYED_LAND_BLOCK_IN_AUCTION + ), + Error::::UndeployedLandBlockAlreadyInAuction + ); + }); +} + +#[test] +fn approve_undeployed_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::Transferable + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, BOB); + assert_eq!(a.approved, None); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_eq!( + EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), + Some(()) + ); + + assert_ok!(EstateModule::approve_undeployed_land_blocks( + RuntimeOrigin::signed(BOB), + ALICE, + undeployed_land_block_id + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockApproved( + BOB, + ALICE, + undeployed_land_block_id, + )) + ); + + let transferred_issued_undeployed_land_block = + EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match transferred_issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, BOB); + assert_eq!(a.approved, Some(ALICE)); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_eq!( + EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), + Some(()) + ); + }); +} + +#[test] +fn unapprove_undeployed_land_block_should_fail_if_not_found() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::unapprove_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), + Error::::UndeployedLandBlockNotFound + ); + }); +} + +#[test] +fn unapprove_undeployed_land_block_should_fail_if_not_owner() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_noop!( + EstateModule::unapprove_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), undeployed_land_block_id), + Error::::NoPermission + ); + }); +} + +#[test] +fn unapprove_undeployed_land_block_should_fail_if_freezed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + assert_ok!(EstateModule::freeze_undeployed_land_blocks( + RuntimeOrigin::root(), + undeployed_land_block_id + )); + + assert_noop!( + EstateModule::unapprove_undeployed_land_blocks(RuntimeOrigin::signed(BOB), undeployed_land_block_id), + Error::::UndeployedLandBlockAlreadyFreezed + ); + }); +} + +#[test] +fn unapprove_undeployed_land_block_should_fail_if_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 1, + UndeployedLandBlockType::Transferable, + )); + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 21, + UndeployedLandBlockType::Transferable, + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 21); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_noop!( + EstateModule::unapprove_undeployed_land_blocks( + RuntimeOrigin::signed(BOB), + UNDEPLOYED_LAND_BLOCK_IN_AUCTION + ), + Error::::UndeployedLandBlockAlreadyInAuction + ); + }); +} + +#[test] +fn unapprove_undeployed_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::Transferable + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, BOB); + assert_eq!(a.approved, None); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_eq!( + EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), + Some(()) + ); + assert_ok!(EstateModule::approve_undeployed_land_blocks( + RuntimeOrigin::signed(BOB), + ALICE, + undeployed_land_block_id + )); + + let approved_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match approved_issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, BOB); + assert_eq!(a.approved, Some(ALICE)); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_ok!(EstateModule::unapprove_undeployed_land_blocks( + RuntimeOrigin::signed(BOB), + undeployed_land_block_id + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockUnapproved(undeployed_land_block_id)) + ); + + let unapproved_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match unapproved_issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, BOB); + assert_eq!(a.approved, None); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + }); +} + +#[test] +fn burn_undeployed_land_block_should_fail_if_not_root() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::burn_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), + BadOrigin + ); + }); +} + +#[test] +fn burn_undeployed_land_block_should_fail_not_found() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EstateModule::burn_undeployed_land_blocks(RuntimeOrigin::root(), 0), + Error::::UndeployedLandBlockNotFound + ); + }); +} + +#[test] +fn burn_undeployed_land_block_should_fail_if_already_in_auction() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 1, + UndeployedLandBlockType::Transferable, + )); + + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 21, + UndeployedLandBlockType::Transferable, + )); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 21); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_noop!( + EstateModule::burn_undeployed_land_blocks(RuntimeOrigin::root(), UNDEPLOYED_LAND_BLOCK_IN_AUCTION), + Error::::UndeployedLandBlockAlreadyInAuction + ); + }); +} + +#[test] +fn burn_undeployed_land_block_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 20, + UndeployedLandBlockType::BoundToAddress + )); + + let undeployed_land_block_id: UndeployedLandBlockId = 0; + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); + match issued_undeployed_land_block { + Some(a) => { + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 20); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + assert_eq!( + EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), + Some(()) + ); + + assert_ok!(EstateModule::burn_undeployed_land_blocks( + RuntimeOrigin::root(), + undeployed_land_block_id + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockBurnt(undeployed_land_block_id)) + ); + + assert_eq!( + EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), + None + ); + + assert_eq!(EstateModule::get_undeployed_land_block(undeployed_land_block_id), None) + }); +} + +#[test] +fn ensure_land_unit_within_land_block_bound_should_work() { + // let coordinates: Vec<(i32, i32)> = vec![(-4, 0), (-3, 0), (-3, 0), (0, 5)]; + // assert_eq!(EstateModule::verify_land_unit_in_bound(&(0, 0), &coordinates), true); + + let second_coordinates: Vec<(i32, i32)> = vec![(-204, 25), (-203, 24), (-195, 20), (-197, 16)]; + assert_eq!( + EstateModule::verify_land_unit_in_bound(&(-20, 2), &second_coordinates), + true + ); + + let third_coordinates: Vec<(i32, i32)> = vec![(-64, 5), (-64, 4), (-64, 4), (-55, -4)]; + assert_eq!( + EstateModule::verify_land_unit_in_bound(&(-6, 0), &third_coordinates), + true + ); + + // Combined in and out bound should fail + let fourth_coordinates: Vec<(i32, i32)> = vec![(-5, 3), (-4, 6), (-5, 4)]; + assert_eq!( + EstateModule::verify_land_unit_in_bound(&(0, 0), &fourth_coordinates), + false + ); +} + +#[test] +fn ensure_land_unit_out_of_land_block_bound_should_fail() { + let coordinates: Vec<(i32, i32)> = vec![(-51, 0), (-48, 0), (-47, 0), (0, 51)]; + assert_eq!(EstateModule::verify_land_unit_in_bound(&(0, 0), &coordinates), false); + + let second_coordinates: Vec<(i32, i32)> = vec![(-250, 2), (-248, 2), (-150, 2), (-151, 6)]; + assert_eq!( + EstateModule::verify_land_unit_in_bound(&(-200, 2), &second_coordinates), + false + ); +} + +#[test] +fn issue_land_block_and_create_estate_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::issue_undeployed_land_blocks( + RuntimeOrigin::root(), + BOB, + 1, + 2, + UndeployedLandBlockType::BoundToAddress + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(BOB, 0)) + ); + + assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); + + let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); + match issued_undeployed_land_block { + Some(a) => { + // Verify details of UndeployedLandBlock + assert_eq!(a.owner, BOB); + assert_eq!(a.number_land_units, 2); + assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); + assert_eq!(a.is_locked, false); + } + _ => { + // Should fail test + assert_eq!(0, 1); + } + } + + // Bob can deploy raw land block to his metaverse + assert_ok!(EstateModule::deploy_land_block( + RuntimeOrigin::signed(BOB), + 0, + BOB_METAVERSE_ID, + LANDBLOCK_COORDINATE, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + assert_eq!(Balances::free_balance(BOB), 99998); + + assert_eq!( + EstateModule::get_land_units(BOB_METAVERSE_ID, COORDINATE_IN_1), + Some(OwnerId::Token(METAVERSE_LAND_CLASS, 2)) + ); + + assert_eq!( + EstateModule::get_land_units(BOB_METAVERSE_ID, COORDINATE_IN_2), + Some(OwnerId::Token(METAVERSE_LAND_CLASS, 2)) + ); + }); +} + +#[test] +fn create_estate_lease_offer_should_fail() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + )); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 1u64, 10u128, 8u32), + Error::::EstateDoesNotExist + ); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, 10u128, 8u32), + Error::::NoPermission + ); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, 0u128, 8u32), + Error::::LeaseOfferPriceBelowMinimum + ); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, 2u128, 1000u32), + Error::::LeaseOfferDurationAboveMaximum + ); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, 2u128, 7u32), + Error::::LeaseOfferAlreadyExists + ); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(CHARLIE), 0u64, 12u128, 8u32), + Error::::EstateIsAlreadyLeased + ); + + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + AUCTION_BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_2] + )); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(BOB), 1u64, 100u128, 8u32), + Error::::EstateAlreadyInAuction + ); + + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_3] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(BOB), + 2u64, + 12u128, + 8u32 + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 2u64, + 13u128, + 8u32 + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(CHARLIE), + 2u64, + 14u128, + 8u32 + )); + + assert_noop!( + EstateModule::create_lease_offer(RuntimeOrigin::signed(DOM), 2u64, 15u128, 8u32), + Error::::EstateLeaseOffersQueueLimitIsReached + ); + }); +} + +#[test] +fn create_estate_lease_offer_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseOfferCreated(ALICE, 0, 80)) + ); + + let lease_contract = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 7, + start_block: 8, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); + + assert_eq!(Balances::free_balance(ALICE), 99920); + }); +} + +#[test] +fn accept_estate_lease_offer_should_fail() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(BOB), + 0u64, + 10u128, + 8u32 + )); + + assert_noop!( + EstateModule::accept_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, BOB), + Error::::NoPermission + ); + //TO DO: Offer cannot be accepted after asset is listed on auction + + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(BOB), + 1u64, + 10u128, + 8u32 + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 1u64, + 10u128, + 8u32 + )); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 1u64, + ALICE + )); + + assert_noop!( + EstateModule::accept_lease_offer(RuntimeOrigin::signed(BENEFICIARY_ID), 1u64, BOB), + Error::::EstateIsAlreadyLeased + ); + + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_3] + )); + + assert_noop!( + EstateModule::accept_lease_offer(RuntimeOrigin::signed(BENEFICIARY_ID), 2u64, BOB), + Error::::LeaseOfferDoesNotExist + ); + }); +} + +#[test] +fn accept_estate_lease_offer_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + let lease_contract = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 7, + start_block: 8, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) + ); + + let lease = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 9, + start_block: 1, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::leases(0u64), Some(lease)); + + assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); + + assert_eq!(Balances::free_balance(ALICE), 99920); + }); +} + +#[test] +fn cancel_lease_should_fail() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + )); + + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_noop!( + EstateModule::cancel_lease(RuntimeOrigin::signed(BOB), BENEFICIARY_ID, 0u64, ALICE), + BadOrigin + ); + + assert_noop!( + EstateModule::cancel_lease(RuntimeOrigin::root(), BENEFICIARY_ID, 1u64, ALICE), + Error::::LeaseDoesNotExist + ); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_noop!( + EstateModule::cancel_lease(RuntimeOrigin::root(), BENEFICIARY_ID, 0u64, BOB), + Error::::LeaseDoesNotExist + ); + assert_noop!( + EstateModule::cancel_lease(RuntimeOrigin::root(), BOB, 0u64, ALICE), + Error::::NoPermission + ); + + run_to_block(22); + + assert_noop!( + EstateModule::cancel_lease(RuntimeOrigin::root(), BENEFICIARY_ID, 0u64, ALICE), + Error::::LeaseIsExpired + ); + }); +} + +#[test] +fn cancel_lease_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + let lease_contract = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 7, + start_block: 8, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) + ); + + let lease = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 9, + start_block: 1, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::leases(0u64), Some(lease)); + + assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + run_to_block(5); + + assert_ok!(EstateModule::cancel_lease( + RuntimeOrigin::root(), + BENEFICIARY_ID, + 0u64, + ALICE + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseContractCancelled(0)) + ); + + assert_eq!(EstateModule::leases(0u64), None); + + assert_eq!(EstateModule::leasors(ALICE, 0u64), None); + + assert_eq!(Balances::free_balance(ALICE), 99960); + assert_eq!(Balances::free_balance(BENEFICIARY_ID), 1000039); + }); +} + +#[test] +fn remove_expired_lease_should_fail() { + ExtBuilder::default().build().execute_with(|| { + // Mint estate + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_noop!( + EstateModule::remove_expired_lease(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), + Error::::LeaseDoesNotExist + ); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + run_to_block(3); + + assert_noop!( + EstateModule::remove_expired_lease(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), + Error::::LeaseIsNotExpired + ); + + run_to_block(22); + + assert_noop!( + EstateModule::remove_expired_lease(RuntimeOrigin::signed(BOB), 0u64, ALICE), + Error::::NoPermission + ); + }); +} + +#[test] +fn remove_expired_lease_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + let lease_contract = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 7, + start_block: 8, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) + ); + + let lease = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 9, + start_block: 1, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::leases(0u64), Some(lease)); + + assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + run_to_block(10); + + assert_ok!(EstateModule::remove_expired_lease( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseContractEnded(0u64)) + ); + + assert_eq!(EstateModule::leases(0u64), None); + + assert_eq!(EstateModule::leasors(ALICE, 0u64), None); + + assert_eq!(Balances::free_balance(ALICE), 99920); + assert_eq!(Balances::free_balance(BENEFICIARY_ID), 1000079); + }); +} + +#[test] +fn remove_lease_offer_should_fail() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1] + )); + + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_noop!( + EstateModule::remove_lease_offer(RuntimeOrigin::signed(BOB), 0u64), + Error::::LeaseOfferDoesNotExist + ); + + assert_noop!( + EstateModule::remove_lease_offer(RuntimeOrigin::signed(ALICE), 1u64), + Error::::LeaseOfferDoesNotExist + ); + }); +} + +#[test] +fn remove_lease_offer_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + let lease_contract = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 7, + start_block: 8, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); + + assert_ok!(EstateModule::remove_lease_offer(RuntimeOrigin::signed(ALICE), 0u64)); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseOfferRemoved(ALICE, 0u64)) + ); + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); + assert_eq!(Balances::free_balance(ALICE), 100000); + }); +} + +#[test] +fn collect_rent_should_fail() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_noop!( + EstateModule::collect_rent(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), + Error::::LeaseDoesNotExist + ); + + assert_noop!( + EstateModule::collect_rent(RuntimeOrigin::signed(ALICE), 0u64, BENEFICIARY_ID), + Error::::NoPermission + ); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_noop!( + EstateModule::collect_rent(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, BOB), + Error::::LeaseDoesNotExist + ); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + run_to_block(22); + + assert_noop!( + EstateModule::collect_rent(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), + Error::::LeaseIsExpired + ); + }); +} + +#[test] +fn collect_rent_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EstateModule::mint_estate( + RuntimeOrigin::root(), + BENEFICIARY_ID, + METAVERSE_ID, + vec![COORDINATE_IN_1, COORDINATE_IN_2] + )); + + assert_ok!(EstateModule::create_lease_offer( + RuntimeOrigin::signed(ALICE), + 0u64, + 10u128, + 8u32 + )); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + let lease_contract = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 7, + start_block: 8, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); + + assert_ok!(EstateModule::accept_lease_offer( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) + ); + + let mut lease = LeaseContract { + price_per_block: 10u128, + duration: 8u32, + end_block: 9, + start_block: 1, + unclaimed_rent: 80u128, + }; + + assert_eq!(EstateModule::leases(0u64), Some(lease.clone())); + + assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); + + assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); + + assert_eq!(Balances::free_balance(ALICE), 99920); + + run_to_block(4); + + assert_ok!(EstateModule::collect_rent( + RuntimeOrigin::signed(BENEFICIARY_ID), + 0u64, + ALICE + )); + + assert_eq!( + last_event(), + RuntimeEvent::Estate(crate::Event::EstateRentCollected(0, 30)) + ); + + lease.unclaimed_rent = 50u128; + + assert_eq!(EstateModule::leases(0u64), Some(lease)); + + assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); + + assert_eq!(Balances::free_balance(ALICE), 99920); + assert_eq!(Balances::free_balance(BENEFICIARY_ID), 1000029); + }); +} diff --git a/pallets/land-minting/src/weights.rs b/pallets/land-minting/src/weights.rs new file mode 100644 index 000000000..f848ec15c --- /dev/null +++ b/pallets/land-minting/src/weights.rs @@ -0,0 +1,699 @@ +// This file is part of Metaverse.Network & Bit.Country. + +// Copyright (C) 2020-2022 Metaverse.Network & Bit.Country . +// 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. + +//! Autogenerated weights for estate +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-18, STEPS: `20`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 + +// Executed Command: +// ./target/release/metaverse-node +// benchmark +// pallet +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// estate +// --extrinsic +// * +// --steps +// 20 +// --repeat +// 10 +// --template=./template/weight-template.hbs +// --output +// ./pallets/estate/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for estate. +pub trait WeightInfo { fn mint_land() -> Weight; fn mint_lands() -> Weight; fn transfer_land() -> Weight; fn mint_estate() -> Weight; fn dissolve_estate() -> Weight; fn add_land_unit_to_estate() -> Weight; fn remove_land_unit_from_estate() -> Weight; fn create_estate() -> Weight; fn transfer_estate() -> Weight; fn issue_undeployed_land_blocks() -> Weight; fn freeze_undeployed_land_blocks() -> Weight; fn unfreeze_undeployed_land_blocks() -> Weight; fn approve_undeployed_land_blocks() -> Weight; fn unapprove_undeployed_land_blocks() -> Weight; fn transfer_undeployed_land_blocks() -> Weight; fn deploy_land_block() -> Weight; fn burn_undeployed_land_blocks() -> Weight; fn create_lease_offer() -> Weight; fn accept_lease_offer() -> Weight; fn cancel_lease() -> Weight; fn remove_expired_lease() -> Weight; fn remove_lease_offer() -> Weight; fn collect_rent() -> Weight; fn on_initialize() -> Weight;} + +/// Weights for estate using the for collator node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { // Storage: Estate LandUnits (r:1 w:1) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: Metaverse Metaverses (r:1 w:0) + // Proof Skipped: Metaverse Metaverses (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: OrmlNFT NextTokenId (r:1 w:1) + // Proof Skipped: OrmlNFT NextTokenId (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:1) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: Estate AllLandUnitsCount (r:1 w:1) + // Proof Skipped: Estate AllLandUnitsCount (max_values: Some(1), max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:1) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn mint_land() -> Weight { + // Proof Size summary in bytes: + // Measured: `2339` + // Estimated: `36660` + // Minimum execution time: 56_882 nanoseconds. + Weight::from_parts(59_273_000, 36660) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) + } + // Storage: Estate LandUnits (r:2 w:2) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: Metaverse Metaverses (r:1 w:0) + // Proof Skipped: Metaverse Metaverses (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: OrmlNFT NextTokenId (r:1 w:1) + // Proof Skipped: OrmlNFT NextTokenId (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:2 w:2) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:1) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: Estate AllLandUnitsCount (r:1 w:1) + // Proof Skipped: Estate AllLandUnitsCount (max_values: Some(1), max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:2) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn mint_lands() -> Weight { + // Proof Size summary in bytes: + // Measured: `2339` + // Estimated: `41610` + // Minimum execution time: 82_026 nanoseconds. + Weight::from_parts(83_541_000, 41610) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } + // Storage: Estate LandUnits (r:1 w:1) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT StackableCollection (r:1 w:0) + // Proof Skipped: OrmlNFT StackableCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:0) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:2) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn transfer_land() -> Weight { + // Proof Size summary in bytes: + // Measured: `1915` + // Estimated: `28255` + // Minimum execution time: 46_193 nanoseconds. + Weight::from_parts(47_423_000, 28255) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Estate NextEstateId (r:1 w:1) + // Proof Skipped: Estate NextEstateId (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Metaverse Metaverses (r:1 w:0) + // Proof Skipped: Metaverse Metaverses (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: OrmlNFT NextTokenId (r:1 w:1) + // Proof Skipped: OrmlNFT NextTokenId (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:1) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: Estate LandUnits (r:1 w:1) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: Estate AllLandUnitsCount (r:1 w:1) + // Proof Skipped: Estate AllLandUnitsCount (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate AllEstatesCount (r:1 w:1) + // Proof Skipped: Estate AllEstatesCount (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate EstateOwner (r:0 w:1) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Estate Estates (r:0 w:1) + // Proof Skipped: Estate Estates (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:1) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn mint_estate() -> Weight { + // Proof Size summary in bytes: + // Measured: `2356` + // Estimated: `47210` + // Minimum execution time: 62_931 nanoseconds. + Weight::from_parts(64_479_000, 47210) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(11)) + } + // Storage: Estate EstateOwner (r:1 w:1) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:2 w:2) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Estate Estates (r:1 w:1) + // Proof Skipped: Estate Estates (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:2 w:2) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: Estate AllEstatesCount (r:1 w:1) + // Proof Skipped: Estate AllEstatesCount (max_values: Some(1), max_size: None, mode: Measured) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate LandUnits (r:1 w:1) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: Metaverse Metaverses (r:1 w:0) + // Proof Skipped: Metaverse Metaverses (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT NextTokenId (r:1 w:1) + // Proof Skipped: OrmlNFT NextTokenId (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:2) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn dissolve_estate() -> Weight { + // Proof Size summary in bytes: + // Measured: `3118` + // Estimated: `67224` + // Minimum execution time: 99_063 nanoseconds. + Weight::from_parts(110_008_000, 67224) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(13)) + } + // Storage: Estate EstateOwner (r:1 w:0) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:2 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Estate Estates (r:1 w:1) + // Proof Skipped: Estate Estates (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate LandUnits (r:1 w:1) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:1) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:1) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn add_land_unit_to_estate() -> Weight { + // Proof Size summary in bytes: + // Measured: `2593` + // Estimated: `38079` + // Minimum execution time: 69_608 nanoseconds. + Weight::from_parts(71_706_000, 38079) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: Estate EstateOwner (r:1 w:0) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:2 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Estate Estates (r:1 w:1) + // Proof Skipped: Estate Estates (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate LandUnits (r:1 w:1) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: Metaverse Metaverses (r:1 w:0) + // Proof Skipped: Metaverse Metaverses (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT NextTokenId (r:1 w:1) + // Proof Skipped: OrmlNFT NextTokenId (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:1) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:1) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn remove_land_unit_from_estate() -> Weight { + // Proof Size summary in bytes: + // Measured: `3027` + // Estimated: `60226` + // Minimum execution time: 86_578 nanoseconds. + Weight::from_parts(90_257_000, 60226) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(8)) + } + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate NextEstateId (r:1 w:1) + // Proof Skipped: Estate NextEstateId (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Metaverse Metaverses (r:1 w:0) + // Proof Skipped: Metaverse Metaverses (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT NextTokenId (r:1 w:1) + // Proof Skipped: OrmlNFT NextTokenId (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:3 w:3) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:2 w:2) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: Estate LandUnits (r:2 w:2) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: Estate AllEstatesCount (r:1 w:1) + // Proof Skipped: Estate AllEstatesCount (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate EstateOwner (r:0 w:1) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Estate Estates (r:0 w:1) + // Proof Skipped: Estate Estates (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:3) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn create_estate() -> Weight { + // Proof Size summary in bytes: + // Measured: `3192` + // Estimated: `66058` + // Minimum execution time: 121_302 nanoseconds. + Weight::from_parts(131_741_000, 66058) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(17)) + } + // Storage: Estate EstateOwner (r:1 w:1) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Estate Estates (r:1 w:0) + // Proof Skipped: Estate Estates (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeases (r:1 w:0) + // Proof Skipped: Estate EstateLeases (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT StackableCollection (r:1 w:0) + // Proof Skipped: OrmlNFT StackableCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:0) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:2) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn transfer_estate() -> Weight { + // Proof Size summary in bytes: + // Measured: `2025` + // Estimated: `38025` + // Minimum execution time: 52_643 nanoseconds. + Weight::from_parts(54_808_000, 38025) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate NextUndeployedLandBlockId (r:1 w:1) + // Proof Skipped: Estate NextUndeployedLandBlockId (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate TotalUndeployedLandUnit (r:1 w:1) + // Proof Skipped: Estate TotalUndeployedLandUnit (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate UndeployedLandBlocks (r:0 w:20) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: Estate UndeployedLandBlocksOwner (r:0 w:20) + // Proof Skipped: Estate UndeployedLandBlocksOwner (max_values: None, max_size: None, mode: Measured) + fn issue_undeployed_land_blocks() -> Weight { + // Proof Size summary in bytes: + // Measured: `1558` + // Estimated: `9825` + // Minimum execution time: 159_581 nanoseconds. + Weight::from_parts(162_875_000, 9825) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(43)) + } + // Storage: Estate UndeployedLandBlocks (r:1 w:1) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + fn freeze_undeployed_land_blocks() -> Weight { + // Proof Size summary in bytes: + // Measured: `1442` + // Estimated: `7834` + // Minimum execution time: 21_762 nanoseconds. + Weight::from_parts(22_833_000, 7834) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Estate UndeployedLandBlocks (r:1 w:1) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + fn unfreeze_undeployed_land_blocks() -> Weight { + // Proof Size summary in bytes: + // Measured: `1442` + // Estimated: `7834` + // Minimum execution time: 20_225 nanoseconds. + Weight::from_parts(21_423_000, 7834) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Estate UndeployedLandBlocks (r:1 w:1) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + fn approve_undeployed_land_blocks() -> Weight { + // Proof Size summary in bytes: + // Measured: `1442` + // Estimated: `7834` + // Minimum execution time: 20_246 nanoseconds. + Weight::from_parts(21_819_000, 7834) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Estate UndeployedLandBlocks (r:1 w:1) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + fn unapprove_undeployed_land_blocks() -> Weight { + // Proof Size summary in bytes: + // Measured: `1475` + // Estimated: `7900` + // Minimum execution time: 20_346 nanoseconds. + Weight::from_parts(21_237_000, 7900) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate UndeployedLandBlocks (r:1 w:1) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: Estate UndeployedLandBlocksOwner (r:0 w:2) + // Proof Skipped: Estate UndeployedLandBlocksOwner (max_values: None, max_size: None, mode: Measured) + fn transfer_undeployed_land_blocks() -> Weight { + // Proof Size summary in bytes: + // Measured: `2040` + // Estimated: `13673` + // Minimum execution time: 38_679 nanoseconds. + Weight::from_parts(41_173_000, 13673) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: Metaverse MetaverseOwner (r:1 w:0) + // Proof Skipped: Metaverse MetaverseOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Estate UndeployedLandBlocks (r:1 w:1) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate LandUnits (r:1 w:1) + // Proof Skipped: Estate LandUnits (max_values: None, max_size: None, mode: Measured) + // Storage: Metaverse Metaverses (r:1 w:0) + // Proof Skipped: Metaverse Metaverses (max_values: None, max_size: None, mode: Measured) + // Storage: Nft LockedCollection (r:1 w:0) + // Proof Skipped: Nft LockedCollection (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT NextTokenId (r:1 w:1) + // Proof Skipped: OrmlNFT NextTokenId (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Classes (r:1 w:1) + // Proof Skipped: OrmlNFT Classes (max_values: None, max_size: None, mode: Measured) + // Storage: Estate AllLandUnitsCount (r:1 w:1) + // Proof Skipped: Estate AllLandUnitsCount (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate TotalUndeployedLandUnit (r:1 w:1) + // Proof Skipped: Estate TotalUndeployedLandUnit (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate UndeployedLandBlocksOwner (r:0 w:1) + // Proof Skipped: Estate UndeployedLandBlocksOwner (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT TokensByOwner (r:0 w:1) + // Proof Skipped: OrmlNFT TokensByOwner (max_values: None, max_size: None, mode: Measured) + fn deploy_land_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `2802` + // Estimated: `64897` + // Minimum execution time: 91_163 nanoseconds. + Weight::from_parts(100_659_000, 64897) + .saturating_add(T::DbWeight::get().reads(13)) + .saturating_add(T::DbWeight::get().writes(11)) + } + // Storage: Estate UndeployedLandBlocks (r:1 w:1) + // Proof Skipped: Estate UndeployedLandBlocks (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: Estate TotalUndeployedLandUnit (r:1 w:1) + // Proof Skipped: Estate TotalUndeployedLandUnit (max_values: Some(1), max_size: None, mode: Measured) + // Storage: Estate UndeployedLandBlocksOwner (r:0 w:1) + // Proof Skipped: Estate UndeployedLandBlocksOwner (max_values: None, max_size: None, mode: Measured) + fn burn_undeployed_land_blocks() -> Weight { + // Proof Size summary in bytes: + // Measured: `1304` + // Estimated: `10661` + // Minimum execution time: 23_162 nanoseconds. + Weight::from_parts(32_196_000, 10661) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Estate EstateOwner (r:1 w:0) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:0) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeaseOffers (r:2 w:1) + // Proof Skipped: Estate EstateLeaseOffers (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeases (r:1 w:0) + // Proof Skipped: Estate EstateLeases (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn create_lease_offer() -> Weight { + // Proof Size summary in bytes: + // Measured: `1986` + // Estimated: `27383` + // Minimum execution time: 94_497 nanoseconds. + Weight::from_parts(98_902_000, 27383) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Estate EstateLeases (r:1 w:1) + // Proof Skipped: Estate EstateLeases (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateOwner (r:1 w:0) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: Auction ItemsInAuction (r:1 w:0) + // Proof Skipped: Auction ItemsInAuction (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeaseOffers (r:1 w:1) + // Proof Skipped: Estate EstateLeaseOffers (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Estate EstateLeasors (r:0 w:1) + // Proof Skipped: Estate EstateLeasors (max_values: None, max_size: None, mode: Measured) + fn accept_lease_offer() -> Weight { + // Proof Size summary in bytes: + // Measured: `2311` + // Estimated: `28844` + // Minimum execution time: 53_956 nanoseconds. + Weight::from_parts(57_019_000, 28844) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: Estate EstateLeases (r:1 w:1) + // Proof Skipped: Estate EstateLeases (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeasors (r:1 w:1) + // Proof Skipped: Estate EstateLeasors (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateOwner (r:1 w:0) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn cancel_lease() -> Weight { + // Proof Size summary in bytes: + // Measured: `4017` + // Estimated: `28571` + // Minimum execution time: 55_907 nanoseconds. + Weight::from_parts(57_720_000, 28571) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Estate EstateLeases (r:1 w:1) + // Proof Skipped: Estate EstateLeases (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeasors (r:1 w:1) + // Proof Skipped: Estate EstateLeasors (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateOwner (r:1 w:0) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:1) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn remove_expired_lease() -> Weight { + // Proof Size summary in bytes: + // Measured: `4017` + // Estimated: `28571` + // Minimum execution time: 56_789 nanoseconds. + Weight::from_parts(57_681_000, 28571) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Estate EstateLeaseOffers (r:1 w:1) + // Proof Skipped: Estate EstateLeaseOffers (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn remove_lease_offer() -> Weight { + // Proof Size summary in bytes: + // Measured: `3250` + // Estimated: `8328` + // Minimum execution time: 33_999 nanoseconds. + Weight::from_parts(41_923_000, 8328) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Estate EstateOwner (r:1 w:0) + // Proof Skipped: Estate EstateOwner (max_values: None, max_size: None, mode: Measured) + // Storage: OrmlNFT Tokens (r:1 w:0) + // Proof Skipped: OrmlNFT Tokens (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeasors (r:1 w:0) + // Proof Skipped: Estate EstateLeasors (max_values: None, max_size: None, mode: Measured) + // Storage: Estate EstateLeases (r:1 w:1) + // Proof Skipped: Estate EstateLeases (max_values: None, max_size: None, mode: Measured) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn collect_rent() -> Weight { + // Proof Size summary in bytes: + // Measured: `4017` + // Estimated: `28571` + // Minimum execution time: 51_821 nanoseconds. + Weight::from_parts(53_171_000, 28571) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn on_initialize() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 176 nanoseconds. + Weight::from_parts(191_000, 0) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { fn mint_land() -> Weight { + Weight::from_parts(59_273_000, 36660) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(7)) + } + fn mint_lands() -> Weight { + Weight::from_parts(83_541_000, 41610) + .saturating_add(RocksDbWeight::get().reads(10)) + .saturating_add(RocksDbWeight::get().writes(10)) + } + fn transfer_land() -> Weight { + Weight::from_parts(47_423_000, 28255) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + fn mint_estate() -> Weight { + Weight::from_parts(64_479_000, 47210) + .saturating_add(RocksDbWeight::get().reads(10)) + .saturating_add(RocksDbWeight::get().writes(11)) + } + fn dissolve_estate() -> Weight { + Weight::from_parts(110_008_000, 67224) + .saturating_add(RocksDbWeight::get().reads(14)) + .saturating_add(RocksDbWeight::get().writes(13)) + } + fn add_land_unit_to_estate() -> Weight { + Weight::from_parts(71_706_000, 38079) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(6)) + } + fn remove_land_unit_from_estate() -> Weight { + Weight::from_parts(90_257_000, 60226) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(8)) + } + fn create_estate() -> Weight { + Weight::from_parts(131_741_000, 66058) + .saturating_add(RocksDbWeight::get().reads(14)) + .saturating_add(RocksDbWeight::get().writes(17)) + } + fn transfer_estate() -> Weight { + Weight::from_parts(54_808_000, 38025) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + fn issue_undeployed_land_blocks() -> Weight { + Weight::from_parts(162_875_000, 9825) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(43)) + } + fn freeze_undeployed_land_blocks() -> Weight { + Weight::from_parts(22_833_000, 7834) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + fn unfreeze_undeployed_land_blocks() -> Weight { + Weight::from_parts(21_423_000, 7834) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + fn approve_undeployed_land_blocks() -> Weight { + Weight::from_parts(21_819_000, 7834) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + fn unapprove_undeployed_land_blocks() -> Weight { + Weight::from_parts(21_237_000, 7900) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + fn transfer_undeployed_land_blocks() -> Weight { + Weight::from_parts(41_173_000, 13673) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + fn deploy_land_block() -> Weight { + Weight::from_parts(100_659_000, 64897) + .saturating_add(RocksDbWeight::get().reads(13)) + .saturating_add(RocksDbWeight::get().writes(11)) + } + fn burn_undeployed_land_blocks() -> Weight { + Weight::from_parts(32_196_000, 10661) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + fn create_lease_offer() -> Weight { + Weight::from_parts(98_902_000, 27383) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + fn accept_lease_offer() -> Weight { + Weight::from_parts(57_019_000, 28844) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(5)) + } + fn cancel_lease() -> Weight { + Weight::from_parts(57_720_000, 28571) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + fn remove_expired_lease() -> Weight { + Weight::from_parts(57_681_000, 28571) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + fn remove_lease_offer() -> Weight { + Weight::from_parts(41_923_000, 8328) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + fn collect_rent() -> Weight { + Weight::from_parts(53_171_000, 28571) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + fn on_initialize() -> Weight { + Weight::from_parts(191_000, 0) + } +} diff --git a/pallets/reward/src/tests.rs b/pallets/reward/src/tests.rs index ea8d3466d..96cd76536 100644 --- a/pallets/reward/src/tests.rs +++ b/pallets/reward/src/tests.rs @@ -2288,7 +2288,7 @@ fn js_generated_leafs_match_blockchain_generated_leafs() { } #[test] -fn merkle_proof_based_cmapaing_works_with_js_generated_root() { +fn merkle_proof_based_campaign_works_with_js_generated_root() { ExtBuilder::default().build().execute_with(|| { let campaign_id = 0; assert_ok!(Reward::add_set_reward_origin(RuntimeOrigin::signed(ALICE), ALICE)); From 540d309c81454a213b1bc247e80cbe768c518da1 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 29 Oct 2023 17:25:22 +1300 Subject: [PATCH 002/114] Create the first basic implementation of the land minting logic --- Cargo.lock | 24 + pallets/land-minting/Cargo.toml | 66 + pallets/land-minting/src/lib.rs | 2148 +------------------------------ 3 files changed, 101 insertions(+), 2137 deletions(-) create mode 100644 pallets/land-minting/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 23b66e533..f3fe62500 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7583,6 +7583,30 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-land-minting" +version = "2.0.0-rc6" +dependencies = [ + "auction-manager", + "bit-country-primitives", + "core-primitives", + "frame-benchmarking", + "frame-support", + "frame-system", + "orml-tokens", + "orml-traits", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", + "substrate-fixed", +] + [[package]] name = "pallet-membership" version = "4.0.0-dev" diff --git a/pallets/land-minting/Cargo.toml b/pallets/land-minting/Cargo.toml new file mode 100644 index 000000000..1b68911fd --- /dev/null +++ b/pallets/land-minting/Cargo.toml @@ -0,0 +1,66 @@ +[package] +authors = ['Metaverse Network '] +description = 'Metaverse Network pallet for land minting logic.' +edition = '2021' +homepage = 'https://metaverse.network' +license = 'Unlicense' +name = 'pallet-land-minting' +repository = 'https://github.com/bit-country' +version = '2.0.0-rc6' + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } +serde = { workspace = true, optional = true } +scale-info = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +frame-benchmarking = { workspace = true, optional = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-version = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } +substrate-fixed = { workspace = true } +pallet-balances = { workspace = true, optional = true } +orml-traits = { workspace = true } +orml-tokens = { workspace = true } + +# local packages +core-primitives = { path = "../../traits/core-primitives", default-features = false } +primitives = { package = "bit-country-primitives", path = "../../primitives/metaverse", default-features = false } + + +[dependencies.auction-manager] +default-features = false +package = 'auction-manager' +path = '../../traits/auction-manager' +version = '2.0.0-rc6' + + + +[features] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +default = ['std'] +std = [ + 'codec/std', + "serde", + 'scale-info/std', + 'frame-support/std', + 'frame-system/std', + 'sp-runtime/std', + 'sp-core/std', + 'core-primitives/std', + 'primitives/std', + 'sp-io/std', + 'pallet-balances/std', + 'auction-manager/std', + # 'pallet-nft/std', + 'frame-benchmarking/std' +] \ No newline at end of file diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 3c124e779..ecc87bf3c 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -27,7 +27,6 @@ use frame_support::{ use frame_system::pallet_prelude::*; use frame_system::{ensure_root, ensure_signed}; use scale_info::TypeInfo; - use sp_runtime::{ traits::{AccountIdConversion, Convert, One, Saturating, Zero}, ArithmeticError, DispatchError, Perbill, SaturatedConversion, @@ -61,11 +60,13 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { use frame_support::traits::{Currency, Imbalance, ReservableCurrency}; + use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero}; + use sp_runtime::Permill; use primitives::estate::EstateInfo; use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; - use primitives::{Balance, RoundIndex, UndeployedLandBlockId}; + use primitives::{Balance, CurrencyId, RoundIndex, UndeployedLandBlockId}; use crate::rate::{round_issuance_range, MintingRateInfo}; @@ -90,19 +91,8 @@ pub mod pallet { /// Currency type type Currency: Currency + ReservableCurrency; - - /// Minimum land price - type MinimumLandPrice: Get>; - - /// Council origin which allows to update max bound - type CouncilOrigin: EnsureOrigin; - - /// Auction handler - type AuctionHandler: Auction + CheckAuctionItemHandler>; - - /// Minimum number of blocks per round - #[pallet::constant] - type MinBlocksPerRound: Get; + /// Multi currencies type that handles different currency type in auction + type MultiCurrency: MultiReservableCurrency; /// Weight implementation for estate extrinsics type WeightInfo: WeightInfo; @@ -125,22 +115,6 @@ pub mod pallet { #[pallet::constant] type NetworkFee: Get>; - /// Maximum lease offers for an estate - #[pallet::constant] - type MaxOffersPerEstate: Get; - - /// Minimum lease price per block - #[pallet::constant] - type MinLeasePricePerBlock: Get>; - - /// Maximum lease period duration (in number of blocks) - #[pallet::constant] - type MaxLeasePeriod: Get; - - /// The period for each lease offer will be available for acceptance (in number of blocks) - #[pallet::constant] - type LeaseOfferExpiryPeriod: Get; - /// Storage deposit free charged when saving data into the blockchain. /// The fee will be unreserved after the storage is freed. #[pallet::constant] @@ -151,102 +125,16 @@ pub mod pallet { } pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; + pub type CurrencyIdOf = + <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; #[pallet::storage] - #[pallet::getter(fn all_land_units_count)] - /// Track the total number of land units - pub(super) type AllLandUnitsCount = StorageValue<_, u64, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn all_undeployed_land_unit)] - /// Track the total of undeployed land units - pub(super) type TotalUndeployedLandUnit = StorageValue<_, u64, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn get_land_units)] - /// Index land owners by metaverse ID and coordinate - pub type LandUnits = StorageDoubleMap< - _, - Twox64Concat, - MetaverseId, - Twox64Concat, - (i32, i32), - OwnerId, - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn next_estate_id)] - /// Track the next estate ID - pub type NextEstateId = StorageValue<_, EstateId, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn all_estates_count)] - /// Track the total of estates - pub(super) type AllEstatesCount = StorageValue<_, u64, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn get_estates)] - /// Store estate information - pub(super) type Estates = StorageMap<_, Twox64Concat, EstateId, EstateInfo, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn get_estate_owner)] - /// Track estate owners - pub type EstateOwner = - StorageMap<_, Twox64Concat, EstateId, OwnerId, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn next_undeployed_land_block_id)] - /// Track the next undeployed land ID - pub(super) type NextUndeployedLandBlockId = StorageValue<_, UndeployedLandBlockId, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn get_undeployed_land_block)] - /// Store undeployed land blocks - pub(super) type UndeployedLandBlocks = - StorageMap<_, Blake2_128Concat, UndeployedLandBlockId, UndeployedLandBlock, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn get_undeployed_land_block_owner)] - /// Index undeployed land blocks by account ID - pub type UndeployedLandBlocksOwner = - StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, UndeployedLandBlockId, (), OptionQuery>; + #[pallet::getter(fn fees)] + pub type Fees = StorageValue<_, (Permill, Permill), ValueQuery>; #[pallet::storage] - #[pallet::getter(fn round)] - /// Current round index and next round scheduled transition - pub type Round = StorageValue<_, RoundInfo, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn minting_rate_config)] - /// Minting rate configuration - pub type MintingRateConfig = StorageValue<_, MintingRateInfo, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn leases)] - /// Current active estate leases - pub type EstateLeases = - StorageMap<_, Twox64Concat, EstateId, LeaseContract, T::BlockNumber>, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn leasors)] - /// Current estate leasors - pub type EstateLeasors = - StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Twox64Concat, EstateId, (), OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn lease_offers)] - /// Current estate lease offers - pub type EstateLeaseOffers = StorageDoubleMap< - _, - Twox64Concat, - EstateId, - Blake2_128Concat, - T::AccountId, - LeaseContract, T::BlockNumber>, - OptionQuery, - >; + #[pallet::getter(fn token_pool)] + pub type TokenPool = StorageMap<_, Twox64Concat, (ClassId, CurrencyIdOf), BalanceOf, ValueQuery>; #[pallet::genesis_config] pub struct GenesisConfig { @@ -262,85 +150,9 @@ pub mod pallet { } } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - >::put(self.minting_rate_config.clone()); - - // Start Round 1 at Block 0 - let round: RoundInfo = RoundInfo::new(1u32, 0u32.into(), T::MinBlocksPerRound::get()); - - let round_issuance_per_round = round_issuance_range::(self.minting_rate_config.clone()); - - >::put(round); - >::deposit_event(Event::NewRound( - T::BlockNumber::zero(), - 1u32, - round_issuance_per_round.max, - )); - } - } - #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { - /// New lands are minted [Beneficial Account Id, Metaverse Id, Coordinates] - NewLandsMinted(T::AccountId, MetaverseId, Vec<(i32, i32)>), - /// Land unit is transferred [Metaverse Id, Coordinates, From Account Id, To Account Id] - TransferredLandUnit(MetaverseId, (i32, i32), T::AccountId, T::AccountId), - /// Estate unit is transferred [Estate Id, From Account Id, To Account Id] - TransferredEstate(EstateId, T::AccountId, T::AccountId), - /// New land is minted [Beneficial Account Id, Metaverse Id, Coordinates] - NewLandUnitMinted(OwnerId, MetaverseId, (i32, i32)), - /// New estate is minted [Estate Id, OwnerId, Metaverse Id, Coordinates] - NewEstateMinted( - EstateId, - OwnerId, - MetaverseId, - Vec<(i32, i32)>, - ), - /// Max bound is set for a metaverse [Metaverse Id, Min and Max Coordinate] - MaxBoundSet(MetaverseId, (i32, i32)), - /// Land block is deployed [From Account Id, Metaverse Id, Undeployed Land Block Id, - /// Coordinates] - LandBlockDeployed(T::AccountId, MetaverseId, UndeployedLandBlockId, Vec<(i32, i32)>), - /// Undeployed land block is issued [Beneficial Account Id, Undeployed Land - /// Block Id] - UndeployedLandBlockIssued(T::AccountId, UndeployedLandBlockId), - /// Undeployed land block is transferred [From Account Id, To Account Id, Undeployed - /// Land Block Id] - UndeployedLandBlockTransferred(T::AccountId, T::AccountId, UndeployedLandBlockId), - /// Undeployed land block is approved [Owner Account Id, Approved Account Id, Undeployed - /// Land Block Id] - UndeployedLandBlockApproved(T::AccountId, T::AccountId, UndeployedLandBlockId), - /// Estate is destroyed [Estate Id, Owner Id] - EstateDestroyed(EstateId, OwnerId), - /// Estate is updated [Estate Id, Owner Id, Coordinates] - EstateUpdated(EstateId, OwnerId, Vec<(i32, i32)>), - /// Land unit is added to an estate [Estate Id, Owner Id, Coordinates] - LandUnitAdded(EstateId, OwnerId, Vec<(i32, i32)>), - /// Land unit is removed from an estate [Estate Id, Owner Id, Coordinates] - LandUnitsRemoved(EstateId, OwnerId, Vec<(i32, i32)>), - /// Undeployed land block is unapproved [Undeployed Land Block Id] - UndeployedLandBlockUnapproved(UndeployedLandBlockId), - /// Undeployed land block is freezed [Undeployed Land Block Id] - UndeployedLandBlockFreezed(UndeployedLandBlockId), - /// Undeployed land block is unfreezed [Undeployed Land Block Id] - UndeployedLandBlockUnfreezed(UndeployedLandBlockId), - /// Undeployed land block is burnt [Undeployed Land Block Id] - UndeployedLandBlockBurnt(UndeployedLandBlockId), - /// Estate lease offer is created [AccountId, Estate Id, Total rent] - EstateLeaseOfferCreated(T::AccountId, EstateId, BalanceOf), - /// Estate lease offer is accepted [Estate Id, Leasor account Id, Lease End Block] - EstateLeaseOfferAccepted(EstateId, T::AccountId, T::BlockNumber), - /// Estate lease offer is removed [AccountId, Estate Id] - EstateLeaseOfferRemoved(T::AccountId, EstateId), - /// Estate lease contract ended [Estate Id] - EstateLeaseContractEnded(EstateId), - /// Estate lease contract was cancelled [Estate Id] - EstateLeaseContractCancelled(EstateId), - /// Estate rent collected [EstateId, Balance] - EstateRentCollected(EstateId, BalanceOf), /// New staking round started [Starting Block, Round, Total Land Unit] NewRound(T::BlockNumber, RoundIndex, u64), } @@ -349,101 +161,8 @@ pub mod pallet { pub enum Error { /// No permission NoPermission, - /// No available estate ID - NoAvailableEstateId, - /// Insufficient fund - InsufficientFund, - /// Estate ID already exist - EstateIdAlreadyExist, - /// Land unit is not available - LandUnitIsNotAvailable, - /// Land unit is out of bound - LandUnitIsOutOfBound, - /// Undeployed land block is not found - UndeployedLandBlockNotFound, - /// Undeployed land block is not transferable - UndeployedLandBlockIsNotTransferable, - /// Undeployed land block does not hae enough land units - UndeployedLandBlockDoesNotHaveEnoughLandUnits, - /// Number of land block credit and land unit does not match - UndeployedLandBlockUnitAndInputDoesNotMatch, - /// Account is not the owner of a given undeployed land block - UndeployedLandBlockNotOwned, - /// Already own the undeployed land block - AlreadyOwnTheUndeployedLandBlock, - /// Undeployed land block is freezed - UndeployedLandBlockFreezed, - /// Undeployed land block is already freezed - UndeployedLandBlockAlreadyFreezed, - /// Undeployed land block is not frozen - UndeployedLandBlockNotFrozen, - /// Already owning the estate - AlreadyOwnTheEstate, - /// Already owning the land unit - AlreadyOwnTheLandUnit, - /// Estate is not in auction - EstateNotInAuction, - /// Land unit is not in auction - LandUnitNotInAuction, - /// Estate is already in auction - EstateAlreadyInAuction, - /// Land unit is already in auction - LandUnitAlreadyInAuction, - /// Undeployed land block is already in auction - UndeployedLandBlockAlreadyInAuction, - /// Estate is does not exist - EstateDoesNotExist, - /// Land unit does not exist - LandUnitDoesNotExist, - /// Only frozen undeployed land block can be destroyed - OnlyFrozenUndeployedLandBlockCanBeDestroyed, - /// Below minimum staking amount - BelowMinimumStake, - /// Value overflow - Overflow, - /// Estate stake is already left - EstateStakeAlreadyLeft, - /// Account has not staked anything - AccountHasNoStake, - /// Invalid owner value - InvalidOwnerValue, - /// Coordinate for estate is not valid - CoordinatesForEstateIsNotValid, - /// Insufficient balance for deploying land blocks or creating estates - InsufficientBalanceForDeployingLandOrCreatingEstate, - // Land Unit already formed in Estate - LandUnitAlreadyInEstate, - /// Estate is already leased - EstateIsAlreadyLeased, - /// Estate lease offer limit is reached - EstateLeaseOffersQueueLimitIsReached, - /// Lease offer price per block is below the minimum - LeaseOfferPriceBelowMinimum, - /// Lease offer does not exist - LeaseOfferDoesNotExist, - /// Lease offer already exists - LeaseOfferAlreadyExists, - /// Lease offer is not expired - LeaseOfferIsNotExpired, - /// Lease does not exist - LeaseDoesNotExist, - /// Lease is not expired - LeaseIsNotExpired, - /// Lease is expired - LeaseIsExpired, - /// Lease duration beyond max duration - LeaseOfferDurationAboveMaximum, - /// No unclaimed rent balance - NoUnclaimedRentLeft, } - // TO DO: Implement offchain removal of expired lease offers - //#[pallet::hooks] - //impl Hooks> for Pallet { - // fn offchain_worker(block_number: T::BlockNumber) { - // } - //} - #[pallet::call] impl Pallet { /// Minting of a land unit, only used by council to manually mint single land for @@ -464,1115 +183,8 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - // Mint land unit - let owner = Self::mint_land_unit( - metaverse_id, - OwnerId::Account(beneficiary.clone()), - beneficiary, - coordinate, - LandUnitStatus::NonExisting, - )?; - - // Update total land count - Self::set_total_land_unit(One::one(), false)?; - - Self::deposit_event(Event::::NewLandUnitMinted(owner, metaverse_id, coordinate)); - - Ok(().into()) - } - - /// Minting of a land units, only used by council to manually mint number of lands for - /// beneficiary - /// - /// The dispatch origin for this call must be _Root_. - /// - `beneficiary`: the account which will be the owner of the land units - /// - `metaverse_id`: the metaverse id that the land units will be minted on - /// - `coordinates`: list of land units coordinates - /// - /// Emits `NewLandsMinted` if successful. - #[pallet::weight(T::WeightInfo::mint_lands())] - pub fn mint_lands( - origin: OriginFor, - beneficiary: T::AccountId, - metaverse_id: MetaverseId, - coordinates: Vec<(i32, i32)>, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - // Mint land units - for coordinate in coordinates.clone() { - Self::mint_land_unit( - metaverse_id, - OwnerId::Account(beneficiary.clone()), - beneficiary.clone(), - coordinate, - LandUnitStatus::NonExisting, - )?; - } - - // Update total land count - Self::set_total_land_unit(coordinates.len() as u64, false)?; - - Self::deposit_event(Event::::NewLandsMinted( - beneficiary.clone(), - metaverse_id.clone(), - coordinates.clone(), - )); - - Ok(().into()) - } - - /// Mint new estate with no existing land units, only used for council to manually mint - /// estate for beneficiary - /// - /// The dispatch origin for this call must be _Root_. - /// - `beneficiary`: the account which will be the owner of the land units - /// - `metaverse_id`: the metaverse id that the land units will be minted on - /// - `coordinates`: list of land units coordinates - /// - /// Emits `NewEstateMinted` if successful. - #[pallet::weight(T::WeightInfo::mint_estate())] - pub fn mint_estate( - origin: OriginFor, - beneficiary: T::AccountId, - metaverse_id: MetaverseId, - coordinates: Vec<(i32, i32)>, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - // Generate new estate id - let new_estate_id = Self::get_new_estate_id()?; - - // Generate sub account from estate - let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(new_estate_id); - - // Estate as owner - let token_properties = Self::get_estate_token_properties(metaverse_id, new_estate_id); - let class_id = T::MetaverseInfoSource::get_metaverse_estate_class(metaverse_id)?; - let token_id: TokenId = - T::NFTTokenizationSource::mint_token(&beneficiary, class_id, token_properties.0, token_properties.1)?; - let token_owner = OwnerId::Token(class_id, token_id); - - // Mint land units - for coordinate in coordinates.clone() { - Self::mint_land_unit( - metaverse_id, - token_owner.clone(), - estate_account_id.clone(), - coordinate, - LandUnitStatus::NonExistingWithEstate, - )?; - } - // Update total land count - Self::set_total_land_unit(coordinates.len() as u64, false)?; - - // Update estate information - Self::update_estate_information(new_estate_id, metaverse_id, token_owner, coordinates)?; - Ok(().into()) - } - - /// Transferring a land unit if it is not already in auction - /// - /// The dispatch origin for this call must be _Signed_. - /// Only the owner of a land can make this call. - /// - `to`: the account which will be the owner of the land units - /// - `metaverse_id`: the metaverse id of the land unit - /// - `coordinate`: the coordinate of the land unit - /// - /// Emits `TransferredLandUnit` if successful. - #[pallet::weight(T::WeightInfo::transfer_land())] - pub fn transfer_land( - origin: OriginFor, - to: T::AccountId, - metaverse_id: MetaverseId, - coordinate: (i32, i32), - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - Self::do_transfer_landunit(coordinate, &who, &to, metaverse_id)?; - Ok(().into()) - } - - /// Create new estate from existing land units - /// - /// The dispatch origin for this call must be _Signed_. - /// - `metaverse_id`: the metaverse id that the land units will be minted on - /// - `coordinates`: list of land units coordinates - /// - /// Emits `NewEstateMinted` if successful. - #[pallet::weight(T::WeightInfo::create_estate().saturating_mul(coordinates.len() as u64))] - #[transactional] - pub fn create_estate( - origin: OriginFor, - metaverse_id: MetaverseId, - coordinates: Vec<(i32, i32)>, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - ensure!( - Self::verify_land_unit_for_estate(coordinates.clone()), - Error::::CoordinatesForEstateIsNotValid - ); - // Collect network fee - Self::collect_network_fee(&who)?; - // Generate new estate id - let new_estate_id = Self::get_new_estate_id()?; - // Generate sub account from estate - let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(new_estate_id); - - let token_properties = Self::get_estate_token_properties(metaverse_id, new_estate_id); - let class_id = T::MetaverseInfoSource::get_metaverse_estate_class(metaverse_id)?; - let token_id: TokenId = - T::NFTTokenizationSource::mint_token(&who, class_id, token_properties.0, token_properties.1)?; - let beneficiary = OwnerId::Token(class_id, token_id); - - let storage_fee: BalanceOf = - Perbill::from_percent(100u32.saturating_mul(coordinates.len() as u32)) * T::StorageDepositFee::get(); - T::Currency::transfer( - &who, - &T::MetaverseInfoSource::get_network_treasury(), - storage_fee.saturated_into(), - ExistenceRequirement::KeepAlive, - )?; - - // Mint land units - for coordinate in coordinates.clone() { - Self::mint_land_unit( - metaverse_id, - beneficiary.clone(), - estate_account_id.clone(), - coordinate, - LandUnitStatus::Existing(who.clone()), - )?; - } - - // Update estate information - Self::update_estate_information(new_estate_id, metaverse_id, beneficiary, coordinates.clone())?; - - Ok(().into()) - } - - /// Transfer estate ownership if it is not in auction. - /// - /// The dispatch origin for this call must be _Signed_. - /// Only the owner of an estate can make this call. - /// - `to`: the account which will be the owner of the estate - /// - `estate_id`: the estate ID of the the estate that will be transferred - /// - /// Emits `TransferredEstate` if successful. - #[pallet::weight(T::WeightInfo::transfer_estate())] - #[transactional] - pub fn transfer_estate( - origin: OriginFor, - to: T::AccountId, - estate_id: EstateId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - Self::do_transfer_estate(estate_id, &who, &to)?; - Ok(().into()) - } - - /// Deploy raw land block to metaverse and turn raw land block to land unit with given - /// coordinates - /// - /// The dispatch origin for this call must be _Signed_. - /// Only the undeployed land block owner can make this call. - /// - `undeployed_land_block_id`: the undeployed land block ID - /// - `metaverse_id`: the metaverse ID that the land block will be deployed on - /// - `land_block_coordinates`: the coordinates of the land block - /// - `coordinates`: list of land units coordinates - /// - /// Emits `LandBlockDeployed` if successful. - #[pallet::weight(T::WeightInfo::deploy_land_block() * coordinates.len() as u64)] - #[transactional] - pub fn deploy_land_block( - origin: OriginFor, - undeployed_land_block_id: UndeployedLandBlockId, - metaverse_id: MetaverseId, - land_block_coordinate: (i32, i32), - coordinates: Vec<(i32, i32)>, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), - Error::::UndeployedLandBlockAlreadyInAuction - ); - - ensure!( - T::MetaverseInfoSource::check_ownership(&who, &metaverse_id), - Error::::NoPermission - ); - - // Ensure the max bound is set for the metaverse - let max_bound = T::DefaultMaxBound::get(); - - // Check whether the coordinate is within the bound - ensure!( - (land_block_coordinate.0 >= max_bound.0 && max_bound.1 >= land_block_coordinate.0) - && (land_block_coordinate.1 >= max_bound.0 && max_bound.1 >= land_block_coordinate.1), - Error::::LandUnitIsOutOfBound - ); - - ensure!( - Self::verify_land_unit_in_bound(&land_block_coordinate, &coordinates), - Error::::LandUnitIsOutOfBound - ); - - let undeployed_land_block_record = UndeployedLandBlocks::::get(undeployed_land_block_id) - .ok_or(Error::::UndeployedLandBlockNotFound)?; - - ensure!( - undeployed_land_block_record.owner == who.clone(), - Error::::NoPermission - ); - - ensure!( - undeployed_land_block_record.is_locked == false, - Error::::UndeployedLandBlockFreezed - ); - - let land_units_to_mint = coordinates.len() as u32; - - // Ensure undeployed land block only deployed once - ensure!( - undeployed_land_block_record.number_land_units == land_units_to_mint, - Error::::UndeployedLandBlockUnitAndInputDoesNotMatch - ); - - // Collect network fee - Self::collect_network_fee(&who)?; - - // Mint land units - for coordinate in coordinates.clone() { - Self::mint_land_unit( - metaverse_id, - OwnerId::Account(who.clone()), - who.clone(), - coordinate, - LandUnitStatus::NonExisting, - )?; - } - - // Update total land count - Self::set_total_land_unit(coordinates.len() as u64, false)?; - - // Burn undeployed land block - Self::do_burn_undeployed_land_block(undeployed_land_block_id)?; - - Self::deposit_event(Event::::LandBlockDeployed( - who.clone(), - metaverse_id, - undeployed_land_block_id, - coordinates, - )); - - Ok(().into()) - } - - /// Issues new undeployed land block(s) - /// - /// The dispatch origin for this call must be _Root_. - /// - `beneficiary`: the account which will be the owner of the undeployed land block(s) - /// - `number_of_land_block`: the number of undeployed land block(s) that will be created - /// - `number_land_units_per_land_block`: the number of land units in each undeployed land - /// block - /// - `land_block_coordinates`: the coordinates of the undeployed land block - /// - /// Emits `UndeployedLandBlockIssued` if successful. - #[pallet::weight(T::WeightInfo::issue_undeployed_land_blocks())] - #[transactional] - pub fn issue_undeployed_land_blocks( - who: OriginFor, - beneficiary: T::AccountId, - number_of_land_block: u32, - number_land_units_per_land_block: u32, - undeployed_land_block_type: UndeployedLandBlockType, - ) -> DispatchResultWithPostInfo { - ensure_root(who)?; - - Self::do_issue_undeployed_land_blocks( - &beneficiary, - number_of_land_block, - number_land_units_per_land_block, - undeployed_land_block_type, - )?; - - Ok(().into()) - } - - /// Freezes undeployed land block which is not already frozen - /// - /// The dispatch origin for this call must be _Root_. - /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be freezed - /// - /// Emits `UndeployedLandBlockFreezed` if successful. - #[pallet::weight(T::WeightInfo::freeze_undeployed_land_blocks())] - #[transactional] - pub fn freeze_undeployed_land_blocks( - origin: OriginFor, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - Self::do_freeze_undeployed_land_block(undeployed_land_block_id)?; - - Ok(().into()) - } - - /// Unfreezes undeployed land block which is frozen. - /// - /// The dispatch origin for this call must be _Root_. - /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be unfreezed - /// - /// Emits `UndeployedLandBlockUnfreezed` if successful. - #[pallet::weight(T::WeightInfo::unfreeze_undeployed_land_blocks())] - #[transactional] - pub fn unfreeze_undeployed_land_blocks( - origin: OriginFor, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - UndeployedLandBlocks::::try_mutate_exists( - &undeployed_land_block_id, - |undeployed_land_block| -> DispatchResultWithPostInfo { - let undeployed_land_block_record = undeployed_land_block - .as_mut() - .ok_or(Error::::UndeployedLandBlockNotFound)?; - - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock( - undeployed_land_block_id - )), - Error::::UndeployedLandBlockAlreadyInAuction - ); - - ensure!( - undeployed_land_block_record.is_locked == true, - Error::::UndeployedLandBlockNotFrozen - ); - - undeployed_land_block_record.is_locked = false; - - Self::deposit_event(Event::::UndeployedLandBlockUnfreezed(undeployed_land_block_id)); - - Ok(().into()) - }, - ) - } - - /// Transfer undeployed land block owner if it is not in auction. - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the undeployed land block owner can make this call. - /// - `to`: the account that will receive the undeployed land block - /// - `undeployed_land_block_id`: the ID of the land block that will be transferred - /// - /// Emits `UndeployedLandBlockTransferred` if successful. - #[pallet::weight(T::WeightInfo::transfer_undeployed_land_blocks())] - #[transactional] - pub fn transfer_undeployed_land_blocks( - origin: OriginFor, - to: T::AccountId, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), - Error::::UndeployedLandBlockAlreadyInAuction - ); - - Self::do_transfer_undeployed_land_block(&who, &to, undeployed_land_block_id)?; - - Ok(().into()) - } - - /// Burn raw land block that will reduce total supply - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the undeployed land block owner can make this call. - /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be burned - /// - /// Emits `UndeployedLandBlockBurnt` if successful. - #[pallet::weight(T::WeightInfo::burn_undeployed_land_blocks())] - #[transactional] - pub fn burn_undeployed_land_blocks( - origin: OriginFor, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - Self::do_burn_undeployed_land_block(undeployed_land_block_id)?; - - Ok(().into()) - } - - /// Approve existing undeployed land block which is not frozen. - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the undeployed land block owner can make this call. - /// - `to`: the account for which the undeployed land block will be approved - /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be burned - /// - /// Emits `UndeployedLandBlockApproved` if successful - #[pallet::weight(T::WeightInfo::approve_undeployed_land_blocks())] - #[transactional] - pub fn approve_undeployed_land_blocks( - origin: OriginFor, - to: T::AccountId, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - UndeployedLandBlocks::::try_mutate_exists( - &undeployed_land_block_id, - |undeployed_land_block| -> DispatchResultWithPostInfo { - let mut undeployed_land_block_record = undeployed_land_block - .as_mut() - .ok_or(Error::::UndeployedLandBlockNotFound)?; - - ensure!( - undeployed_land_block_record.owner == who.clone(), - Error::::NoPermission - ); - - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock( - undeployed_land_block_id - )), - Error::::UndeployedLandBlockAlreadyInAuction - ); - - ensure!( - undeployed_land_block_record.is_locked == false, - Error::::UndeployedLandBlockAlreadyFreezed - ); - - undeployed_land_block_record.approved = Some(to.clone()); - - Self::deposit_event(Event::::UndeployedLandBlockApproved( - who.clone(), - to.clone(), - undeployed_land_block_id.clone(), - )); - - Ok(().into()) - }, - ) - } - - /// Unapprove existing undeployed land block which is not frozen. - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the undeployed land block owner can make this call. - /// - `undeployed_land_block_id`: the ID of the undeployed land block that will be - /// unapproved - /// - /// Emits `UndeployedLandBlockUnapproved` if successful - #[pallet::weight(T::WeightInfo::unapprove_undeployed_land_blocks())] - #[transactional] - pub fn unapprove_undeployed_land_blocks( - origin: OriginFor, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - UndeployedLandBlocks::::try_mutate_exists( - &undeployed_land_block_id, - |undeployed_land_block| -> DispatchResultWithPostInfo { - let mut undeployed_land_block_record = undeployed_land_block - .as_mut() - .ok_or(Error::::UndeployedLandBlockNotFound)?; - - ensure!( - undeployed_land_block_record.owner == who.clone(), - Error::::NoPermission - ); - - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock( - undeployed_land_block_id - )), - Error::::UndeployedLandBlockAlreadyInAuction - ); - - ensure!( - undeployed_land_block_record.is_locked == false, - Error::::UndeployedLandBlockAlreadyFreezed - ); - - undeployed_land_block_record.approved = None; - - Self::deposit_event(Event::::UndeployedLandBlockUnapproved( - undeployed_land_block_id.clone(), - )); - - Ok(().into()) - }, - ) - } - - /// Dissolve estate to land units if it is not in auction. - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the estate owner can make this call. - /// - `estate_id`: the ID of the estate that will be dissolved - /// - /// Emits `EstateDestroyed` if successful - #[pallet::weight(T::WeightInfo::dissolve_estate())] - #[transactional] - pub fn dissolve_estate(origin: OriginFor, estate_id: EstateId) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; - match estate_owner_value { - OwnerId::Token(c, t) => { - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::NFT(c, t)), - Error::::EstateAlreadyInAuction - ); - //ensure there is record of the estate owner with estate id and account id - ensure!( - Self::check_if_land_or_estate_owner(&who, &estate_owner_value), - Error::::NoPermission - ); - let estate_info = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; - EstateOwner::::try_mutate_exists(&estate_id, |estate_owner| { - // Reset estate ownership - match estate_owner_value { - OwnerId::Token(class_id, token_id) => { - T::NFTTokenizationSource::burn_nft(&who, &(class_id, token_id)); - *estate_owner = None; - } - OwnerId::Account(ref a) => { - *estate_owner = None; - } - } - - // Remove estate - Estates::::remove(&estate_id); - - // Update total estates - let total_estates_count = Self::all_estates_count(); - let new_total_estates_count = total_estates_count - .checked_sub(One::one()) - .ok_or("Overflow adding new count to total estates")?; - AllEstatesCount::::put(new_total_estates_count); - - // Mint new land tokens to replace the lands in the dissolved estate - let estate_account_id: T::AccountId = - T::LandTreasury::get().into_sub_account_truncating(estate_id); - let storage_fee: BalanceOf = - Perbill::from_percent(100u32.saturating_mul(estate_info.land_units.len() as u32)) - * T::StorageDepositFee::get(); - T::Currency::transfer( - &who, - &T::MetaverseInfoSource::get_network_treasury(), - storage_fee.saturated_into(), - ExistenceRequirement::KeepAlive, - )?; - for land_unit in estate_info.land_units { - // Transfer land unit from treasury to estate owner - Self::mint_land_unit( - estate_info.metaverse_id, - estate_owner_value.clone(), - who.clone(), - land_unit, - LandUnitStatus::RemovedFromEstate, - )?; - } - - Self::deposit_event(Event::::EstateDestroyed( - estate_id.clone(), - estate_owner_value.clone(), - )); - - Ok(().into()) - }) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - - /// Add more land units to existing estate that is not in auction - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the estate owner can make this call. - /// They must also own the land units. - /// - `estate_id`: the ID of the estate that the land units will be added to - /// - `land_units`: list of land unit coordinates that will be added to estate - /// - /// Emits `LandUnitAdded` if successful - #[pallet::weight(T::WeightInfo::add_land_unit_to_estate())] - #[transactional] - pub fn add_land_unit_to_estate( - origin: OriginFor, - estate_id: EstateId, - land_units: Vec<(i32, i32)>, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; - match estate_owner_value { - OwnerId::Token(c, t) => { - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::NFT(c, t)), - Error::::EstateAlreadyInAuction - ); - ensure!( - Self::check_if_land_or_estate_owner(&who, &estate_owner_value), - Error::::NoPermission - ); - - let estate_info: EstateInfo = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; - let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(estate_id); - - let storage_fee: BalanceOf = - Perbill::from_percent(100u32.saturating_mul(land_units.len() as u32)) - * T::StorageDepositFee::get(); - T::Currency::transfer( - &who, - &T::MetaverseInfoSource::get_network_treasury(), - storage_fee.saturated_into(), - ExistenceRequirement::KeepAlive, - )?; - - // Check land unit ownership - for land_unit in land_units.clone() { - let metaverse_land_unit = Self::get_land_units(estate_info.metaverse_id, land_unit) - .ok_or(Error::::UndeployedLandBlockNotFound)?; - ensure!( - Self::check_if_land_or_estate_owner(&who, &metaverse_land_unit,), - Error::::LandUnitDoesNotExist - ); - - // Mint land unit - Self::mint_land_unit( - estate_info.metaverse_id, - estate_owner_value.clone(), - estate_account_id.clone(), - land_unit, - LandUnitStatus::Existing(who.clone()), - )?; - } - - // Mutate estates - Estates::::try_mutate_exists(&estate_id, |maybe_estate_info| { - // Append new coordinates to estate - let mut_estate_info = maybe_estate_info.as_mut().ok_or(Error::::EstateDoesNotExist)?; - mut_estate_info.land_units.append(&mut land_units.clone()); - - Self::deposit_event(Event::::LandUnitAdded( - estate_id.clone(), - estate_owner_value.clone(), - land_units.clone(), - )); - - Ok(().into()) - }) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - - /// Remove land units from existing estate if it is not in auction. - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the estate owner can make this call. - /// - `estate_id`: the ID of the estate that the land units will be removed from - /// - `land_units`: list of land unit coordinates that will be added to estate - /// - /// Emits `LandUnitsRemoved` if successful - #[pallet::weight(T::WeightInfo::remove_land_unit_from_estate())] - #[transactional] - pub fn remove_land_unit_from_estate( - origin: OriginFor, - estate_id: EstateId, - land_units: Vec<(i32, i32)>, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; - match estate_owner_value { - OwnerId::Token(c, t) => { - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::NFT(c, t)), - Error::::EstateAlreadyInAuction - ); - ensure!( - Self::check_if_land_or_estate_owner(&who, &estate_owner_value), - Error::::NoPermission - ); - let estate_info: EstateInfo = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; - let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(estate_id); - - // Mutate estates - Estates::::try_mutate_exists(&estate_id, |maybe_estate_info| { - let mut mut_estate_info = maybe_estate_info.as_mut().ok_or(Error::::EstateDoesNotExist)?; - - let storage_fee: BalanceOf = - Perbill::from_percent(100u32.saturating_mul(land_units.len() as u32)) - * T::StorageDepositFee::get(); - T::Currency::transfer( - &who, - &T::MetaverseInfoSource::get_network_treasury(), - storage_fee.saturated_into(), - ExistenceRequirement::KeepAlive, - )?; - // Mutate land unit ownership - for land_unit in land_units.clone() { - // Transfer land unit from treasury to estate owner - Self::mint_land_unit( - estate_info.metaverse_id, - estate_owner_value.clone(), - who.clone(), - land_unit, - LandUnitStatus::RemovedFromEstate, - )?; - // Remove coordinates from estate - let index = mut_estate_info - .land_units - .iter() - .position(|x| *x == land_unit) - .ok_or(Error::::LandUnitIsNotAvailable)?; - mut_estate_info.land_units.remove(index); - } - - Self::deposit_event(Event::::LandUnitsRemoved( - estate_id.clone(), - estate_owner_value.clone(), - land_units.clone(), - )); - - Ok(().into()) - }) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - - /// Create a lease offer for estate that is not leased - /// - /// The dispatch origin for this call must be _Singed_. - /// Only origin that is not the estate owner can make this call. - /// - `estate_id`: the ID of the estate that will be leased - /// - `price_per_block`: lease price per block - /// - `duration`: lease duration (in number of blocks) - /// - /// Emits `EstateLeaseOfferCreated` if successful - #[pallet::weight(T::WeightInfo::create_lease_offer())] - #[transactional] - pub fn create_lease_offer( - origin: OriginFor, - estate_id: EstateId, - price_per_block: BalanceOf, - duration: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; - ensure!( - !Self::check_if_land_or_estate_owner(&who, &estate_owner_value), - Error::::NoPermission - ); - ensure!( - !EstateLeaseOffers::::contains_key(estate_id, who.clone()), - Error::::LeaseOfferAlreadyExists - ); - ensure!( - !EstateLeases::::contains_key(estate_id), - Error::::EstateIsAlreadyLeased - ); - ensure!( - price_per_block >= T::MinLeasePricePerBlock::get(), - Error::::LeaseOfferPriceBelowMinimum - ); - ensure!( - duration <= T::MaxLeasePeriod::get(), - Error::::LeaseOfferDurationAboveMaximum - ); - - match estate_owner_value { - OwnerId::Token(class_id, token_id) => { - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::NFT(class_id, token_id)), - Error::::EstateAlreadyInAuction - ); - ensure!( - EstateLeaseOffers::::iter_key_prefix(estate_id).count() as u32 - <= T::MaxOffersPerEstate::get(), - Error::::EstateLeaseOffersQueueLimitIsReached - ); - - let current_block_number = >::block_number(); - let end_block = current_block_number + T::LeaseOfferExpiryPeriod::get().into(); - let unclaimed_rent: BalanceOf = price_per_block * duration.into(); - - let lease_offer = LeaseContract { - price_per_block, - duration, - end_block, - start_block: end_block + 1u32.into(), - unclaimed_rent, - }; - - EstateLeaseOffers::::insert(estate_id, who.clone(), lease_offer); - T::Currency::reserve(&who, unclaimed_rent); - - Self::deposit_event(Event::::EstateLeaseOfferCreated(who, estate_id, unclaimed_rent)); - - Ok(().into()) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - - /// Accept lease offer for estate that is not leased - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the estate owner can make this call. - /// - `estate_id`: the ID of the estate that will be leased - /// - `recipient`: the account that made the lease offer - /// - /// Emits `EstateLeaseOfferAccepted` if successful - #[pallet::weight(T::WeightInfo::accept_lease_offer())] - #[transactional] - pub fn accept_lease_offer( - origin: OriginFor, - estate_id: EstateId, - recipient: T::AccountId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - ensure!( - !EstateLeases::::contains_key(estate_id), - Error::::EstateIsAlreadyLeased - ); - - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; - match estate_owner_value { - OwnerId::Token(class_id, token_id) => { - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::NFT(class_id, token_id)), - Error::::EstateAlreadyInAuction - ); - ensure!( - Self::check_if_land_or_estate_owner(&who, &estate_owner_value), - Error::::NoPermission - ); - - let mut lease = - Self::lease_offers(estate_id, recipient.clone()).ok_or(Error::::LeaseOfferDoesNotExist)?; - - lease.start_block = >::block_number(); - lease.end_block = lease.start_block + lease.duration.into(); - // 200% storage fee since there are 2 storage inserts - let storage_fee: BalanceOf = Perbill::from_percent(200) * T::StorageDepositFee::get(); - T::Currency::transfer( - &who, - &T::MetaverseInfoSource::get_network_treasury(), - storage_fee.saturated_into(), - ExistenceRequirement::KeepAlive, - )?; - - EstateLeaseOffers::::remove_prefix(estate_id, None); - EstateLeases::::insert(estate_id, lease.clone()); - EstateLeasors::::insert(recipient.clone(), estate_id, ()); - T::NFTTokenizationSource::set_lock_nft((class_id, token_id), true)?; - - Self::deposit_event(Event::::EstateLeaseOfferAccepted( - estate_id, - recipient.clone(), - lease.end_block, - )); - - Ok(().into()) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - - /// Cancels existing lease - /// - /// The dispatch origin for this call must be _Root_. - /// - `estate_id`: the ID of the estate that will be leased - /// - `leasor`: the account that is leasing the estate - /// - /// Emits `EstateLeaseContractCancelled` if successful - #[pallet::weight(T::WeightInfo::cancel_lease())] - #[transactional] - pub fn cancel_lease( - origin: OriginFor, - estate_owner: T::AccountId, - estate_id: EstateId, - leasor: T::AccountId, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - let lease = Self::leases(estate_id).ok_or(Error::::LeaseDoesNotExist)?; - ensure!( - EstateLeasors::::contains_key(leasor.clone(), estate_id), - Error::::LeaseDoesNotExist - ); - ensure!( - lease.end_block > >::block_number(), - Error::::LeaseIsExpired - ); - - let total_rent: BalanceOf = lease.price_per_block * lease.duration.into(); - let rent_period = >::block_number() - lease.start_block; - let rent_claim_amount = lease.price_per_block * T::BlockNumberToBalance::convert(rent_period) - + lease.unclaimed_rent - - total_rent; - - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; - match estate_owner_value { - OwnerId::Token(class_id, token_id) => { - //let estate_owner = T::NFTTokenizationSource::get_asset_owner(&(class_id, token_id))?; - ensure!( - Self::check_if_land_or_estate_owner(&estate_owner, &estate_owner_value), - Error::::NoPermission - ); - T::Currency::unreserve(&leasor, lease.unclaimed_rent.into()); - ::Currency::transfer( - &leasor, - &estate_owner, - rent_claim_amount, - ExistenceRequirement::KeepAlive, - )?; - - EstateLeasors::::remove(leasor.clone(), estate_id); - EstateLeases::::remove(estate_id); - T::NFTTokenizationSource::set_lock_nft((class_id, token_id), false)?; - - Self::deposit_event(Event::::EstateLeaseContractCancelled(estate_id)); - Ok(().into()) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - - /// Removes expired lease - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the estate owner can make this call. - /// - `estate_id`: the ID of the estate that will be leased - /// - `leasor`: the account that is leasing the estate - /// - /// Emits `EstateLeaseContractEnded` if successful - #[pallet::weight(T::WeightInfo::remove_expired_lease())] - #[transactional] - pub fn remove_expired_lease( - origin: OriginFor, - estate_id: EstateId, - leasor: T::AccountId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let lease = Self::leases(estate_id).ok_or(Error::::LeaseDoesNotExist)?; - ensure!( - EstateLeasors::::contains_key(leasor.clone(), estate_id), - Error::::LeaseDoesNotExist - ); - ensure!( - lease.end_block <= >::block_number(), - Error::::LeaseIsNotExpired - ); - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::EstateDoesNotExist)?; - match estate_owner_value { - OwnerId::Token(class_id, token_id) => { - ensure!( - Self::check_if_land_or_estate_owner(&who, &estate_owner_value), - Error::::NoPermission - ); - - T::Currency::unreserve(&leasor, lease.unclaimed_rent.into()); - ::Currency::transfer( - &leasor, - &who, - lease.unclaimed_rent.into(), - ExistenceRequirement::KeepAlive, - )?; - - EstateLeasors::::remove(leasor, estate_id); - EstateLeases::::remove(estate_id); - T::NFTTokenizationSource::set_lock_nft((class_id, token_id), false)?; - - Self::deposit_event(Event::::EstateLeaseContractEnded(estate_id)); - Ok(().into()) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - - /// Removes lease offer - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the account made the lease offer can make this call. - /// - `estate_id`: the ID of the estate that will be leased - /// - /// Emits `EstateLeaseOfferRemoved` if successful - #[pallet::weight(T::WeightInfo::remove_lease_offer())] - #[transactional] - pub fn remove_lease_offer(origin: OriginFor, estate_id: EstateId) -> DispatchResultWithPostInfo { - let leasor = ensure_signed(origin)?; - let lease_offer = - Self::lease_offers(estate_id, leasor.clone()).ok_or(Error::::LeaseOfferDoesNotExist)?; - EstateLeaseOffers::::remove(estate_id, leasor.clone()); - T::Currency::unreserve(&leasor, lease_offer.unclaimed_rent.into()); - Self::deposit_event(Event::::EstateLeaseOfferRemoved(leasor, estate_id)); Ok(().into()) } - - /// Collect rent for a leased estate - /// - /// The dispatch origin for this call must be _Singed_. - /// Only the estate owner can make this call. - /// - `estate_id`: the ID of the estate that will be leased - /// - /// Emits `EstateRentCollected` if successful - #[pallet::weight(T::WeightInfo::collect_rent())] - #[transactional] - pub fn collect_rent( - origin: OriginFor, - estate_id: EstateId, - leasor: T::AccountId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - ensure!( - Self::check_estate_ownership(who.clone(), estate_id)?, - Error::::NoPermission - ); - ensure!( - EstateLeasors::::contains_key(leasor.clone(), estate_id), - Error::::LeaseDoesNotExist - ); - let current_block = >::block_number(); - EstateLeases::::try_mutate_exists(&estate_id, |estate_lease_value| { - let mut lease = estate_lease_value.as_mut().ok_or(Error::::LeaseDoesNotExist)?; - - ensure!(lease.end_block > current_block, Error::::LeaseIsExpired); - - let total_rent: BalanceOf = lease.price_per_block * lease.duration.into(); - let rent_period = >::block_number() - lease.start_block; - let rent_claim_amount = lease.price_per_block * T::BlockNumberToBalance::convert(rent_period) - + lease.unclaimed_rent - - total_rent; - - ::Currency::unreserve(&leasor, rent_claim_amount); - ::Currency::transfer( - &leasor, - &who, - rent_claim_amount.into(), - ExistenceRequirement::KeepAlive, - )?; - - lease.unclaimed_rent -= rent_claim_amount; - - Self::deposit_event(Event::::EstateRentCollected(estate_id, rent_claim_amount.into())); - Ok(().into()) - }) - } } } @@ -1586,742 +198,4 @@ impl Pallet { })?; Ok(estate_id) } - - /// Internal minting of land unit - fn mint_land_unit( - metaverse_id: MetaverseId, - token_owner: OwnerId, - to: T::AccountId, - coordinate: (i32, i32), - land_unit_status: LandUnitStatus, - ) -> Result, DispatchError> { - let mut owner = token_owner.clone(); - - match land_unit_status { - // Use case - create new estate. - LandUnitStatus::Existing(a) => { - ensure!( - LandUnits::::contains_key(metaverse_id, coordinate), - Error::::LandUnitIsNotAvailable - ); - - let existing_owner_value = Self::get_land_units(metaverse_id, coordinate); - match existing_owner_value { - Some(owner_value) => match owner_value { - OwnerId::Token(class_id, token_id) => { - // Implement check if user own nft - ensure!( - T::NFTTokenizationSource::check_ownership(&a, &(class_id, token_id))?, - Error::::NoPermission - ); - - if let OwnerId::Token(owner_class_id, owner_token_id) = token_owner { - ensure!(owner_class_id != class_id, Error::::LandUnitAlreadyInEstate) - } - - // Ensure not locked - T::NFTTokenizationSource::set_lock_nft((class_id, token_id), false)?; - T::NFTTokenizationSource::burn_nft(&a, &(class_id, token_id)); - - LandUnits::::insert(metaverse_id, coordinate, token_owner.clone()); - } - _ => (), - }, - /* It doesn't make sense to mint existing land unit when ownership doesn't exists */ - _ => (), - } - } - LandUnitStatus::NonExisting => { - ensure!( - !LandUnits::::contains_key(metaverse_id, coordinate), - Error::::LandUnitIsNotAvailable - ); - - let token_properties = Self::get_land_token_properties(metaverse_id, coordinate); - let class_id = T::MetaverseInfoSource::get_metaverse_land_class(metaverse_id)?; - let token_id = - T::NFTTokenizationSource::mint_token(&to, class_id, token_properties.0, token_properties.1)?; - owner = OwnerId::Token(class_id, token_id); - LandUnits::::insert(metaverse_id, coordinate, OwnerId::Token(class_id, token_id.clone())); - } - LandUnitStatus::NonExistingWithEstate => { - ensure!( - !LandUnits::::contains_key(metaverse_id, coordinate), - Error::::LandUnitIsNotAvailable - ); - - owner = token_owner.clone(); - - LandUnits::::insert(metaverse_id, coordinate, token_owner.clone()); - } - LandUnitStatus::RemovedFromEstate => { - ensure!( - LandUnits::::contains_key(metaverse_id, coordinate), - Error::::LandUnitIsNotAvailable - ); - - let token_properties = Self::get_land_token_properties(metaverse_id, coordinate); - let class_id = T::MetaverseInfoSource::get_metaverse_land_class(metaverse_id)?; - let token_id = - T::NFTTokenizationSource::mint_token(&to, class_id, token_properties.0, token_properties.1)?; - owner = OwnerId::Token(class_id, token_id); - LandUnits::::remove(metaverse_id, coordinate); - LandUnits::::insert(metaverse_id, coordinate, OwnerId::Token(class_id, token_id)); - } - } - Ok(owner) - } - - /// Internal updating information about an estate - fn update_estate_information( - new_estate_id: EstateId, - metaverse_id: MetaverseId, - estate_owner: OwnerId, - coordinates: Vec<(i32, i32)>, - ) -> DispatchResult { - // Update total estates - let total_estates_count = Self::all_estates_count(); - let new_total_estates_count = total_estates_count - .checked_add(One::one()) - .ok_or("Overflow adding new count to total estates")?; - AllEstatesCount::::put(new_total_estates_count); - - // Update estates - let estate_info = EstateInfo { - metaverse_id, - land_units: coordinates.clone(), - }; - - Estates::::insert(new_estate_id, estate_info); - EstateOwner::::insert(new_estate_id, estate_owner.clone()); - - Self::deposit_event(Event::::NewEstateMinted( - new_estate_id.clone(), - estate_owner, - metaverse_id, - coordinates.clone(), - )); - - Ok(()) - } - - /// Internal getter of new undeployed land block ID - fn get_new_undeployed_land_block_id() -> Result { - let undeployed_land_block_id = - NextUndeployedLandBlockId::::try_mutate(|id| -> Result { - let current_id = *id; - *id = id.checked_add(One::one()).ok_or(Error::::NoAvailableEstateId)?; - Ok(current_id) - })?; - Ok(undeployed_land_block_id) - } - - /// Internal transfer of undeployed land block - fn do_transfer_undeployed_land_block( - who: &T::AccountId, - to: &T::AccountId, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - // 200% storage fee since there are 2 storage inserts - let storage_fee: BalanceOf = Perbill::from_percent(200) * T::StorageDepositFee::get(); - T::Currency::transfer( - &who, - &T::MetaverseInfoSource::get_network_treasury(), - storage_fee.saturated_into(), - ExistenceRequirement::KeepAlive, - )?; - - UndeployedLandBlocks::::try_mutate( - &undeployed_land_block_id, - |undeployed_land_block| -> Result { - let undeployed_land_block_record = undeployed_land_block - .as_mut() - .ok_or(Error::::UndeployedLandBlockNotFound)?; - - ensure!( - undeployed_land_block_record.owner == who.clone(), - Error::::NoPermission - ); - - ensure!( - undeployed_land_block_record.is_locked == false, - Error::::UndeployedLandBlockAlreadyFreezed - ); - - ensure!( - undeployed_land_block_record.undeployed_land_block_type == UndeployedLandBlockType::Transferable, - Error::::UndeployedLandBlockIsNotTransferable - ); - - undeployed_land_block_record.owner = to.clone(); - - UndeployedLandBlocksOwner::::remove(who.clone(), &undeployed_land_block_id); - UndeployedLandBlocksOwner::::insert(to.clone(), &undeployed_land_block_id, ()); - - Self::deposit_event(Event::::UndeployedLandBlockTransferred( - who.clone(), - to.clone(), - undeployed_land_block_id.clone(), - )); - - Ok(undeployed_land_block_id) - }, - ) - } - - /// Internal burn of undeployed land block - fn do_burn_undeployed_land_block( - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - let undeployed_land_block_info = - UndeployedLandBlocks::::get(undeployed_land_block_id).ok_or(Error::::UndeployedLandBlockNotFound)?; - - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), - Error::::UndeployedLandBlockAlreadyInAuction - ); - - ensure!( - !undeployed_land_block_info.is_locked, - Error::::OnlyFrozenUndeployedLandBlockCanBeDestroyed - ); - - Self::set_total_undeployed_land_unit(undeployed_land_block_info.number_land_units as u64, true)?; - UndeployedLandBlocksOwner::::remove(undeployed_land_block_info.owner, &undeployed_land_block_id); - UndeployedLandBlocks::::remove(&undeployed_land_block_id); - - Self::deposit_event(Event::::UndeployedLandBlockBurnt(undeployed_land_block_id.clone())); - - Ok(undeployed_land_block_id) - } - - /// Internal freeze of undeployed land block - fn do_freeze_undeployed_land_block( - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - UndeployedLandBlocks::::try_mutate_exists( - &undeployed_land_block_id, - |undeployed_land_block| -> Result { - let undeployed_land_block_record = undeployed_land_block - .as_mut() - .ok_or(Error::::UndeployedLandBlockNotFound)?; - - ensure!( - undeployed_land_block_record.is_locked == false, - Error::::UndeployedLandBlockAlreadyFreezed - ); - - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::UndeployedLandBlock(undeployed_land_block_id)), - Error::::UndeployedLandBlockAlreadyInAuction - ); - - undeployed_land_block_record.is_locked = true; - - Self::deposit_event(Event::::UndeployedLandBlockFreezed(undeployed_land_block_id)); - - Ok(undeployed_land_block_id) - }, - ) - } - - /// Internal issue of undeployed land block - fn do_issue_undeployed_land_blocks( - beneficiary: &T::AccountId, - number_of_land_block: u32, - number_land_units_per_land_block: u32, - undeployed_land_block_type: UndeployedLandBlockType, - ) -> Result, DispatchError> { - let mut undeployed_land_block_ids: Vec = Vec::new(); - - // 2 inserts per land blocks - let storage_fee: BalanceOf = - Perbill::from_percent(number_of_land_block.saturating_mul(2).saturating_mul(100)) - * T::StorageDepositFee::get(); - - T::Currency::transfer( - beneficiary, - &T::MetaverseInfoSource::get_network_treasury(), - storage_fee.saturated_into(), - ExistenceRequirement::KeepAlive, - )?; - - for _ in 0..number_of_land_block { - let new_undeployed_land_block_id = Self::get_new_undeployed_land_block_id()?; - - let undeployed_land_block = UndeployedLandBlock { - id: new_undeployed_land_block_id, - number_land_units: number_land_units_per_land_block, - undeployed_land_block_type, - approved: None, - is_locked: false, - owner: beneficiary.clone(), - }; - - UndeployedLandBlocks::::insert(new_undeployed_land_block_id, undeployed_land_block); - UndeployedLandBlocksOwner::::insert(beneficiary.clone(), new_undeployed_land_block_id, ()); - - // Update total undeployed land count - Self::set_total_undeployed_land_unit(number_land_units_per_land_block as u64, false)?; - - Self::deposit_event(Event::::UndeployedLandBlockIssued( - beneficiary.clone(), - new_undeployed_land_block_id.clone(), - )); - - undeployed_land_block_ids.push(new_undeployed_land_block_id); - } - - Ok(undeployed_land_block_ids) - } - - /// Internal transfer of estate - fn do_transfer_estate( - estate_id: EstateId, - from: &T::AccountId, - to: &T::AccountId, - ) -> Result { - EstateOwner::::try_mutate_exists(&estate_id, |estate_owner| -> Result { - //ensure there is record of the estate owner with estate id and account id - ensure!(from != to, Error::::AlreadyOwnTheEstate); - let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; - let estate_info = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; - ensure!( - !EstateLeases::::contains_key(estate_id), - Error::::EstateIsAlreadyLeased - ); - - match estate_owner_value { - OwnerId::Token(class_id, token_id) => { - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::NFT(class_id, token_id)), - Error::::EstateAlreadyInAuction - ); - ensure!( - Self::check_if_land_or_estate_owner(from, &estate_owner_value), - Error::::NoPermission - ); - T::NFTTokenizationSource::transfer_nft(from, to, &(class_id, token_id)); - - Self::deposit_event(Event::::TransferredEstate( - estate_id.clone(), - from.clone(), - to.clone(), - )); - - Ok(estate_id) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - }) - } - - /// Internal transfer of land unit - fn do_transfer_landunit( - coordinate: (i32, i32), - from: &T::AccountId, - to: &T::AccountId, - metaverse_id: MetaverseId, - ) -> Result<(i32, i32), DispatchError> { - LandUnits::::try_mutate_exists( - &metaverse_id, - &coordinate, - |land_unit_owner| -> Result<(i32, i32), DispatchError> { - // ensure there is record of the land unit with bit country id and coordinate - ensure!(land_unit_owner.is_some(), Error::::NoPermission); - ensure!(from != to, Error::::AlreadyOwnTheLandUnit); - match land_unit_owner { - Some(owner) => { - ensure!( - Self::check_if_land_or_estate_owner(from, owner), - Error::::NoPermission - ); - match owner { - OwnerId::Token(class_id, token_id) => { - ensure!( - !T::AuctionHandler::check_item_in_auction(ItemId::NFT(*class_id, *token_id)), - Error::::LandUnitAlreadyInAuction - ); - - T::NFTTokenizationSource::transfer_nft(from, to, &(*class_id, *token_id)); - // Update - Self::deposit_event(Event::::TransferredLandUnit( - metaverse_id.clone(), - coordinate.clone(), - from.clone(), - to.clone(), - )); - - Ok(coordinate) - } - _ => Err(Error::::InvalidOwnerValue.into()), - } - } - None => Err(DispatchError::Other("No Permissson")), - } - }, - ) - } - - /// Internal setting of total undeployed land units - fn set_total_undeployed_land_unit(total: u64, deduct: bool) -> Result<(), DispatchError> { - let total_undeployed_land_units = Self::all_undeployed_land_unit(); - - if deduct { - let new_total_undeployed_land_unit_count = total_undeployed_land_units - .checked_sub(total) - .ok_or("Overflow deducting new count to total undeployed lands")?; - TotalUndeployedLandUnit::::put(new_total_undeployed_land_unit_count); - } else { - let new_total_undeployed_land_unit_count = total_undeployed_land_units - .checked_add(total) - .ok_or("Overflow adding new count to total undeployed lands")?; - TotalUndeployedLandUnit::::put(new_total_undeployed_land_unit_count); - } - - Ok(()) - } - - /// Internal setting of total land units - fn set_total_land_unit(total: u64, deduct: bool) -> Result<(), DispatchError> { - let total_land_units_count = Self::all_land_units_count(); - - if deduct { - let new_total_land_units_count = total_land_units_count - .checked_sub(total) - .ok_or("Overflow deducting new count to total lands")?; - AllLandUnitsCount::::put(new_total_land_units_count); - } else { - let new_total_land_units_count = total_land_units_count - .checked_add(total) - .ok_or("Overflow adding new count to total lands")?; - AllLandUnitsCount::::put(new_total_land_units_count); - } - Ok(()) - } - - /// Internal getter of land token properties - fn get_land_token_properties(metaverse_id: MetaverseId, coordinate: (i32, i32)) -> (NftMetadata, Attributes) { - let mut land_coordinate_attribute = Vec::::new(); - land_coordinate_attribute.append(&mut coordinate.0.to_be_bytes().to_vec()); - land_coordinate_attribute.append(&mut coordinate.1.to_be_bytes().to_vec()); - - let mut nft_metadata: NftMetadata = NftMetadata::new(); - nft_metadata.append(&mut land_coordinate_attribute.clone()); - - let mut nft_attributes: Attributes = Attributes::new(); - nft_attributes.insert("MetaverseId:".as_bytes().to_vec(), metaverse_id.to_be_bytes().to_vec()); - nft_attributes.insert("Coordinate:".as_bytes().to_vec(), land_coordinate_attribute); - - return (nft_metadata, nft_attributes); - } - - /// Internal getter of estate token properties - fn get_estate_token_properties(metaverse_id: MetaverseId, estate_id: EstateId) -> (NftMetadata, Attributes) { - let mut nft_metadata: NftMetadata = NftMetadata::new(); - nft_metadata.append(&mut metaverse_id.to_be_bytes().to_vec()); - nft_metadata.append(&mut estate_id.to_be_bytes().to_vec()); - - let mut nft_attributes: Attributes = Attributes::new(); - nft_attributes.insert("MetaverseId:".as_bytes().to_vec(), metaverse_id.to_be_bytes().to_vec()); - nft_attributes.insert("Estate Id:".as_bytes().to_vec(), estate_id.to_be_bytes().to_vec()); - - return (nft_metadata, nft_attributes); - } - - fn check_if_land_or_estate_owner(who: &T::AccountId, owner_id: &OwnerId) -> bool { - match owner_id { - OwnerId::Token(class_id, token_id) => { - return T::NFTTokenizationSource::check_ownership(who, &(*class_id, *token_id)).unwrap_or(false); - } - _ => return false, - } - } - - fn verify_land_unit_for_estate(land_units: Vec<(i32, i32)>) -> bool { - if land_units.len() == 1 { - return false; - } - - let mut vec_axis = land_units.iter().map(|lu| lu.0).collect::>(); - let mut vec_yaxis = land_units.iter().map(|lu| lu.1).collect::>(); - - // Sort by ascending and dedup - vec_axis.sort(); - vec_axis.dedup(); - vec_yaxis.sort(); - vec_yaxis.dedup(); - - let mut is_axis_valid = true; - let mut is_yaxis_valid = true; - - // Ensure axis is next to each other - for (i, axis) in vec_axis.iter().enumerate() { - if axis != &vec_axis[i] { - let valid = axis.saturating_sub(vec_axis[i + 1]); - if valid != 1 { - is_axis_valid = false; - break; - } - } - } - - // Ensure yaxis is next to each other - for (i, yaxis) in vec_yaxis.iter().enumerate() { - if yaxis != &vec_yaxis[i] { - let valid = yaxis.saturating_sub(vec_yaxis[i + 1]); - if valid != 1 { - is_yaxis_valid = false; - break; - } - } - } - - is_axis_valid && is_yaxis_valid - } - - fn verify_land_unit_in_bound(block_coordinate: &(i32, i32), land_unit_coordinates: &Vec<(i32, i32)>) -> bool { - let mut vec_axis = land_unit_coordinates.iter().map(|lu| lu.0).collect::>(); - let mut vec_yaxis = land_unit_coordinates.iter().map(|lu| lu.1).collect::>(); - - let max_axis = vec_axis.iter().max().unwrap_or(&i32::MAX); - let max_yaxis = vec_yaxis.iter().max().unwrap_or(&i32::MAX); - let min_axis = vec_axis.iter().min().unwrap_or(&i32::MIN); - let min_yaxis = vec_yaxis.iter().min().unwrap_or(&i32::MIN); - - let top_left_axis = block_coordinate - .0 - .saturating_mul(100i32) - .saturating_sub(50i32) - .saturating_div(10i32) - .saturating_add(1i32); - let top_right_axis = block_coordinate - .0 - .saturating_mul(100i32) - .saturating_add(50i32) - .saturating_div(10i32); - let top_left_yaxis = block_coordinate - .1 - .saturating_mul(100i32) - .saturating_add(50i32) - .saturating_div(10i32); - let top_right_yaxis = block_coordinate - .1 - .saturating_mul(100i32) - .saturating_sub(50i32) - .saturating_div(10i32) - .saturating_add(1i32); - - top_left_axis <= *min_axis - && top_right_axis >= *max_axis - && top_left_yaxis >= *max_yaxis - && top_right_yaxis <= *min_yaxis - } - - /// Remove all land unit and estate - pub fn remove_all_estate_storage() -> Weight { - log::info!("Start removing all land unit and estates"); - LandUnits::::remove_all(None); - Estates::::remove_all(None); - EstateOwner::::remove_all(None); - NextEstateId::::put(1); - AllLandUnitsCount::::put(0); - AllEstatesCount::::put(0); - Weight::from_ref_time(0) - } - - fn collect_network_fee( - recipient: &T::AccountId, - // social_currency_id: FungibleTokenId, - ) -> DispatchResult { - let network_fund = T::MetaverseInfoSource::get_network_treasury(); - //if social_currency_id == FungibleTokenId::NativeToken(0) { - ::Currency::transfer( - &recipient, - &network_fund, - T::NetworkFee::get(), - ExistenceRequirement::KeepAlive, - )?; - // } else { - // T::FungibleTokenCurrency::transfer( - // social_currency_id.clone(), - // &recipient, - // &network_fund, - // T::NetworkFee::get(), - //)?; - //} - Ok(()) - } -} - -impl MetaverseLandTrait for Pallet { - fn get_user_land_units(who: &T::AccountId, metaverse_id: &MetaverseId) -> Vec<(i32, i32)> { - // Check land units owner. - let mut total_land_units: Vec<(i32, i32)> = Vec::default(); - - let land_in_metaverse = LandUnits::::iter_prefix(metaverse_id) - .filter(|(_, owner)| Self::check_if_land_or_estate_owner(who, owner)) - .collect::>(); - - for land_unit in land_in_metaverse { - let land = land_unit.0; - total_land_units.push(land); - } - - total_land_units - } - - fn is_user_own_metaverse_land(who: &T::AccountId, metaverse_id: &MetaverseId) -> bool { - Self::get_user_land_units(&who, metaverse_id).len() > 0 - } - - fn check_landunit(metaverse_id: MetaverseId, coordinate: (i32, i32)) -> Result { - Ok(LandUnits::::contains_key(metaverse_id, coordinate)) - } -} - -impl UndeployedLandBlocksTrait for Pallet { - fn issue_undeployed_land_blocks( - beneficiary: &T::AccountId, - number_of_land_block: u32, - number_land_units_per_land_block: u32, - undeployed_land_block_type: UndeployedLandBlockType, - ) -> Result, DispatchError> { - let new_undeployed_land_block_id = Self::do_issue_undeployed_land_blocks( - &beneficiary, - number_of_land_block, - number_land_units_per_land_block, - undeployed_land_block_type, - )?; - - Ok(new_undeployed_land_block_id) - } - - fn transfer_undeployed_land_block( - who: &T::AccountId, - to: &T::AccountId, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - Self::do_transfer_undeployed_land_block(who, to, undeployed_land_block_id)?; - - Ok(undeployed_land_block_id) - } - - fn burn_undeployed_land_block( - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - let undeployed_land_block_id = Self::do_burn_undeployed_land_block(undeployed_land_block_id)?; - - Ok(undeployed_land_block_id) - } - - fn freeze_undeployed_land_block( - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - let undeployed_land_block_id = Self::do_freeze_undeployed_land_block(undeployed_land_block_id)?; - - Ok(undeployed_land_block_id) - } - - fn check_undeployed_land_block( - owner: &T::AccountId, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - let undeployed_land_block = - Self::get_undeployed_land_block(undeployed_land_block_id).ok_or(Error::::UndeployedLandBlockNotFound)?; - - if undeployed_land_block.is_locked - || undeployed_land_block.undeployed_land_block_type == UndeployedLandBlockType::BoundToAddress - || undeployed_land_block.owner != *owner - { - return Ok(false); - } - return Ok(true); - } -} - -impl Estate for Pallet { - fn transfer_estate(estate_id: EstateId, from: &T::AccountId, to: &T::AccountId) -> Result { - let estate_id = Self::do_transfer_estate(estate_id, from, to)?; - Ok(estate_id) - } - - fn transfer_landunit( - coordinate: (i32, i32), - from: &T::AccountId, - to: &(T::AccountId, MetaverseId), - ) -> Result<(i32, i32), DispatchError> { - let coordinate = Self::do_transfer_landunit(coordinate, from, &(to).0, to.1)?; - Ok(coordinate) - } - - fn transfer_undeployed_land_block( - who: &T::AccountId, - to: &T::AccountId, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - let undeployed_land_block_id = Self::do_transfer_undeployed_land_block(who, to, undeployed_land_block_id)?; - - Ok(undeployed_land_block_id) - } - - fn check_estate(estate_id: EstateId) -> Result { - Ok(Estates::::contains_key(estate_id)) - } - - fn check_landunit(metaverse_id: MetaverseId, coordinate: (i32, i32)) -> Result { - Ok(LandUnits::::contains_key(metaverse_id, coordinate)) - } - - fn check_undeployed_land_block( - owner: &T::AccountId, - undeployed_land_block_id: UndeployedLandBlockId, - ) -> Result { - let undeployed_land_block = - Self::get_undeployed_land_block(undeployed_land_block_id).ok_or(Error::::UndeployedLandBlockNotFound)?; - - if undeployed_land_block.is_locked - || undeployed_land_block.undeployed_land_block_type == UndeployedLandBlockType::BoundToAddress - || undeployed_land_block.owner != *owner - { - return Ok(false); - } - return Ok(true); - } - - fn get_total_land_units(estate_id: Option) -> u64 { - match estate_id { - Some(id) => { - if let Some(estate_info) = Estates::::get(id) { - estate_info.land_units.len() as u64 - } else { - 0 - } - } - None => AllLandUnitsCount::::get(), - } - } - - fn get_total_undeploy_land_units() -> u64 { - TotalUndeployedLandUnit::::get() - } - - fn check_estate_ownership(owner: T::AccountId, estate_id: EstateId) -> Result { - let owner_value = Self::get_estate_owner(estate_id); - match owner_value { - Some(token_value) => match token_value { - OwnerId::Token(c, t) => T::NFTTokenizationSource::check_ownership(&owner, &(c, t)), - OwnerId::Account(_) => Err(Error::::InvalidOwnerValue.into()), - }, - None => Ok(false), - } - } - - fn is_estate_leasor(leasor: T::AccountId, estate_id: EstateId) -> Result { - Ok(EstateLeasors::::contains_key(leasor, estate_id)) - } - - fn is_estate_leased(estate_id: EstateId) -> Result { - Ok(EstateLeases::::contains_key(estate_id)) - } } From 3c236a610dd0d7822c89d0ba867724ba3461aec1 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 29 Oct 2023 22:04:19 +1300 Subject: [PATCH 003/114] WIP - create pool function --- Cargo.lock | 1 + pallets/asset-manager/Cargo.toml | 4 +- pallets/asset-manager/src/lib.rs | 7 ++ pallets/estate/src/rate.rs | 2 +- pallets/land-minting/src/lib.rs | 66 ++++++++++--------- .../land-minting/src/{rate.rs => utils.rs} | 13 +++- primitives/metaverse/src/lib.rs | 2 +- traits/core-primitives/src/lib.rs | 4 ++ 8 files changed, 65 insertions(+), 34 deletions(-) rename pallets/land-minting/src/{rate.rs => utils.rs} (93%) diff --git a/Cargo.lock b/Cargo.lock index f3fe62500..4c1b16e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -411,6 +411,7 @@ name = "asset-manager" version = "0.1.0" dependencies = [ "bit-country-primitives", + "core-primitives", "frame-support", "frame-system", "hex", diff --git a/pallets/asset-manager/Cargo.toml b/pallets/asset-manager/Cargo.toml index ca6303094..61d84ad3b 100644 --- a/pallets/asset-manager/Cargo.toml +++ b/pallets/asset-manager/Cargo.toml @@ -12,12 +12,13 @@ version = '0.1.0' log = { workspace = true } serde = { workspace = true, optional = true } scale-info = { workspace = true } -codec = { package = "parity-scale-codec", version ="3.1.5", default-features = false } +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } sp-runtime = { workspace = true } sp-std = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } primitives = { package = "bit-country-primitives", path = "../../primitives/metaverse", default-features = false } +core-primitives = { path = "../../traits/core-primitives", default-features = false } xcm = { workspace = true } [dev-dependencies] @@ -42,5 +43,6 @@ std = [ "frame-support/std", "frame-system/std", "primitives/std", + "core-primitives/std", "xcm/std", ] \ No newline at end of file diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs index c8c33008d..ec1f418e6 100644 --- a/pallets/asset-manager/src/lib.rs +++ b/pallets/asset-manager/src/lib.rs @@ -37,6 +37,7 @@ use sp_std::{boxed::Box, vec::Vec}; use xcm::v3::MultiLocation; use xcm::VersionedMultiLocation; +use core_primitives::CurrencyIdManagement; pub use pallet::*; use primitives::{ AssetIds, AssetMetadata, BuyWeightRate, CurrencyId, ForeignAssetIdMapping, FungibleTokenId, Ratio, TokenId, @@ -346,3 +347,9 @@ where None } } + +impl CurrencyIdManagement for ForeignAssetMapping { + fn check_token_exist(currency_id: CurrencyId) -> bool { + return true; + } +} diff --git a/pallets/estate/src/rate.rs b/pallets/estate/src/rate.rs index 747a72029..788907d6d 100644 --- a/pallets/estate/src/rate.rs +++ b/pallets/estate/src/rate.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_runtime::{Perbill, RuntimeDebug}; +use sp_runtime::{Perbill, Permill, RuntimeDebug}; use crate::{AllLandUnitsCount, TotalUndeployedLandUnit}; // Helper methods to compute the issuance rate for undeployed land. diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index ecc87bf3c..15d98415f 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -42,7 +42,7 @@ use primitives::{ Attributes, ClassId, EstateId, FungibleTokenId, ItemId, MetaverseId, NftMetadata, TokenId, UndeployedLandBlock, UndeployedLandBlockId, UndeployedLandBlockType, }; -pub use rate::{MintingRateInfo, Range}; +pub use utils::{MintingRateInfo, Range}; pub use weights::WeightInfo; //#[cfg(feature = "runtime-benchmarks")] @@ -50,7 +50,7 @@ pub use weights::WeightInfo; #[cfg(test)] mod mock; -mod rate; +mod utils; #[cfg(test)] mod tests; @@ -68,7 +68,7 @@ pub mod pallet { use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; use primitives::{Balance, CurrencyId, RoundIndex, UndeployedLandBlockId}; - use crate::rate::{round_issuance_range, MintingRateInfo}; + use crate::utils::{round_issuance_range, MintingRateInfo}; use super::*; @@ -155,47 +155,53 @@ pub mod pallet { pub enum Event { /// New staking round started [Starting Block, Round, Total Land Unit] NewRound(T::BlockNumber, RoundIndex, u64), + /// New pool created + PoolCreated(T::AccountId, ClassId, u32, CurrencyIdOf), } #[pallet::error] pub enum Error { /// No permission NoPermission, + /// Currency is not supported + CurrencyIsNotSupported, } #[pallet::call] impl Pallet { - /// Minting of a land unit, only used by council to manually mint single land for - /// beneficiary - /// - /// The dispatch origin for this call must be _Root_. - /// - `beneficiary`: the account which will be the owner of the land unit - /// - `metaverse_id`: the metaverse id that the land united will be minted on - /// - `coordinate`: coordinate of the land unit - /// - /// Emits `NewLandsMinted` if successful. #[pallet::weight(T::WeightInfo::mint_land())] - pub fn mint_land( - origin: OriginFor, - beneficiary: T::AccountId, - metaverse_id: MetaverseId, - coordinate: (i32, i32), - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; + pub fn create_pool(origin: OriginFor, max_supply: u32, currency_id: CurrencyIdOf) -> DispatchResult { + // Check if user signed + // Emit events + // Check if user signed + let who = ensure_signed(origin)?; + + // Ensure currency_id supported + ensure!( + currency_id == FungibleTokenId::NativeToken(0) || currency_id == FungibleTokenId::NativeToken(1), + Error::::CurrencyIsNotSupported + ); + + // Collect pool creation fee + Self::collect_pool_creation_fee(&who)?; + + // Create new NFT collection + // This will return a unique collection ID for the new pool + let class_id = T::NFTTokenizationSource.create_collection(who.clone(), max_supply, currency_id)?; + + // Add tuple class_id, currency_id + TokenPool::::insert((class_id, currency_id), Zero::zero); + + // Emit event for pool creation + Self::deposit_event(Event::PoolCreated(who, class_id, max_supply, currency_id)); + Ok(().into()) + } + #[pallet::weight(T::WeightInfo::mint_land())] + pub fn deposit(origin: OriginFor, class_id: ClassId, amount: BalanceOf) -> DispatchResult { Ok(().into()) } } } -impl Pallet { - /// Internal getter for new estate ID - fn get_new_estate_id() -> Result { - let estate_id = NextEstateId::::try_mutate(|id| -> Result { - let current_id = *id; - *id = id.checked_add(One::one()).ok_or(Error::::NoAvailableEstateId)?; - Ok(current_id) - })?; - Ok(estate_id) - } -} +impl Pallet {} diff --git a/pallets/land-minting/src/rate.rs b/pallets/land-minting/src/utils.rs similarity index 93% rename from pallets/land-minting/src/rate.rs rename to pallets/land-minting/src/utils.rs index 747a72029..b08d85bf2 100644 --- a/pallets/land-minting/src/rate.rs +++ b/pallets/land-minting/src/utils.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_runtime::{Perbill, RuntimeDebug}; +use sp_runtime::{Perbill, Permill, RuntimeDebug}; use crate::{AllLandUnitsCount, TotalUndeployedLandUnit}; // Helper methods to compute the issuance rate for undeployed land. @@ -29,6 +29,17 @@ const SECONDS_PER_YEAR: u32 = 31557600; const SECONDS_PER_BLOCK: u32 = 12; const BLOCKS_PER_YEAR: u32 = SECONDS_PER_YEAR / SECONDS_PER_BLOCK; +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +pub struct PoolInfo { + pub creator: AccountId, + pub commission: Permill, + /// Currency id of the pool + pub currency_id: CurrencyId, + /// Max total supply + pub max: u32, +} + fn rounds_per_year() -> u32 { let blocks_per_round = >::round().length; BLOCKS_PER_YEAR / blocks_per_round diff --git a/primitives/metaverse/src/lib.rs b/primitives/metaverse/src/lib.rs index c149368ba..61be8ba4e 100644 --- a/primitives/metaverse/src/lib.rs +++ b/primitives/metaverse/src/lib.rs @@ -192,7 +192,7 @@ impl FungibleTokenId { pub fn decimals(&self) -> u8 { match self { FungibleTokenId::NativeToken(0) => 18, // Native token - FungibleTokenId::NativeToken(1) | FungibleTokenId::NativeToken(2) | FungibleTokenId::Stable(0) => 12, // KSM KAR KUSD + FungibleTokenId::NativeToken(1) | FungibleTokenId::NativeToken(2) | FungibleTokenId::Stable(0) => 12, // KSM FungibleTokenId::MiningResource(0) => 18, _ => 18, } diff --git a/traits/core-primitives/src/lib.rs b/traits/core-primitives/src/lib.rs index 4ea550bab..5946eb38b 100644 --- a/traits/core-primitives/src/lib.rs +++ b/traits/core-primitives/src/lib.rs @@ -400,3 +400,7 @@ impl MiningResourceRateInfo { self.mining_reward = mining_reward; } } + +pub trait CurrencyIdManagement { + fn check_token_exist(currency_id: CurrencyId) -> bool; +} From e3f70f0a12b9e1a0a615988e8e4396c7ef24f8e2 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 30 Oct 2023 15:59:35 +1300 Subject: [PATCH 004/114] WIP - update new pool creation concept --- pallets/land-minting/src/lib.rs | 50 ++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 15d98415f..74d2430a4 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -66,9 +66,9 @@ pub mod pallet { use primitives::estate::EstateInfo; use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; - use primitives::{Balance, CurrencyId, RoundIndex, UndeployedLandBlockId}; + use primitives::{AccountId, Balance, CurrencyId, PoolId, RoundIndex, UndeployedLandBlockId}; - use crate::utils::{round_issuance_range, MintingRateInfo}; + use crate::utils::{round_issuance_range, MintingRateInfo, PoolInfo}; use super::*; @@ -128,13 +128,17 @@ pub mod pallet { pub type CurrencyIdOf = <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; + #[pallet::storage] + #[pallet::getter(fn next_class_id)] + pub type NextPoolId = StorageValue<_, PoolId, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn fees)] pub type Fees = StorageValue<_, (Permill, Permill), ValueQuery>; #[pallet::storage] #[pallet::getter(fn token_pool)] - pub type TokenPool = StorageMap<_, Twox64Concat, (ClassId, CurrencyIdOf), BalanceOf, ValueQuery>; + pub type Pool = StorageMap<_, Twox64Concat, PoolId, PoolInfo, T::AccountId>, ValueQuery>; #[pallet::genesis_config] pub struct GenesisConfig { @@ -156,7 +160,7 @@ pub mod pallet { /// New staking round started [Starting Block, Round, Total Land Unit] NewRound(T::BlockNumber, RoundIndex, u64), /// New pool created - PoolCreated(T::AccountId, ClassId, u32, CurrencyIdOf), + PoolCreated(T::AccountId, u32, CurrencyIdOf), } #[pallet::error] @@ -165,15 +169,19 @@ pub mod pallet { NoPermission, /// Currency is not supported CurrencyIsNotSupported, + /// No available next pool id + NoAvailablePoolId, } #[pallet::call] impl Pallet { #[pallet::weight(T::WeightInfo::mint_land())] - pub fn create_pool(origin: OriginFor, max_supply: u32, currency_id: CurrencyIdOf) -> DispatchResult { - // Check if user signed - // Emit events - // Check if user signed + pub fn create_pool( + origin: OriginFor, + currency_id: CurrencyIdOf, + max_nft_reward: u32, + commission: Permill, + ) -> DispatchResult { let who = ensure_signed(origin)?; // Ensure currency_id supported @@ -182,23 +190,39 @@ pub mod pallet { Error::::CurrencyIsNotSupported ); + // TODO Check commission below threshold + // Collect pool creation fee Self::collect_pool_creation_fee(&who)?; - // Create new NFT collection - // This will return a unique collection ID for the new pool - let class_id = T::NFTTokenizationSource.create_collection(who.clone(), max_supply, currency_id)?; + // Next pool id + let next_pool_id = NextPoolId::::try_mutate(|id| -> Result { + let current_id = *id; + *id = id.checked_add(&1u32).ok_or(Error::::NoAvailablePoolId)?; + Ok(current_id) + })?; + + let new_pool = PoolInfo { + creator: who.clone(), + commission: commission, + currency_id: currency_id, + max: max_nft_reward, + }; // Add tuple class_id, currency_id - TokenPool::::insert((class_id, currency_id), Zero::zero); + Pool::::insert(next_pool_id, new_pool); // Emit event for pool creation - Self::deposit_event(Event::PoolCreated(who, class_id, max_supply, currency_id)); + Self::deposit_event(Event::PoolCreated(who, max_nft_reward, currency_id)); Ok(().into()) } #[pallet::weight(T::WeightInfo::mint_land())] pub fn deposit(origin: OriginFor, class_id: ClassId, amount: BalanceOf) -> DispatchResult { + // Ensure user is signed + // Check if pool is full from max supply + // Calculate exchange rate and take fee + // Mint new token Ok(().into()) } } From 99e3d93b0b1f0970c63d41576dc5c46b6fc95188 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 30 Oct 2023 15:59:57 +1300 Subject: [PATCH 005/114] WIP - Add PoolId primitives and max nft reward --- pallets/land-minting/src/utils.rs | 2 +- primitives/metaverse/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/land-minting/src/utils.rs b/pallets/land-minting/src/utils.rs index b08d85bf2..164224e2c 100644 --- a/pallets/land-minting/src/utils.rs +++ b/pallets/land-minting/src/utils.rs @@ -36,7 +36,7 @@ pub struct PoolInfo { pub commission: Permill, /// Currency id of the pool pub currency_id: CurrencyId, - /// Max total supply + /// Max nft rewards pub max: u32, } diff --git a/primitives/metaverse/src/lib.rs b/primitives/metaverse/src/lib.rs index 61be8ba4e..a867f350a 100644 --- a/primitives/metaverse/src/lib.rs +++ b/primitives/metaverse/src/lib.rs @@ -132,6 +132,8 @@ pub type TrieIndex = u32; pub type CampaignId = u32; /// Index used for claim rewrads for merkle root campaigns pub type ClaimId = u64; +/// Pool Id to keep track of each pool +pub type PoolId = u32; /// Land Token Class Id pub const LAND_CLASS_ID: ClassId = 15; From e4e4379f141a527e0aaef7f8b7c2a1431ced1004 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 30 Oct 2023 17:58:17 +1300 Subject: [PATCH 006/114] WIP - Implement the first version of deposit --- pallets/land-minting/src/lib.rs | 81 ++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 74d2430a4..c0753d490 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -61,6 +61,7 @@ pub mod weights; pub mod pallet { use frame_support::traits::{Currency, Imbalance, ReservableCurrency}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; + use sp_core::U256; use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero}; use sp_runtime::Permill; @@ -122,6 +123,9 @@ pub mod pallet { /// Allows converting block numbers into balance type BlockNumberToBalance: Convert>; + + #[pallet::constant] + type PoolAccount: Get; } pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -136,10 +140,21 @@ pub mod pallet { #[pallet::getter(fn fees)] pub type Fees = StorageValue<_, (Permill, Permill), ValueQuery>; + /// Keep track of Pool detail #[pallet::storage] - #[pallet::getter(fn token_pool)] + #[pallet::getter(fn pool)] pub type Pool = StorageMap<_, Twox64Concat, PoolId, PoolInfo, T::AccountId>, ValueQuery>; + /// Pool ledger that keeps track of Pool id and balance + #[pallet::storage] + #[pallet::getter(fn pool_ledger)] + pub type PoolLedger = StorageMap<_, Twox64Concat, PoolId, BalanceOf, ValueQuery>; + + /// Network ledger + #[pallet::storage] + #[pallet::getter(fn network_ledger)] + pub type NetworkLedger = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { pub minting_rate_config: MintingRateInfo, @@ -171,6 +186,10 @@ pub mod pallet { CurrencyIsNotSupported, /// No available next pool id NoAvailablePoolId, + /// Pool doesn't exists + PoolDoesNotExist, + /// Overflow + Overflow, } #[pallet::call] @@ -198,7 +217,7 @@ pub mod pallet { // Next pool id let next_pool_id = NextPoolId::::try_mutate(|id| -> Result { let current_id = *id; - *id = id.checked_add(&1u32).ok_or(Error::::NoAvailablePoolId)?; + *id = id.checked_add(1u32).ok_or(Error::::NoAvailablePoolId)?; Ok(current_id) })?; @@ -218,11 +237,61 @@ pub mod pallet { } #[pallet::weight(T::WeightInfo::mint_land())] - pub fn deposit(origin: OriginFor, class_id: ClassId, amount: BalanceOf) -> DispatchResult { + pub fn deposit(origin: OriginFor, pool_id: PoolId, amount: BalanceOf) -> DispatchResult { + // Ensure user is signed + // Check if pool is exists + // Get pool detail and add it to pool_instance + // Get currencyId from pool detail + // Get network ledger balance from currency id + // Collect deposit fee for protocol + // Calculate vAmount as receipt of amount locked. The formula based on vAmount = (amount * vAmount + // total issuance)/network ledger balance Deposit vAmount to user using T::MultiCurrency::deposit + // Transfer amount to PoolAccount using T::MultiCurrency::transfer + // Emit deposit event + // Ensure user is signed - // Check if pool is full from max supply - // Calculate exchange rate and take fee - // Mint new token + let who = ensure_signed(origin)?; + // Check if pool exists + let pool_instance = Pool::::get(pool_id).ok_or(Error::::PoolDoesNotExist)?; + + // Get currencyId from pool detail + let currency_id = pool_instance.currency_id; + + // Get network ledger balance from currency id + let network_ledger_balance = Self::network_ledger(currency_id); + + // Collect deposit fee for protocol + // Assuming there's a function `collect_deposit_fee` that deducts a fee for deposits. + Self::collect_deposit_fee(&who, amount)?; + + let v_currency_id = T::CurrencyIdManagement::convert_to_vcurrency(currency_id) + .map_err(|_| Error::::NotSupportTokenType)?; + // Calculate vAmount as receipt of amount locked. The formula based on vAmount = (amount * vAmount + // total issuance)/network ledger balance + let v_amount_total_issuance = T::MultiCurrency::total_issuance(v_currency_id); + let v_amount = U256::from(amount.saturated_into::()) + .saturating_mul(v_amount_total_issuance.saturated_into::().into()) + .checked_div(network_ledger_balance.saturated_into::().into()) + .ok_or(Error::::CalculationOverflow)? + .as_u128() + .saturated_into(); + + // Deposit vAmount to user using T::MultiCurrency::deposit + T::MultiCurrency::deposit(currency_id, &who, vamount)?; + + // Transfer amount to PoolAccount using T::MultiCurrency::transfer + // Assuming `PoolAccount` is an associated type that represents the pool's account ID or a method to + // get it. + T::MultiCurrency::transfer( + currency_id, + &who, + &T::PoolAccount::get().into_account_truncating(), + amount, + )?; + + // Emit deposit event + Self::deposit_event(Event::Deposited(who, pool_id, amount)); + Ok(().into()) } } From 33057f3175069cf9feb8ca3afacc1bb188ca6a43 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 31 Oct 2023 22:44:39 +1300 Subject: [PATCH 007/114] WIP - Implement a test redeem --- pallets/land-minting/src/lib.rs | 75 ++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index c0753d490..be8676e56 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -155,6 +155,10 @@ pub mod pallet { #[pallet::getter(fn network_ledger)] pub type NetworkLedger = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn minimum_redeem)] + pub type MinimumRedeem = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { pub minting_rate_config: MintingRateInfo, @@ -190,6 +194,8 @@ pub mod pallet { PoolDoesNotExist, /// Overflow Overflow, + /// Below minimum redemption + BelowMinimumRedeem, } #[pallet::call] @@ -238,17 +244,6 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::mint_land())] pub fn deposit(origin: OriginFor, pool_id: PoolId, amount: BalanceOf) -> DispatchResult { - // Ensure user is signed - // Check if pool is exists - // Get pool detail and add it to pool_instance - // Get currencyId from pool detail - // Get network ledger balance from currency id - // Collect deposit fee for protocol - // Calculate vAmount as receipt of amount locked. The formula based on vAmount = (amount * vAmount - // total issuance)/network ledger balance Deposit vAmount to user using T::MultiCurrency::deposit - // Transfer amount to PoolAccount using T::MultiCurrency::transfer - // Emit deposit event - // Ensure user is signed let who = ensure_signed(origin)?; // Check if pool exists @@ -262,22 +257,22 @@ pub mod pallet { // Collect deposit fee for protocol // Assuming there's a function `collect_deposit_fee` that deducts a fee for deposits. - Self::collect_deposit_fee(&who, amount)?; + let amount_after_fee = Self::collect_deposit_fee(&who, amount)?; let v_currency_id = T::CurrencyIdManagement::convert_to_vcurrency(currency_id) - .map_err(|_| Error::::NotSupportTokenType)?; + .map_err(|_| Error::::CurrencyIsNotSupported)?; // Calculate vAmount as receipt of amount locked. The formula based on vAmount = (amount * vAmount // total issuance)/network ledger balance let v_amount_total_issuance = T::MultiCurrency::total_issuance(v_currency_id); - let v_amount = U256::from(amount.saturated_into::()) + let v_amount = U256::from(amount_after_fee.saturated_into::()) .saturating_mul(v_amount_total_issuance.saturated_into::().into()) .checked_div(network_ledger_balance.saturated_into::().into()) - .ok_or(Error::::CalculationOverflow)? + .ok_or(ArithmeticError::Overflow)? .as_u128() .saturated_into(); // Deposit vAmount to user using T::MultiCurrency::deposit - T::MultiCurrency::deposit(currency_id, &who, vamount)?; + T::MultiCurrency::deposit(currency_id, &who, v_amount)?; // Transfer amount to PoolAccount using T::MultiCurrency::transfer // Assuming `PoolAccount` is an associated type that represents the pool's account ID or a method to @@ -291,7 +286,55 @@ pub mod pallet { // Emit deposit event Self::deposit_event(Event::Deposited(who, pool_id, amount)); + Ok(().into()) + } + + #[pallet::weight(T::WeightInfo::mint_land())] + pub fn redeem( + origin: OriginFor, + pool_id: PoolId, + vcurrency_id: CurrencyIdOf, + vamount: BalanceOf, + ) -> DispatchResult { + // Ensure user is signed + let who = ensure_signed(origin)?; + ensure!( + vamount >= MinimumRedeem::::get(vcurrency_id), + Error::::BelowMinimumRedeem + ); + let currency_id = T::CurrencyIdManagement::convert_to_currency(vcurrency_id) + .map_err(|_| Error::::NotSupportTokenType)?; + + // Check if pool exists + let pool_instance = Pool::::get(pool_id).ok_or(Error::::PoolDoesNotExist)?; + + ensure!( + currency_id == pool_instance.currency_id, + Error::::CurrencyIsNotSupported + ); + + // Get network ledger balance from currency id + let network_ledger_balance = Self::network_ledger(currency_id); + + // Collect deposit fee for protocol + // Assuming there's a function `collect_deposit_fee` that deducts a fee for deposits. + let amount_after_fee = Self::collect_deposit_fee(&who, vamount)?; + let vamount = vamount + .checked_sub(&amount_after_fee) + .ok_or(ArithmeticError::Overflow)?; + // Calculate vAmount as receipt of amount locked. The formula based on vAmount = (amount * vAmount + // total issuance)/network ledger balance + let v_amount_total_issuance = T::MultiCurrency::total_issuance(vcurrency_id); + let currency_amount = U256::from(vamount.saturated_into::()) + .saturating_mul(network_ledger_balance.saturated_into::().into()) + .checked_div(v_amount_total_issuance.saturated_into::().into()) + .ok_or(Error::::CalculationOverflow)? + .as_u128() + .saturated_into(); + + // Emit deposit event + Self::deposit_event(Event::Deposited(who, pool_id, vamount)); Ok(().into()) } } From 1eda34a39510fa64f3fc5e17a1d5e65e2d9214a9 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 1 Nov 2023 15:46:39 +1300 Subject: [PATCH 008/114] WIP - Add StakingRound as time unit for redeeming staking --- primitives/metaverse/src/lib.rs | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/primitives/metaverse/src/lib.rs b/primitives/metaverse/src/lib.rs index a867f350a..662d68839 100644 --- a/primitives/metaverse/src/lib.rs +++ b/primitives/metaverse/src/lib.rs @@ -462,3 +462,53 @@ pub struct CampaignInfo, } +// For multiple time calculation type +#[derive(Encode, Decode, Clone, RuntimeDebug, Eq, TypeInfo, MaxEncodedLen)] +pub enum StakingRound { + Era(#[codec(compact)] u32), + Round(#[codec(compact)] u32), + Epoch(#[codec(compact)] u32), + Hour(#[codec(compact)] u32), +} + +impl Default for TimeUnit { + fn default() -> Self { + StakingRound::Era(0u32) + } +} + +impl PartialEq for StakingRound { + fn eq(&self, other: &Self) -> bool { + match (&self, other) { + (Self::Era(a), Self::Era(b)) => a.eq(b), + (Self::Round(a), Self::Round(b)) => a.eq(b), + (Self::Epoch(a), Self::Epoch(b)) => a.eq(b), + (Self::Hour(a), Self::Hour(b)) => a.eq(b), + _ => false, + } + } +} + +impl Ord for StakingRound { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + match (&self, other) { + (Self::Era(a), Self::Era(b)) => a.cmp(b), + (Self::Round(a), Self::Round(b)) => a.cmp(b), + (Self::Epoch(a), Self::Epoch(b)) => a.cmp(b), + (Self::Hour(a), Self::Hour(b)) => a.cmp(b), + _ => sp_std::cmp::Ordering::Less, + } + } +} + +impl PartialOrd for StakingRound { + fn partial_cmp(&self, other: &Self) -> Option { + match (&self, other) { + (Self::Era(a), Self::Era(b)) => Some(a.cmp(b)), + (Self::Round(a), Self::Round(b)) => Some(a.cmp(b)), + (Self::Epoch(a), Self::Kblock(b)) => Some(a.cmp(b)), + (Self::Hour(a), Self::Hour(b)) => Some(a.cmp(b)), + _ => None, + } + } +} From ab081da4cef3bdbae0f95910bf3fdd1a4901bddc Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 1 Nov 2023 15:47:00 +1300 Subject: [PATCH 009/114] WIP - Implement redeem unstaking system --- pallets/land-minting/src/lib.rs | 38 +++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index be8676e56..1fe2dc679 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -45,6 +45,7 @@ use primitives::{ pub use utils::{MintingRateInfo, Range}; pub use weights::WeightInfo; +pub type QueueId = u32; //#[cfg(feature = "runtime-benchmarks")] //pub mod benchmarking; @@ -67,7 +68,7 @@ pub mod pallet { use primitives::estate::EstateInfo; use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; - use primitives::{AccountId, Balance, CurrencyId, PoolId, RoundIndex, UndeployedLandBlockId}; + use primitives::{AccountId, Balance, CurrencyId, PoolId, RoundIndex, StakingRound, UndeployedLandBlockId}; use crate::utils::{round_issuance_range, MintingRateInfo, PoolInfo}; @@ -126,6 +127,9 @@ pub mod pallet { #[pallet::constant] type PoolAccount: Get; + + #[pallet::constant] + type MaximumQueue: Get; } pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -159,6 +163,30 @@ pub mod pallet { #[pallet::getter(fn minimum_redeem)] pub type MinimumRedeem = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn network_redeem_requests)] + pub type NetworkRedeemQueue = StorageDoubleMap< + _, + Blake2_128Concat, + StakingRound, + Blake2_128Concat, + CurrencyIdOf, + (BalanceOf, BoundedVec, CurrencyIdOf), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn user_unlock_request)] + pub type UserUnlockRequest = StorageDoubleMap< + _, + Blake2_128Concat, + CurrencyIdOf, + Blake2_128Concat, + QueueId, + (T::AccountId, BalanceOf, StakingRound), + OptionQuery, + >; + #[pallet::genesis_config] pub struct GenesisConfig { pub minting_rate_config: MintingRateInfo, @@ -329,10 +357,16 @@ pub mod pallet { let currency_amount = U256::from(vamount.saturated_into::()) .saturating_mul(network_ledger_balance.saturated_into::().into()) .checked_div(v_amount_total_issuance.saturated_into::().into()) - .ok_or(Error::::CalculationOverflow)? + .ok_or(ArithmeticError::Overflow)? .as_u128() .saturated_into(); + // Check if there is ongoing staking round in queue + // Burn v_amount from account + // Deduct total amount from PoolLedger + // Keep track of total unlock + // + // Emit deposit event Self::deposit_event(Event::Deposited(who, pool_id, vamount)); Ok(().into()) From 38b1ed462108e81d6cee09dae6078bd680be0a12 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 2 Nov 2023 09:49:26 +1300 Subject: [PATCH 010/114] WIP - Calculate the next staking round --- pallets/land-minting/src/lib.rs | 63 ++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 1fe2dc679..022b2f4fb 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -39,8 +39,8 @@ pub use pallet::*; use primitives::estate::EstateInfo; use primitives::{ estate::{Estate, LandUnitStatus, LeaseContract, OwnerId}, - Attributes, ClassId, EstateId, FungibleTokenId, ItemId, MetaverseId, NftMetadata, TokenId, UndeployedLandBlock, - UndeployedLandBlockId, UndeployedLandBlockType, + Attributes, ClassId, EstateId, FungibleTokenId, ItemId, MetaverseId, NftMetadata, StakingRound, TokenId, + UndeployedLandBlock, UndeployedLandBlockId, UndeployedLandBlockType, }; pub use utils::{MintingRateInfo, Range}; pub use weights::WeightInfo; @@ -187,6 +187,18 @@ pub mod pallet { OptionQuery, >; + #[pallet::storage] + #[pallet::getter(fn unlock_duration)] + pub type UnlockDuration = StorageMap<_, Twox64Concat, CurrencyIdOf, StakingRound>; + + #[pallet::storage] + #[pallet::getter(fn current_staking_round)] + pub type CurrentStakingRound = StorageMap<_, Twox64Concat, CurrencyIdOf, StakingRound>; + + #[pallet::storage] + #[pallet::getter(fn queue_next_id)] + pub type QueueNextId = StorageMap<_, Twox64Concat, CurrencyIdOf, u32, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { pub minting_rate_config: MintingRateInfo, @@ -224,6 +236,10 @@ pub mod pallet { Overflow, /// Below minimum redemption BelowMinimumRedeem, + /// No current staking round + NoCurrentStakingRound, + /// Unexpected + Unexpected, } #[pallet::call] @@ -361,11 +377,10 @@ pub mod pallet { .as_u128() .saturated_into(); - // Check if there is ongoing staking round in queue - // Burn v_amount from account - // Deduct total amount from PoolLedger - // Keep track of total unlock - // + match CurrentStakingRound::::get(currency_id) { + Some(staking_round) => {} + None => return Err(Error::::NoCurrentStakingRound.into()), + } // Emit deposit event Self::deposit_event(Event::Deposited(who, pool_id, vamount)); @@ -374,4 +389,36 @@ pub mod pallet { } } -impl Pallet {} +impl Pallet { + #[transactional] + pub fn calculate_next_staking_round(a: StakingRound, b: StakingRound) -> Result { + let result = match a { + StakingRound::Era(era_a) => match b { + StakingRound::Era(era_b) => { + StakingRound::Era(era_a.checked_add(era_b).ok_or(ArithmeticError::Overflow)?) + } + _ => return Err(Error::::Unexpected.into()), + }, + StakingRound::Round(round_a) => match b { + StakingRound::Round(round_b) => { + StakingRound::Round(round_a.checked_add(round_b).ok_or(ArithmeticError::Overflow)?) + } + _ => return Err(Error::::Unexpected.into()), + }, + StakingRound::Epoch(epoch_a) => match b { + StakingRound::Epoch(epoch_b) => { + StakingRound::Epoch(epoch_a.checked_add(epoch_b).ok_or(ArithmeticError::Overflow)?) + } + _ => return Err(Error::::Unexpected.into()), + }, + StakingRound::Hour(hour_a) => match b { + StakingRound::Hour(hour_b) => { + StakingRound::Hour(hour_a.checked_add(hour_b).ok_or(ArithmeticError::Overflow)?) + } + _ => return Err(Error::::Unexpected.into()), + }, + }; + + Ok(result) + } +} From c373e38c314f64a04884207355da7ef0bd40aed6 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 2 Nov 2023 10:08:22 +1300 Subject: [PATCH 011/114] WIP - Update pool and network ledger --- pallets/land-minting/src/lib.rs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 022b2f4fb..1850b11cf 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -149,12 +149,12 @@ pub mod pallet { #[pallet::getter(fn pool)] pub type Pool = StorageMap<_, Twox64Concat, PoolId, PoolInfo, T::AccountId>, ValueQuery>; - /// Pool ledger that keeps track of Pool id and balance + /// Pool ledger that keeps track of Pool id and balance of base currency #[pallet::storage] #[pallet::getter(fn pool_ledger)] pub type PoolLedger = StorageMap<_, Twox64Concat, PoolId, BalanceOf, ValueQuery>; - /// Network ledger + /// Network ledger that keep track of all staking across all pools #[pallet::storage] #[pallet::getter(fn network_ledger)] pub type NetworkLedger = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; @@ -318,6 +318,16 @@ pub mod pallet { // Deposit vAmount to user using T::MultiCurrency::deposit T::MultiCurrency::deposit(currency_id, &who, v_amount)?; + // Update this specific pool ledger to keep track of pool balance + PoolLedger::::mutate(&pool_id, |pool| -> Result<(), Error> { + *pool = pool.checked_add(&amount_after_fee).ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + + NetworkLedger::::mutate(¤cy_id, |pool| -> Result<(), Error> { + *pool = pool.checked_add(&amount_after_fee).ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; // Transfer amount to PoolAccount using T::MultiCurrency::transfer // Assuming `PoolAccount` is an associated type that represents the pool's account ID or a method to // get it. @@ -377,8 +387,24 @@ pub mod pallet { .as_u128() .saturated_into(); + // Check current staking era - only failed when there is no current staking era + // Staking era get checked and updated every blocks match CurrentStakingRound::::get(currency_id) { - Some(staking_round) => {} + Some(staking_round) => { + // Calculate the staking duration to be locked + let new_staking_round = Self::calculate_next_staking_round( + Self::unlock_duration(currency_id).ok_or(Error::::UnlockDurationNotFound)?, + staking_round, + )?; + // Burn currency + T::MultiCurrency::withdraw(vcurrency_id, &who, vamount)?; + + // Update pool ledger + PoolLedger::::mutate(&pool_id, |pool| -> Result<(), Error> { + *pool = pool.checked_sub(¤cy_amount).ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + } None => return Err(Error::::NoCurrentStakingRound.into()), } From 250e85d169c197603c93affce9bb490a778c3e24 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 3 Nov 2023 14:04:47 +1300 Subject: [PATCH 012/114] WIP - Implement redeem logic --- pallets/land-minting/src/lib.rs | 63 +++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 1850b11cf..c5922dc54 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -219,7 +219,11 @@ pub mod pallet { /// New staking round started [Starting Block, Round, Total Land Unit] NewRound(T::BlockNumber, RoundIndex, u64), /// New pool created - PoolCreated(T::AccountId, u32, CurrencyIdOf), + PoolCreated(T::AccountId, PoolId, CurrencyIdOf), + /// Deposited + Deposited(T::AccountId, PoolId, BalanceOf), + /// Redeemed + Redeemed(T::AccountId, PoolId, BalanceOf), } #[pallet::error] @@ -404,12 +408,67 @@ pub mod pallet { *pool = pool.checked_sub(¤cy_amount).ok_or(ArithmeticError::Overflow)?; Ok(()) })?; + + let next_queue_id = Self::queue_next_id(currency_id); + UserUnlockRequest::::insert( + ¤cy_id, + &next_queue_id, + (&who, currency_amount, &new_staking_round), + ); + + if UserUnlockRequest::::get(&who, ¤cy_id).is_some() { + UserUnlockRequest::::mutate(&who, ¤cy_id, |value| -> Result<(), Error> { + if let Some((total_locked, ledger_list)) = value { + ledger_list.try_push(next_id).map_err(|_| Error::::TooManyRedeems)?; + + *total_locked = total_locked + .checked_add(&token_amount) + .ok_or(Error::::CalculationOverflow)?; + }; + Ok(()) + })?; + } else { + let mut ledger_list_origin = BoundedVec::::default(); + ledger_list_origin + .try_push(next_queue_id) + .map_err(|_| Error::::TooManyRedeems)?; + UserUnlockRequest::::insert(&who, ¤cy_id, (currency_amount, ledger_list_origin)); + } + + if let Some((_, _, _token_id)) = NetworkRedeemQueue::::get(&new_staking_round, ¤cy_id) { + NetworkRedeemQueue::::mutate( + &new_staking_round, + ¤cy_id, + |value| -> Result<(), Error> { + if let Some((total_locked, ledger_list, _token_id)) = value { + ledger_list + .try_push(next_queue_id) + .map_err(|_| Error::::TooManyRedeems)?; + *total_locked = total_locked + .checked_add(¤cy_amount) + .ok_or(Error::::CalculationOverflow)?; + }; + Ok(()) + }, + )?; + } else { + let mut ledger_list_origin = BoundedVec::::default(); + ledger_list_origin + .try_push(next_queue_id) + .map_err(|_| Error::::TooManyRedeems)?; + + NetworkRedeemQueue::::insert( + &new_staking_round, + ¤cy_id, + (currency_amount, ledger_list_origin, currency_id), + ); + } } None => return Err(Error::::NoCurrentStakingRound.into()), } // Emit deposit event - Self::deposit_event(Event::Deposited(who, pool_id, vamount)); + Self::deposit_event(Event::Redeemed(who, pool_id, vamount)); Ok(().into()) } } From b2f5e89754bbd138a5a96d53b99b4da357141bbc Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 3 Nov 2023 14:17:23 +1300 Subject: [PATCH 013/114] WIP - Update StakingRound implementation --- primitives/metaverse/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primitives/metaverse/src/lib.rs b/primitives/metaverse/src/lib.rs index 662d68839..5b2e39ea3 100644 --- a/primitives/metaverse/src/lib.rs +++ b/primitives/metaverse/src/lib.rs @@ -471,7 +471,7 @@ pub enum StakingRound { Hour(#[codec(compact)] u32), } -impl Default for TimeUnit { +impl Default for StakingRound { fn default() -> Self { StakingRound::Era(0u32) } @@ -506,7 +506,7 @@ impl PartialOrd for StakingRound { match (&self, other) { (Self::Era(a), Self::Era(b)) => Some(a.cmp(b)), (Self::Round(a), Self::Round(b)) => Some(a.cmp(b)), - (Self::Epoch(a), Self::Kblock(b)) => Some(a.cmp(b)), + (Self::Epoch(a), Self::Epoch(b)) => Some(a.cmp(b)), (Self::Hour(a), Self::Hour(b)) => Some(a.cmp(b)), _ => None, } From af6c8ff312023739bc58b92c164b159c6715e4d2 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 3 Nov 2023 14:17:44 +1300 Subject: [PATCH 014/114] WIP - Implement Error item --- pallets/land-minting/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index c5922dc54..50f823f20 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -244,6 +244,8 @@ pub mod pallet { NoCurrentStakingRound, /// Unexpected Unexpected, + /// Too many redeems + TooManyRedeems, } #[pallet::call] @@ -419,11 +421,13 @@ pub mod pallet { if UserUnlockRequest::::get(&who, ¤cy_id).is_some() { UserUnlockRequest::::mutate(&who, ¤cy_id, |value| -> Result<(), Error> { if let Some((total_locked, ledger_list)) = value { - ledger_list.try_push(next_id).map_err(|_| Error::::TooManyRedeems)?; + ledger_list + .try_push(next_queue_id) + .map_err(|_| Error::::TooManyRedeems)?; *total_locked = total_locked - .checked_add(&token_amount) - .ok_or(Error::::CalculationOverflow)?; + .checked_add(¤cy_amount) + .ok_or(ArithmeticError::Overflow)?; }; Ok(()) })?; @@ -446,7 +450,7 @@ pub mod pallet { .map_err(|_| Error::::TooManyRedeems)?; *total_locked = total_locked .checked_add(¤cy_amount) - .ok_or(Error::::CalculationOverflow)?; + .ok_or(ArithmeticError::Overflow)?; }; Ok(()) }, From cbd68b5cd03b3c9c266a0cf0e8c457ef3cd75bd6 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 3 Nov 2023 14:18:05 +1300 Subject: [PATCH 015/114] WIP - Change correct CurrencyIdManagement data type --- pallets/asset-manager/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs index ec1f418e6..d4df52a9b 100644 --- a/pallets/asset-manager/src/lib.rs +++ b/pallets/asset-manager/src/lib.rs @@ -349,7 +349,7 @@ where } impl CurrencyIdManagement for ForeignAssetMapping { - fn check_token_exist(currency_id: CurrencyId) -> bool { + fn check_token_exist(token_id: TokenId) -> bool { return true; } } From 1df30c0608ccddb6ac61f12211b34e3627a2ab61 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 3 Nov 2023 14:32:07 +1300 Subject: [PATCH 016/114] WIP - Update failed build cargo --- pallets/land-minting/src/lib.rs | 19 +--- pallets/land-minting/src/utils.rs | 142 ------------------------------ 2 files changed, 2 insertions(+), 159 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 50f823f20..e285532b2 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -35,14 +35,12 @@ use sp_std::vec::Vec; use auction_manager::{Auction, CheckAuctionItemHandler}; use core_primitives::*; -pub use pallet::*; use primitives::estate::EstateInfo; use primitives::{ estate::{Estate, LandUnitStatus, LeaseContract, OwnerId}, Attributes, ClassId, EstateId, FungibleTokenId, ItemId, MetaverseId, NftMetadata, StakingRound, TokenId, UndeployedLandBlock, UndeployedLandBlockId, UndeployedLandBlockType, }; -pub use utils::{MintingRateInfo, Range}; pub use weights::WeightInfo; pub type QueueId = u32; @@ -58,6 +56,7 @@ mod tests; pub mod weights; +pub use pallet::*; #[frame_support::pallet] pub mod pallet { use frame_support::traits::{Currency, Imbalance, ReservableCurrency}; @@ -70,7 +69,7 @@ pub mod pallet { use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; use primitives::{AccountId, Balance, CurrencyId, PoolId, RoundIndex, StakingRound, UndeployedLandBlockId}; - use crate::utils::{round_issuance_range, MintingRateInfo, PoolInfo}; + use crate::utils::PoolInfo; use super::*; @@ -199,20 +198,6 @@ pub mod pallet { #[pallet::getter(fn queue_next_id)] pub type QueueNextId = StorageMap<_, Twox64Concat, CurrencyIdOf, u32, ValueQuery>; - #[pallet::genesis_config] - pub struct GenesisConfig { - pub minting_rate_config: MintingRateInfo, - } - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - minting_rate_config: Default::default(), - } - } - } - #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { diff --git a/pallets/land-minting/src/utils.rs b/pallets/land-minting/src/utils.rs index 164224e2c..3379ae201 100644 --- a/pallets/land-minting/src/utils.rs +++ b/pallets/land-minting/src/utils.rs @@ -21,14 +21,9 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{Perbill, Permill, RuntimeDebug}; -use crate::{AllLandUnitsCount, TotalUndeployedLandUnit}; // Helper methods to compute the issuance rate for undeployed land. use crate::pallet::{Config, Pallet}; -const SECONDS_PER_YEAR: u32 = 31557600; -const SECONDS_PER_BLOCK: u32 = 12; -const BLOCKS_PER_YEAR: u32 = SECONDS_PER_YEAR / SECONDS_PER_BLOCK; - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug, TypeInfo)] pub struct PoolInfo { @@ -39,140 +34,3 @@ pub struct PoolInfo { /// Max nft rewards pub max: u32, } - -fn rounds_per_year() -> u32 { - let blocks_per_round = >::round().length; - BLOCKS_PER_YEAR / blocks_per_round -} - -fn get_annual_max_issuance(max_supply: u64, annual_percentage: u64) -> u64 { - let total_land_unit_circulating = >::get(); - let total_undeployed_land_unit_circulating = >::get(); - let circulating = total_land_unit_circulating.saturating_add(total_undeployed_land_unit_circulating); - max_supply.saturating_sub(circulating).saturating_mul(annual_percentage) -} - -/// Compute round issuance range from round inflation range and current total issuance -pub fn round_issuance_range(config: MintingRateInfo) -> Range { - // Get total round per year - let total_round_per_year = rounds_per_year::(); - - // Get total land unit circulating - let total_land_unit_circulating = >::get(); - - // Get total undeployed land unit circulating - let total_undeployed_land_unit_circulating = >::get(); - - // Total circulating - let circulating = total_land_unit_circulating.saturating_add(total_undeployed_land_unit_circulating); - - // Total annual minting percent - let annual_percentage = Perbill::from_percent(config.annual as u32).deconstruct(); - - // Round percentage minting rate - let round_percentage = annual_percentage.checked_div(total_round_per_year).unwrap(); - - // Convert to percentage - let round_percentage_per_bill = Perbill::from_parts(round_percentage); - - // Return range - could implement more cases in the future. - Range { - min: round_percentage_per_bill * circulating, - ideal: round_percentage_per_bill * circulating, - max: round_percentage_per_bill * circulating, - } -} - -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct Range { - pub min: T, - pub ideal: T, - pub max: T, -} - -impl Range { - pub fn is_valid(&self) -> bool { - self.max >= self.ideal && self.ideal >= self.min - } -} - -impl From for Range { - fn from(other: T) -> Range { - Range { - min: other, - ideal: other, - max: other, - } - } -} - -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct MintingRateInfo { - /// Number of metaverse expectations - pub expect: Range, - /// Annual minting range - pub annual: u64, - /// Max total supply - pub max: u64, -} - -impl MintingRateInfo { - pub fn new(annual: u64, expect: Range, max: u64) -> MintingRateInfo { - MintingRateInfo { expect, annual, max } - } - - /// Set minting rate expectations - pub fn set_expectations(&mut self, expect: Range) { - self.expect = expect; - } - - /// Set minting rate expectations - pub fn set_max(&mut self, max: u64) { - self.max = max; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Compute round issuance range from round inflation range and current total issuance - pub fn mock_round_issuance_per_year( - config: MintingRateInfo, - circulation: u64, - total_round_per_year: u32, - ) -> Range { - let annual_percentage = Perbill::from_percent(config.annual as u32).deconstruct(); - let round_percentage = annual_percentage.checked_div(total_round_per_year).unwrap(); - - let round_percentage_per_bill = Perbill::from_parts(round_percentage); - - Range { - min: round_percentage_per_bill * circulation, - ideal: round_percentage_per_bill * circulation, - max: round_percentage_per_bill * circulation, - } - } - - #[test] - fn simple_round_issuance() { - // 5% minting rate for 100_000 land unit = 100 minted over the year - // let's assume there are 10 periods in a year - // => mint 100 over 10 periods => 10 minted per period - - let mock_config: MintingRateInfo = MintingRateInfo { - expect: Default::default(), - annual: 5, - max: 100_000, - }; - - let round_issuance = mock_round_issuance_per_year(mock_config, 2_000, 10); - - // make sure 10 land unit deploy per period - assert_eq!(round_issuance.min, 10); - assert_eq!(round_issuance.ideal, 10); - assert_eq!(round_issuance.max, 10); - } -} From 300ca2400af530798cec39f96d27b54a1062007b Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 7 Nov 2023 21:05:24 +1300 Subject: [PATCH 017/114] WIP - Collect fees update --- pallets/land-minting/src/lib.rs | 41 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index e285532b2..2b2cc56bd 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -26,7 +26,9 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use frame_system::{ensure_root, ensure_signed}; +use orml_traits::MultiCurrency; use scale_info::TypeInfo; +use sp_runtime::traits::CheckedSub; use sp_runtime::{ traits::{AccountIdConversion, Convert, One, Saturating, Zero}, ArithmeticError, DispatchError, Perbill, SaturatedConversion, @@ -35,6 +37,7 @@ use sp_std::vec::Vec; use auction_manager::{Auction, CheckAuctionItemHandler}; use core_primitives::*; +pub use pallet::*; use primitives::estate::EstateInfo; use primitives::{ estate::{Estate, LandUnitStatus, LeaseContract, OwnerId}, @@ -56,7 +59,6 @@ mod tests; pub mod weights; -pub use pallet::*; #[frame_support::pallet] pub mod pallet { use frame_support::traits::{Currency, Imbalance, ReservableCurrency}; @@ -112,7 +114,7 @@ pub mod pallet { /// Default max bound for each metaverse mapping system, this could change through proposal type DefaultMaxBound: Get<(i32, i32)>; - /// Network fee charged when deploying a land block or creating an estate + /// Network fee charged when depositing or redeeming #[pallet::constant] type NetworkFee: Get>; @@ -292,7 +294,7 @@ pub mod pallet { // Collect deposit fee for protocol // Assuming there's a function `collect_deposit_fee` that deducts a fee for deposits. - let amount_after_fee = Self::collect_deposit_fee(&who, amount)?; + let amount_after_fee = Self::collect_deposit_fee(&who, currency_id, amount)?; let v_currency_id = T::CurrencyIdManagement::convert_to_vcurrency(currency_id) .map_err(|_| Error::::CurrencyIsNotSupported)?; @@ -363,8 +365,8 @@ pub mod pallet { let network_ledger_balance = Self::network_ledger(currency_id); // Collect deposit fee for protocol - // Assuming there's a function `collect_deposit_fee` that deducts a fee for deposits. - let amount_after_fee = Self::collect_deposit_fee(&who, vamount)?; + // Assuming there's a function `collect_redeem_fee` that deducts a fee for deposits. + let amount_after_fee = Self::collect_redeem_fee(&who, vcurrency_id, vamount)?; let vamount = vamount .checked_sub(&amount_after_fee) .ok_or(ArithmeticError::Overflow)?; @@ -495,4 +497,33 @@ impl Pallet { Ok(result) } + + #[transactional] + pub fn collect_deposit_fee( + who: T::AccountId, + currency_id: BalanceOf, + amount: BalanceOf, + ) -> Result, DispatchError> { + let (deposit_rate, _redeem_rate) = Fees::::get(); + + let deposit_fee = deposit_rate * amount; + let amount_exclude_fee = amount.checked_sub(&deposit_fee).ok_or(ArithmeticError::Overflow)?; + T::MultiCurrency::transfer(currency_id, who, &T::NetworkFee::get(), deposit_fee)?; + + return amount_exclude_fee; + } + + #[transactional] + pub fn collect_redeem_fee( + who: T::AccountId, + currency_id: BalanceOf, + amount: BalanceOf, + ) -> Result, DispatchError> { + let (_mint_rate, redeem_rate) = Fees::::get(); + let redeem_fee = redeem_rate * amount; + let amount_exclude_fee = amount.checked_sub(&deposit_fee).ok_or(ArithmeticError::Overflow)?; + T::MultiCurrency::transfer(currency_id, who, &T::NetworkFee::get(), redeem_fee)?; + + return amount_exclude_fee; + } } From 6ff25635ed37abc3c42e533d8f5fefe2dbfb01b6 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 11:01:44 +1300 Subject: [PATCH 018/114] WIP - Convert native to fungible --- pallets/asset-manager/src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs index d4df52a9b..475dca7bf 100644 --- a/pallets/asset-manager/src/lib.rs +++ b/pallets/asset-manager/src/lib.rs @@ -348,8 +348,19 @@ where } } -impl CurrencyIdManagement for ForeignAssetMapping { - fn check_token_exist(token_id: TokenId) -> bool { +impl CurrencyIdManagement for ForeignAssetMapping { + fn check_token_exist(token_id: FungibleTokenId) -> bool { return true; } + + fn convert_to_rcurrency(currency_id: FungibleTokenId) -> Result { + match currency_id { + FungibleTokenId::NativeToken(token_id) => Ok(FungibleTokenId::FungibleToken(token_id)), + _ => Err(()), + } + } + + fn convert_to_currency(currency_id: FungibleTokenId) -> Result { + todo!() + } } From 8500dca06c4ce4048881d0bbe17bda36178253a2 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 11:02:12 +1300 Subject: [PATCH 019/114] WIP - Update logic of redeeming queue --- pallets/land-minting/src/lib.rs | 210 ++++++++++++++++++++------------ 1 file changed, 130 insertions(+), 80 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 2b2cc56bd..f998b4319 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -95,7 +95,11 @@ pub mod pallet { /// Currency type type Currency: Currency + ReservableCurrency; /// Multi currencies type that handles different currency type in auction - type MultiCurrency: MultiReservableCurrency; + type MultiCurrency: MultiReservableCurrency< + Self::AccountId, + CurrencyId = FungibleTokenId, + Balance = BalanceOf, + >; /// Weight implementation for estate extrinsics type WeightInfo: WeightInfo; @@ -114,7 +118,7 @@ pub mod pallet { /// Default max bound for each metaverse mapping system, this could change through proposal type DefaultMaxBound: Get<(i32, i32)>; - /// Network fee charged when depositing or redeeming + /// Network fee charged on pool creation #[pallet::constant] type NetworkFee: Get>; @@ -131,11 +135,11 @@ pub mod pallet { #[pallet::constant] type MaximumQueue: Get; + + type CurrencyIdConversion: CurrencyIdManagement; } pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; - pub type CurrencyIdOf = - <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; #[pallet::storage] #[pallet::getter(fn next_class_id)] @@ -148,7 +152,7 @@ pub mod pallet { /// Keep track of Pool detail #[pallet::storage] #[pallet::getter(fn pool)] - pub type Pool = StorageMap<_, Twox64Concat, PoolId, PoolInfo, T::AccountId>, ValueQuery>; + pub type Pool = StorageMap<_, Twox64Concat, PoolId, PoolInfo, OptionQuery>; /// Pool ledger that keeps track of Pool id and balance of base currency #[pallet::storage] @@ -158,30 +162,45 @@ pub mod pallet { /// Network ledger that keep track of all staking across all pools #[pallet::storage] #[pallet::getter(fn network_ledger)] - pub type NetworkLedger = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + pub type NetworkLedger = StorageMap<_, Twox64Concat, FungibleTokenId, BalanceOf, ValueQuery>; #[pallet::storage] #[pallet::getter(fn minimum_redeem)] - pub type MinimumRedeem = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + pub type MinimumRedeem = StorageMap<_, Twox64Concat, FungibleTokenId, BalanceOf, ValueQuery>; + /// Keep track of each staking round, how many items in queue need to be redeem #[pallet::storage] - #[pallet::getter(fn network_redeem_requests)] - pub type NetworkRedeemQueue = StorageDoubleMap< + #[pallet::getter(fn staking_round_redeem_requests)] + pub type StakingRoundRedeemQueue = StorageDoubleMap< _, Blake2_128Concat, StakingRound, Blake2_128Concat, - CurrencyIdOf, - (BalanceOf, BoundedVec, CurrencyIdOf), + FungibleTokenId, + (BalanceOf, BoundedVec, FungibleTokenId), + OptionQuery, + >; + + /// Keep track of user ledger that how many queue items that needs to be unlocked + #[pallet::storage] + #[pallet::getter(fn user_redeem_requests)] + pub type UserCurrencyRedeemQueue = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + FungibleTokenId, + (BalanceOf, BoundedVec), OptionQuery, >; + /// Keep track of queue item as well as account that locked amount of currency can be redeemed #[pallet::storage] - #[pallet::getter(fn user_unlock_request)] - pub type UserUnlockRequest = StorageDoubleMap< + #[pallet::getter(fn currency_redeem_requests)] + pub type CurrencyRedeemQueue = StorageDoubleMap< _, Blake2_128Concat, - CurrencyIdOf, + FungibleTokenId, Blake2_128Concat, QueueId, (T::AccountId, BalanceOf, StakingRound), @@ -190,15 +209,15 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn unlock_duration)] - pub type UnlockDuration = StorageMap<_, Twox64Concat, CurrencyIdOf, StakingRound>; + pub type UnlockDuration = StorageMap<_, Twox64Concat, FungibleTokenId, StakingRound>; #[pallet::storage] #[pallet::getter(fn current_staking_round)] - pub type CurrentStakingRound = StorageMap<_, Twox64Concat, CurrencyIdOf, StakingRound>; + pub type CurrentStakingRound = StorageMap<_, Twox64Concat, FungibleTokenId, StakingRound>; #[pallet::storage] #[pallet::getter(fn queue_next_id)] - pub type QueueNextId = StorageMap<_, Twox64Concat, CurrencyIdOf, u32, ValueQuery>; + pub type QueueNextId = StorageMap<_, Twox64Concat, FungibleTokenId, u32, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] @@ -206,7 +225,7 @@ pub mod pallet { /// New staking round started [Starting Block, Round, Total Land Unit] NewRound(T::BlockNumber, RoundIndex, u64), /// New pool created - PoolCreated(T::AccountId, PoolId, CurrencyIdOf), + PoolCreated(T::AccountId, PoolId, FungibleTokenId), /// Deposited Deposited(T::AccountId, PoolId, BalanceOf), /// Redeemed @@ -233,6 +252,12 @@ pub mod pallet { Unexpected, /// Too many redeems TooManyRedeems, + /// Arthimetic Overflow + ArithmeticOverflow, + /// Token type is not supported + NotSupportTokenType, + /// Unlock duration not found + UnlockDurationNotFound, } #[pallet::call] @@ -240,7 +265,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::mint_land())] pub fn create_pool( origin: OriginFor, - currency_id: CurrencyIdOf, + currency_id: FungibleTokenId, max_nft_reward: u32, commission: Permill, ) -> DispatchResult { @@ -255,7 +280,7 @@ pub mod pallet { // TODO Check commission below threshold // Collect pool creation fee - Self::collect_pool_creation_fee(&who)?; + Self::collect_pool_creation_fee(&who, currency_id)?; // Next pool id let next_pool_id = NextPoolId::::try_mutate(|id| -> Result { @@ -266,8 +291,8 @@ pub mod pallet { let new_pool = PoolInfo { creator: who.clone(), - commission: commission, - currency_id: currency_id, + commission, + currency_id, max: max_nft_reward, }; @@ -296,29 +321,33 @@ pub mod pallet { // Assuming there's a function `collect_deposit_fee` that deducts a fee for deposits. let amount_after_fee = Self::collect_deposit_fee(&who, currency_id, amount)?; - let v_currency_id = T::CurrencyIdManagement::convert_to_vcurrency(currency_id) + let r_currency_id = T::CurrencyIdConversion::convert_to_rcurrency(currency_id) .map_err(|_| Error::::CurrencyIsNotSupported)?; - // Calculate vAmount as receipt of amount locked. The formula based on vAmount = (amount * vAmount + // Calculate rAmount as receipt of amount locked. The formula based on rAmount = (amount * rAmount // total issuance)/network ledger balance - let v_amount_total_issuance = T::MultiCurrency::total_issuance(v_currency_id); - let v_amount = U256::from(amount_after_fee.saturated_into::()) - .saturating_mul(v_amount_total_issuance.saturated_into::().into()) + let r_amount_total_issuance = T::MultiCurrency::total_issuance(r_currency_id); + let r_amount = U256::from(amount_after_fee.saturated_into::()) + .saturating_mul(r_amount_total_issuance.saturated_into::().into()) .checked_div(network_ledger_balance.saturated_into::().into()) - .ok_or(ArithmeticError::Overflow)? + .ok_or(Error::::ArithmeticOverflow)? .as_u128() .saturated_into(); - // Deposit vAmount to user using T::MultiCurrency::deposit - T::MultiCurrency::deposit(currency_id, &who, v_amount)?; + // Deposit rAmount to user using T::MultiCurrency::deposit + T::MultiCurrency::deposit(currency_id, &who, r_amount)?; // Update this specific pool ledger to keep track of pool balance PoolLedger::::mutate(&pool_id, |pool| -> Result<(), Error> { - *pool = pool.checked_add(&amount_after_fee).ok_or(ArithmeticError::Overflow)?; + *pool = pool + .checked_add(&amount_after_fee) + .ok_or(Error::::ArithmeticOverflow)?; Ok(()) })?; NetworkLedger::::mutate(¤cy_id, |pool| -> Result<(), Error> { - *pool = pool.checked_add(&amount_after_fee).ok_or(ArithmeticError::Overflow)?; + *pool = pool + .checked_add(&amount_after_fee) + .ok_or(Error::::ArithmeticOverflow)?; Ok(()) })?; // Transfer amount to PoolAccount using T::MultiCurrency::transfer @@ -328,7 +357,7 @@ pub mod pallet { currency_id, &who, &T::PoolAccount::get().into_account_truncating(), - amount, + amount_after_fee, )?; // Emit deposit event @@ -340,17 +369,17 @@ pub mod pallet { pub fn redeem( origin: OriginFor, pool_id: PoolId, - vcurrency_id: CurrencyIdOf, - vamount: BalanceOf, + v_currency_id: FungibleTokenId, + r_amount: BalanceOf, ) -> DispatchResult { // Ensure user is signed let who = ensure_signed(origin)?; ensure!( - vamount >= MinimumRedeem::::get(vcurrency_id), + r_amount >= MinimumRedeem::::get(v_currency_id), Error::::BelowMinimumRedeem ); - let currency_id = T::CurrencyIdManagement::convert_to_currency(vcurrency_id) + let currency_id = T::CurrencyIdConversion::convert_to_currency(v_currency_id) .map_err(|_| Error::::NotSupportTokenType)?; // Check if pool exists @@ -366,17 +395,17 @@ pub mod pallet { // Collect deposit fee for protocol // Assuming there's a function `collect_redeem_fee` that deducts a fee for deposits. - let amount_after_fee = Self::collect_redeem_fee(&who, vcurrency_id, vamount)?; - let vamount = vamount + let amount_after_fee = Self::collect_redeem_fee(&who, v_currency_id, r_amount)?; + let r_amount = r_amount .checked_sub(&amount_after_fee) - .ok_or(ArithmeticError::Overflow)?; - // Calculate vAmount as receipt of amount locked. The formula based on vAmount = (amount * vAmount + .ok_or(Error::::ArithmeticOverflow)?; + // Calculate rAmount as receipt of amount locked. The formula based on rAmount = (amount * rAmount // total issuance)/network ledger balance - let v_amount_total_issuance = T::MultiCurrency::total_issuance(vcurrency_id); - let currency_amount = U256::from(vamount.saturated_into::()) + let r_amount_total_issuance = T::MultiCurrency::total_issuance(v_currency_id); + let currency_amount = U256::from(r_amount.saturated_into::()) .saturating_mul(network_ledger_balance.saturated_into::().into()) - .checked_div(v_amount_total_issuance.saturated_into::().into()) - .ok_or(ArithmeticError::Overflow)? + .checked_div(r_amount_total_issuance.saturated_into::().into()) + .ok_or(Error::::ArithmeticOverflow)? .as_u128() .saturated_into(); @@ -390,65 +419,80 @@ pub mod pallet { staking_round, )?; // Burn currency - T::MultiCurrency::withdraw(vcurrency_id, &who, vamount)?; + T::MultiCurrency::withdraw(v_currency_id, &who, amount_after_fee)?; // Update pool ledger PoolLedger::::mutate(&pool_id, |pool| -> Result<(), Error> { - *pool = pool.checked_sub(¤cy_amount).ok_or(ArithmeticError::Overflow)?; + *pool = pool + .checked_sub(¤cy_amount) + .ok_or(Error::::ArithmeticOverflow)?; Ok(()) })?; + // Get current queue_id let next_queue_id = Self::queue_next_id(currency_id); - UserUnlockRequest::::insert( + + // Add request into network currency redeem queue + CurrencyRedeemQueue::::insert( ¤cy_id, &next_queue_id, - (&who, currency_amount, &new_staking_round), + (who, currency_amount, new_staking_round), ); - if UserUnlockRequest::::get(&who, ¤cy_id).is_some() { - UserUnlockRequest::::mutate(&who, ¤cy_id, |value| -> Result<(), Error> { - if let Some((total_locked, ledger_list)) = value { - ledger_list + // Handle ledger of user and currency - user,currency: total_amount_unlocked, vec![queue_id] + // Check if you already has any redeem requests + if UserCurrencyRedeemQueue::::get(&who, ¤cy_id).is_some() { + // Add new queue id into the list + UserCurrencyRedeemQueue::::mutate(&who, ¤cy_id, |value| -> Result<(), Error> { + // + if let Some((amount_need_unlocked, existing_queue)) = value { + existing_queue .try_push(next_queue_id) .map_err(|_| Error::::TooManyRedeems)?; - *total_locked = total_locked + *amount_need_unlocked = amount_need_unlocked .checked_add(¤cy_amount) - .ok_or(ArithmeticError::Overflow)?; + .ok_or(Error::::ArithmeticOverflow)?; }; Ok(()) })?; } else { - let mut ledger_list_origin = BoundedVec::::default(); - ledger_list_origin + let mut new_queue = BoundedVec::::default(); + new_queue .try_push(next_queue_id) .map_err(|_| Error::::TooManyRedeems)?; - UserUnlockRequest::::insert(&who, ¤cy_id, (currency_amount, ledger_list_origin)); + UserCurrencyRedeemQueue::::insert(&who, ¤cy_id, (currency_amount, new_queue)); } - if let Some((_, _, _token_id)) = NetworkRedeemQueue::::get(&new_staking_round, ¤cy_id) { - NetworkRedeemQueue::::mutate( + // Handle ledger of staking round - executed by hooks on every block - staking_round,currency: + // total_amount_unlocked, vec![queue_id], currency + + // Check if there any existing claim of the next staking round + if let Some((_, _, _token_id)) = StakingRoundRedeemQueue::::get(&new_staking_round, ¤cy_id) + { + StakingRoundRedeemQueue::::mutate( &new_staking_round, ¤cy_id, |value| -> Result<(), Error> { - if let Some((total_locked, ledger_list, _token_id)) = value { - ledger_list + // Add new queue item + if let Some((amount_need_unlocked, existing_queue, _token_id)) = value { + existing_queue .try_push(next_queue_id) .map_err(|_| Error::::TooManyRedeems)?; - *total_locked = total_locked + *amount_need_unlocked = amount_need_unlocked .checked_add(¤cy_amount) - .ok_or(ArithmeticError::Overflow)?; + .ok_or(Error::::ArithmeticOverflow)?; }; Ok(()) }, )?; } else { - let mut ledger_list_origin = BoundedVec::::default(); - ledger_list_origin + let mut new_queue = BoundedVec::::default(); + new_queue .try_push(next_queue_id) .map_err(|_| Error::::TooManyRedeems)?; - NetworkRedeemQueue::::insert( + StakingRoundRedeemQueue::::insert( &new_staking_round, ¤cy_id, (currency_amount, ledger_list_origin, currency_id), @@ -459,7 +503,7 @@ pub mod pallet { } // Emit deposit event - Self::deposit_event(Event::Redeemed(who, pool_id, vamount)); + Self::deposit_event(Event::Redeemed(who, pool_id, r_amount)); Ok(().into()) } } @@ -471,25 +515,25 @@ impl Pallet { let result = match a { StakingRound::Era(era_a) => match b { StakingRound::Era(era_b) => { - StakingRound::Era(era_a.checked_add(era_b).ok_or(ArithmeticError::Overflow)?) + StakingRound::Era(era_a.checked_add(era_b).ok_or(Error::::ArithmeticOverflow)?) } _ => return Err(Error::::Unexpected.into()), }, StakingRound::Round(round_a) => match b { StakingRound::Round(round_b) => { - StakingRound::Round(round_a.checked_add(round_b).ok_or(ArithmeticError::Overflow)?) + StakingRound::Round(round_a.checked_add(round_b).ok_or(Error::::ArithmeticOverflow)?) } _ => return Err(Error::::Unexpected.into()), }, StakingRound::Epoch(epoch_a) => match b { StakingRound::Epoch(epoch_b) => { - StakingRound::Epoch(epoch_a.checked_add(epoch_b).ok_or(ArithmeticError::Overflow)?) + StakingRound::Epoch(epoch_a.checked_add(epoch_b).ok_or(Error::::ArithmeticOverflow)?) } _ => return Err(Error::::Unexpected.into()), }, StakingRound::Hour(hour_a) => match b { StakingRound::Hour(hour_b) => { - StakingRound::Hour(hour_a.checked_add(hour_b).ok_or(ArithmeticError::Overflow)?) + StakingRound::Hour(hour_a.checked_add(hour_b).ok_or(Error::::ArithmeticOverflow)?) } _ => return Err(Error::::Unexpected.into()), }, @@ -500,30 +544,36 @@ impl Pallet { #[transactional] pub fn collect_deposit_fee( - who: T::AccountId, - currency_id: BalanceOf, + who: &T::AccountId, + currency_id: FungibleTokenId, amount: BalanceOf, ) -> Result, DispatchError> { let (deposit_rate, _redeem_rate) = Fees::::get(); let deposit_fee = deposit_rate * amount; - let amount_exclude_fee = amount.checked_sub(&deposit_fee).ok_or(ArithmeticError::Overflow)?; - T::MultiCurrency::transfer(currency_id, who, &T::NetworkFee::get(), deposit_fee)?; + let amount_exclude_fee = amount.checked_sub(&deposit_fee).ok_or(Error::::ArithmeticOverflow)?; + T::MultiCurrency::transfer(currency_id, who, &T::PoolAccount::get(), deposit_fee)?; return amount_exclude_fee; } #[transactional] pub fn collect_redeem_fee( - who: T::AccountId, - currency_id: BalanceOf, + who: &T::AccountId, + currency_id: FungibleTokenId, amount: BalanceOf, ) -> Result, DispatchError> { let (_mint_rate, redeem_rate) = Fees::::get(); let redeem_fee = redeem_rate * amount; - let amount_exclude_fee = amount.checked_sub(&deposit_fee).ok_or(ArithmeticError::Overflow)?; - T::MultiCurrency::transfer(currency_id, who, &T::NetworkFee::get(), redeem_fee)?; + let amount_exclude_fee = amount.checked_sub(&redeem_fee).ok_or(Error::::ArithmeticOverflow)?; + T::MultiCurrency::transfer(currency_id, who, &T::PoolAccount::get(), redeem_fee)?; return amount_exclude_fee; } + + #[transactional] + pub fn collect_pool_creation_fee(who: &T::AccountId, currency_id: FungibleTokenId) -> DispatchResult { + let pool_fee = T::NetworkFee::get(); + T::MultiCurrency::transfer(currency_id, who, &T::PoolAccount::get(), pool_fee) + } } From 6c5fb998d089dddc82042b670118d797b85b7227 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 11:02:35 +1300 Subject: [PATCH 020/114] WIP - Update pool info currency id type --- pallets/land-minting/src/utils.rs | 8 +++++--- traits/core-primitives/src/lib.rs | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pallets/land-minting/src/utils.rs b/pallets/land-minting/src/utils.rs index 3379ae201..32213d80e 100644 --- a/pallets/land-minting/src/utils.rs +++ b/pallets/land-minting/src/utils.rs @@ -21,16 +21,18 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{Perbill, Permill, RuntimeDebug}; +use primitives::FungibleTokenId; + // Helper methods to compute the issuance rate for undeployed land. use crate::pallet::{Config, Pallet}; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct PoolInfo { +#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PoolInfo { pub creator: AccountId, pub commission: Permill, /// Currency id of the pool - pub currency_id: CurrencyId, + pub currency_id: FungibleTokenId, /// Max nft rewards pub max: u32, } diff --git a/traits/core-primitives/src/lib.rs b/traits/core-primitives/src/lib.rs index 5946eb38b..72f15aa0d 100644 --- a/traits/core-primitives/src/lib.rs +++ b/traits/core-primitives/src/lib.rs @@ -401,6 +401,8 @@ impl MiningResourceRateInfo { } } -pub trait CurrencyIdManagement { - fn check_token_exist(currency_id: CurrencyId) -> bool; +pub trait CurrencyIdManagement { + fn check_token_exist(currency_id: FungibleTokenId) -> bool; + fn convert_to_rcurrency(currency_id: FungibleTokenId) -> Result; + fn convert_to_currency(currency_id: FungibleTokenId) -> Result; } From b0be447b07997e1797f1fb9b8a7e5085d5e7be50 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 11:14:40 +1300 Subject: [PATCH 021/114] WIP - Fix build errors of inserting into redeem queue --- pallets/land-minting/src/lib.rs | 37 +++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index f998b4319..5db8bfb35 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -436,7 +436,7 @@ pub mod pallet { CurrencyRedeemQueue::::insert( ¤cy_id, &next_queue_id, - (who, currency_amount, new_staking_round), + (&who, currency_amount, &new_staking_round), ); // Handle ledger of user and currency - user,currency: total_amount_unlocked, vec![queue_id] @@ -495,7 +495,7 @@ pub mod pallet { StakingRoundRedeemQueue::::insert( &new_staking_round, ¤cy_id, - (currency_amount, ledger_list_origin, currency_id), + (currency_amount, new_queue, currency_id), ); } } @@ -510,7 +510,6 @@ pub mod pallet { } impl Pallet { - #[transactional] pub fn calculate_next_staking_round(a: StakingRound, b: StakingRound) -> Result { let result = match a { StakingRound::Era(era_a) => match b { @@ -542,7 +541,6 @@ impl Pallet { Ok(result) } - #[transactional] pub fn collect_deposit_fee( who: &T::AccountId, currency_id: FungibleTokenId, @@ -552,12 +550,16 @@ impl Pallet { let deposit_fee = deposit_rate * amount; let amount_exclude_fee = amount.checked_sub(&deposit_fee).ok_or(Error::::ArithmeticOverflow)?; - T::MultiCurrency::transfer(currency_id, who, &T::PoolAccount::get(), deposit_fee)?; - - return amount_exclude_fee; + T::MultiCurrency::transfer( + currency_id, + who, + &T::PoolAccount::get().into_account_truncating(), + deposit_fee, + )?; + + return Ok(amount_exclude_fee); } - #[transactional] pub fn collect_redeem_fee( who: &T::AccountId, currency_id: FungibleTokenId, @@ -566,14 +568,23 @@ impl Pallet { let (_mint_rate, redeem_rate) = Fees::::get(); let redeem_fee = redeem_rate * amount; let amount_exclude_fee = amount.checked_sub(&redeem_fee).ok_or(Error::::ArithmeticOverflow)?; - T::MultiCurrency::transfer(currency_id, who, &T::PoolAccount::get(), redeem_fee)?; - - return amount_exclude_fee; + T::MultiCurrency::transfer( + currency_id, + who, + &T::PoolAccount::get().into_account_truncating(), + redeem_fee, + )?; + + return Ok(amount_exclude_fee); } - #[transactional] pub fn collect_pool_creation_fee(who: &T::AccountId, currency_id: FungibleTokenId) -> DispatchResult { let pool_fee = T::NetworkFee::get(); - T::MultiCurrency::transfer(currency_id, who, &T::PoolAccount::get(), pool_fee) + T::MultiCurrency::transfer( + currency_id, + who, + &T::PoolAccount::get().into_account_truncating(), + pool_fee, + ) } } From daf6ce2fe3c0e71099ae361766a098faea96abf8 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 12:22:39 +1300 Subject: [PATCH 022/114] WIP - Update queue next id after user claimed --- pallets/land-minting/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pallets/land-minting/src/lib.rs b/pallets/land-minting/src/lib.rs index 5db8bfb35..84a68be55 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/land-minting/src/lib.rs @@ -502,6 +502,11 @@ pub mod pallet { None => return Err(Error::::NoCurrentStakingRound.into()), } + QueueNextId::::mutate(¤cy_id, |queue_id| -> Result<(), Error> { + *queue_id = queue_id.checked_add(1).ok_or(Error::::ArithmeticOverflow)?; + Ok(()) + })?; + // Emit deposit event Self::deposit_event(Event::Redeemed(who, pool_id, r_amount)); Ok(().into()) From 23d4f20ee0afff4983feaee61edf8361e211523b Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 14:44:54 +1300 Subject: [PATCH 023/114] WIP - Create spp logic --- Cargo.lock | 48 ++++++++++++------------ pallets/{land-minting => spp}/Cargo.toml | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) rename pallets/{land-minting => spp}/Cargo.toml (98%) diff --git a/Cargo.lock b/Cargo.lock index 4c1b16e5b..0a3200878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7584,30 +7584,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-land-minting" -version = "2.0.0-rc6" -dependencies = [ - "auction-manager", - "bit-country-primitives", - "core-primitives", - "frame-benchmarking", - "frame-support", - "frame-system", - "orml-tokens", - "orml-traits", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", - "sp-version", - "substrate-fixed", -] - [[package]] name = "pallet-membership" version = "4.0.0-dev" @@ -8045,6 +8021,30 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-spp" +version = "2.0.0-rc6" +dependencies = [ + "auction-manager", + "bit-country-primitives", + "core-primitives", + "frame-benchmarking", + "frame-support", + "frame-system", + "orml-tokens", + "orml-traits", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", + "substrate-fixed", +] + [[package]] name = "pallet-staking" version = "4.0.0-dev" diff --git a/pallets/land-minting/Cargo.toml b/pallets/spp/Cargo.toml similarity index 98% rename from pallets/land-minting/Cargo.toml rename to pallets/spp/Cargo.toml index 1b68911fd..bb0c5c93e 100644 --- a/pallets/land-minting/Cargo.toml +++ b/pallets/spp/Cargo.toml @@ -4,7 +4,7 @@ description = 'Metaverse Network pallet for land minting logic.' edition = '2021' homepage = 'https://metaverse.network' license = 'Unlicense' -name = 'pallet-land-minting' +name = 'pallet-spp' repository = 'https://github.com/bit-country' version = '2.0.0-rc6' From 106e7d12f123d7343e16b7ff890266e50720edcf Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 14:45:17 +1300 Subject: [PATCH 024/114] WIP - Update folder structure on ssp logic --- pallets/{land-minting => spp}/src/mock.rs | 0 pallets/{land-minting => spp}/src/tests.rs | 0 pallets/{land-minting => spp}/src/utils.rs | 0 pallets/{land-minting => spp}/src/weights.rs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename pallets/{land-minting => spp}/src/mock.rs (100%) rename pallets/{land-minting => spp}/src/tests.rs (100%) rename pallets/{land-minting => spp}/src/utils.rs (100%) rename pallets/{land-minting => spp}/src/weights.rs (100%) diff --git a/pallets/land-minting/src/mock.rs b/pallets/spp/src/mock.rs similarity index 100% rename from pallets/land-minting/src/mock.rs rename to pallets/spp/src/mock.rs diff --git a/pallets/land-minting/src/tests.rs b/pallets/spp/src/tests.rs similarity index 100% rename from pallets/land-minting/src/tests.rs rename to pallets/spp/src/tests.rs diff --git a/pallets/land-minting/src/utils.rs b/pallets/spp/src/utils.rs similarity index 100% rename from pallets/land-minting/src/utils.rs rename to pallets/spp/src/utils.rs diff --git a/pallets/land-minting/src/weights.rs b/pallets/spp/src/weights.rs similarity index 100% rename from pallets/land-minting/src/weights.rs rename to pallets/spp/src/weights.rs From be61add45c7c16252c95f79d5113fe4b28d8d1a3 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 14:45:44 +1300 Subject: [PATCH 025/114] WIP - Implement hooks on initialize --- pallets/{land-minting => spp}/src/lib.rs | 292 ++++++++++++++++++++++- 1 file changed, 283 insertions(+), 9 deletions(-) rename pallets/{land-minting => spp}/src/lib.rs (68%) diff --git a/pallets/land-minting/src/lib.rs b/pallets/spp/src/lib.rs similarity index 68% rename from pallets/land-minting/src/lib.rs rename to pallets/spp/src/lib.rs index 84a68be55..842237564 100644 --- a/pallets/land-minting/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -28,7 +28,7 @@ use frame_system::pallet_prelude::*; use frame_system::{ensure_root, ensure_signed}; use orml_traits::MultiCurrency; use scale_info::TypeInfo; -use sp_runtime::traits::CheckedSub; +use sp_runtime::traits::{CheckedAdd, CheckedSub}; use sp_runtime::{ traits::{AccountIdConversion, Convert, One, Saturating, Zero}, ArithmeticError, DispatchError, Perbill, SaturatedConversion, @@ -215,23 +215,47 @@ pub mod pallet { #[pallet::getter(fn current_staking_round)] pub type CurrentStakingRound = StorageMap<_, Twox64Concat, FungibleTokenId, StakingRound>; + #[pallet::storage] + #[pallet::getter(fn last_staking_round)] + pub type LastStakingRound = StorageMap<_, Twox64Concat, FungibleTokenId, StakingRound, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn queue_next_id)] pub type QueueNextId = StorageMap<_, Twox64Concat, FungibleTokenId, u32, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn iteration_limit)] + pub type IterationLimit = StorageValue<_, u32, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { - /// New staking round started [Starting Block, Round, Total Land Unit] - NewRound(T::BlockNumber, RoundIndex, u64), /// New pool created - PoolCreated(T::AccountId, PoolId, FungibleTokenId), + PoolCreated { + from: T::AccountId, + pool_id: PoolId, + currency_id: FungibleTokenId, + }, /// Deposited - Deposited(T::AccountId, PoolId, BalanceOf), + Deposited { + from: T::AccountId, + pool_id: PoolId, + amount: BalanceOf, + }, /// Redeemed - Redeemed(T::AccountId, PoolId, BalanceOf), + Redeemed { + from: T::AccountId, + pool_id: PoolId, + amount: BalanceOf, + }, + /// Redeemed success + RedeemSuccess { + queue_id: QueueId, + currency_id: FungibleTokenId, + to: T::AccountId, + token_amount: BalanceOf, + }, } - #[pallet::error] pub enum Error { /// No permission @@ -258,6 +282,8 @@ pub mod pallet { NotSupportTokenType, /// Unlock duration not found UnlockDurationNotFound, + /// Staking round not found + StakingRoundNotFound, } #[pallet::call] @@ -361,7 +387,7 @@ pub mod pallet { )?; // Emit deposit event - Self::deposit_event(Event::Deposited(who, pool_id, amount)); + Self::deposit_event(Event::Deposited { who, pool_id, amount }); Ok(().into()) } @@ -508,7 +534,11 @@ pub mod pallet { })?; // Emit deposit event - Self::deposit_event(Event::Redeemed(who, pool_id, r_amount)); + Self::deposit_event(Event::Redeemed { + who, + pool_id, + amount: r_amount, + }); Ok(().into()) } } @@ -592,4 +622,248 @@ impl Pallet { pool_fee, ) } + + #[transactional] + fn on_initialize() -> DispatchResult { + for currency in CurrentStakingRound::::iter_keys() { + Self::handle_redeem_staking_round_hook(currency)?; + } + Ok(()) + } + + fn handle_redeem_staking_round_hook(currency: FungibleTokenId) -> DispatchResult { + let last_staking_round = LastStakingRound::::get(currency); + let unlock_duration = match UnlockDuration::::get(currency) { + Some(StakingRound::Era(unlock_duration_era)) => unlock_duration_era, + Some(StakingRound::Round(unlock_duration_round)) => unlock_duration_round, + Some(StakingRound::Epoch(unlock_duration_epoch)) => unlock_duration_epoch, + Some(StakingRound::Hour(unlock_duration_hour)) => unlock_duration_hour, + _ => 0, + }; + + let current_staking_round = match CurrentStakingRound::::get(currency) { + Some(StakingRound::Era(unlock_duration_era)) => unlock_duration_era, + Some(StakingRound::Round(unlock_duration_round)) => unlock_duration_round, + Some(StakingRound::Epoch(unlock_duration_epoch)) => unlock_duration_epoch, + Some(StakingRound::Hour(unlock_duration_hour)) => unlock_duration_hour, + _ => 0, + }; + + // Check current staking round queue with last staking round if there is any pending redeem requests + if let Some((_total_locked, existing_queue, currency_id)) = + StakingRoundRedeemQueue::::get(last_staking_round.clone(), currency) + { + for queue_id in existing_queue.iter().take(Self::iteration_limit() as usize) { + if let Some((account, unlock_amount, staking_round)) = + CurrencyRedeemQueue::::get(currency_id, queue_id) + { + let pool_account_balance = + T::MultiCurrency::free_balance(currency_id, &T::PoolAccount::get().into_account_truncating()); + if pool_account_balance != BalanceOf::::zero() { + Self::update_queue_request( + currency_id, + account, + queue_id, + unlock_amount, + pool_account_balance, + staking_round, + ) + .ok(); + } + } + } + } else { + LastStakingRound::::mutate(currency, |last_staking_round| -> Result<(), Error> { + match last_staking_round { + StakingRound::Era(era) => { + if current_staking_round + unlock_duration > *era { + *era = era.checked_add(1).ok_or(Error::::ArithmeticOverflow)?; + } + Ok(()) + } + StakingRound::Round(round) => { + if current_staking_round + unlock_duration > *round { + *round = round.checked_add(1).ok_or(Error::::ArithmeticOverflow)?; + } + Ok(()) + } + StakingRound::Epoch(epoch) => { + if current_staking_round + unlock_duration > *epoch { + *kblock = kblock.checked_add(1).ok_or(Error::::ArithmeticOverflow)?; + } + Ok(()) + } + StakingRound::Hour(hour) => { + if current_staking_round + unlock_duration > *hour { + *hour = hour.checked_add(1).ok_or(Error::::ArithmeticOverflow)?; + } + Ok(()) + } + _ => Ok(()), + } + })?; + }; + + Ok(()) + } + + #[transactional] + fn update_queue_request( + currency_id: FungibleTokenId, + account: T::AccountId, + queue_id: &QueueId, + mut unlock_amount: BalanceOf, + pool_account_balance: BalanceOf, + staking_round: StakingRound, + ) -> DispatchResult { + // Get minimum balance of currency + let ed = T::MultiCurrency::minimum_balance(currency_id); + let mut account_to_send = account.clone(); + + // If unlock amount less than existential deposit, to avoid error kill the process, transfer the + // unlock_amount to pool address instead + if unlock_amount < ed { + let receiver_balance = T::MultiCurrency::total_balance(currency_id, &account); + + // Check if even after receiving unlock amount, account still below ED then transfer fund to + // PoolAccount + let receiver_balance_after = receiver_balance + .checked_add(&unlock_amount) + .ok_or(ArithmeticError::Overflow)?; + if receiver_balance_after < ed { + account_to_send = T::PoolAccount::get(); + } + } + + // If pool account balance greater than unlock amount + if pool_account_balance >= unlock_amount { + // Transfer amount from PoolAccount to users + T::MultiCurrency::transfer( + currency_id, + &T::PoolAccount::get().into_account_truncating(), + &account_to_send, + unlock_amount, + )?; + + // Remove currency redeem queue + CurrencyRedeemQueue::::remove(¤cy_id, &queue_id); + + // Edit staking round redeem queue with locked amount + StakingRoundRedeemQueue::::mutate_exists( + &staking_round, + ¤cy_id, + |value| -> Result<(), Error> { + if let Some((total_locked_origin, existing_queue, _)) = value { + // If total locked == unlock_amount, then set value to zero + if total_locked_origin == &unlock_amount { + *value = None; + return Ok(()); + } + // Otherwise, deduct unlock amount + *total_locked_origin = total_locked_origin + .checked_sub(&unlock_amount) + .ok_or(Error::::ArithmeticOverflow)?; + // Only keep items that not with processed queue_id + existing_queue.retain(|x| x != queue_id); + } else { + return Err(Error::::StakingRoundRedeemNotFound); + } + Ok(()) + }, + )?; + + UserCurrencyRedeemQueue::::mutate_exists(&account, ¤cy_id, |value| -> Result<(), Error> { + if let Some((total_locked_origin, existing_queue)) = value { + if total_locked_origin == &unlock_amount { + *value = None; + return Ok(()); + } + existing_queue.retain(|x| x != queue_id); + *total_locked_origin = total_locked_origin + .checked_sub(&unlock_amount) + .ok_or(Error::::ArithmeticOverflow)?; + } else { + return Err(Error::::UserCurrencyRedeemQueueNotFound); + } + Ok(()) + })?; + } else { + // When pool account balance less than amount need to be unlocked then use pool remaining balance as + // unlock amount + unlock_amount = pool_account_balance; + T::MultiCurrency::transfer( + currency_id, + &T::PoolAccount::get().into_account_truncating(), + &account_to_send, + unlock_amount, + )?; + + CurrencyRedeemQueue::::mutate_exists(¤cy_id, &queue_id, |value| -> Result<(), Error> { + if let Some((_, total_locked_origin, _, _)) = value { + if total_locked_origin == &unlock_amount { + *value = None; + return Ok(()); + } + *total_locked_origin = total_locked_origin + .checked_sub(&unlock_amount) + .ok_or(Error::::ArithmeticOverflow)?; + } else { + return Err(Error::::CurrencyRedeemQueueNotFound); + } + Ok(()) + })?; + + StakingRoundRedeemQueue::::mutate_exists( + &staking_round, + ¤cy_id, + |value| -> Result<(), Error> { + if let Some((total_locked_origin, _existing_queue, _)) = value { + if total_locked_origin == &unlock_amount { + *value = None; + return Ok(()); + } + *total_locked_origin = total_locked_origin + .checked_sub(&unlock_amount) + .ok_or(Error::::ArithmeticOverflow)?; + } else { + return Err(Error::::StakingRoundRedeemNotFound); + } + Ok(()) + }, + )?; + + UserCurrencyRedeemQueue::::mutate_exists(&account, ¤cy_id, |value| -> Result<(), Error> { + if let Some((total_locked_origin, _existing_queue)) = value { + if total_locked_origin == &unlock_amount { + *value = None; + return Ok(()); + } + + *total_locked_origin = total_locked_origin + .checked_sub(&unlock_amount) + .ok_or(Error::::ArithmeticOverflow)?; + } else { + return Err(Error::::UserUnlockLedgerNotFound); + } + Ok(()) + })?; + } + + pool_account_balance + .checked_sub(&unlock_amount) + .ok_or(Error::::ArithmeticOverflow)?; + + PoolLedger::::mutate(¤cy_id, |pool| -> Result<(), Error> { + *pool = pool.checked_sub(&unlock_amount).ok_or(Error::::ArithmeticOverflow)?; + Ok(()) + })?; + + Self::deposit_event(Event::RedeemSuccess { + queue_id: *queue_id, + currency_id, + to: account_to_send, + token_amount: unlock_amount, + }); + Ok(()) + } } From e7a0edde83b8fefbeddbb5ae66971734f1c2dffa Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 14:59:39 +1300 Subject: [PATCH 026/114] WIP - Update that fix all build error --- pallets/spp/src/lib.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 842237564..a97ca9cff 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -284,6 +284,12 @@ pub mod pallet { UnlockDurationNotFound, /// Staking round not found StakingRoundNotFound, + /// Staking round redeem queue not found + StakingRoundRedeemNotFound, + /// User currency redeem queue not found + UserCurrencyRedeemQueueNotFound, + /// Redeem queue per currency not found + CurrencyRedeemQueueNotFound, } #[pallet::call] @@ -326,7 +332,11 @@ pub mod pallet { Pool::::insert(next_pool_id, new_pool); // Emit event for pool creation - Self::deposit_event(Event::PoolCreated(who, max_nft_reward, currency_id)); + Self::deposit_event(Event::PoolCreated { + from: who, + pool_id: next_pool_id, + currency_id, + }); Ok(().into()) } @@ -387,7 +397,11 @@ pub mod pallet { )?; // Emit deposit event - Self::deposit_event(Event::Deposited { who, pool_id, amount }); + Self::deposit_event(Event::Deposited { + from: who, + pool_id, + amount, + }); Ok(().into()) } @@ -535,7 +549,7 @@ pub mod pallet { // Emit deposit event Self::deposit_event(Event::Redeemed { - who, + from: who, pool_id, amount: r_amount, }); @@ -689,7 +703,7 @@ impl Pallet { } StakingRound::Epoch(epoch) => { if current_staking_round + unlock_duration > *epoch { - *kblock = kblock.checked_add(1).ok_or(Error::::ArithmeticOverflow)?; + *epoch = epoch.checked_add(1).ok_or(Error::::ArithmeticOverflow)?; } Ok(()) } @@ -731,7 +745,7 @@ impl Pallet { .checked_add(&unlock_amount) .ok_or(ArithmeticError::Overflow)?; if receiver_balance_after < ed { - account_to_send = T::PoolAccount::get(); + account_to_send = T::PoolAccount::get().into_account_truncating(); } } @@ -799,7 +813,7 @@ impl Pallet { )?; CurrencyRedeemQueue::::mutate_exists(¤cy_id, &queue_id, |value| -> Result<(), Error> { - if let Some((_, total_locked_origin, _, _)) = value { + if let Some((_, total_locked_origin, _)) = value { if total_locked_origin == &unlock_amount { *value = None; return Ok(()); @@ -843,7 +857,7 @@ impl Pallet { .checked_sub(&unlock_amount) .ok_or(Error::::ArithmeticOverflow)?; } else { - return Err(Error::::UserUnlockLedgerNotFound); + return Err(Error::::UserCurrencyRedeemQueueNotFound); } Ok(()) })?; @@ -853,7 +867,7 @@ impl Pallet { .checked_sub(&unlock_amount) .ok_or(Error::::ArithmeticOverflow)?; - PoolLedger::::mutate(¤cy_id, |pool| -> Result<(), Error> { + NetworkLedger::::mutate(¤cy_id, |pool| -> Result<(), Error> { *pool = pool.checked_sub(&unlock_amount).ok_or(Error::::ArithmeticOverflow)?; Ok(()) })?; From 768fc917e5551c70fae639ff262ce746906032d6 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 15:25:22 +1300 Subject: [PATCH 027/114] WIP - Update cargo fix all suggestions --- pallets/spp/src/lib.rs | 30 +++++++++++------------------- pallets/spp/src/utils.rs | 3 +-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index a97ca9cff..88470592a 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -20,30 +20,24 @@ use frame_support::pallet_prelude::*; use frame_support::{ dispatch::DispatchResult, - ensure, log, - traits::{Currency, ExistenceRequirement, Get}, + ensure, + traits::{Currency, Get}, transactional, PalletId, }; +use frame_system::ensure_signed; use frame_system::pallet_prelude::*; -use frame_system::{ensure_root, ensure_signed}; use orml_traits::MultiCurrency; -use scale_info::TypeInfo; + use sp_runtime::traits::{CheckedAdd, CheckedSub}; use sp_runtime::{ - traits::{AccountIdConversion, Convert, One, Saturating, Zero}, - ArithmeticError, DispatchError, Perbill, SaturatedConversion, + traits::{AccountIdConversion, Convert, Saturating, Zero}, + ArithmeticError, DispatchError, SaturatedConversion, }; -use sp_std::vec::Vec; -use auction_manager::{Auction, CheckAuctionItemHandler}; use core_primitives::*; pub use pallet::*; -use primitives::estate::EstateInfo; -use primitives::{ - estate::{Estate, LandUnitStatus, LeaseContract, OwnerId}, - Attributes, ClassId, EstateId, FungibleTokenId, ItemId, MetaverseId, NftMetadata, StakingRound, TokenId, - UndeployedLandBlock, UndeployedLandBlockId, UndeployedLandBlockType, -}; + +use primitives::{ClassId, FungibleTokenId, StakingRound, TokenId}; pub use weights::WeightInfo; pub type QueueId = u32; @@ -61,15 +55,13 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { - use frame_support::traits::{Currency, Imbalance, ReservableCurrency}; + use frame_support::traits::{Currency, ReservableCurrency}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; - use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero}; + use sp_runtime::traits::{CheckedAdd, CheckedSub}; use sp_runtime::Permill; - use primitives::estate::EstateInfo; - use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; - use primitives::{AccountId, Balance, CurrencyId, PoolId, RoundIndex, StakingRound, UndeployedLandBlockId}; + use primitives::{PoolId, StakingRound}; use crate::utils::PoolInfo; diff --git a/pallets/spp/src/utils.rs b/pallets/spp/src/utils.rs index 32213d80e..bec2e5c66 100644 --- a/pallets/spp/src/utils.rs +++ b/pallets/spp/src/utils.rs @@ -19,12 +19,11 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_runtime::{Perbill, Permill, RuntimeDebug}; +use sp_runtime::{Permill, RuntimeDebug}; use primitives::FungibleTokenId; // Helper methods to compute the issuance rate for undeployed land. -use crate::pallet::{Config, Pallet}; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] From c1191263ce547cbafe8bad537c10d9e32dc70f13 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 15:34:19 +1300 Subject: [PATCH 028/114] WIP - Fix all warning from cargo compilation --- modules/bridge/src/lib.rs | 18 +++-- modules/bridge/src/mock.rs | 84 +++++++++++------------ modules/bridge/src/tests.rs | 11 ++- pallets/auction/src/lib.rs | 28 +++----- pallets/auction/src/mock.rs | 46 ++++++------- pallets/auction/src/tests.rs | 12 ++-- pallets/continuum/src/lib.rs | 10 +-- pallets/continuum/src/mock.rs | 22 +++--- pallets/continuum/src/tests.rs | 4 +- pallets/crowdloan/src/lib.rs | 25 ++----- pallets/crowdloan/src/mock.rs | 31 ++++----- pallets/crowdloan/src/tests.rs | 2 +- pallets/economy/src/lib.rs | 28 ++++---- pallets/economy/src/mock.rs | 30 ++++---- pallets/economy/src/tests.rs | 5 +- pallets/estate/src/lib.rs | 34 +++++----- pallets/estate/src/mock.rs | 94 +++++++++++++------------- pallets/estate/src/rate.rs | 2 +- pallets/governance/src/lib.rs | 12 ++-- pallets/governance/src/mock.rs | 84 +++++++++++------------ pallets/governance/src/tests.rs | 2 +- pallets/metaverse/src/lib.rs | 11 +-- pallets/metaverse/src/mock.rs | 69 ++++++++++--------- pallets/metaverse/src/tests.rs | 3 +- pallets/mining/src/lib.rs | 25 +++---- pallets/mining/src/mining.rs | 11 +-- pallets/mining/src/mock.rs | 40 +++++------ pallets/mining/src/tests.rs | 10 ++- pallets/nft/src/lib.rs | 18 +++-- pallets/nft/src/mock.rs | 8 +-- pallets/nft/src/tests.rs | 8 +-- pallets/reward/src/lib.rs | 53 +++++++-------- pallets/reward/src/mock.rs | 13 ++-- pallets/reward/src/tests.rs | 26 +++---- runtime/common/src/currencies.rs | 20 +++--- runtime/common/src/lib.rs | 9 +-- runtime/common/src/mock.rs | 30 ++++---- runtime/common/src/nft.rs | 12 ++-- runtime/common/src/precompiles.rs | 4 +- runtime/common/src/tests/currencies.rs | 4 +- runtime/common/src/tests/nft.rs | 13 ++-- 41 files changed, 454 insertions(+), 517 deletions(-) diff --git a/modules/bridge/src/lib.rs b/modules/bridge/src/lib.rs index e8c26764e..5390eda15 100644 --- a/modules/bridge/src/lib.rs +++ b/modules/bridge/src/lib.rs @@ -11,7 +11,7 @@ use sp_core::{H160, U256}; use sp_std::prelude::*; pub use pallet::*; -use primitives::{Balance, FungibleTokenId}; +use primitives::FungibleTokenId; pub type ResourceId = H160; pub type ChainId = u8; @@ -29,12 +29,12 @@ pub mod pallet { use frame_support::traits::{Currency, ExistenceRequirement, LockableCurrency, ReservableCurrency}; use frame_support::PalletId; use orml_traits::MultiCurrency; - use sp_arithmetic::traits::{CheckedMul, Saturating, Zero}; - use sp_runtime::traits::{AccountIdConversion, CheckedDiv}; - use sp_runtime::{ArithmeticError, ModuleError}; + use sp_arithmetic::traits::{Saturating, Zero}; + use sp_runtime::traits::AccountIdConversion; + use sp_runtime::ModuleError; use core_primitives::NFTTrait; - use primitives::evm::CurrencyIdType::FungibleToken; + use primitives::{Attributes, ClassId, NftMetadata, TokenId}; use super::*; @@ -300,7 +300,11 @@ pub mod pallet { Ok(()) } Err(err) => match err { - DispatchError::Module(ModuleError { index, error, message }) => { + DispatchError::Module(ModuleError { + index: _, + error: _, + message, + }) => { if message == Some("AssetInfoNotFound") { if let Ok(_mint_succeeded) = T::NFTHandler::mint_token_with_id(&to, class_id, token_id, metadata, Attributes::new()) @@ -335,7 +339,7 @@ pub mod pallet { let bridge_id = T::PalletId::get().into_account_truncating(); ensure!(BridgeFee::::contains_key(&chain_id), Error::::FeeOptionsMissing); - let (min_fee, fee_scale) = Self::bridge_fee(chain_id); + let (min_fee, _fee_scale) = Self::bridge_fee(chain_id); T::Currency::transfer(&source, &bridge_id, min_fee.into(), ExistenceRequirement::AllowDeath)?; diff --git a/modules/bridge/src/mock.rs b/modules/bridge/src/mock.rs index 1fbfa7bd9..32894d6bf 100644 --- a/modules/bridge/src/mock.rs +++ b/modules/bridge/src/mock.rs @@ -3,22 +3,18 @@ use std::collections::BTreeMap; use std::vec; -use frame_support::traits::{EqualPrivilegeOnly, Nothing}; +use frame_support::traits::Nothing; use frame_support::{construct_runtime, ord_parameter_types, pallet_prelude::Hooks, parameter_types, PalletId}; -use frame_system::{EnsureRoot, EnsureSignedBy}; +use frame_system::EnsureRoot; use orml_traits::parameter_type_with_key; use sp_core::H256; use sp_runtime::traits::AccountIdConversion; use sp_runtime::{testing::Header, traits::IdentityLookup, ModuleError, Perbill}; -use auction_manager::{Auction, AuctionInfo, AuctionItem, AuctionType, CheckAuctionItemHandler, ListingLevel}; -use core_primitives::{ - Attributes, CollectionType, MetaverseInfo, MetaverseMetadata, MetaverseTrait, NFTTrait, NftAssetData, NftClassData, - NftMetadata, TokenType, -}; +use core_primitives::{Attributes, CollectionType, NFTTrait, NftClassData, NftMetadata, TokenType}; use primitives::{ - continuum::MapTrait, estate::Estate, Amount, AuctionId, ClassId, EstateId, FungibleTokenId, GroupCollectionId, - ItemId, MapSpotId, NftOffer, TokenId, UndeployedLandBlockId, + continuum::MapTrait, Amount, ClassId, EstateId, FungibleTokenId, GroupCollectionId, MapSpotId, TokenId, + UndeployedLandBlockId, }; use crate as bridge; @@ -283,7 +279,7 @@ impl NFTTrait for MockNFTHandler { { return Ok(true); } - if (nft_value.1 == 5) { + if nft_value.1 == 5 { return Err(DispatchError::Module(ModuleError { index: 5, error: [0, 0, 0, 0], @@ -293,7 +289,7 @@ impl NFTTrait for MockNFTHandler { Ok(false) } - fn is_stackable(asset_id: (Self::ClassId, Self::TokenId)) -> Result { + fn is_stackable(_asset_id: (Self::ClassId, Self::TokenId)) -> Result { Ok(false) } @@ -306,19 +302,19 @@ impl NFTTrait for MockNFTHandler { } Ok(false) } - fn get_nft_group_collection(nft_collection: &Self::ClassId) -> Result { + fn get_nft_group_collection(_nft_collection: &Self::ClassId) -> Result { Ok(ASSET_COLLECTION_ID) } fn create_token_class( sender: &AccountId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, collection_id: GroupCollectionId, - token_type: TokenType, - collection_type: CollectionType, - royalty_fee: Perbill, - mint_limit: Option, + _token_type: TokenType, + _collection_type: CollectionType, + _royalty_fee: Perbill, + _mint_limit: Option, ) -> Result { match *sender { ALICE => { @@ -339,8 +335,8 @@ impl NFTTrait for MockNFTHandler { fn mint_token( sender: &AccountId, class_id: ClassId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -364,26 +360,26 @@ impl NFTTrait for MockNFTHandler { } } - fn transfer_nft(from: &AccountId, to: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn transfer_nft(_from: &AccountId, _to: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn check_item_on_listing(class_id: Self::ClassId, token_id: Self::TokenId) -> Result { + fn check_item_on_listing(_class_id: Self::ClassId, _token_id: Self::TokenId) -> Result { Ok(true) } - fn burn_nft(account: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn burn_nft(_account: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn is_transferable(nft: &(Self::ClassId, Self::TokenId)) -> Result { + fn is_transferable(_nft: &(Self::ClassId, Self::TokenId)) -> Result { Ok(true) } - fn get_class_fund(class_id: &Self::ClassId) -> AccountId { + fn get_class_fund(_class_id: &Self::ClassId) -> AccountId { CLASS_FUND_ID } - fn get_nft_detail(asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { + fn get_nft_detail(_asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { let new_data = NftClassData { deposit: 0, attributes: test_attributes(1), @@ -397,11 +393,11 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn set_lock_collection(class_id: Self::ClassId, is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_collection(_class_id: Self::ClassId, _is_locked: bool) -> sp_runtime::DispatchResult { todo!() } - fn set_lock_nft(token_id: (Self::ClassId, Self::TokenId), is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_nft(_token_id: (Self::ClassId, Self::TokenId), _is_locked: bool) -> sp_runtime::DispatchResult { todo!() } @@ -419,20 +415,20 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn get_total_issuance(class_id: Self::ClassId) -> Result { + fn get_total_issuance(_class_id: Self::ClassId) -> Result { Ok(10u64) } - fn get_asset_owner(asset_id: &(Self::ClassId, Self::TokenId)) -> Result { + fn get_asset_owner(_asset_id: &(Self::ClassId, Self::TokenId)) -> Result { Ok(ALICE) } fn mint_token_with_id( sender: &AccountId, class_id: Self::ClassId, - token_id: Self::TokenId, - metadata: NftMetadata, - attributes: Attributes, + _token_id: Self::TokenId, + _metadata: NftMetadata, + _attributes: Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -456,31 +452,31 @@ impl NFTTrait for MockNFTHandler { } } - fn get_free_stackable_nft_balance(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { + fn get_free_stackable_nft_balance(_who: &AccountId, _asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { 1000 } fn reserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> DispatchResult { Ok(()) } fn unreserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } fn transfer_stackable_nft( - sender: &AccountId, - to: &AccountId, - nft: &(Self::ClassId, Self::TokenId), - amount: Balance, + _sender: &AccountId, + _to: &AccountId, + _nft: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } diff --git a/modules/bridge/src/tests.rs b/modules/bridge/src/tests.rs index c3e5f13a4..0308dcb72 100644 --- a/modules/bridge/src/tests.rs +++ b/modules/bridge/src/tests.rs @@ -1,16 +1,13 @@ #![cfg(test)] -use std::str::{from_utf8, FromStr}; +use std::str::FromStr; -use frame_support::{assert_noop, assert_ok}; +use frame_support::assert_ok; use sp_core::H160; -use mock::{Balances, BridgeModule, ExtBuilder, One, Runtime, RuntimeEvent, RuntimeOrigin, System, Tokens}; -use primitives::evm::{CurrencyIdType, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_TOKEN}; -use primitives::FungibleTokenId::FungibleToken; -use primitives::{TokenId, TokenSymbol}; +use mock::{Balances, BridgeModule, ExtBuilder, RuntimeOrigin}; -use crate::mock::{BridgeSovereignPalletId, ALICE, BOB}; +use crate::mock::{ALICE, BOB}; use super::*; diff --git a/pallets/auction/src/lib.rs b/pallets/auction/src/lib.rs index 8afd48829..67403e434 100644 --- a/pallets/auction/src/lib.rs +++ b/pallets/auction/src/lib.rs @@ -31,7 +31,7 @@ use frame_system::{self as system, ensure_signed}; use sp_core::sp_std::convert::TryInto; use sp_runtime::SaturatedConversion; use sp_runtime::{ - traits::{CheckedDiv, One, Saturating, Zero}, + traits::{One, Saturating, Zero}, DispatchError, DispatchResult, Perbill, }; use sp_std::vec::Vec; @@ -58,17 +58,13 @@ pub mod weights; pub struct AuctionLogicHandler; pub mod migration_v2 { - use codec::FullCodec; + use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; - use sp_runtime::{traits::AtLeast32BitUnsigned, DispatchError, RuntimeDebug}; - use sp_std::{ - cmp::{Eq, PartialEq}, - fmt::Debug, - vec::Vec, - }; + use sp_runtime::RuntimeDebug; + use sp_std::cmp::{Eq, PartialEq}; use auction_manager::{AuctionType, ListingLevel}; use primitives::{AssetId, EstateId, FungibleTokenId, MetaverseId}; @@ -104,20 +100,18 @@ pub mod migration_v2 { #[frame_support::pallet] pub mod pallet { + use frame_support::dispatch::DispatchResultWithPostInfo; use frame_support::log; use frame_support::sp_runtime::traits::CheckedSub; - use frame_support::{dispatch::DispatchResultWithPostInfo, traits::tokens::currency}; - use frame_system::ensure_root; + use frame_system::pallet_prelude::OriginFor; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_runtime::traits::CheckedAdd; use sp_runtime::ArithmeticError; - use auction_manager::{AuctionItemV1, CheckAuctionItemHandler, ListingLevel}; + use auction_manager::{CheckAuctionItemHandler, ListingLevel}; use core_primitives::{MetaverseTrait, NFTTrait}; - use primitives::{AssetId, Balance, ClassId, FungibleTokenId, MetaverseId, TokenId}; - - use crate::migration_v2::V1ItemId; + use primitives::{Balance, ClassId, FungibleTokenId, MetaverseId, TokenId}; use super::*; @@ -1191,7 +1185,7 @@ pub mod pallet { /// Internal auction bid handler fn auction_bid_handler(from: T::AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { - let mut auction_item: AuctionItem> = + let auction_item: AuctionItem> = Self::get_auction_item(id.clone()).ok_or(Error::::AuctionDoesNotExist)?; ensure!( auction_item.auction_type == AuctionType::Auction, @@ -1426,7 +1420,7 @@ pub mod pallet { ::Currency::unreserve(&auction_item.recipient, T::NetworkFeeReserve::get()); // Transfer balance from buy it now user to asset owner - let mut currency_transfer; + let currency_transfer; if auction_item.currency_id == FungibleTokenId::NativeToken(0) { currency_transfer = ::Currency::transfer( &from, @@ -1618,7 +1612,7 @@ pub mod pallet { } // Handle balance transfer - let mut currency_transfer; + let currency_transfer; if auction_item.currency_id == FungibleTokenId::NativeToken(0) { currency_transfer = ::Currency::transfer( &high_bidder, diff --git a/pallets/auction/src/mock.rs b/pallets/auction/src/mock.rs index be22e2920..0352d86ce 100644 --- a/pallets/auction/src/mock.rs +++ b/pallets/auction/src/mock.rs @@ -12,7 +12,7 @@ use sp_runtime::{testing::Header, traits::IdentityLookup, MultiSignature, Perbil use auction_manager::{CheckAuctionItemHandler, ListingLevel}; use core_primitives::{MetaverseInfo, MetaverseMetadata, MetaverseTrait, NftAssetData, NftClassData}; use primitives::{ - continuum::MapTrait, estate::Estate, Amount, AuctionId, ClassId, EstateId, FungibleTokenId, MapSpotId, NftOffer, + continuum::MapTrait, estate::Estate, Amount, AuctionId, ClassId, EstateId, FungibleTokenId, MapSpotId, UndeployedLandBlockId, }; @@ -117,9 +117,9 @@ impl Estate for EstateHandler { } fn transfer_undeployed_land_block( - who: &AccountId, - to: &AccountId, - undeployed_land_block_id: UndeployedLandBlockId, + _who: &AccountId, + _to: &AccountId, + _undeployed_land_block_id: UndeployedLandBlockId, ) -> Result { Ok(2) } @@ -141,7 +141,7 @@ impl Estate for EstateHandler { } fn check_undeployed_land_block( - owner: &AccountId, + _owner: &AccountId, undeployed_land_block_id: UndeployedLandBlockId, ) -> Result { match undeployed_land_block_id { @@ -151,7 +151,7 @@ impl Estate for EstateHandler { } } - fn get_total_land_units(estate_id: Option) -> u64 { + fn get_total_land_units(_estate_id: Option) -> u64 { 100 } @@ -159,15 +159,15 @@ impl Estate for EstateHandler { 100 } - fn check_estate_ownership(owner: AccountId, estate_id: EstateId) -> Result { + fn check_estate_ownership(_owner: AccountId, _estate_id: EstateId) -> Result { Ok(false) } - fn is_estate_leasor(leasor: AccountId, estate_id: EstateId) -> Result { + fn is_estate_leasor(_leasor: AccountId, _estate_id: EstateId) -> Result { Ok(false) } - fn is_estate_leased(estate_id: EstateId) -> Result { + fn is_estate_leased(_estate_id: EstateId) -> Result { Ok(false) } } @@ -241,7 +241,7 @@ parameter_types! { pub struct MetaverseInfoSource {} impl MetaverseTrait for MetaverseInfoSource { - fn create_metaverse(who: &AccountId, metadata: MetaverseMetadata) -> MetaverseId { + fn create_metaverse(_who: &AccountId, _metadata: MetaverseMetadata) -> MetaverseId { 1u64 } @@ -267,19 +267,19 @@ impl MetaverseTrait for MetaverseInfoSource { Ok(()) } - fn get_metaverse_land_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_land_class(_metaverse_id: MetaverseId) -> Result { Ok(15u32) } - fn get_metaverse_estate_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_estate_class(_metaverse_id: MetaverseId) -> Result { Ok(16u32) } - fn get_metaverse_marketplace_listing_fee(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_marketplace_listing_fee(_metaverse_id: MetaverseId) -> Result { Ok(Perbill::from_percent(1u32)) } - fn get_metaverse_treasury(metaverse_id: MetaverseId) -> AccountId { + fn get_metaverse_treasury(_metaverse_id: MetaverseId) -> AccountId { [102; 32].into() } @@ -288,7 +288,7 @@ impl MetaverseTrait for MetaverseInfoSource { } fn check_if_metaverse_estate( - metaverse_id: primitives::MetaverseId, + _metaverse_id: primitives::MetaverseId, class_id: &ClassId, ) -> Result { if class_id == &15u32 || class_id == &16u32 { @@ -490,7 +490,7 @@ impl Auction for MockAuctionManager { None } - fn auction_item(id: AuctionId) -> Option> { + fn auction_item(_id: AuctionId) -> Option> { None } @@ -498,7 +498,7 @@ impl Auction for MockAuctionManager { Ok(()) } - fn update_auction_item(id: AuctionId, item_id: ItemId) -> frame_support::dispatch::DispatchResult { + fn update_auction_item(_id: AuctionId, _item_id: ItemId) -> frame_support::dispatch::DispatchResult { Ok(()) } @@ -528,17 +528,17 @@ impl Auction for MockAuctionManager { fn remove_auction(_id: u64, _item_id: ItemId) {} fn auction_bid_handler( - from: AccountId, - id: AuctionId, - value: Self::Balance, + _from: AccountId, + _id: AuctionId, + _value: Self::Balance, ) -> frame_support::dispatch::DispatchResult { Ok(()) } fn buy_now_handler( - from: AccountId, - auction_id: AuctionId, - value: Self::Balance, + _from: AccountId, + _auction_id: AuctionId, + _value: Self::Balance, ) -> frame_support::dispatch::DispatchResult { Ok(()) } diff --git a/pallets/auction/src/tests.rs b/pallets/auction/src/tests.rs index d40984cdd..9a0616e6d 100644 --- a/pallets/auction/src/tests.rs +++ b/pallets/auction/src/tests.rs @@ -7,8 +7,8 @@ use sp_std::collections::btree_map::BTreeMap; use auction_manager::ListingLevel; use core_primitives::{Attributes, CollectionType, NFTTrait, TokenType}; use mock::{RuntimeEvent, *}; -use primitives::ItemId::NFT; -use primitives::{ClassId, FungibleTokenId}; + +use primitives::FungibleTokenId; use super::*; @@ -1691,7 +1691,7 @@ fn auction_bundle_should_update_new_price_according_new_bid() { assert_eq!(Balances::free_balance(ALICE), 99991); let tokens_after_bid = vec![(0, 0, 150), (0, 1, 150)]; - let item_updated_after_bid = AuctionModule::items_in_auction(ItemId::Bundle(tokens.clone())); + let _item_updated_after_bid = AuctionModule::items_in_auction(ItemId::Bundle(tokens.clone())); let auction_item = AuctionModule::get_auction_item(0).unwrap(); assert_eq!(auction_item.item_id, ItemId::Bundle(tokens_after_bid)); @@ -2112,7 +2112,7 @@ fn withdraw_offer_should_work() { fn finalize_auction_should_fail() { ExtBuilder::default().build().execute_with(|| { let owner = RuntimeOrigin::signed(BOB); - let bidder = RuntimeOrigin::signed(ALICE); + let _bidder = RuntimeOrigin::signed(ALICE); init_test_nft(owner.clone()); init_test_nft(owner.clone()); assert_ok!(AuctionModule::create_auction( @@ -2152,7 +2152,7 @@ fn finalize_auction_should_fail() { fn cancel_listing_should_work() { ExtBuilder::default().build().execute_with(|| { let owner = RuntimeOrigin::signed(BOB); - let bidder = RuntimeOrigin::signed(ALICE); + let _bidder = RuntimeOrigin::signed(ALICE); init_test_nft(owner.clone()); assert_ok!(AuctionModule::create_auction( AuctionType::Auction, @@ -2192,7 +2192,7 @@ fn cancel_listing_should_work() { fn cancel_listing_should_fail() { ExtBuilder::default().build().execute_with(|| { let owner = RuntimeOrigin::signed(BOB); - let bidder = RuntimeOrigin::signed(ALICE); + let _bidder = RuntimeOrigin::signed(ALICE); init_test_nft(owner.clone()); init_test_nft(owner.clone()); assert_ok!(AuctionModule::create_auction( diff --git a/pallets/continuum/src/lib.rs b/pallets/continuum/src/lib.rs index e73c0aca7..52649c14f 100644 --- a/pallets/continuum/src/lib.rs +++ b/pallets/continuum/src/lib.rs @@ -47,18 +47,15 @@ use codec::{Decode, Encode}; use frame_support::{ dispatch::DispatchResult, - ensure, log, + ensure, traits::ExistenceRequirement, traits::{Currency, Get, LockableCurrency, ReservableCurrency}, transactional, PalletId, }; use frame_system::{ensure_root, ensure_signed}; use scale_info::TypeInfo; -use sp_runtime::traits::CheckedAdd; -use sp_runtime::{ - traits::{AccountIdConversion, One, Zero}, - DispatchError, Perbill, RuntimeDebug, -}; + +use sp_runtime::{traits::AccountIdConversion, DispatchError, Perbill, RuntimeDebug}; use sp_std::vec; use sp_std::vec::Vec; @@ -112,7 +109,6 @@ pub struct AuctionSlot { pub mod pallet { use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; use frame_system::pallet_prelude::OriginFor; - use sp_arithmetic::traits::UniqueSaturatedInto; use core_primitives::TokenType; use primitives::{AuctionId, MapSpotId}; diff --git a/pallets/continuum/src/mock.rs b/pallets/continuum/src/mock.rs index a49353f1f..a57a3abf0 100644 --- a/pallets/continuum/src/mock.rs +++ b/pallets/continuum/src/mock.rs @@ -25,7 +25,7 @@ use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; use auction_manager::{Auction, AuctionInfo, AuctionItem, CheckAuctionItemHandler, ListingLevel}; use core_primitives::{MetaverseInfo, MetaverseMetadata, MetaverseTrait}; -use primitives::FungibleTokenId::FungibleToken; + use primitives::{AuctionId, ClassId, FungibleTokenId}; use crate as continuum; @@ -147,11 +147,11 @@ impl Auction for MockAuctionManager { return None; } - fn update_auction(id: AuctionId, _info: AuctionInfo) -> DispatchResult { + fn update_auction(_id: AuctionId, _info: AuctionInfo) -> DispatchResult { Ok(()) } - fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { + fn update_auction_item(_id: AuctionId, _item_id: ItemId) -> DispatchResult { Ok(()) } @@ -180,11 +180,11 @@ impl Auction for MockAuctionManager { fn remove_auction(_id: u64, _item_id: ItemId) {} - fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { + fn auction_bid_handler(_from: AccountId, _id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } - fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { + fn buy_now_handler(_from: AccountId, _auction_id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } @@ -228,7 +228,7 @@ parameter_types! { pub struct MetaverseInfoSource {} impl MetaverseTrait for MetaverseInfoSource { - fn create_metaverse(who: &AccountId, metadata: MetaverseMetadata) -> MetaverseId { + fn create_metaverse(_who: &AccountId, _metadata: MetaverseMetadata) -> MetaverseId { 1u64 } @@ -253,15 +253,15 @@ impl MetaverseTrait for MetaverseInfoSource { Ok(()) } - fn get_metaverse_land_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_land_class(_metaverse_id: MetaverseId) -> Result { Ok(15u32) } - fn get_metaverse_estate_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_estate_class(_metaverse_id: MetaverseId) -> Result { Ok(16u32) } - fn get_metaverse_marketplace_listing_fee(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_marketplace_listing_fee(_metaverse_id: MetaverseId) -> Result { Ok(Perbill::from_percent(1u32)) } @@ -278,7 +278,7 @@ impl MetaverseTrait for MetaverseInfoSource { } fn check_if_metaverse_estate( - metaverse_id: primitives::MetaverseId, + _metaverse_id: primitives::MetaverseId, class_id: &ClassId, ) -> Result { if class_id == &15u32 || class_id == &16u32 { @@ -295,7 +295,7 @@ impl MetaverseTrait for MetaverseInfoSource { } } - fn is_metaverse_owner(who: &AccountId) -> bool { + fn is_metaverse_owner(_who: &AccountId) -> bool { true } } diff --git a/pallets/continuum/src/tests.rs b/pallets/continuum/src/tests.rs index db3b7c10c..048f61345 100644 --- a/pallets/continuum/src/tests.rs +++ b/pallets/continuum/src/tests.rs @@ -21,8 +21,8 @@ use frame_support::{assert_noop, assert_ok}; use sp_runtime::traits::BadOrigin; use core_primitives::TokenType; -use mock::BlockNumber as MBlockNumber; -use mock::{RuntimeEvent, *}; + +use mock::*; use super::*; diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index ebe710e1e..ed189b346 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -18,26 +18,17 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::*; -use frame_support::traits::{Currency, ExistenceRequirement, VestingSchedule}; -use frame_support::{dispatch::DispatchResult, ensure, traits::Get, PalletId}; +use frame_support::traits::{Currency, VestingSchedule}; +use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; use frame_system::pallet_prelude::*; use frame_system::{ensure_root, ensure_signed}; use pallet_vesting::{Pallet as VestingModule, VestingInfo}; -use scale_info::TypeInfo; -use sp_runtime::traits::Convert; -use sp_runtime::{ - traits::{AccountIdConversion, One, Saturating, Zero}, - DispatchError, -}; + +use sp_runtime::traits::Saturating; use sp_std::{convert::TryInto, vec::Vec}; -use auction_manager::{Auction, CheckAuctionItemHandler}; -use core_primitives::*; pub use pallet::*; -use primitives::{ - estate::Estate, Balance, EstateId, ItemId, MetaverseId, UndeployedLandBlock, UndeployedLandBlockId, - UndeployedLandBlockType, -}; + pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -53,11 +44,9 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { - use frame_support::traits::{Currency, ExistenceRequirement, Imbalance, ReservableCurrency, VestingSchedule}; + use frame_support::traits::{Currency, ExistenceRequirement, VestingSchedule}; use pallet_vesting::VestingInfo; - use sp_runtime::traits::{CheckedAdd, CheckedSub, Convert, ConvertInto, StaticLookup, Zero}; - - use primitives::UndeployedLandBlockId; + use sp_runtime::traits::{Convert, StaticLookup}; use super::*; diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index d13741bbb..d7366fa24 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -1,14 +1,11 @@ #![cfg(test)] use frame_support::traits::WithdrawReasons; -use frame_support::{construct_runtime, ord_parameter_types, parameter_types, PalletId}; -use frame_system::EnsureSignedBy; -use sp_core::H256; -use sp_runtime::traits::{ConvertInto, Identity}; -use sp_runtime::{testing::Header, traits::IdentityLookup, DispatchError, Perbill}; +use frame_support::{construct_runtime, ord_parameter_types, parameter_types}; -use auction_manager::{Auction, AuctionInfo, AuctionType, CheckAuctionItemHandler, ListingLevel}; -use primitives::FungibleTokenId; +use sp_core::H256; +use sp_runtime::traits::ConvertInto; +use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; use crate as crowdloan; @@ -124,29 +121,29 @@ impl VestingSchedule for VestingScheduleTrait { type Moment = (); type Currency = Balances; - fn vesting_balance(who: &AccountId) -> Option { + fn vesting_balance(_who: &AccountId) -> Option { None } fn add_vesting_schedule( - who: &AccountId, - locked: Balance, - per_block: Balance, - starting_block: Self::Moment, + _who: &AccountId, + _locked: Balance, + _per_block: Balance, + _starting_block: Self::Moment, ) -> DispatchResult { Ok(()) } fn can_add_vesting_schedule( - who: &AccountId, - locked: Balance, - per_block: Balance, - starting_block: Self::Moment, + _who: &AccountId, + _locked: Balance, + _per_block: Balance, + _starting_block: Self::Moment, ) -> DispatchResult { Ok(()) } - fn remove_vesting_schedule(who: &AccountId, schedule_index: u32) -> DispatchResult { + fn remove_vesting_schedule(_who: &AccountId, _schedule_index: u32) -> DispatchResult { Ok(()) } } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index e64629b57..79d295b2d 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -17,7 +17,7 @@ #![cfg(test)] -use frame_support::{assert_err, assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok}; use sp_runtime::traits::BadOrigin; use mock::{RuntimeEvent, *}; diff --git a/pallets/economy/src/lib.rs b/pallets/economy/src/lib.rs index be695632a..ea06898a0 100644 --- a/pallets/economy/src/lib.rs +++ b/pallets/economy/src/lib.rs @@ -17,16 +17,16 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, HasCompact}; -use frame_support::traits::{LockIdentifier, WithdrawReasons}; +use codec::Encode; + use frame_support::{ - ensure, log, + ensure, pallet_prelude::*, - traits::{Currency, ExistenceRequirement, LockableCurrency, ReservableCurrency}, + traits::{Currency, LockableCurrency, ReservableCurrency}, transactional, PalletId, }; use frame_system::{ensure_signed, pallet_prelude::*}; -use orml_traits::{DataFeeder, DataProvider, MultiCurrency, MultiReservableCurrency}; +use orml_traits::{DataProvider, MultiCurrency, MultiReservableCurrency}; use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, Saturating}; use sp_runtime::{ traits::{AccountIdConversion, One, Zero}, @@ -38,7 +38,7 @@ use core_primitives::NFTTrait; use core_primitives::*; pub use pallet::*; use primitives::{estate::Estate, EstateId}; -use primitives::{AssetId, Balance, ClassId, DomainId, FungibleTokenId, MetaverseId, NftId, PowerAmount, RoundIndex}; +use primitives::{Balance, ClassId, DomainId, FungibleTokenId, PowerAmount, RoundIndex}; pub use weights::WeightInfo; //#[cfg(feature = "runtime-benchmarks")] @@ -54,12 +54,12 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { - use orml_traits::MultiCurrencyExtended; + use sp_runtime::traits::{CheckedAdd, CheckedSub, Saturating}; use sp_runtime::ArithmeticError; - use primitives::staking::{Bond, RoundInfo}; - use primitives::{ClassId, GroupCollectionId, NftId}; + use primitives::staking::Bond; + use primitives::{ClassId, NftId}; use super::*; @@ -338,7 +338,7 @@ pub mod pallet { Error::::ExitQueueAlreadyScheduled ); - let mut staked_balance = StakingInfo::::get(&who); + let staked_balance = StakingInfo::::get(&who); let total = staked_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; ensure!(total >= T::MinimumStake::get(), Error::::StakeBelowMinimum); @@ -440,7 +440,7 @@ pub mod pallet { match estate { None => { - let mut staked_balance = StakingInfo::::get(&who); + let staked_balance = StakingInfo::::get(&who); ensure!(amount <= staked_balance, Error::::UnstakeAmountExceedStakedAmount); let remaining = staked_balance.checked_sub(&amount).ok_or(ArithmeticError::Underflow)?; @@ -672,7 +672,7 @@ pub mod pallet { match estate { None => { - let mut staked_balance = StakingInfo::::get(&who); + let staked_balance = StakingInfo::::get(&who); ensure!(amount <= staked_balance, Error::::UnstakeAmountExceedStakedAmount); let remaining = staked_balance.checked_sub(&amount).ok_or(ArithmeticError::Underflow)?; @@ -771,7 +771,7 @@ pub mod pallet { ensure!(!amount.is_zero(), Error::::UnstakeAmountIsZero); // Update staking info - let mut staked_reserved_balance = T::Currency::reserved_balance(&who); + let staked_reserved_balance = T::Currency::reserved_balance(&who); ensure!( amount <= staked_reserved_balance, Error::::UnstakeAmountExceedStakedAmount @@ -795,7 +795,7 @@ impl Pallet { pub fn convert_power_to_bit(power_amount: Balance, commission: Perbill) -> (Balance, Balance) { let rate = Self::get_bit_power_exchange_rate(); - let mut bit_required = power_amount + let bit_required = power_amount .checked_mul(rate) .ok_or(ArithmeticError::Overflow) .unwrap_or(Zero::zero()); diff --git a/pallets/economy/src/mock.rs b/pallets/economy/src/mock.rs index caccaa029..6969f2bf4 100644 --- a/pallets/economy/src/mock.rs +++ b/pallets/economy/src/mock.rs @@ -3,7 +3,7 @@ use frame_support::traits::Nothing; use frame_support::{construct_runtime, ord_parameter_types, parameter_types, PalletId}; use frame_system::EnsureSignedBy; -use orml_traits::currency::MutationHooks; + use orml_traits::parameter_type_with_key; use sp_core::crypto::AccountId32; use sp_core::H256; @@ -139,9 +139,9 @@ impl Estate for EstateHandler { } fn transfer_undeployed_land_block( - who: &AccountId, - to: &AccountId, - undeployed_land_block_id: UndeployedLandBlockId, + _who: &AccountId, + _to: &AccountId, + _undeployed_land_block_id: UndeployedLandBlockId, ) -> Result { Ok(2) } @@ -160,18 +160,18 @@ impl Estate for EstateHandler { Ok(false) } - fn check_landunit(_metaverse_id: primitives::MetaverseId, coordinate: (i32, i32)) -> Result { + fn check_landunit(_metaverse_id: primitives::MetaverseId, _coordinate: (i32, i32)) -> Result { Ok(true) } fn check_undeployed_land_block( - owner: &AccountId, - undeployed_land_block_id: UndeployedLandBlockId, + _owner: &AccountId, + _undeployed_land_block_id: UndeployedLandBlockId, ) -> Result { Ok(true) } - fn get_total_land_units(estate_id: Option) -> u64 { + fn get_total_land_units(_estate_id: Option) -> u64 { 10 } @@ -179,11 +179,11 @@ impl Estate for EstateHandler { 10 } - fn is_estate_leasor(leasor: AccountId, estate_id: EstateId) -> Result { + fn is_estate_leasor(_leasor: AccountId, _estate_id: EstateId) -> Result { Ok(false) } - fn is_estate_leased(estate_id: EstateId) -> Result { + fn is_estate_leased(_estate_id: EstateId) -> Result { Ok(false) } } @@ -191,7 +191,7 @@ impl Estate for EstateHandler { pub struct MetaverseStakingHandler; impl MetaverseStakingTrait for MetaverseStakingHandler { - fn update_staking_reward(round: RoundIndex, total_reward: u128) -> sp_runtime::DispatchResult { + fn update_staking_reward(_round: RoundIndex, _total_reward: u128) -> sp_runtime::DispatchResult { Ok(()) } } @@ -284,7 +284,7 @@ impl Auction for MockAuctionManager { None } - fn auction_item(id: AuctionId) -> Option> { + fn auction_item(_id: AuctionId) -> Option> { None } @@ -292,7 +292,7 @@ impl Auction for MockAuctionManager { Ok(()) } - fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { + fn update_auction_item(_id: AuctionId, _item_id: ItemId) -> DispatchResult { Ok(()) } @@ -321,11 +321,11 @@ impl Auction for MockAuctionManager { fn remove_auction(_id: u64, _item_id: ItemId) {} - fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { + fn auction_bid_handler(_from: AccountId, _id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } - fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { + fn buy_now_handler(_from: AccountId, _auction_id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } diff --git a/pallets/economy/src/tests.rs b/pallets/economy/src/tests.rs index 957d005d9..64e848a46 100644 --- a/pallets/economy/src/tests.rs +++ b/pallets/economy/src/tests.rs @@ -17,12 +17,11 @@ #![cfg(test)] -use frame_support::{assert_err, assert_noop, assert_ok}; -use orml_nft::Tokens; +use frame_support::{assert_noop, assert_ok}; + use sp_runtime::traits::BadOrigin; use sp_std::default::Default; -use auction_manager::ListingLevel; use core_primitives::{Attributes, CollectionType, TokenType}; use mock::{RuntimeEvent, *}; use primitives::staking::Bond; diff --git a/pallets/estate/src/lib.rs b/pallets/estate/src/lib.rs index 3c124e779..b7a31ca30 100644 --- a/pallets/estate/src/lib.rs +++ b/pallets/estate/src/lib.rs @@ -26,11 +26,10 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use frame_system::{ensure_root, ensure_signed}; -use scale_info::TypeInfo; use sp_runtime::{ - traits::{AccountIdConversion, Convert, One, Saturating, Zero}, - ArithmeticError, DispatchError, Perbill, SaturatedConversion, + traits::{AccountIdConversion, Convert, One, Saturating}, + DispatchError, Perbill, SaturatedConversion, }; use sp_std::vec::Vec; @@ -40,7 +39,7 @@ pub use pallet::*; use primitives::estate::EstateInfo; use primitives::{ estate::{Estate, LandUnitStatus, LeaseContract, OwnerId}, - Attributes, ClassId, EstateId, FungibleTokenId, ItemId, MetaverseId, NftMetadata, TokenId, UndeployedLandBlock, + Attributes, ClassId, EstateId, ItemId, MetaverseId, NftMetadata, TokenId, UndeployedLandBlock, UndeployedLandBlockId, UndeployedLandBlockType, }; pub use rate::{MintingRateInfo, Range}; @@ -61,11 +60,11 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { use frame_support::traits::{Currency, Imbalance, ReservableCurrency}; - use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero}; + use sp_runtime::traits::{CheckedSub, Zero}; use primitives::estate::EstateInfo; - use primitives::staking::{Bond, RoundInfo, StakeSnapshot}; - use primitives::{Balance, RoundIndex, UndeployedLandBlockId}; + use primitives::staking::RoundInfo; + use primitives::{RoundIndex, UndeployedLandBlockId}; use crate::rate::{round_issuance_range, MintingRateInfo}; @@ -1047,7 +1046,7 @@ pub mod pallet { T::NFTTokenizationSource::burn_nft(&who, &(class_id, token_id)); *estate_owner = None; } - OwnerId::Account(ref a) => { + OwnerId::Account(ref _a) => { *estate_owner = None; } } @@ -1063,7 +1062,7 @@ pub mod pallet { AllEstatesCount::::put(new_total_estates_count); // Mint new land tokens to replace the lands in the dissolved estate - let estate_account_id: T::AccountId = + let _estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(estate_id); let storage_fee: BalanceOf = Perbill::from_percent(100u32.saturating_mul(estate_info.land_units.len() as u32)) @@ -1207,11 +1206,12 @@ pub mod pallet { Error::::NoPermission ); let estate_info: EstateInfo = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; - let estate_account_id: T::AccountId = T::LandTreasury::get().into_sub_account_truncating(estate_id); + let _estate_account_id: T::AccountId = + T::LandTreasury::get().into_sub_account_truncating(estate_id); // Mutate estates Estates::::try_mutate_exists(&estate_id, |maybe_estate_info| { - let mut mut_estate_info = maybe_estate_info.as_mut().ok_or(Error::::EstateDoesNotExist)?; + let mut_estate_info = maybe_estate_info.as_mut().ok_or(Error::::EstateDoesNotExist)?; let storage_fee: BalanceOf = Perbill::from_percent(100u32.saturating_mul(land_units.len() as u32)) @@ -1549,7 +1549,7 @@ pub mod pallet { ); let current_block = >::block_number(); EstateLeases::::try_mutate_exists(&estate_id, |estate_lease_value| { - let mut lease = estate_lease_value.as_mut().ok_or(Error::::LeaseDoesNotExist)?; + let lease = estate_lease_value.as_mut().ok_or(Error::::LeaseDoesNotExist)?; ensure!(lease.end_block > current_block, Error::::LeaseIsExpired); @@ -1615,7 +1615,7 @@ impl Pallet { Error::::NoPermission ); - if let OwnerId::Token(owner_class_id, owner_token_id) = token_owner { + if let OwnerId::Token(owner_class_id, _owner_token_id) = token_owner { ensure!(owner_class_id != class_id, Error::::LandUnitAlreadyInEstate) } @@ -1881,11 +1881,11 @@ impl Pallet { from: &T::AccountId, to: &T::AccountId, ) -> Result { - EstateOwner::::try_mutate_exists(&estate_id, |estate_owner| -> Result { + EstateOwner::::try_mutate_exists(&estate_id, |_estate_owner| -> Result { //ensure there is record of the estate owner with estate id and account id ensure!(from != to, Error::::AlreadyOwnTheEstate); let estate_owner_value = Self::get_estate_owner(&estate_id).ok_or(Error::::NoPermission)?; - let estate_info = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; + let _estate_info = Estates::::get(estate_id).ok_or(Error::::EstateDoesNotExist)?; ensure!( !EstateLeases::::contains_key(estate_id), Error::::EstateIsAlreadyLeased @@ -2081,8 +2081,8 @@ impl Pallet { } fn verify_land_unit_in_bound(block_coordinate: &(i32, i32), land_unit_coordinates: &Vec<(i32, i32)>) -> bool { - let mut vec_axis = land_unit_coordinates.iter().map(|lu| lu.0).collect::>(); - let mut vec_yaxis = land_unit_coordinates.iter().map(|lu| lu.1).collect::>(); + let vec_axis = land_unit_coordinates.iter().map(|lu| lu.0).collect::>(); + let vec_yaxis = land_unit_coordinates.iter().map(|lu| lu.1).collect::>(); let max_axis = vec_axis.iter().max().unwrap_or(&i32::MAX); let max_yaxis = vec_yaxis.iter().max().unwrap_or(&i32::MAX); diff --git a/pallets/estate/src/mock.rs b/pallets/estate/src/mock.rs index 3856a489b..c20c8137d 100644 --- a/pallets/estate/src/mock.rs +++ b/pallets/estate/src/mock.rs @@ -14,9 +14,7 @@ use sp_std::vec::Vec; use auction_manager::{Auction, AuctionInfo, AuctionItem, AuctionType, CheckAuctionItemHandler, ListingLevel}; use core_primitives::{CollectionType, NftClassData, TokenType}; -use primitives::{ - AssetId, Attributes, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, NftMetadata, TokenId, LAND_CLASS_ID, -}; +use primitives::{Attributes, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, NftMetadata, TokenId}; use crate as estate; @@ -144,7 +142,7 @@ parameter_types! { pub struct MetaverseInfoSource {} impl MetaverseTrait for MetaverseInfoSource { - fn create_metaverse(who: &AccountId, metadata: MetaverseMetadata) -> MetaverseId { + fn create_metaverse(_who: &AccountId, _metadata: MetaverseMetadata) -> MetaverseId { 1u64 } @@ -168,19 +166,19 @@ impl MetaverseTrait for MetaverseInfoSource { Ok(()) } - fn get_metaverse_land_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_land_class(_metaverse_id: MetaverseId) -> Result { Ok(METAVERSE_LAND_CLASS) } - fn get_metaverse_estate_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_estate_class(_metaverse_id: MetaverseId) -> Result { Ok(METAVERSE_ESTATE_CLASS) } - fn get_metaverse_marketplace_listing_fee(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_marketplace_listing_fee(_metaverse_id: MetaverseId) -> Result { Ok(Perbill::from_percent(1u32)) } - fn get_metaverse_treasury(metaverse_id: MetaverseId) -> AccountId { + fn get_metaverse_treasury(_metaverse_id: MetaverseId) -> AccountId { GENERAL_METAVERSE_FUND } @@ -189,7 +187,7 @@ impl MetaverseTrait for MetaverseInfoSource { } fn check_if_metaverse_estate( - metaverse_id: primitives::MetaverseId, + _metaverse_id: primitives::MetaverseId, class_id: &ClassId, ) -> Result { if class_id == &METAVERSE_LAND_CLASS || class_id == &METAVERSE_ESTATE_CLASS { @@ -202,7 +200,7 @@ impl MetaverseTrait for MetaverseInfoSource { Ok(true) } - fn is_metaverse_owner(who: &AccountId) -> bool { + fn is_metaverse_owner(_who: &AccountId) -> bool { true } } @@ -216,7 +214,7 @@ impl Auction for MockAuctionManager { None } - fn auction_item(id: AuctionId) -> Option> { + fn auction_item(_id: AuctionId) -> Option> { None } @@ -224,7 +222,7 @@ impl Auction for MockAuctionManager { Ok(()) } - fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { + fn update_auction_item(_id: AuctionId, _item_id: ItemId) -> DispatchResult { Ok(()) } @@ -253,11 +251,11 @@ impl Auction for MockAuctionManager { fn remove_auction(_id: u64, _item_id: ItemId) {} - fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { + fn auction_bid_handler(_from: AccountId, _id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } - fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { + fn buy_now_handler(_from: AccountId, _auction_id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } @@ -335,23 +333,23 @@ impl NFTTrait for MockNFTHandler { } Ok(false) } - fn get_nft_group_collection(nft_collection: &Self::ClassId) -> Result { + fn get_nft_group_collection(_nft_collection: &Self::ClassId) -> Result { Ok(ASSET_COLLECTION_ID) } - fn is_stackable(asset_id: (Self::ClassId, Self::TokenId)) -> Result { + fn is_stackable(_asset_id: (Self::ClassId, Self::TokenId)) -> Result { Ok(false) } fn create_token_class( sender: &AccountId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, collection_id: GroupCollectionId, - token_type: TokenType, - collection_type: CollectionType, - royalty_fee: Perbill, - mint_limit: Option, + _token_type: TokenType, + _collection_type: CollectionType, + _royalty_fee: Perbill, + _mint_limit: Option, ) -> Result { match *sender { ALICE => { @@ -372,8 +370,8 @@ impl NFTTrait for MockNFTHandler { fn mint_token( sender: &AccountId, class_id: ClassId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -406,26 +404,26 @@ impl NFTTrait for MockNFTHandler { } } - fn transfer_nft(from: &AccountId, to: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn transfer_nft(_from: &AccountId, _to: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn check_item_on_listing(class_id: Self::ClassId, token_id: Self::TokenId) -> Result { + fn check_item_on_listing(_class_id: Self::ClassId, _token_id: Self::TokenId) -> Result { Ok(true) } - fn burn_nft(account: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn burn_nft(_account: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn is_transferable(nft: &(Self::ClassId, Self::TokenId)) -> Result { + fn is_transferable(_nft: &(Self::ClassId, Self::TokenId)) -> Result { Ok(true) } - fn get_class_fund(class_id: &Self::ClassId) -> AccountId { + fn get_class_fund(_class_id: &Self::ClassId) -> AccountId { CLASS_FUND_ID } - fn get_nft_detail(asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { + fn get_nft_detail(_asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { let new_data = NftClassData { deposit: 0, attributes: test_attributes(1), @@ -439,11 +437,11 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn set_lock_collection(class_id: Self::ClassId, is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_collection(_class_id: Self::ClassId, _is_locked: bool) -> sp_runtime::DispatchResult { Ok(()) } - fn set_lock_nft(token_id: (Self::ClassId, Self::TokenId), is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_nft(_token_id: (Self::ClassId, Self::TokenId), _is_locked: bool) -> sp_runtime::DispatchResult { Ok(()) } @@ -461,20 +459,20 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn get_total_issuance(class_id: Self::ClassId) -> Result { + fn get_total_issuance(_class_id: Self::ClassId) -> Result { Ok(10u64) } - fn get_asset_owner(asset_id: &(Self::ClassId, Self::TokenId)) -> Result { + fn get_asset_owner(_asset_id: &(Self::ClassId, Self::TokenId)) -> Result { Ok(ALICE) } fn mint_token_with_id( sender: &AccountId, class_id: Self::ClassId, - token_id: Self::TokenId, - metadata: core_primitives::NftMetadata, - attributes: core_primitives::Attributes, + _token_id: Self::TokenId, + _metadata: core_primitives::NftMetadata, + _attributes: core_primitives::Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -507,31 +505,31 @@ impl NFTTrait for MockNFTHandler { } } - fn get_free_stackable_nft_balance(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { + fn get_free_stackable_nft_balance(_who: &AccountId, _asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { 1000 } fn reserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> DispatchResult { Ok(()) } fn unreserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } fn transfer_stackable_nft( - sender: &AccountId, - to: &AccountId, - nft: &(Self::ClassId, Self::TokenId), - amount: Balance, + _sender: &AccountId, + _to: &AccountId, + _nft: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } diff --git a/pallets/estate/src/rate.rs b/pallets/estate/src/rate.rs index 788907d6d..747a72029 100644 --- a/pallets/estate/src/rate.rs +++ b/pallets/estate/src/rate.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_runtime::{Perbill, Permill, RuntimeDebug}; +use sp_runtime::{Perbill, RuntimeDebug}; use crate::{AllLandUnitsCount, TotalUndeployedLandUnit}; // Helper methods to compute the issuance rate for undeployed land. diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index 0f748ebf7..a1dfa13bd 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -459,7 +459,7 @@ pub mod pallet { let mut metaverse_has_referendum_running: bool = false; for (_, referendum_info) in ReferendumInfoOf::::iter_prefix(metaverse_id) { match referendum_info { - ReferendumInfo::Ongoing(status) => { + ReferendumInfo::Ongoing(_status) => { metaverse_has_referendum_running = true; break; } @@ -514,7 +514,7 @@ pub mod pallet { metaverse_id: MetaverseId, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - let proposal_info = Self::proposals(metaverse_id, proposal).ok_or(Error::::ProposalDoesNotExist)?; + let _proposal_info = Self::proposals(metaverse_id, proposal).ok_or(Error::::ProposalDoesNotExist)?; if let Some((depositors, deposit)) = >::take(proposal) { >::remove(metaverse_id, proposal); Self::update_proposals_per_metaverse_number(metaverse_id, false); // slash depositors @@ -670,7 +670,7 @@ pub mod pallet { ReferendumInfoOf::::insert(&metaverse, &referendum, ReferendumInfo::Ongoing(status)); Self::deposit_event(Event::VoteRemoved(from, referendum)); } - Some(ReferendumInfo::Finished { end, passed, title }) => { + Some(ReferendumInfo::Finished { end, passed, title: _ }) => { let prior = &mut voting_record.prior; if let Some((lock_periods, balance)) = vote.locked_if(passed) { let mut lock_value: T::BlockNumber = @@ -893,7 +893,7 @@ impl Pallet { } /// Table the waiting public proposal with the highest backing for a vote. - fn launch_public(now: T::BlockNumber, metaverse_id: MetaverseId) -> DispatchResult { + fn launch_public(_now: T::BlockNumber, metaverse_id: MetaverseId) -> DispatchResult { let launch_block = Self::get_proposal_launch_block(metaverse_id)?; if let Some((_, proposal)) = Proposals::::iter_prefix(metaverse_id).enumerate().max_by_key( // defensive only: All current public proposals have an amount locked @@ -1059,7 +1059,7 @@ impl Pallet { } else { let preimage = >::take(&metaverse_id, &referendum_status.proposal_hash); if let Some(PreimageStatus::Available { - data, + data: _, provider, deposit, .. @@ -1075,7 +1075,7 @@ impl Pallet { /// Internal enacting of successfully passed proposal fn do_enact_proposal( - proposal_id: ProposalId, + _proposal_id: ProposalId, metaverse_id: MetaverseId, referendum_id: ReferendumId, proposal_hash: T::Hash, diff --git a/pallets/governance/src/mock.rs b/pallets/governance/src/mock.rs index 85d0294b0..6e9f212f8 100644 --- a/pallets/governance/src/mock.rs +++ b/pallets/governance/src/mock.rs @@ -136,7 +136,7 @@ impl pallet_scheduler::Config for Runtime { pub struct MetaverseInfo {} impl MetaverseTrait for MetaverseInfo { - fn create_metaverse(who: &AccountId, metadata: MetaverseMetadata) -> MetaverseId { + fn create_metaverse(_who: &AccountId, _metadata: MetaverseMetadata) -> MetaverseId { 1u64 } @@ -160,19 +160,19 @@ impl MetaverseTrait for MetaverseInfo { Ok(()) } - fn get_metaverse_land_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_land_class(_metaverse_id: MetaverseId) -> Result { Ok(15u32) } - fn get_metaverse_estate_class(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_estate_class(_metaverse_id: MetaverseId) -> Result { Ok(16u32) } - fn get_metaverse_marketplace_listing_fee(metaverse_id: MetaverseId) -> Result { + fn get_metaverse_marketplace_listing_fee(_metaverse_id: MetaverseId) -> Result { Ok(Perbill::from_percent(1u32)) } - fn get_metaverse_treasury(metaverse_id: MetaverseId) -> AccountId { + fn get_metaverse_treasury(_metaverse_id: MetaverseId) -> AccountId { GENERAL_METAVERSE_FUND } @@ -181,7 +181,7 @@ impl MetaverseTrait for MetaverseInfo { } fn check_if_metaverse_estate( - metaverse_id: primitives::MetaverseId, + _metaverse_id: primitives::MetaverseId, class_id: &ClassId, ) -> Result { if class_id == &15u32 || class_id == &16u32 { @@ -194,7 +194,7 @@ impl MetaverseTrait for MetaverseInfo { Ok(true) } - fn is_metaverse_owner(who: &AccountId) -> bool { + fn is_metaverse_owner(_who: &AccountId) -> bool { true } } @@ -213,7 +213,7 @@ impl MetaverseLandTrait for MetaverseLandInfo { _ => false, } } - fn check_landunit(metaverse_id: MetaverseId, coordinate: (i32, i32)) -> Result { + fn check_landunit(_metaverse_id: MetaverseId, _coordinate: (i32, i32)) -> Result { Ok(false) } } @@ -258,7 +258,7 @@ impl NFTTrait for MockNFTHandler { Ok(false) } - fn is_stackable(asset_id: (Self::ClassId, Self::TokenId)) -> Result { + fn is_stackable(_asset_id: (Self::ClassId, Self::TokenId)) -> Result { Ok(false) } @@ -271,19 +271,19 @@ impl NFTTrait for MockNFTHandler { } Ok(false) } - fn get_nft_group_collection(nft_collection: &Self::ClassId) -> Result { + fn get_nft_group_collection(_nft_collection: &Self::ClassId) -> Result { Ok(ASSET_COLLECTION_ID) } fn create_token_class( sender: &AccountId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, collection_id: GroupCollectionId, - token_type: TokenType, - collection_type: CollectionType, - royalty_fee: Perbill, - mint_limit: Option, + _token_type: TokenType, + _collection_type: CollectionType, + _royalty_fee: Perbill, + _mint_limit: Option, ) -> Result { match *sender { ALICE => { @@ -304,8 +304,8 @@ impl NFTTrait for MockNFTHandler { fn mint_token( sender: &AccountId, class_id: ClassId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -329,26 +329,26 @@ impl NFTTrait for MockNFTHandler { } } - fn transfer_nft(from: &AccountId, to: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn transfer_nft(_from: &AccountId, _to: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn check_item_on_listing(class_id: Self::ClassId, token_id: Self::TokenId) -> Result { + fn check_item_on_listing(_class_id: Self::ClassId, _token_id: Self::TokenId) -> Result { Ok(true) } - fn burn_nft(account: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn burn_nft(_account: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn is_transferable(nft: &(Self::ClassId, Self::TokenId)) -> Result { + fn is_transferable(_nft: &(Self::ClassId, Self::TokenId)) -> Result { Ok(true) } - fn get_class_fund(class_id: &Self::ClassId) -> AccountId { + fn get_class_fund(_class_id: &Self::ClassId) -> AccountId { CLASS_FUND_ID } - fn get_nft_detail(asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { + fn get_nft_detail(_asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { let new_data = NftClassData { deposit: 0, attributes: test_attributes(1), @@ -362,11 +362,11 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn set_lock_collection(class_id: Self::ClassId, is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_collection(_class_id: Self::ClassId, _is_locked: bool) -> sp_runtime::DispatchResult { todo!() } - fn set_lock_nft(token_id: (Self::ClassId, Self::TokenId), is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_nft(_token_id: (Self::ClassId, Self::TokenId), _is_locked: bool) -> sp_runtime::DispatchResult { todo!() } @@ -384,20 +384,20 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn get_total_issuance(class_id: Self::ClassId) -> Result { + fn get_total_issuance(_class_id: Self::ClassId) -> Result { Ok(10u64) } - fn get_asset_owner(asset_id: &(Self::ClassId, Self::TokenId)) -> Result { + fn get_asset_owner(_asset_id: &(Self::ClassId, Self::TokenId)) -> Result { Ok(ALICE) } fn mint_token_with_id( sender: &AccountId, class_id: Self::ClassId, - token_id: Self::TokenId, - metadata: NftMetadata, - attributes: Attributes, + _token_id: Self::TokenId, + _metadata: NftMetadata, + _attributes: Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -420,31 +420,31 @@ impl NFTTrait for MockNFTHandler { } } } - fn get_free_stackable_nft_balance(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { + fn get_free_stackable_nft_balance(_who: &AccountId, _asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { 1000 } fn reserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> DispatchResult { Ok(()) } fn unreserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } fn transfer_stackable_nft( - sender: &AccountId, - to: &AccountId, - nft: &(Self::ClassId, Self::TokenId), - amount: Balance, + _sender: &AccountId, + _to: &AccountId, + _nft: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } diff --git a/pallets/governance/src/tests.rs b/pallets/governance/src/tests.rs index 6433a6905..886638493 100644 --- a/pallets/governance/src/tests.rs +++ b/pallets/governance/src/tests.rs @@ -382,7 +382,7 @@ fn vote_work() { #[test] fn vote_when_not_country_member_does_not_work() { ExtBuilder::default().build().execute_with(|| { - let origin = RuntimeOrigin::signed(BOB); + let _origin = RuntimeOrigin::signed(BOB); let hash = set_freeze_metaverse_proposal_hash(1); add_freeze_metaverse_preimage_alice(hash); assert_ok!(GovernanceModule::propose( diff --git a/pallets/metaverse/src/lib.rs b/pallets/metaverse/src/lib.rs index 68bca1bce..a0621c741 100644 --- a/pallets/metaverse/src/lib.rs +++ b/pallets/metaverse/src/lib.rs @@ -80,7 +80,7 @@ pub struct MetaverseStakingPoints { #[frame_support::pallet] pub mod pallet { use orml_traits::MultiCurrencyExtended; - use sp_runtime::traits::{CheckedAdd, Saturating}; + use sp_runtime::ArithmeticError; use primitives::staking::RoundInfo; @@ -545,7 +545,7 @@ impl Pallet { } /// Minting of a land class for the metaverse - fn mint_metaverse_land_class(sender: &T::AccountId, metaverse_id: MetaverseId) -> Result { + fn mint_metaverse_land_class(_sender: &T::AccountId, metaverse_id: MetaverseId) -> Result { // Pre-mint class for lands let mut land_class_attributes = Attributes::new(); land_class_attributes.insert("MetaverseId:".as_bytes().to_vec(), "MetaverseId:".as_bytes().to_vec()); @@ -565,7 +565,10 @@ impl Pallet { } /// Minting of an estate class for the metaverse - fn mint_metaverse_estate_class(sender: &T::AccountId, metaverse_id: MetaverseId) -> Result { + fn mint_metaverse_estate_class( + _sender: &T::AccountId, + metaverse_id: MetaverseId, + ) -> Result { // Pre-mint class for estates let mut estate_class_attributes = Attributes::new(); estate_class_attributes.insert("MetaverseId:".as_bytes().to_vec(), metaverse_id.to_be_bytes().to_vec()); @@ -611,7 +614,7 @@ impl Pallet { let default_land_class_id = TryInto::::try_into(0u32).unwrap_or_default(); let default_estate_class_id = TryInto::::try_into(1u32).unwrap_or_default(); - Metaverses::::translate(|k, metaverse_info_v1: MetaverseInfoV1| { + Metaverses::::translate(|_k, metaverse_info_v1: MetaverseInfoV1| { upgraded_metaverse_items += 1; let v2: MetaverseInfo = MetaverseInfo { diff --git a/pallets/metaverse/src/mock.rs b/pallets/metaverse/src/mock.rs index 5b12d9bf5..a238459de 100644 --- a/pallets/metaverse/src/mock.rs +++ b/pallets/metaverse/src/mock.rs @@ -7,7 +7,6 @@ use orml_traits::parameter_type_with_key; use sp_core::H256; use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; -use primitives::staking::RoundInfo; use primitives::{Amount, ClassId, GroupCollectionId, TokenId}; use crate as metaverse; @@ -128,22 +127,22 @@ impl NFTTrait for MockNFTHandler { } Ok(false) } - fn get_nft_group_collection(nft_collection: &Self::ClassId) -> Result { + fn get_nft_group_collection(_nft_collection: &Self::ClassId) -> Result { Ok(ASSET_COLLECTION_ID) } - fn is_stackable(asset_id: (Self::ClassId, Self::TokenId)) -> Result { + fn is_stackable(_asset_id: (Self::ClassId, Self::TokenId)) -> Result { Ok(false) } fn create_token_class( sender: &AccountId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, collection_id: GroupCollectionId, - token_type: TokenType, - collection_type: CollectionType, - royalty_fee: Perbill, - mint_limit: Option, + _token_type: TokenType, + _collection_type: CollectionType, + _royalty_fee: Perbill, + _mint_limit: Option, ) -> Result { match *sender { ALICE => Ok(100), @@ -164,8 +163,8 @@ impl NFTTrait for MockNFTHandler { fn mint_token( sender: &AccountId, class_id: ClassId, - metadata: NftMetadata, - attributes: Attributes, + _metadata: NftMetadata, + _attributes: Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -189,26 +188,26 @@ impl NFTTrait for MockNFTHandler { } } - fn transfer_nft(from: &AccountId, to: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn transfer_nft(_from: &AccountId, _to: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn check_item_on_listing(class_id: Self::ClassId, token_id: Self::TokenId) -> Result { + fn check_item_on_listing(_class_id: Self::ClassId, _token_id: Self::TokenId) -> Result { Ok(true) } - fn burn_nft(account: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { + fn burn_nft(_account: &AccountId, _nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { Ok(()) } - fn is_transferable(nft: &(Self::ClassId, Self::TokenId)) -> Result { + fn is_transferable(_nft: &(Self::ClassId, Self::TokenId)) -> Result { Ok(true) } - fn get_class_fund(class_id: &Self::ClassId) -> AccountId { + fn get_class_fund(_class_id: &Self::ClassId) -> AccountId { CLASS_FUND_ID } - fn get_nft_detail(asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { + fn get_nft_detail(_asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { let new_data = NftClassData { deposit: 0, attributes: test_attributes(1), @@ -222,11 +221,11 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn set_lock_collection(class_id: Self::ClassId, is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_collection(_class_id: Self::ClassId, _is_locked: bool) -> sp_runtime::DispatchResult { todo!() } - fn set_lock_nft(token_id: (Self::ClassId, Self::TokenId), is_locked: bool) -> sp_runtime::DispatchResult { + fn set_lock_nft(_token_id: (Self::ClassId, Self::TokenId), _is_locked: bool) -> sp_runtime::DispatchResult { todo!() } @@ -244,20 +243,20 @@ impl NFTTrait for MockNFTHandler { Ok(new_data) } - fn get_total_issuance(class_id: Self::ClassId) -> Result { + fn get_total_issuance(_class_id: Self::ClassId) -> Result { Ok(10u64) } - fn get_asset_owner(asset_id: &(Self::ClassId, Self::TokenId)) -> Result { + fn get_asset_owner(_asset_id: &(Self::ClassId, Self::TokenId)) -> Result { Ok(ALICE) } fn mint_token_with_id( sender: &AccountId, class_id: Self::ClassId, - token_id: Self::TokenId, - metadata: NftMetadata, - attributes: Attributes, + _token_id: Self::TokenId, + _metadata: NftMetadata, + _attributes: Attributes, ) -> Result { match *sender { ALICE => Ok(1), @@ -281,31 +280,31 @@ impl NFTTrait for MockNFTHandler { } } - fn get_free_stackable_nft_balance(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { + fn get_free_stackable_nft_balance(_who: &AccountId, _asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { 1000 } fn reserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> DispatchResult { Ok(()) } fn unreserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, + _who: &AccountId, + _asset_id: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } fn transfer_stackable_nft( - sender: &AccountId, - to: &AccountId, - nft: &(Self::ClassId, Self::TokenId), - amount: Balance, + _sender: &AccountId, + _to: &AccountId, + _nft: &(Self::ClassId, Self::TokenId), + _amount: Balance, ) -> sp_runtime::DispatchResult { Ok(()) } diff --git a/pallets/metaverse/src/tests.rs b/pallets/metaverse/src/tests.rs index 39ba4822c..f75e1203d 100644 --- a/pallets/metaverse/src/tests.rs +++ b/pallets/metaverse/src/tests.rs @@ -17,12 +17,11 @@ #![cfg(test)] -use frame_support::{assert_err, assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok}; use sp_runtime::traits::BadOrigin; use sp_runtime::Perbill; use mock::{RuntimeEvent, *}; -use primitives::staking::RoundInfo; #[cfg(test)] use super::*; diff --git a/pallets/mining/src/lib.rs b/pallets/mining/src/lib.rs index 1d1b34f55..bdb6ce7f4 100644 --- a/pallets/mining/src/lib.rs +++ b/pallets/mining/src/lib.rs @@ -19,34 +19,28 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; -use frame_support::traits::{Currency, Get, WithdrawReasons}; +use frame_support::traits::{Currency, Get}; use frame_support::PalletId; use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo}, ensure, pallet_prelude::*, traits::ExistenceRequirement, - transactional, Parameter, }; use frame_system::pallet_prelude::*; use frame_system::{self as system, ensure_signed}; -use orml_traits::{ - arithmetic::{Signed, SimpleArithmetic}, - BalanceStatus, BasicCurrency, BasicCurrencyExtended, BasicLockableCurrency, BasicReservableCurrency, - LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, -}; +use orml_traits::{BasicCurrency, LockIdentifier, MultiCurrency, MultiCurrencyExtended}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AccountIdConversion, AtLeast32Bit, One, StaticLookup, Zero}, - DispatchError, Perbill, + traits::{AccountIdConversion, Zero}, + DispatchError, }; use sp_std::vec::Vec; -use auction_manager::SwapManager; use core_primitives::*; pub use pallet::*; use primitives::staking::RoundInfo; -use primitives::{Balance, CurrencyId, FungibleTokenId, MetaverseId}; +use primitives::{Balance, FungibleTokenId}; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -80,16 +74,15 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { - use frame_support::sp_runtime::traits::Saturating; - use frame_support::sp_runtime::{FixedPointNumber, SaturatedConversion}; - use frame_support::traits::OnUnbalanced; - use pallet_balances::NegativeImbalance; + + use frame_support::sp_runtime::SaturatedConversion; + use sp_runtime::Perbill; use sp_std::convert::TryInto; use primitives::estate::Estate; use primitives::staking::{MetaverseStakingTrait, RoundInfo}; - use primitives::{FungibleTokenId, RoundIndex, TokenId, VestingSchedule}; + use primitives::{FungibleTokenId, RoundIndex, VestingSchedule}; use crate::mining::round_issuance_range; diff --git a/pallets/mining/src/mining.rs b/pallets/mining/src/mining.rs index 750e698bf..a35a4a0ff 100644 --- a/pallets/mining/src/mining.rs +++ b/pallets/mining/src/mining.rs @@ -15,17 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use codec::{Decode, Encode}; -use orml_traits::arithmetic::{CheckedDiv, CheckedMul}; use orml_traits::MultiCurrency; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_runtime::traits::{Saturating, Zero}; -use sp_runtime::{ArithmeticError, Perbill, RuntimeDebug}; + +use sp_runtime::Perbill; use core_primitives::{MiningRange, MiningResourceRateInfo}; -use primitives::{estate::Estate, Balance, FungibleTokenId}; +use primitives::{Balance, FungibleTokenId}; // Helper methods to compute the issuance rate for undeployed land. use crate::pallet::{Config, Pallet}; diff --git a/pallets/mining/src/mock.rs b/pallets/mining/src/mock.rs index cac0f11ba..007245e98 100644 --- a/pallets/mining/src/mock.rs +++ b/pallets/mining/src/mock.rs @@ -1,8 +1,8 @@ -use frame_support::pallet_prelude::{GenesisBuild, Hooks, MaybeSerializeDeserialize}; -use frame_support::sp_runtime::traits::AtLeast32Bit; +use frame_support::pallet_prelude::GenesisBuild; + use frame_support::traits::Nothing; -use frame_support::{construct_runtime, ord_parameter_types, parameter_types, traits::EnsureOrigin, weights::Weight}; -use frame_system::{EnsureRoot, EnsureSignedBy}; +use frame_support::{construct_runtime, ord_parameter_types, parameter_types}; +use frame_system::EnsureSignedBy; use orml_traits::parameter_type_with_key; use sp_core::H256; use sp_runtime::{ @@ -13,11 +13,11 @@ use sp_runtime::{ use primitives::estate::Estate; use primitives::staking::MetaverseStakingTrait; -use primitives::FungibleTokenId::FungibleToken; + use primitives::{Amount, CurrencyId, EstateId, FungibleTokenId, RoundIndex, UndeployedLandBlockId}; use crate as mining; -use crate::{Config, Module}; +use crate::Config; use super::*; @@ -137,22 +137,22 @@ parameter_types! { pub struct EstateHandler; impl Estate for EstateHandler { - fn transfer_estate(estate_id: EstateId, from: &u128, to: &u128) -> Result { + fn transfer_estate(estate_id: EstateId, _from: &u128, _to: &u128) -> Result { Ok(estate_id) } fn transfer_landunit( coordinate: (i32, i32), - from: &u128, - to: &(u128, primitives::MetaverseId), + _from: &u128, + _to: &(u128, primitives::MetaverseId), ) -> Result<(i32, i32), DispatchError> { Ok(coordinate) } fn transfer_undeployed_land_block( - who: &AccountId, - to: &AccountId, - undeployed_land_block_id: UndeployedLandBlockId, + _who: &AccountId, + _to: &AccountId, + _undeployed_land_block_id: UndeployedLandBlockId, ) -> Result { Ok(2) } @@ -161,18 +161,18 @@ impl Estate for EstateHandler { Ok(true) } - fn check_landunit(_metaverse_id: primitives::MetaverseId, coordinate: (i32, i32)) -> Result { + fn check_landunit(_metaverse_id: primitives::MetaverseId, _coordinate: (i32, i32)) -> Result { Ok(true) } fn check_undeployed_land_block( - owner: &AccountId, - undeployed_land_block_id: UndeployedLandBlockId, + _owner: &AccountId, + _undeployed_land_block_id: UndeployedLandBlockId, ) -> Result { Ok(true) } - fn get_total_land_units(estate_id: Option) -> u64 { + fn get_total_land_units(_estate_id: Option) -> u64 { 10 } @@ -180,15 +180,15 @@ impl Estate for EstateHandler { 10 } - fn check_estate_ownership(owner: AccountId, estate_id: EstateId) -> Result { + fn check_estate_ownership(_owner: AccountId, _estate_id: EstateId) -> Result { Ok(false) } - fn is_estate_leasor(leasor: AccountId, estate_id: EstateId) -> Result { + fn is_estate_leasor(_leasor: AccountId, _estate_id: EstateId) -> Result { Ok(false) } - fn is_estate_leased(estate_id: EstateId) -> Result { + fn is_estate_leased(_estate_id: EstateId) -> Result { Ok(false) } } @@ -196,7 +196,7 @@ impl Estate for EstateHandler { pub struct MetaverseStakingHandler; impl MetaverseStakingTrait for MetaverseStakingHandler { - fn update_staking_reward(round: RoundIndex, total_reward: u128) -> sp_runtime::DispatchResult { + fn update_staking_reward(_round: RoundIndex, _total_reward: u128) -> sp_runtime::DispatchResult { Ok(()) } } diff --git a/pallets/mining/src/tests.rs b/pallets/mining/src/tests.rs index 52586b8ef..3b6198186 100644 --- a/pallets/mining/src/tests.rs +++ b/pallets/mining/src/tests.rs @@ -1,10 +1,8 @@ use frame_support::{assert_noop, assert_ok}; -use sp_core::blake2_256; -use sp_runtime::traits::BadOrigin; -use sp_runtime::{AccountId32, Perbill}; -use sp_std::vec::Vec; -use mock::{RuntimeEvent, *}; +use sp_runtime::Perbill; + +use mock::*; use primitives::Balance; // Unit testing for metaverse currency, metaverse treasury @@ -55,7 +53,7 @@ fn burn_mining_resource_should_work() { assert_eq!(get_mining_balance(), 1000); - let event = mock::RuntimeEvent::MiningModule(crate::Event::MiningResourceMintedTo(BOB, 1000)); + let _event = mock::RuntimeEvent::MiningModule(crate::Event::MiningResourceMintedTo(BOB, 1000)); assert_ok!(MiningModule::burn(origin, BOB, 300)); assert_eq!(get_mining_balance(), 700); diff --git a/pallets/nft/src/lib.rs b/pallets/nft/src/lib.rs index a2709e35e..d4333af59 100644 --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -26,8 +26,6 @@ #![allow(clippy::upper_case_acronyms)] #![cfg_attr(not(feature = "std"), no_std)] -use core::result; - use codec::Encode; use frame_support::traits::Len; use frame_support::{ @@ -39,7 +37,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use orml_nft::{ClassInfo, ClassInfoOf, Classes, Pallet as NftModule, TokenInfo, TokenInfoOf, TokenMetadataOf, Tokens}; -use scale_info::TypeInfo; + use sp_runtime::traits::Saturating; use sp_runtime::Perbill; use sp_runtime::{ @@ -52,8 +50,8 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use auction_manager::{Auction, CheckAuctionItemHandler}; pub use pallet::*; pub use primitive_traits::{Attributes, NFTTrait, NftClassData, NftGroupCollectionData, NftMetadata, TokenType}; -use primitive_traits::{CollectionType, NftAssetData, NftAssetDataV1, NftClassDataV1, PreSignedMint}; -use primitives::{AssetId, BlockNumber, ClassId, GroupCollectionId, Hash, ItemId, TokenId}; +use primitive_traits::{CollectionType, NftAssetData, NftClassDataV1, PreSignedMint}; +use primitives::{AssetId, ClassId, GroupCollectionId, ItemId, TokenId}; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -76,7 +74,7 @@ pub enum StorageVersion { #[frame_support::pallet] pub mod pallet { - use frame_system::offchain::Signer; + use orml_traits::{MultiCurrency, MultiCurrencyExtended}; use sp_runtime::traits::{CheckedSub, IdentifyAccount, Verify}; use sp_runtime::ArithmeticError; @@ -474,7 +472,7 @@ pub mod pallet { mint_limit: Option, ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; - let class_id = Self::do_create_class( + let _class_id = Self::do_create_class( &sender, metadata, attributes, @@ -556,7 +554,7 @@ pub mod pallet { let result = NftModule::::mint_stackable_nft(&sender, class_id, metadata, new_stackable_nft_data, amount); match result { - Ok((token_id, balance)) => { + Ok((token_id, _balance)) => { Self::deposit_event(Event::::NewStackableNftMinted(sender, class_id, token_id, amount)); Ok(().into()) } @@ -1370,8 +1368,8 @@ impl Pallet { log::info!("Start upgrading nft class data v2"); log::info!("Start upgrading nft token data v2"); let mut num_nft_classes = 0; - let mut num_nft_tokens = 0; - let mut asset_by_owner_updates = 0; + let _num_nft_tokens = 0; + let _asset_by_owner_updates = 0; Classes::::translate( |k, diff --git a/pallets/nft/src/mock.rs b/pallets/nft/src/mock.rs index 6fc8ecaf0..ab7304ee9 100644 --- a/pallets/nft/src/mock.rs +++ b/pallets/nft/src/mock.rs @@ -99,7 +99,7 @@ impl Auction for MockAuctionManager { None } - fn auction_item(id: AuctionId) -> Option> { + fn auction_item(_id: AuctionId) -> Option> { None } @@ -107,7 +107,7 @@ impl Auction for MockAuctionManager { Ok(()) } - fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { + fn update_auction_item(_id: AuctionId, _item_id: ItemId) -> DispatchResult { Ok(()) } @@ -136,11 +136,11 @@ impl Auction for MockAuctionManager { fn remove_auction(_id: u64, _item_id: ItemId) {} - fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { + fn auction_bid_handler(_from: AccountId, _id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } - fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { + fn buy_now_handler(_from: AccountId, _auction_id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } diff --git a/pallets/nft/src/tests.rs b/pallets/nft/src/tests.rs index a54463fbe..d2cd87bda 100644 --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -863,7 +863,7 @@ fn setting_hard_limit_should_work() { fn force_updating_total_issuance_should_work() { ExtBuilder::default().build().execute_with(|| { let origin = RuntimeOrigin::signed(account(1)); - let class_deposit = ::ClassMintingFee::get(); + let _class_deposit = ::ClassMintingFee::get(); assert_ok!(Nft::create_group(RuntimeOrigin::root(), vec![1], vec![1],)); assert_ok!(Nft::create_class( origin.clone(), @@ -894,7 +894,7 @@ fn force_updating_total_issuance_should_work() { fn force_updating_total_issuance_should_fail() { ExtBuilder::default().build().execute_with(|| { let origin = RuntimeOrigin::signed(account(1)); - let class_deposit = ::ClassMintingFee::get(); + let _class_deposit = ::ClassMintingFee::get(); assert_ok!(Nft::create_group(RuntimeOrigin::root(), vec![1], vec![1],)); assert_ok!(Nft::create_class( origin.clone(), @@ -922,7 +922,7 @@ fn force_updating_total_issuance_should_fail() { fn force_updating_new_royal_fee_should_work() { ExtBuilder::default().build().execute_with(|| { let origin = RuntimeOrigin::signed(account(1)); - let class_deposit = ::ClassMintingFee::get(); + let _class_deposit = ::ClassMintingFee::get(); assert_ok!(Nft::create_group(RuntimeOrigin::root(), vec![1], vec![1],)); assert_ok!(Nft::create_class( origin.clone(), @@ -956,7 +956,7 @@ fn force_updating_new_royal_fee_should_work() { fn force_updating_new_royal_fee_should_fail() { ExtBuilder::default().build().execute_with(|| { let origin = RuntimeOrigin::signed(account(2)); - let class_deposit = ::ClassMintingFee::get(); + let _class_deposit = ::ClassMintingFee::get(); assert_ok!(Nft::create_group(RuntimeOrigin::root(), vec![1], vec![1],)); assert_ok!(Nft::create_class( origin.clone(), diff --git a/pallets/reward/src/lib.rs b/pallets/reward/src/lib.rs index 28681399c..ac4a96a4c 100644 --- a/pallets/reward/src/lib.rs +++ b/pallets/reward/src/lib.rs @@ -17,9 +17,9 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, HasCompact}; +use codec::Encode; use frame_support::storage::{child, ChildTriePrefixIterator}; -use frame_support::traits::{LockIdentifier, WithdrawReasons}; + use frame_support::{ ensure, log, pallet_prelude::*, @@ -27,23 +27,21 @@ use frame_support::{ transactional, PalletId, }; use frame_system::{ensure_signed, pallet_prelude::*}; -use orml_traits::{DataFeeder, DataProvider, MultiCurrency, MultiReservableCurrency}; +use orml_traits::{DataProvider, MultiCurrency, MultiReservableCurrency}; use sp_core::Encode as SPEncode; use sp_io::hashing::keccak_256; -use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, Hash as Hasher, Saturating}; +use sp_runtime::traits::{Hash as Hasher, Saturating}; use sp_runtime::{ - traits::{AccountIdConversion, One, Zero}, - ArithmeticError, DispatchError, Perbill, SaturatedConversion, + traits::{AccountIdConversion, Zero}, + DispatchError, Perbill, SaturatedConversion, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::*, vec::Vec}; +use sp_std::{prelude::*, vec::Vec}; use core_primitives::NFTTrait; use core_primitives::*; pub use pallet::*; -use primitives::{ - estate::Estate, CampaignId, CampaignInfo, CampaignInfoV1, CampaignInfoV2, EstateId, Hash, RewardType, TrieIndex, -}; -use primitives::{Balance, ClassId, FungibleTokenId, NftId}; +use primitives::{Balance, ClassId, FungibleTokenId}; +use primitives::{CampaignId, CampaignInfo, CampaignInfoV2, Hash, RewardType, TrieIndex}; pub use weights::WeightInfo; //#[cfg(feature = "runtime-benchmarks")] @@ -59,13 +57,12 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { - use frame_support::traits::tokens::currency; + use frame_support::traits::ExistenceRequirement::AllowDeath; - use orml_traits::{rewards, MultiCurrencyExtended}; - use sp_runtime::traits::{CheckedAdd, CheckedSub, Saturating}; + + use sp_runtime::traits::{CheckedAdd, Saturating}; use sp_runtime::ArithmeticError; - use primitives::staking::RoundInfo; use primitives::{CampaignId, CampaignInfo, ClassId, NftId}; use super::*; @@ -379,7 +376,7 @@ pub mod pallet { /// - `properties`: information relevant for the campaign. /// /// Emits `NewRewardCampaignCreated` if successful. - #[pallet::weight(T::WeightInfo::create_campaign().saturating_mul((1u64.saturating_add(reward.len() as u64))))] + #[pallet::weight(T::WeightInfo::create_campaign().saturating_mul(1u64.saturating_add(reward.len() as u64)))] #[transactional] pub fn create_nft_campaign( origin: OriginFor, @@ -518,7 +515,7 @@ pub mod pallet { /// - `leaf_nodes`: list of the merkle tree nodes required for merkle-proof calculation. /// /// Emits `RewardClaimed` if successful. - #[pallet::weight(T::WeightInfo::claim_reward_root().saturating_mul((1u64.saturating_add(leaf_nodes.len() as u64))))] + #[pallet::weight(T::WeightInfo::claim_reward_root().saturating_mul(1u64.saturating_add(leaf_nodes.len() as u64)))] #[transactional] pub fn claim_reward_root( origin: OriginFor, @@ -582,7 +579,7 @@ pub mod pallet { /// - `amount`: the amount of NFTs that the account is going to claim /// /// Emits `RewardClaimed` if successful. - #[pallet::weight(T::WeightInfo::claim_nft_reward().saturating_mul((1u64.saturating_add(*amount))))] + #[pallet::weight(T::WeightInfo::claim_nft_reward().saturating_mul(1u64.saturating_add(*amount)))] #[transactional] pub fn claim_nft_reward(origin: OriginFor, id: CampaignId, amount: u64) -> DispatchResult { let who = ensure_signed(origin)?; @@ -641,7 +638,7 @@ pub mod pallet { /// - `leaf_nodes`: list of the merkle tree nodes required for merkle-proof calculation. /// /// Emits `RewardClaimed` if successful. - #[pallet::weight(T::WeightInfo::claim_nft_reward_root().saturating_mul((1u64.saturating_add(reward_tokens.len() as u64))))] + #[pallet::weight(T::WeightInfo::claim_nft_reward_root().saturating_mul(1u64.saturating_add(reward_tokens.len() as u64)))] #[transactional] pub fn claim_nft_reward_root( origin: OriginFor, @@ -855,7 +852,7 @@ pub mod pallet { let mut new_cap = cap.clone(); let mut rewards_list: Vec<(T::AccountId, Vec<(ClassId, NftId)>)> = Vec::new(); let mut tokens: Vec<(ClassId, TokenId)> = Vec::new(); - let mut total_amount_left: u64 = total_nfts_amount; + let total_amount_left: u64 = total_nfts_amount; for (to, amount) in rewards { let (t, _) = Self::reward_get_nft(campaign.trie_index, &to); ensure!(t.is_empty(), Error::::AccountAlreadyRewarded); @@ -866,7 +863,7 @@ pub mod pallet { ); total_amount_left.saturating_sub(amount); - for l in 0..amount { + for _l in 0..amount { let token = new_cap.pop().ok_or(Error::::RewardExceedCap)?; tokens.push(token); } @@ -945,13 +942,13 @@ pub mod pallet { /// - `merkle_roots_quanity`: the amount of merkle roots that were used for setting rewards. /// /// Emits `RewardCampaignClosed` and/or `RewardCampaignRootClosed` if successful. - #[pallet::weight(T::WeightInfo::close_campaign().saturating_mul((1u64.saturating_add(*merkle_roots_quantity))))] + #[pallet::weight(T::WeightInfo::close_campaign().saturating_mul(1u64.saturating_add(*merkle_roots_quantity)))] #[transactional] pub fn close_campaign(origin: OriginFor, id: CampaignId, merkle_roots_quantity: u64) -> DispatchResult { let who = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); - let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; + let campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; ensure!(who == campaign.creator, Error::::NotCampaignCreator); @@ -1012,7 +1009,7 @@ pub mod pallet { let who = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); - let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; + let campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; ensure!(who == campaign.creator, Error::::NotCampaignCreator); @@ -1069,7 +1066,7 @@ pub mod pallet { T::AdminOrigin::ensure_origin(origin)?; let now = frame_system::Pallet::::block_number(); - let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; + let campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; ensure!(campaign.end > now, Error::::CampaignEnded); @@ -1100,7 +1097,7 @@ pub mod pallet { T::AdminOrigin::ensure_origin(origin)?; let now = frame_system::Pallet::::block_number(); - let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; + let campaign = Self::campaigns(id).ok_or(Error::::CampaignIsNotFound)?; ensure!(campaign.end > now, Error::::CampaignEnded); @@ -1179,7 +1176,7 @@ pub mod pallet { impl Hooks for Pallet { /// Hook that is called every time a new block is finalized. fn on_finalize(block_number: T::BlockNumber) { - for (id, info) in Campaigns::::iter() + for (id, _info) in Campaigns::::iter() .filter(|(_, campaign_info)| campaign_info.end == block_number) .collect::>() { @@ -1394,7 +1391,7 @@ impl Pallet { let mut upgraded_campaign_items = 0; Campaigns::::translate( - |k, campaign_info_v2: CampaignInfoV2, T::BlockNumber>| { + |_k, campaign_info_v2: CampaignInfoV2, T::BlockNumber>| { upgraded_campaign_items += 1; let v3_reward = RewardType::FungibleTokens(FungibleTokenId::NativeToken(0), campaign_info_v2.reward); diff --git a/pallets/reward/src/mock.rs b/pallets/reward/src/mock.rs index 6c045b21b..2fa488767 100644 --- a/pallets/reward/src/mock.rs +++ b/pallets/reward/src/mock.rs @@ -11,9 +11,8 @@ use sp_runtime::{testing::Header, traits::IdentityLookup, MultiSignature, Perbil use auction_manager::*; use core_primitives::NftAssetData; -use primitives::estate::Estate; -use primitives::staking::MetaverseStakingTrait; -use primitives::{Amount, AuctionId, EstateId, FungibleTokenId, ItemId, UndeployedLandBlockId}; + +use primitives::{Amount, AuctionId, FungibleTokenId, ItemId}; use crate as reward; @@ -170,7 +169,7 @@ impl Auction for MockAuctionManager { None } - fn auction_item(id: AuctionId) -> Option> { + fn auction_item(_id: AuctionId) -> Option> { None } @@ -178,7 +177,7 @@ impl Auction for MockAuctionManager { Ok(()) } - fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { + fn update_auction_item(_id: AuctionId, _item_id: ItemId) -> DispatchResult { Ok(()) } @@ -207,11 +206,11 @@ impl Auction for MockAuctionManager { fn remove_auction(_id: u64, _item_id: ItemId) {} - fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { + fn auction_bid_handler(_from: AccountId, _id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } - fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { + fn buy_now_handler(_from: AccountId, _auction_id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } diff --git a/pallets/reward/src/tests.rs b/pallets/reward/src/tests.rs index 96cd76536..7cf44dbcb 100644 --- a/pallets/reward/src/tests.rs +++ b/pallets/reward/src/tests.rs @@ -17,13 +17,13 @@ #![cfg(test)] -use frame_support::{assert_err, assert_noop, assert_ok, sp_runtime::runtime_logger}; +use frame_support::{assert_noop, assert_ok}; use hex_literal::hex; use sp_std::collections::btree_map::BTreeMap; use sp_std::default::Default; use core_primitives::Attributes; -use mock::{Balance, RuntimeEvent, *}; +use mock::{Balance, *}; use primitives::{CampaignInfo, FungibleTokenId, Hash}; use super::*; @@ -235,7 +235,7 @@ fn create_multicurrency_campaign_works() { #[test] fn create_nft_campaign_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); assert_noop!( @@ -270,7 +270,7 @@ fn create_nft_campaign_fails() { #[test] fn create_campaign_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_noop!( Reward::create_campaign( @@ -718,7 +718,7 @@ fn set_reward_root_fails() { #[test] fn set_nft_reward_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_ok!(Reward::add_set_reward_origin(RuntimeOrigin::signed(ALICE), ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); @@ -814,7 +814,7 @@ fn set_nft_reward_fails() { #[test] fn set_nft_reward_root_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_ok!(Reward::add_set_reward_origin(RuntimeOrigin::signed(ALICE), ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); @@ -1397,7 +1397,7 @@ fn claim_reward_root_fails() { #[test] fn claim_nft_reward_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_ok!(Reward::add_set_reward_origin(RuntimeOrigin::signed(ALICE), ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); @@ -1480,7 +1480,7 @@ fn claim_nft_reward_fails() { #[test] fn claim_nft_reward_root_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_ok!(Reward::add_set_reward_origin(RuntimeOrigin::signed(ALICE), ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); @@ -1816,7 +1816,7 @@ fn close_multicurrency_campaign_works() { #[test] fn close_campaign_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_ok!(Reward::create_campaign( RuntimeOrigin::signed(ALICE), BOB, @@ -1869,7 +1869,7 @@ fn close_campaign_fails() { #[test] fn close_campaign_using_merkle_root_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_ok!(Reward::add_set_reward_origin(RuntimeOrigin::signed(ALICE), ALICE)); assert_ok!(Reward::create_campaign( RuntimeOrigin::signed(ALICE), @@ -1951,7 +1951,7 @@ fn close_campaign_using_merkle_root_fails() { #[test] fn close_nft_campaign_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); @@ -2108,7 +2108,7 @@ fn cancel_multicurrency_campaign_works() { #[test] fn cancel_nft_campaign_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); init_test_nft(RuntimeOrigin::signed(ALICE)); @@ -2159,7 +2159,7 @@ fn cancel_nft_campaign_fails() { #[test] fn cancel_campaign_fails() { ExtBuilder::default().build().execute_with(|| { - let campaign_id = 0; + let _campaign_id = 0; assert_ok!(Reward::create_campaign( RuntimeOrigin::signed(ALICE), BOB, diff --git a/runtime/common/src/currencies.rs b/runtime/common/src/currencies.rs index 540aae788..3a9fc1e6e 100644 --- a/runtime/common/src/currencies.rs +++ b/runtime/common/src/currencies.rs @@ -1,24 +1,23 @@ use frame_support::pallet_prelude::Get; use frame_support::traits::{Currency, OriginTrait}; use orml_traits::{BasicCurrency, MultiCurrency as MultiCurrencyTrait}; -use pallet_evm::Context; + use pallet_evm::{ - AddressMapping, ExitRevert, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, - PrecompileResult, PrecompileSet, + ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, PrecompileSet, }; use sp_core::{H160, U256}; -use sp_runtime::traits::{AccountIdConversion, Dispatchable, Zero}; +use sp_runtime::traits::Dispatchable; use sp_std::{marker::PhantomData, prelude::*}; use evm_mapping::AddressMapping as EvmMapping; -use evm_mapping::EvmAddressMapping; + use precompile_utils::data::{Address, EvmData, EvmDataWriter}; use precompile_utils::handle::PrecompileHandleExt; use precompile_utils::modifier::FunctionModifier; use precompile_utils::prelude::RuntimeHelper; use precompile_utils::{succeed, EvmResult}; use primitives::evm::{Erc20Mapping, Output}; -use primitives::{evm, AssetIds, AssetMetadata, Balance, FungibleTokenId}; +use primitives::{AssetIds, Balance, FungibleTokenId}; #[precompile_utils_macro::generate_function_selector] #[derive(Debug, PartialEq)] @@ -114,7 +113,7 @@ where })) } - fn is_precompile(&self, address: H160) -> bool { + fn is_precompile(&self, _address: H160) -> bool { todo!() } } @@ -143,7 +142,7 @@ where if let Some(currency_id) = Runtime::decode_evm_address(address) { log::debug!(target: "evm", "multicurrency: currency id: {:?}", currency_id); - let result = { + let _result = { let selector = match handle.read_selector() { Ok(selector) => selector, Err(e) => return Err(e), @@ -193,7 +192,10 @@ where >, BalanceOf: TryFrom + Into + EvmData, { - fn not_supported(currency_id: FungibleTokenId, handle: &mut impl PrecompileHandle) -> EvmResult { + fn not_supported( + _currency_id: FungibleTokenId, + _handle: &mut impl PrecompileHandle, + ) -> EvmResult { Err(PrecompileFailure::Error { exit_status: pallet_evm::ExitError::Other("not supported".into()), }) diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 7dd0aa42d..56fc2368e 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -16,22 +16,17 @@ // limitations under the License. #![cfg_attr(not(feature = "std"), no_std)] -use codec::Encode; -use cumulus_pallet_parachain_system::CheckAssociatedRelayNumber; use frame_support::{ traits::Get, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use orml_traits::currency::MutationHooks; -use polkadot_parachain::primitives::RelayChainBlockNumber; + use sp_runtime::{FixedPointNumber, FixedU128}; use sp_std::{marker::PhantomData, prelude::*}; use xcm::latest::prelude::*; use xcm_builder::TakeRevenue; -use xcm_executor::{ - traits::{DropAssets, WeightTrader}, - Assets, -}; +use xcm_executor::{traits::WeightTrader, Assets}; use primitives::BuyWeightRate; diff --git a/runtime/common/src/mock.rs b/runtime/common/src/mock.rs index 2fefd33be..dbe1ecb1c 100644 --- a/runtime/common/src/mock.rs +++ b/runtime/common/src/mock.rs @@ -1,22 +1,19 @@ -use std::ptr::hash; - use frame_support::{ construct_runtime, dispatch::DispatchResult, parameter_types, - traits::{AsEnsureOriginWithArg, Everything, Nothing}, + traits::{Everything, Nothing}, weights::Weight, PalletId, }; -use frame_system::{EnsureNever, EnsureRoot}; -use hex_literal::hex; +use frame_system::EnsureRoot; + use orml_traits::parameter_type_with_key; -use pallet_evm::{AddressMapping, PrecompileHandle, PrecompileOutput}; use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, HashedAddressMapping, Precompile, PrecompileSet}; -use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; -use sp_core::{Blake2Hasher, Decode, Encode, Hasher, MaxEncodedLen, H160, H256, U256}; -use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, ConstU32, IdentityLookup, Verify}; +use pallet_evm::{PrecompileHandle, PrecompileOutput}; + +use sp_core::{MaxEncodedLen, H160, H256, U256}; +use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Verify}; use sp_runtime::{AccountId32, DispatchError, MultiSignature, Perbill}; use auction_manager::{Auction, AuctionInfo, AuctionItem, AuctionType, CheckAuctionItemHandler, ListingLevel}; @@ -29,11 +26,10 @@ use primitives::evm::{ CurrencyIdType, Erc20Mapping, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_TOKEN, H160_POSITION_TOKEN_NFT, H160_POSITION_TOKEN_NFT_CLASS_ID_END, }; -use primitives::{Amount, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, ItemId, TokenId}; +use primitives::{Amount, AuctionId, ClassId, FungibleTokenId, ItemId, TokenId}; use crate::currencies::MultiCurrencyPrecompile; use crate::nft::NftPrecompile; -use crate::precompiles::MetaverseNetworkPrecompiles; use super::*; @@ -142,7 +138,7 @@ where } } - fn is_precompile(&self, address: H160) -> bool { + fn is_precompile(&self, _address: H160) -> bool { true } } @@ -311,7 +307,7 @@ impl Auction for MockAuctionManager { None } - fn auction_item(id: AuctionId) -> Option> { + fn auction_item(_id: AuctionId) -> Option> { None } @@ -319,7 +315,7 @@ impl Auction for MockAuctionManager { Ok(()) } - fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { + fn update_auction_item(_id: AuctionId, _item_id: ItemId) -> DispatchResult { Ok(()) } @@ -348,11 +344,11 @@ impl Auction for MockAuctionManager { fn remove_auction(_id: u64, _item_id: ItemId) {} - fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { + fn auction_bid_handler(_from: AccountId, _id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } - fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { + fn buy_now_handler(_from: AccountId, _auction_id: AuctionId, _value: Self::Balance) -> DispatchResult { Ok(()) } diff --git a/runtime/common/src/nft.rs b/runtime/common/src/nft.rs index e10cebea6..e03599195 100644 --- a/runtime/common/src/nft.rs +++ b/runtime/common/src/nft.rs @@ -1,27 +1,25 @@ use core_primitives::{Attributes, CollectionType, NftMetadata, TokenType}; use evm_mapping::AddressMapping as EvmMapping; -use evm_mapping::EvmAddressMapping; + use frame_support::pallet_prelude::Get; use frame_support::traits::{Currency, OriginTrait}; use frame_system::RawOrigin; use orml_traits::{BasicCurrency, MultiCurrency}; use pallet_evm::{ - AddressMapping, ExitRevert, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, - PrecompileResult, PrecompileSet, + ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, PrecompileSet, }; use sp_core::{H160, U256}; use sp_runtime::traits::{AccountIdConversion, Dispatchable}; use sp_runtime::Perbill; use sp_std::{marker::PhantomData, prelude::*}; -use codec::{DecodeAll, Encode}; use precompile_utils::data::{Address, EvmData, EvmDataWriter}; use precompile_utils::handle::PrecompileHandleExt; use precompile_utils::modifier::FunctionModifier; use precompile_utils::prelude::RuntimeHelper; use precompile_utils::{succeed, EvmResult}; use primitives::evm::{Erc20Mapping, Output}; -use primitives::{evm, Balance, ClassId, GroupCollectionId, TokenId}; +use primitives::{ClassId, GroupCollectionId, TokenId}; #[precompile_utils_macro::generate_function_selector] #[derive(Debug, PartialEq)] @@ -119,7 +117,7 @@ where Some(result) } - fn is_precompile(&self, address: H160) -> bool { + fn is_precompile(&self, _address: H160) -> bool { todo!() } } @@ -449,7 +447,7 @@ where let class_id = input.read::>()?.into(); // Build call info - let owner: H160 = input.read::
()?.into(); + let _owner: H160 = input.read::
()?.into(); let who = ::AddressMapping::get_account_id(&handle.context().caller); log::debug!(target: "evm", "withdraw funds from class {:?} fund", class_id); diff --git a/runtime/common/src/precompiles.rs b/runtime/common/src/precompiles.rs index 6774a1d83..6f1ce2d1e 100644 --- a/runtime/common/src/precompiles.rs +++ b/runtime/common/src/precompiles.rs @@ -1,4 +1,4 @@ -use pallet_evm::{ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet}; +use pallet_evm::{Precompile, PrecompileHandle, PrecompileResult, PrecompileSet}; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dispatch::Dispatch; @@ -78,7 +78,7 @@ where } } - fn is_precompile(&self, address: H160) -> bool { + fn is_precompile(&self, _address: H160) -> bool { // sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 400] // .into_iter() // .map(hash) diff --git a/runtime/common/src/tests/currencies.rs b/runtime/common/src/tests/currencies.rs index 8ab5cf926..612cede31 100644 --- a/runtime/common/src/tests/currencies.rs +++ b/runtime/common/src/tests/currencies.rs @@ -1,5 +1,3 @@ -use asset_manager::BalanceOf; -use frame_support::assert_noop; use hex_literal::hex; use sp_core::{H160, U256}; use sp_runtime::traits::Zero; @@ -242,7 +240,7 @@ fn balance_of_native_currencies_works() { .with_balances(vec![(alice_account_id(), 100000)]) .build() .execute_with(|| { - let mut evm_writer = EvmDataWriter::new_with_selector(Action::BalanceOf); + let _evm_writer = EvmDataWriter::new_with_selector(Action::BalanceOf); EvmMapping::claim_default_account(RuntimeOrigin::signed(alice_account_id())); precompiles() .prepare_test( diff --git a/runtime/common/src/tests/nft.rs b/runtime/common/src/tests/nft.rs index e17f1cc46..06701033c 100644 --- a/runtime/common/src/tests/nft.rs +++ b/runtime/common/src/tests/nft.rs @@ -1,23 +1,20 @@ -use frame_support::assert_noop; -use hex_literal::hex; -use sp_core::{ByteArray, H160, U256}; -use sp_runtime::traits::{AccountIdConversion, Zero}; +use sp_core::{ByteArray, U256}; +use sp_runtime::traits::AccountIdConversion; use sp_runtime::Perbill; use sp_std::collections::btree_map::BTreeMap; use precompile_utils::data::{Address, Bytes, EvmDataWriter}; use precompile_utils::testing::*; -use primitives::evm::Output; + use primitives::FungibleTokenId; use crate::mock::*; use crate::nft::Action; -use evm_mapping::AddressMapping as AddressMappingEvm; + use orml_nft::Pallet as NftModule; use orml_traits::BasicCurrency; -use pallet_evm::AddressMapping; -use core_primitives::{Attributes, CollectionType, NftAssetData, NftClassData, NftMetadata, TokenType}; +use core_primitives::{Attributes, CollectionType, NftMetadata, TokenType}; fn precompiles() -> Precompiles { PrecompilesValue::get() From a3b586b92ffa7b7d4c920102da900a3742bcdeee Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 15:34:49 +1300 Subject: [PATCH 029/114] WIP - Implement convert reciept token to native token --- pallets/asset-manager/src/lib.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs index 475dca7bf..e1c1739ed 100644 --- a/pallets/asset-manager/src/lib.rs +++ b/pallets/asset-manager/src/lib.rs @@ -23,7 +23,6 @@ #![allow(clippy::unused_unit)] use frame_support::{ - assert_ok, dispatch::DispatchResult, ensure, pallet_prelude::*, @@ -31,17 +30,14 @@ use frame_support::{ transactional, }; use frame_system::pallet_prelude::*; -use scale_info::prelude::format; use sp_runtime::{traits::One, ArithmeticError, FixedPointNumber, FixedU128}; -use sp_std::{boxed::Box, vec::Vec}; +use sp_std::boxed::Box; use xcm::v3::MultiLocation; use xcm::VersionedMultiLocation; use core_primitives::CurrencyIdManagement; pub use pallet::*; -use primitives::{ - AssetIds, AssetMetadata, BuyWeightRate, CurrencyId, ForeignAssetIdMapping, FungibleTokenId, Ratio, TokenId, -}; +use primitives::{AssetIds, AssetMetadata, BuyWeightRate, ForeignAssetIdMapping, FungibleTokenId, Ratio, TokenId}; mod mock; mod tests; @@ -349,7 +345,7 @@ where } impl CurrencyIdManagement for ForeignAssetMapping { - fn check_token_exist(token_id: FungibleTokenId) -> bool { + fn check_token_exist(_token_id: FungibleTokenId) -> bool { return true; } @@ -360,7 +356,10 @@ impl CurrencyIdManagement for ForeignAssetMapping { } } - fn convert_to_currency(currency_id: FungibleTokenId) -> Result { - todo!() + fn convert_to_currency(_currency_id: FungibleTokenId) -> Result { + match currency_id { + FungibleTokenId::FungibleToken(token_id) => Ok(FungibleTokenId::NativeToken(token_id)), + _ => Err(()), + } } } From 189fb2ce03583624f43d7c57195e3c05fe26ae5a Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 15:56:30 +1300 Subject: [PATCH 030/114] WIP - Fix build issue on wrong currency_id --- pallets/asset-manager/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs index e1c1739ed..7a793cb42 100644 --- a/pallets/asset-manager/src/lib.rs +++ b/pallets/asset-manager/src/lib.rs @@ -356,7 +356,7 @@ impl CurrencyIdManagement for ForeignAssetMapping { } } - fn convert_to_currency(_currency_id: FungibleTokenId) -> Result { + fn convert_to_currency(currency_id: FungibleTokenId) -> Result { match currency_id { FungibleTokenId::FungibleToken(token_id) => Ok(FungibleTokenId::NativeToken(token_id)), _ => Err(()), From d693ce30cdc3741ed18ef8494fda852a5dcd84d7 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Nov 2023 17:51:24 +1300 Subject: [PATCH 031/114] WIP - Fix build issue from suggestions --- node/src/chain_spec/metaverse.rs | 10 +++------- node/src/cli.rs | 1 - node/src/command.rs | 11 +++++------ node/src/rpc/rpc_metaverse.rs | 12 ++++++------ node/src/service/metaverse.rs | 2 +- 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/node/src/chain_spec/metaverse.rs b/node/src/chain_spec/metaverse.rs index 64d0d0010..1c88f618a 100644 --- a/node/src/chain_spec/metaverse.rs +++ b/node/src/chain_spec/metaverse.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::str::FromStr; use hex_literal::hex; @@ -8,16 +7,13 @@ use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::crypto::UncheckedInto; use sp_core::{sr25519, Pair, Public, H160, U256}; use sp_finality_grandpa::AuthorityId as GrandpaId; -use sp_runtime::{ - traits::{IdentifyAccount, Verify}, - Perbill, -}; +use sp_runtime::traits::{IdentifyAccount, Verify}; use metaverse_runtime::{ constants::currency::*, opaque::SessionKeys, wasm_binary_unwrap, AccountId, AuraConfig, BalancesConfig, BaseFeeConfig, CollatorSelectionConfig, DemocracyConfig, EVMConfig, EstateConfig, EthereumConfig, GenesisAccount, - GenesisConfig, GrandpaConfig, MintingRange, MintingRateInfo, OracleMembershipConfig, SessionConfig, Signature, - SudoConfig, SystemConfig, + GenesisConfig, GrandpaConfig, MintingRateInfo, OracleMembershipConfig, SessionConfig, Signature, SudoConfig, + SystemConfig, }; use primitives::Balance; diff --git a/node/src/cli.rs b/node/src/cli.rs index 53d21dab7..10454a8e5 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -5,7 +5,6 @@ use cumulus_client_cli; use url::Url; use crate::chain_spec; -use crate::chain_spec::Extensions; fn validate_relay_chain_url(arg: &str) -> Result<(), String> { let url = Url::parse(arg).map_err(|e| e.to_string())?; diff --git a/node/src/command.rs b/node/src/command.rs index 41a6fc8a6..6c77ee62c 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -15,21 +15,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{io::Write, net::SocketAddr, sync::Arc}; +use std::{io::Write, net::SocketAddr}; use codec::Encode; use cumulus_client_cli::generate_genesis_block; -use cumulus_primitives_core::ParaId; + use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; use sc_cli::{ - ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams, Result, Role, + ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams, Result, RuntimeVersion, SharedParams, SubstrateCli, }; use sc_service::config::{BasePath, PrometheusConfig}; use sc_service::PartialComponents; use sp_core::hexdisplay::HexDisplay; -use sp_runtime::traits::{AccountIdConversion, Block as BlockT}; +use sp_runtime::traits::Block as BlockT; #[cfg(feature = "with-continuum-runtime")] use continuum_runtime::RuntimeApi; @@ -41,12 +41,11 @@ use pioneer_runtime::RuntimeApi; use crate::service::{continuum_partial, ContinuumParachainRuntimeExecutor}; #[cfg(feature = "with-pioneer-runtime")] use crate::service::{pioneer_partial, ParachainRuntimeExecutor}; -use crate::service::{CONTINUUM_RUNTIME_NOT_AVAILABLE, METAVERSE_RUNTIME_NOT_AVAILABLE, PIONEER_RUNTIME_NOT_AVAILABLE}; +use crate::service::{CONTINUUM_RUNTIME_NOT_AVAILABLE, PIONEER_RUNTIME_NOT_AVAILABLE}; use crate::{ chain_spec, cli::{Cli, RelayChainCli, Subcommand}, service, - service::ExecutorDispatch, }; fn load_spec(id: &str) -> std::result::Result, String> { diff --git a/node/src/rpc/rpc_metaverse.rs b/node/src/rpc/rpc_metaverse.rs index f46faed58..b4113a02e 100644 --- a/node/src/rpc/rpc_metaverse.rs +++ b/node/src/rpc/rpc_metaverse.rs @@ -6,12 +6,12 @@ use fc_rpc::{ EthBlockDataCacheTask, OverrideHandle, RuntimeApiStorageOverride, SchemaV1Override, SchemaV2Override, SchemaV3Override, StorageOverride, }; -use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; +use fc_rpc_core::types::{FeeHistoryCache, FilterPool}; use fp_storage::EthereumStorageSchema; -use jsonrpc_pubsub::manager::SubscriptionManager; + use jsonrpsee::RpcModule; use pallet_transaction_payment_rpc; -use sc_cli::SubstrateCli; + // Substrate use sc_client_api::{ backend::{AuxStore, Backend, StateBackend, StorageProvider}, @@ -23,7 +23,7 @@ use sc_transaction_pool::{ChainApi, Pool}; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; -use sp_blockchain::{Backend as BlockchainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata}; +use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_runtime::traits::BlakeTwo256; use substrate_frame_rpc_system::{System, SystemApiServer}; @@ -138,8 +138,8 @@ where A: ChainApi + 'static, { use fc_rpc::{ - Eth, EthApiServer, EthDevSigner, EthFilter, EthFilterApiServer, EthPubSub, EthPubSubApiServer, EthSigner, Net, - NetApiServer, Web3, Web3ApiServer, + Eth, EthApiServer, EthFilter, EthFilterApiServer, EthPubSub, EthPubSubApiServer, Net, NetApiServer, Web3, + Web3ApiServer, }; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use substrate_frame_rpc_system::SystemApiServer; diff --git a/node/src/service/metaverse.rs b/node/src/service/metaverse.rs index 2dacd366b..1c4c478d1 100644 --- a/node/src/service/metaverse.rs +++ b/node/src/service/metaverse.rs @@ -14,7 +14,7 @@ use fc_consensus::FrontierBlockImport; use fc_rpc::EthTask; use fc_rpc_core::types::{FeeHistoryCache, FilterPool}; use futures::StreamExt; -use sc_client_api::{BlockBackend, BlockchainEvents, ExecutorProvider}; +use sc_client_api::{BlockBackend, BlockchainEvents}; use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams}; use sc_executor::NativeElseWasmExecutor; use sc_finality_grandpa::SharedVoterState; From 15f53037f7d77442cac91804a0ab69cc25f62031 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 13 Nov 2023 16:00:31 +1300 Subject: [PATCH 032/114] WIP - Unit test - setting up mock testing --- Cargo.lock | 1 + pallets/spp/Cargo.toml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a3200878..f6a4f98e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8028,6 +8028,7 @@ dependencies = [ "auction-manager", "bit-country-primitives", "core-primitives", + "currencies", "frame-benchmarking", "frame-support", "frame-system", diff --git a/pallets/spp/Cargo.toml b/pallets/spp/Cargo.toml index bb0c5c93e..e6009caf7 100644 --- a/pallets/spp/Cargo.toml +++ b/pallets/spp/Cargo.toml @@ -31,7 +31,7 @@ orml-tokens = { workspace = true } # local packages core-primitives = { path = "../../traits/core-primitives", default-features = false } primitives = { package = "bit-country-primitives", path = "../../primitives/metaverse", default-features = false } - +currencies = { package = "currencies", path = "../currencies", default-features = false } [dependencies.auction-manager] default-features = false @@ -61,6 +61,7 @@ std = [ 'sp-io/std', 'pallet-balances/std', 'auction-manager/std', - # 'pallet-nft/std', - 'frame-benchmarking/std' + 'frame-benchmarking/std', + 'orml-tokens/std', + "currencies/std" ] \ No newline at end of file From 656926b8dd3a144f74964f8ef78eef19f1e513c0 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 13 Nov 2023 16:00:54 +1300 Subject: [PATCH 033/114] WIP - Unit test - implement all mock pallet to start writing unit test --- pallets/nft/src/lib.rs | 2 - pallets/spp/src/lib.rs | 13 -- pallets/spp/src/mock.rs | 454 ++++------------------------------------ 3 files changed, 45 insertions(+), 424 deletions(-) diff --git a/pallets/nft/src/lib.rs b/pallets/nft/src/lib.rs index d4333af59..b45d145e2 100644 --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -37,7 +37,6 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use orml_nft::{ClassInfo, ClassInfoOf, Classes, Pallet as NftModule, TokenInfo, TokenInfoOf, TokenMetadataOf, Tokens}; - use sp_runtime::traits::Saturating; use sp_runtime::Perbill; use sp_runtime::{ @@ -74,7 +73,6 @@ pub enum StorageVersion { #[frame_support::pallet] pub mod pallet { - use orml_traits::{MultiCurrency, MultiCurrencyExtended}; use sp_runtime::traits::{CheckedSub, IdentifyAccount, Verify}; use sp_runtime::ArithmeticError; diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 88470592a..323cc4216 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -27,7 +27,6 @@ use frame_support::{ use frame_system::ensure_signed; use frame_system::pallet_prelude::*; use orml_traits::MultiCurrency; - use sp_runtime::traits::{CheckedAdd, CheckedSub}; use sp_runtime::{ traits::{AccountIdConversion, Convert, Saturating, Zero}, @@ -36,7 +35,6 @@ use sp_runtime::{ use core_primitives::*; pub use pallet::*; - use primitives::{ClassId, FungibleTokenId, StakingRound, TokenId}; pub use weights::WeightInfo; @@ -76,14 +74,6 @@ pub mod pallet { pub trait Config: frame_system::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Land treasury source - #[pallet::constant] - type LandTreasury: Get; - - /// Source of metaverse info - type MetaverseInfoSource: MetaverseTrait; - /// Currency type type Currency: Currency + ReservableCurrency; /// Multi currencies type that handles different currency type in auction @@ -104,9 +94,6 @@ pub mod pallet { #[pallet::constant] type RewardPaymentDelay: Get; - /// NFT trait required for land and estate tokenization - type NFTTokenizationSource: NFTTrait, ClassId = ClassId, TokenId = TokenId>; - /// Default max bound for each metaverse mapping system, this could change through proposal type DefaultMaxBound: Get<(i32, i32)>; diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index 3856a489b..04adc6cce 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -1,7 +1,9 @@ #![cfg(test)] +use frame_support::traits::Nothing; use frame_support::{construct_runtime, ord_parameter_types, parameter_types, PalletId}; use frame_system::EnsureSignedBy; +use orml_traits::parameter_type_with_key; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -15,10 +17,11 @@ use sp_std::vec::Vec; use auction_manager::{Auction, AuctionInfo, AuctionItem, AuctionType, CheckAuctionItemHandler, ListingLevel}; use core_primitives::{CollectionType, NftClassData, TokenType}; use primitives::{ - AssetId, Attributes, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, NftMetadata, TokenId, LAND_CLASS_ID, + Amount, AssetId, Attributes, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, NftMetadata, TokenId, + LAND_CLASS_ID, }; -use crate as estate; +use crate as spp; use super::*; @@ -48,7 +51,6 @@ pub const COORDINATE_IN_4: (i32, i32) = (-4, 8); pub const COORDINATE_OUT: (i32, i32) = (0, 101); pub const COORDINATE_IN_AUCTION: (i32, i32) = (-4, 7); pub const ESTATE_IN_AUCTION: EstateId = 3; -pub const UNDEPLOYED_LAND_BLOCK_IN_AUCTION: UndeployedLandBlockId = 1; pub const BOND_AMOUNT_1: Balance = 1000; pub const BOND_AMOUNT_2: Balance = 2000; @@ -67,12 +69,6 @@ pub const METAVERSE_LAND_IN_AUCTION_TOKEN: TokenId = 4; pub const METAVERSE_ESTATE_CLASS: ClassId = 16; pub const METAVERSE_ESTATE_IN_AUCTION_TOKEN: TokenId = 3; -pub const OWNER_ACCOUNT_ID: OwnerId = OwnerId::Account(BENEFICIARY_ID); -pub const OWNER_ID_ALICE: OwnerId = OwnerId::Account(ALICE); -pub const OWNER_LAND_ASSET_ID: OwnerId = OwnerId::Token(METAVERSE_LAND_CLASS, ASSET_ID_1); -pub const OWNER_ESTATE_ASSET_ID: OwnerId = - OwnerId::Token(METAVERSE_ESTATE_CLASS, ASSET_ID_2); - pub const GENERAL_METAVERSE_FUND: AccountId = 102; ord_parameter_types! { @@ -136,405 +132,50 @@ impl pallet_balances::Config for Runtime { parameter_types! { pub const GetNativeCurrencyId: FungibleTokenId = FungibleTokenId::NativeToken(0); - pub const MiningCurrencyId: FungibleTokenId = FungibleTokenId::MiningResource(0); + pub const LandTreasuryPalletId: PalletId = PalletId(*b"bit/land"); pub const LandTreasuryPalletId: PalletId = PalletId(*b"bit/land"); pub const MinimumLandPrice: Balance = 10 * DOLLARS; } -pub struct MetaverseInfoSource {} - -impl MetaverseTrait for MetaverseInfoSource { - fn create_metaverse(who: &AccountId, metadata: MetaverseMetadata) -> MetaverseId { - 1u64 - } - - fn check_ownership(who: &AccountId, metaverse_id: &MetaverseId) -> bool { - match *who { - ALICE => *metaverse_id == ALICE_METAVERSE_ID, - BOB => *metaverse_id == BOB_METAVERSE_ID, - _ => false, - } - } - - fn get_metaverse(_metaverse_id: u64) -> Option> { - None - } - - fn get_metaverse_token(_metaverse_id: u64) -> Option { - None - } - - fn update_metaverse_token(_metaverse_id: u64, _currency_id: FungibleTokenId) -> Result<(), DispatchError> { - Ok(()) - } - - fn get_metaverse_land_class(metaverse_id: MetaverseId) -> Result { - Ok(METAVERSE_LAND_CLASS) - } - - fn get_metaverse_estate_class(metaverse_id: MetaverseId) -> Result { - Ok(METAVERSE_ESTATE_CLASS) - } - - fn get_metaverse_marketplace_listing_fee(metaverse_id: MetaverseId) -> Result { - Ok(Perbill::from_percent(1u32)) - } - - fn get_metaverse_treasury(metaverse_id: MetaverseId) -> AccountId { - GENERAL_METAVERSE_FUND - } - - fn get_network_treasury() -> AccountId { - GENERAL_METAVERSE_FUND - } - - fn check_if_metaverse_estate( - metaverse_id: primitives::MetaverseId, - class_id: &ClassId, - ) -> Result { - if class_id == &METAVERSE_LAND_CLASS || class_id == &METAVERSE_ESTATE_CLASS { - return Ok(true); - } - return Ok(false); - } - - fn check_if_metaverse_has_any_land(_metaverse_id: primitives::MetaverseId) -> Result { - Ok(true) - } - - fn is_metaverse_owner(who: &AccountId) -> bool { - true - } -} - -pub struct MockAuctionManager; - -impl Auction for MockAuctionManager { - type Balance = Balance; - - fn auction_info(_id: u64) -> Option> { - None - } - - fn auction_item(id: AuctionId) -> Option> { - None - } - - fn update_auction(_id: u64, _info: AuctionInfo) -> DispatchResult { - Ok(()) - } - - fn update_auction_item(id: AuctionId, item_id: ItemId) -> DispatchResult { - Ok(()) - } - - fn new_auction( - _recipient: u128, - _initial_amount: Self::Balance, - _start: u64, - _end: Option, - ) -> Result { - Ok(1) - } - - fn create_auction( - _auction_type: AuctionType, - _item_id: ItemId, - _end: Option, - _recipient: u128, - _initial_amount: Self::Balance, - _start: u64, - _listing_level: ListingLevel, - _listing_fee: Perbill, - _currency_id: FungibleTokenId, - ) -> Result { - Ok(1) - } - - fn remove_auction(_id: u64, _item_id: ItemId) {} - - fn auction_bid_handler(from: AccountId, id: AuctionId, value: Self::Balance) -> DispatchResult { - Ok(()) - } - - fn buy_now_handler(from: AccountId, auction_id: AuctionId, value: Self::Balance) -> DispatchResult { - Ok(()) - } - - fn local_auction_bid_handler( - _now: u64, - _id: u64, - _new_bid: (u128, Self::Balance), - _last_bid: Option<(u128, Self::Balance)>, - _social_currency_id: FungibleTokenId, - ) -> DispatchResult { - Ok(()) - } - - fn collect_royalty_fee( - _high_bid_price: &Self::Balance, - _high_bidder: &u128, - _asset_id: &(u32, u64), - _social_currency_id: FungibleTokenId, - ) -> DispatchResult { - Ok(()) - } -} - -impl CheckAuctionItemHandler for MockAuctionManager { - fn check_item_in_auction(item_id: ItemId) -> bool { - match item_id { - ItemId::NFT(METAVERSE_LAND_CLASS, METAVERSE_LAND_IN_AUCTION_TOKEN) => { - return true; - } - ItemId::NFT(METAVERSE_ESTATE_CLASS, METAVERSE_ESTATE_IN_AUCTION_TOKEN) => { - return true; - } - ItemId::UndeployedLandBlock(UNDEPLOYED_LAND_BLOCK_IN_AUCTION) => { - return true; - } - _ => { - return false; - } - } - } -} - fn test_attributes(x: u8) -> Attributes { let mut attr: Attributes = BTreeMap::new(); attr.insert(vec![x, x + 5], vec![x, x + 10]); attr } -pub struct MockNFTHandler; - -impl NFTTrait for MockNFTHandler { - type TokenId = TokenId; - type ClassId = ClassId; - - fn check_ownership(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Result { - let nft_value = *asset_id; - if (*who == ALICE && (nft_value.1 == 1 || nft_value.1 == 3)) - || (*who == BOB && (nft_value.1 == 2 || nft_value.1 == 4)) - || (*who == BENEFICIARY_ID && (nft_value.1 == 100 || nft_value.1 == 101)) - | (*who == AUCTION_BENEFICIARY_ID - && (nft_value.1 == METAVERSE_ESTATE_IN_AUCTION_TOKEN - || nft_value.1 == METAVERSE_LAND_IN_AUCTION_TOKEN)) - { - return Ok(true); - } - Ok(false) - } - - fn check_collection_and_class( - collection_id: GroupCollectionId, - class_id: Self::ClassId, - ) -> Result { - if class_id == ASSET_CLASS_ID && collection_id == ASSET_COLLECTION_ID { - return Ok(true); - } - Ok(false) - } - fn get_nft_group_collection(nft_collection: &Self::ClassId) -> Result { - Ok(ASSET_COLLECTION_ID) - } - - fn is_stackable(asset_id: (Self::ClassId, Self::TokenId)) -> Result { - Ok(false) - } - - fn create_token_class( - sender: &AccountId, - metadata: NftMetadata, - attributes: Attributes, - collection_id: GroupCollectionId, - token_type: TokenType, - collection_type: CollectionType, - royalty_fee: Perbill, - mint_limit: Option, - ) -> Result { - match *sender { - ALICE => { - if collection_id == 0 { - Ok(0) - } else if collection_id == 1 { - Ok(1) - } else { - Ok(2) - } - } - BOB => Ok(3), - BENEFICIARY_ID => Ok(ASSET_CLASS_ID), - _ => Ok(100), - } - } - - fn mint_token( - sender: &AccountId, - class_id: ClassId, - metadata: NftMetadata, - attributes: Attributes, - ) -> Result { - match *sender { - ALICE => Ok(1), - BOB => Ok(2), - BENEFICIARY_ID => { - if class_id == METAVERSE_LAND_CLASS { - return Ok(ASSET_ID_1); - } else if class_id == METAVERSE_ESTATE_CLASS { - return Ok(ASSET_ID_2); - } else { - return Ok(200); - } - } - AUCTION_BENEFICIARY_ID => { - if class_id == METAVERSE_LAND_CLASS { - return Ok(METAVERSE_LAND_IN_AUCTION_TOKEN); - } else if class_id == METAVERSE_ESTATE_CLASS { - return Ok(METAVERSE_ESTATE_IN_AUCTION_TOKEN); - } else { - return Ok(201); - } - } - _ => { - if class_id == 0 { - return Ok(1000); - } else { - return Ok(1001); - } - } - } - } - - fn transfer_nft(from: &AccountId, to: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { - Ok(()) - } - - fn check_item_on_listing(class_id: Self::ClassId, token_id: Self::TokenId) -> Result { - Ok(true) - } - - fn burn_nft(account: &AccountId, nft: &(Self::ClassId, Self::TokenId)) -> DispatchResult { - Ok(()) - } - fn is_transferable(nft: &(Self::ClassId, Self::TokenId)) -> Result { - Ok(true) - } - - fn get_class_fund(class_id: &Self::ClassId) -> AccountId { - CLASS_FUND_ID - } - - fn get_nft_detail(asset_id: (Self::ClassId, Self::TokenId)) -> Result, DispatchError> { - let new_data = NftClassData { - deposit: 0, - attributes: test_attributes(1), - token_type: TokenType::Transferable, - collection_type: CollectionType::Collectable, - is_locked: false, - royalty_fee: Perbill::from_percent(0u32), - mint_limit: None, - total_minted_tokens: 0u32, - }; - Ok(new_data) - } - - fn set_lock_collection(class_id: Self::ClassId, is_locked: bool) -> sp_runtime::DispatchResult { - Ok(()) - } - - fn set_lock_nft(token_id: (Self::ClassId, Self::TokenId), is_locked: bool) -> sp_runtime::DispatchResult { - Ok(()) - } - - fn get_nft_class_detail(_class_id: Self::ClassId) -> Result, DispatchError> { - let new_data = NftClassData { - deposit: 0, - attributes: test_attributes(1), - token_type: TokenType::Transferable, - collection_type: CollectionType::Collectable, - is_locked: false, - royalty_fee: Perbill::from_percent(0u32), - mint_limit: None, - total_minted_tokens: 0u32, - }; - Ok(new_data) - } - - fn get_total_issuance(class_id: Self::ClassId) -> Result { - Ok(10u64) - } - - fn get_asset_owner(asset_id: &(Self::ClassId, Self::TokenId)) -> Result { - Ok(ALICE) - } - - fn mint_token_with_id( - sender: &AccountId, - class_id: Self::ClassId, - token_id: Self::TokenId, - metadata: core_primitives::NftMetadata, - attributes: core_primitives::Attributes, - ) -> Result { - match *sender { - ALICE => Ok(1), - BOB => Ok(2), - BENEFICIARY_ID => { - if class_id == METAVERSE_LAND_CLASS { - return Ok(ASSET_ID_1); - } else if class_id == METAVERSE_ESTATE_CLASS { - return Ok(ASSET_ID_2); - } else { - return Ok(200); - } - } - AUCTION_BENEFICIARY_ID => { - if class_id == METAVERSE_LAND_CLASS { - return Ok(METAVERSE_LAND_IN_AUCTION_TOKEN); - } else if class_id == METAVERSE_ESTATE_CLASS { - return Ok(METAVERSE_ESTATE_IN_AUCTION_TOKEN); - } else { - return Ok(201); - } - } - _ => { - if class_id == 0 { - return Ok(1000); - } else { - return Ok(1001); - } - } - } - } +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: FungibleTokenId| -> Balance { + Default::default() + }; +} - fn get_free_stackable_nft_balance(who: &AccountId, asset_id: &(Self::ClassId, Self::TokenId)) -> Balance { - 1000 - } +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = FungibleTokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type ReserveIdentifier = [u8; 8]; + type MaxReserves = (); + type DustRemovalWhitelist = Nothing; +} - fn reserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, - ) -> DispatchResult { - Ok(()) - } +pub type AdaptedBasicCurrency = currencies::BasicCurrencyAdapter; - fn unreserve_stackable_nft_balance( - who: &AccountId, - asset_id: &(Self::ClassId, Self::TokenId), - amount: Balance, - ) -> sp_runtime::DispatchResult { - Ok(()) - } +parameter_types! { + pub const NativeCurrencyId: FungibleTokenId = FungibleTokenId::NativeToken(0); + pub const MiningCurrencyId: FungibleTokenId = FungibleTokenId::MiningResource(0); +} - fn transfer_stackable_nft( - sender: &AccountId, - to: &AccountId, - nft: &(Self::ClassId, Self::TokenId), - amount: Balance, - ) -> sp_runtime::DispatchResult { - Ok(()) - } +impl currencies::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MultiSocialCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = NativeCurrencyId; + type WeightInfo = (); } parameter_types! { @@ -553,25 +194,18 @@ parameter_types! { impl Config for Runtime { type RuntimeEvent = RuntimeEvent; - type LandTreasury = LandTreasuryPalletId; - type MetaverseInfoSource = MetaverseInfoSource; type Currency = Balances; - type MinimumLandPrice = MinimumLandPrice; - type CouncilOrigin = EnsureSignedBy; - type AuctionHandler = MockAuctionManager; - type MinBlocksPerRound = MinBlocksPerRound; type WeightInfo = (); type MinimumStake = MinimumStake; type RewardPaymentDelay = RewardPaymentDelay; - type NFTTokenizationSource = MockNFTHandler; type DefaultMaxBound = DefaultMaxBound; type NetworkFee = NetworkFee; - type MaxOffersPerEstate = MaxOffersPerEstate; - type MinLeasePricePerBlock = MinLeasePricePerBlock; - type MaxLeasePeriod = MaxLeasePeriod; - type LeaseOfferExpiryPeriod = LeaseOfferExpiryPeriod; type BlockNumberToBalance = ConvertInto; type StorageDepositFee = StorageDepositFee; + type MultiCurrency = Currencies; + type PoolAccount = (); + type MaximumQueue = (); + type CurrencyIdConversion = (); } construct_runtime!( @@ -582,11 +216,13 @@ construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Estate: estate:: {Pallet, Call, Storage, Event}, + Currencies: currencies::{ Pallet, Storage, Call, Event}, + Tokens: orml_tokens::{Pallet, Call, Storage, Config, Event}, + Spp: spp:: {Pallet, Call, Storage, Event}, } ); -pub type EstateModule = Pallet; +pub type SppModule = Pallet; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -625,9 +261,9 @@ pub fn last_event() -> RuntimeEvent { } fn next_block() { - EstateModule::on_finalize(System::block_number()); + SppModule::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); - EstateModule::on_initialize(System::block_number()); + SppModule::on_initialize(System::block_number()).unwrap(); } pub fn run_to_block(n: u64) { From 1f80c1dc15da5dde6147900b5b125f0e42cc0b78 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 13 Nov 2023 16:01:23 +1300 Subject: [PATCH 034/114] WIP - Unit test - create the first example test to ensure the compilation success --- pallets/spp/src/tests.rs | 2882 +------------------------------------- 1 file changed, 2 insertions(+), 2880 deletions(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index d597baa73..95eef7654 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -24,2885 +24,7 @@ use mock::{RuntimeEvent, *}; use super::*; -fn estate_sub_account(estate_id: mock::EstateId) -> AccountId { - ::LandTreasury::get().into_sub_account_truncating(estate_id) -} - -#[test] -fn mint_land_should_reject_non_root() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::mint_land( - RuntimeOrigin::signed(ALICE), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - ), - BadOrigin - ); - }); -} - -#[test] -fn mint_land_should_work_with_one_coordinate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( - OWNER_LAND_ASSET_ID, - METAVERSE_ID, - COORDINATE_IN_1, - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 1); - }); -} - -#[test] -fn mint_land_token_should_work_have_correct_owner() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), None); - - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( - OWNER_LAND_ASSET_ID, - METAVERSE_ID, - COORDINATE_IN_1, - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 1); - - assert_eq!( - EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), - Some(OWNER_LAND_ASSET_ID) - ); - }); -} - -#[test] -fn mint_land_should_reject_with_duplicate_coordinates() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( - OWNER_LAND_ASSET_ID, - METAVERSE_ID, - COORDINATE_IN_1, - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 1); - assert_noop!( - EstateModule::mint_land(RuntimeOrigin::root(), BENEFICIARY_ID, METAVERSE_ID, COORDINATE_IN_1), - Error::::LandUnitIsNotAvailable - ); - }); -} - -#[test] -fn mint_lands_should_reject_with_duplicate_coordinates() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_lands( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandsMinted( - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2], - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 2); - assert_noop!( - EstateModule::mint_lands( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - ), - Error::::LandUnitIsNotAvailable - ); - }); -} - -#[test] -fn mint_land_should_work_with_different_coordinate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( - OWNER_LAND_ASSET_ID, - METAVERSE_ID, - COORDINATE_IN_1, - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 1); - - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_2 - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandUnitMinted( - OWNER_LAND_ASSET_ID, - METAVERSE_ID, - COORDINATE_IN_2, - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 2); - }); -} - -#[test] -fn mint_lands_should_reject_non_root() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::mint_lands( - RuntimeOrigin::signed(ALICE), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - ), - BadOrigin - ); - }); -} - -#[test] -fn mint_lands_should_work_with_one_coordinate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_lands( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - )); - - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 1 - ); - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandsMinted( - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1], - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 1); - }); -} - -#[test] -fn mint_lands_should_work_with_more_than_one_coordinate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_lands( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::NewLandsMinted( - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2], - )) - ); - - assert_eq!(EstateModule::all_land_units_count(), 2); - }); -} - -#[test] -fn transfer_land_token_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - assert_eq!( - EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), - Some(OWNER_LAND_ASSET_ID) - ); - - assert_ok!(EstateModule::transfer_land( - RuntimeOrigin::signed(BENEFICIARY_ID), - ALICE, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_eq!( - EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), - Some(OWNER_LAND_ASSET_ID) - ); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::TransferredLandUnit( - METAVERSE_ID, - COORDINATE_IN_1, - BENEFICIARY_ID, - ALICE, - )) - ); - }); -} - -#[test] -fn transfer_land_should_reject_no_permission() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_eq!( - EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), - Some(OWNER_LAND_ASSET_ID) - ); - - assert_noop!( - EstateModule::transfer_land(RuntimeOrigin::signed(BOB), ALICE, METAVERSE_ID, COORDINATE_IN_1), - Error::::NoPermission - ); - }); -} - -#[test] -fn transfer_land_should_do_fail_for_same_account() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_eq!( - EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), - Some(OWNER_LAND_ASSET_ID) - ); - - assert_noop!( - EstateModule::transfer_land( - RuntimeOrigin::signed(BENEFICIARY_ID), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - ), - Error::::AlreadyOwnTheLandUnit - ); - - assert_eq!( - EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_1), - Some(OWNER_LAND_ASSET_ID) - ); - }); -} - -#[test] -fn transfer_land_should_do_fail_for_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - AUCTION_BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_AUCTION - )); - assert_eq!( - EstateModule::get_land_units(METAVERSE_ID, COORDINATE_IN_AUCTION), - Some(OwnerId::Token(METAVERSE_LAND_CLASS, METAVERSE_LAND_IN_AUCTION_TOKEN)) - ); - - assert_noop!( - EstateModule::transfer_land( - RuntimeOrigin::signed(AUCTION_BENEFICIARY_ID), - BOB, - METAVERSE_ID, - COORDINATE_IN_AUCTION - ), - Error::::LandUnitAlreadyInAuction - ); - }); -} - -#[test] -fn mint_estate_should_reject_non_root() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::mint_estate( - RuntimeOrigin::signed(ALICE), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - ), - BadOrigin - ); - }); -} - -#[test] -fn mint_estate_should_fail_for_minted_land() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_err!( - EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - ), - Error::::LandUnitIsNotAvailable - ); - }); -} - -#[test] -fn dissolve_estate_should_work() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 2 - ); - - // Destroy estate - assert_ok!(EstateModule::dissolve_estate( - RuntimeOrigin::signed(BENEFICIARY_ID), - estate_id, - )); - - assert_eq!(EstateModule::all_estates_count(), 0); - assert_eq!(EstateModule::get_estates(estate_id), None); - assert_eq!(EstateModule::get_estate_owner(estate_id), None); - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 2 - ); - }); -} - -#[test] -fn dissolve_estate_should_reject_non_owner() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_err!( - EstateModule::dissolve_estate(RuntimeOrigin::signed(ALICE), 0), - Error::::NoPermission - ); - }); -} - -#[test] -fn add_land_unit_to_estate_should_reject_non_owner() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_2] - )); - - assert_err!( - EstateModule::add_land_unit_to_estate(RuntimeOrigin::signed(ALICE), 0, vec![COORDINATE_IN_2]), - Error::::NoPermission - ); - }); -} - -#[test] -fn add_land_unit_to_estate_should_work() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1] - }) - ); - - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 1 - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - assert_eq!(EstateModule::all_land_units_count(), 1); - - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_2 - )); - // Update estate - assert_ok!(EstateModule::add_land_unit_to_estate( - RuntimeOrigin::signed(BENEFICIARY_ID), - estate_id, - vec![COORDINATE_IN_2] - )); - - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 2 - ); - assert_eq!(EstateModule::all_land_units_count(), 2); - }); -} - -#[test] -fn remove_land_unit_from_estate_should_reject_non_owner() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_err!( - EstateModule::remove_land_unit_from_estate(RuntimeOrigin::signed(ALICE), 0, vec![COORDINATE_IN_2]), - Error::::NoPermission - ); - }); -} - -#[test] -fn remove_land_unit_from_estate_should_work() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 2 - ); - assert_eq!(EstateModule::all_land_units_count(), 2); - - // Update estate - assert_ok!(EstateModule::remove_land_unit_from_estate( - RuntimeOrigin::signed(BENEFICIARY_ID), - estate_id, - vec![COORDINATE_IN_2] - )); - - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1] - }) - ); - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 2 - ); - assert_eq!(EstateModule::all_land_units_count(), 2); - }); -} - -#[test] -fn mint_estate_and_land_should_return_correct_total_land_unit() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 2 - ); - - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - (-6, 6) - )); - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 3 - ); - }); -} - -#[test] -fn mint_estate_should_return_none_for_non_exist_estate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - let estate_id_non_exists: u64 = 999; - assert_eq!(EstateModule::get_estates(estate_id_non_exists), None); - assert_eq!(EstateModule::get_estate_owner(estate_id_non_exists), None); - }); -} - -#[test] -fn transfer_estate_token_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - assert_ok!(EstateModule::transfer_estate( - RuntimeOrigin::signed(BENEFICIARY_ID), - ALICE, - estate_id - )); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::TransferredEstate(estate_id, BENEFICIARY_ID, ALICE)) - ); - }); -} - -#[test] -fn transfer_estate_should_reject_no_permission() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - assert_noop!( - EstateModule::transfer_estate(RuntimeOrigin::signed(BOB), ALICE, estate_id), - Error::::NoPermission - ); - }); -} - -#[test] -fn transfer_estate_should_reject_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - )); - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_2] - )); - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_3] - )); - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - AUCTION_BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_AUCTION] - )); - assert_noop!( - EstateModule::transfer_estate(RuntimeOrigin::signed(AUCTION_BENEFICIARY_ID), ALICE, ESTATE_IN_AUCTION), - Error::::EstateAlreadyInAuction - ); - }); -} - -#[test] -fn transfer_estate_should_fail_with_same_account() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - assert_noop!( - EstateModule::transfer_estate(RuntimeOrigin::signed(BENEFICIARY_ID), BENEFICIARY_ID, estate_id), - Error::::AlreadyOwnTheEstate - ); - - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - }); -} - -#[test] -fn create_estate_token_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_lands( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_estate( - RuntimeOrigin::signed(BENEFICIARY_ID), - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - assert_eq!(Balances::free_balance(BENEFICIARY_ID), 999998); - }); -} - -#[test] -fn create_estate_token_after_minting_account_and_token_based_lands_should_give_correct_total_user_land_units() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_1 - )); - - assert_ok!(EstateModule::mint_land( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - COORDINATE_IN_2 - )); - - assert_ok!(EstateModule::create_estate( - RuntimeOrigin::signed(BENEFICIARY_ID), - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - assert_eq!( - EstateModule::get_user_land_units(&BENEFICIARY_ID, &METAVERSE_ID).len(), - 2 - ); - assert_eq!(EstateModule::all_land_units_count(), 2); - assert_eq!(Balances::free_balance(BENEFICIARY_ID), 999998); - }); -} - -#[test] -fn create_estate_should_return_none_for_non_exist_estate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_lands( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_estate( - RuntimeOrigin::signed(BENEFICIARY_ID), - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - assert_eq!(Balances::free_balance(BENEFICIARY_ID), 999998); - - let estate_id: u64 = 0; - assert_eq!(EstateModule::all_estates_count(), 1); - assert_eq!(EstateModule::next_estate_id(), 1); - assert_eq!( - EstateModule::get_estates(estate_id), - Some(EstateInfo { - metaverse_id: METAVERSE_ID, - land_units: vec![COORDINATE_IN_1, COORDINATE_IN_2] - }) - ); - assert_eq!(EstateModule::get_estate_owner(estate_id), Some(OWNER_ESTATE_ASSET_ID)); - - let estate_id_non_exists: u64 = 999; - assert_eq!(EstateModule::get_estates(estate_id_non_exists), None); - assert_eq!(EstateModule::get_estate_owner(estate_id_non_exists), None); - }); -} - -#[test] -fn issue_land_block_should_fail_if_not_root() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::signed(ALICE), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - ), - BadOrigin - ); - }); -} - -#[test] -fn issue_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(BOB, 0)) - ); - - assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 20); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - }); -} - -#[test] -fn issue_two_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(BOB, 0)) - ); - - assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); - - let first_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); - match first_issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 20); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - ALICE, - 1, - 30, - UndeployedLandBlockType::Transferable - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(ALICE, 1)) - ); - - assert_eq!(EstateModule::get_undeployed_land_block_owner(ALICE, 1), Some(())); - - let second_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); - match second_issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, ALICE); - assert_eq!(a.number_land_units, 30); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - }); -} - -#[test] -fn freeze_undeployed_land_block_should_fail_if_not_root() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), - BadOrigin - ); - }); -} - -#[test] -fn freeze_undeployed_land_block_should_fail_not_found() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), - Error::::UndeployedLandBlockNotFound - ); - }); -} - -#[test] -fn freeze_undeployed_land_block_should_fail_if_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 1, - UndeployedLandBlockType::Transferable, - )); - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 21, - UndeployedLandBlockType::Transferable, - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(UNDEPLOYED_LAND_BLOCK_IN_AUCTION); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 21); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - assert_noop!( - EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), UNDEPLOYED_LAND_BLOCK_IN_AUCTION), - Error::::UndeployedLandBlockAlreadyInAuction - ); - }); -} - -#[test] -fn freeze_undeployed_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 20); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_ok!(EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockFreezed(0)) - ); - - assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); - - let frozen_undeployed_land_block = EstateModule::get_undeployed_land_block(0); - match frozen_undeployed_land_block { - Some(a) => { - assert_eq!(a.is_locked, true); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - }); -} - -#[test] -fn freeze_undeployed_land_block_should_fail_already_freezed() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - assert_ok!(EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockFreezed(0)) - ); - - assert_noop!( - EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), - Error::::UndeployedLandBlockAlreadyFreezed - ); - }); -} - -#[test] -fn unfreeze_undeployed_land_block_should_fail_if_not_root() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), - BadOrigin - ); - }); -} - -#[test] -fn unfreeze_undeployed_land_block_should_fail_not_found() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), - Error::::UndeployedLandBlockNotFound - ); - }); -} - -#[test] -fn unfreeze_undeployed_land_block_should_fail_if_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 1, - UndeployedLandBlockType::Transferable, - )); - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 21, - UndeployedLandBlockType::Transferable, - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 21); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_noop!( - EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), UNDEPLOYED_LAND_BLOCK_IN_AUCTION), - Error::::UndeployedLandBlockAlreadyInAuction - ); - }); -} - -#[test] -fn unfreeze_undeployed_land_block_should_fail_not_frozen() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - assert_noop!( - EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), 0), - Error::::UndeployedLandBlockNotFrozen - ); - }); -} - -#[test] -fn unfreeze_undeployed_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - assert_ok!(EstateModule::freeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); - - let freezed_undeployed_land_block = EstateModule::get_undeployed_land_block(0); - match freezed_undeployed_land_block { - Some(a) => { - assert_eq!(a.is_locked, true); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_ok!(EstateModule::unfreeze_undeployed_land_blocks(RuntimeOrigin::root(), 0)); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockUnfreezed(0)) - ); - - let unfreezed_undeployed_land_block = EstateModule::get_undeployed_land_block(0); - match unfreezed_undeployed_land_block { - Some(a) => { - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - }); -} - -#[test] -fn transfer_undeployed_land_block_should_fail_if_not_found() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, 0), - Error::::UndeployedLandBlockNotFound - ); - }); -} - -#[test] -fn transfer_undeployed_land_block_should_fail_if_not_owner() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_noop!( - EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, undeployed_land_block_id), - Error::::NoPermission - ); - }); -} - -#[test] -fn transfer_undeployed_land_block_should_fail_if_freezed() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_ok!(EstateModule::freeze_undeployed_land_blocks( - RuntimeOrigin::root(), - undeployed_land_block_id - )); - - assert_noop!( - EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(BOB), ALICE, undeployed_land_block_id), - Error::::UndeployedLandBlockAlreadyFreezed - ); - }); -} - -#[test] -fn transfer_undeployed_land_block_should_fail_if_not_transferable() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_noop!( - EstateModule::transfer_undeployed_land_blocks(RuntimeOrigin::signed(BOB), ALICE, undeployed_land_block_id), - Error::::UndeployedLandBlockIsNotTransferable - ); - }); -} - -#[test] -fn transfer_undeployed_land_block_should_fail_if_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 1, - UndeployedLandBlockType::Transferable, - )); - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 21, - UndeployedLandBlockType::Transferable, - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 21); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_noop!( - EstateModule::transfer_undeployed_land_blocks( - RuntimeOrigin::signed(BOB), - ALICE, - UNDEPLOYED_LAND_BLOCK_IN_AUCTION - ), - Error::::UndeployedLandBlockAlreadyInAuction - ); - }); -} - -#[test] -fn transfer_undeployed_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::Transferable - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, BOB); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_eq!( - EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), - Some(()) - ); - - assert_ok!(EstateModule::transfer_undeployed_land_blocks( - RuntimeOrigin::signed(BOB), - ALICE, - undeployed_land_block_id - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockTransferred( - BOB, - ALICE, - undeployed_land_block_id, - )) - ); - - let transferred_issued_undeployed_land_block = - EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match transferred_issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, ALICE); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_eq!( - EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), - None - ); - assert_eq!( - EstateModule::get_undeployed_land_block_owner(ALICE, undeployed_land_block_id), - Some(()) - ); - }); -} - -#[test] -fn deploy_undeployed_land_block_should_fail_if_not_found() { - ExtBuilder::default().build().execute_with(|| { - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_noop!( - EstateModule::deploy_land_block( - RuntimeOrigin::signed(ALICE), - undeployed_land_block_id, - ALICE_METAVERSE_ID, - LANDBLOCK_COORDINATE, - vec![COORDINATE_IN_1] - ), - Error::::UndeployedLandBlockNotFound - ); - assert_eq!(Balances::free_balance(BOB), 100000); - }); -} - -#[test] -fn deploy_undeployed_land_block_should_fail_if_not_owner() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_noop!( - EstateModule::deploy_land_block( - RuntimeOrigin::signed(ALICE), - undeployed_land_block_id, - METAVERSE_ID, - LANDBLOCK_COORDINATE, - vec![COORDINATE_IN_1] - ), - Error::::NoPermission - ); - assert_eq!(Balances::free_balance(ALICE), 100000); - }); -} - -#[test] -fn deploy_undeployed_land_block_should_fail_if_freezed() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_ok!(EstateModule::freeze_undeployed_land_blocks( - RuntimeOrigin::root(), - undeployed_land_block_id - )); - - assert_noop!( - EstateModule::deploy_land_block( - RuntimeOrigin::signed(BOB), - undeployed_land_block_id, - BOB_METAVERSE_ID, - LANDBLOCK_COORDINATE, - vec![COORDINATE_IN_1] - ), - Error::::UndeployedLandBlockFreezed - ); - assert_eq!(Balances::free_balance(BOB), 99999); - }); -} - -#[test] -fn deploy_undeployed_land_block_should_fail_if_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 1, - UndeployedLandBlockType::Transferable, - )); - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 21, - UndeployedLandBlockType::Transferable, - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 21); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_noop!( - EstateModule::deploy_land_block( - RuntimeOrigin::signed(BOB), - UNDEPLOYED_LAND_BLOCK_IN_AUCTION, - METAVERSE_ID, - LANDBLOCK_COORDINATE, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - ), - Error::::UndeployedLandBlockAlreadyInAuction - ); - assert_eq!(Balances::free_balance(BOB), 99998); - }); -} - -#[test] -fn deploy_undeployed_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 2, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - let undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match undeployed_land_block { - Some(a) => { - assert_eq!(a.number_land_units, 2); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_ok!(EstateModule::deploy_land_block( - RuntimeOrigin::signed(BOB), - undeployed_land_block_id, - BOB_METAVERSE_ID, - LANDBLOCK_COORDINATE, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::LandBlockDeployed( - BOB, - BOB_METAVERSE_ID, - undeployed_land_block_id, - vec![COORDINATE_IN_1, COORDINATE_IN_2], - )) - ); - - let updated_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - - assert_eq!(updated_undeployed_land_block, None); - - assert_eq!(EstateModule::all_land_units_count(), 2); - assert_eq!(Balances::free_balance(BOB), 99998); - }); -} - -#[test] -fn approve_undeployed_land_block_should_fail_if_not_found() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::approve_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, 0), - Error::::UndeployedLandBlockNotFound - ); - }); -} - #[test] -fn approve_undeployed_land_block_should_fail_if_not_owner() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_noop!( - EstateModule::approve_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), BOB, undeployed_land_block_id), - Error::::NoPermission - ); - }); -} - -#[test] -fn approve_undeployed_land_block_should_fail_if_freezed() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_ok!(EstateModule::freeze_undeployed_land_blocks( - RuntimeOrigin::root(), - undeployed_land_block_id - )); - - assert_noop!( - EstateModule::approve_undeployed_land_blocks(RuntimeOrigin::signed(BOB), ALICE, undeployed_land_block_id), - Error::::UndeployedLandBlockAlreadyFreezed - ); - }); -} - -#[test] -fn approve_undeployed_land_block_should_fail_if_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 1, - UndeployedLandBlockType::Transferable, - )); - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 21, - UndeployedLandBlockType::Transferable, - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 21); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_noop!( - EstateModule::approve_undeployed_land_blocks( - RuntimeOrigin::signed(BOB), - ALICE, - UNDEPLOYED_LAND_BLOCK_IN_AUCTION - ), - Error::::UndeployedLandBlockAlreadyInAuction - ); - }); -} - -#[test] -fn approve_undeployed_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::Transferable - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, BOB); - assert_eq!(a.approved, None); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_eq!( - EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), - Some(()) - ); - - assert_ok!(EstateModule::approve_undeployed_land_blocks( - RuntimeOrigin::signed(BOB), - ALICE, - undeployed_land_block_id - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockApproved( - BOB, - ALICE, - undeployed_land_block_id, - )) - ); - - let transferred_issued_undeployed_land_block = - EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match transferred_issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, BOB); - assert_eq!(a.approved, Some(ALICE)); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_eq!( - EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), - Some(()) - ); - }); -} - -#[test] -fn unapprove_undeployed_land_block_should_fail_if_not_found() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::unapprove_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), - Error::::UndeployedLandBlockNotFound - ); - }); -} - -#[test] -fn unapprove_undeployed_land_block_should_fail_if_not_owner() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_noop!( - EstateModule::unapprove_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), undeployed_land_block_id), - Error::::NoPermission - ); - }); -} - -#[test] -fn unapprove_undeployed_land_block_should_fail_if_freezed() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - assert_ok!(EstateModule::freeze_undeployed_land_blocks( - RuntimeOrigin::root(), - undeployed_land_block_id - )); - - assert_noop!( - EstateModule::unapprove_undeployed_land_blocks(RuntimeOrigin::signed(BOB), undeployed_land_block_id), - Error::::UndeployedLandBlockAlreadyFreezed - ); - }); -} - -#[test] -fn unapprove_undeployed_land_block_should_fail_if_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 1, - UndeployedLandBlockType::Transferable, - )); - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 21, - UndeployedLandBlockType::Transferable, - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 21); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_noop!( - EstateModule::unapprove_undeployed_land_blocks( - RuntimeOrigin::signed(BOB), - UNDEPLOYED_LAND_BLOCK_IN_AUCTION - ), - Error::::UndeployedLandBlockAlreadyInAuction - ); - }); -} - -#[test] -fn unapprove_undeployed_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::Transferable - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, BOB); - assert_eq!(a.approved, None); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_eq!( - EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), - Some(()) - ); - assert_ok!(EstateModule::approve_undeployed_land_blocks( - RuntimeOrigin::signed(BOB), - ALICE, - undeployed_land_block_id - )); - - let approved_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match approved_issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, BOB); - assert_eq!(a.approved, Some(ALICE)); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_ok!(EstateModule::unapprove_undeployed_land_blocks( - RuntimeOrigin::signed(BOB), - undeployed_land_block_id - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockUnapproved(undeployed_land_block_id)) - ); - - let unapproved_issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match unapproved_issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, BOB); - assert_eq!(a.approved, None); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - }); -} - -#[test] -fn burn_undeployed_land_block_should_fail_if_not_root() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::burn_undeployed_land_blocks(RuntimeOrigin::signed(ALICE), 0), - BadOrigin - ); - }); -} - -#[test] -fn burn_undeployed_land_block_should_fail_not_found() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EstateModule::burn_undeployed_land_blocks(RuntimeOrigin::root(), 0), - Error::::UndeployedLandBlockNotFound - ); - }); -} - -#[test] -fn burn_undeployed_land_block_should_fail_if_already_in_auction() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 1, - UndeployedLandBlockType::Transferable, - )); - - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 21, - UndeployedLandBlockType::Transferable, - )); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(1); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 21); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::Transferable); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_noop!( - EstateModule::burn_undeployed_land_blocks(RuntimeOrigin::root(), UNDEPLOYED_LAND_BLOCK_IN_AUCTION), - Error::::UndeployedLandBlockAlreadyInAuction - ); - }); -} - -#[test] -fn burn_undeployed_land_block_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 20, - UndeployedLandBlockType::BoundToAddress - )); - - let undeployed_land_block_id: UndeployedLandBlockId = 0; - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(undeployed_land_block_id); - match issued_undeployed_land_block { - Some(a) => { - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 20); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - assert_eq!( - EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), - Some(()) - ); - - assert_ok!(EstateModule::burn_undeployed_land_blocks( - RuntimeOrigin::root(), - undeployed_land_block_id - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockBurnt(undeployed_land_block_id)) - ); - - assert_eq!( - EstateModule::get_undeployed_land_block_owner(BOB, undeployed_land_block_id), - None - ); - - assert_eq!(EstateModule::get_undeployed_land_block(undeployed_land_block_id), None) - }); -} - -#[test] -fn ensure_land_unit_within_land_block_bound_should_work() { - // let coordinates: Vec<(i32, i32)> = vec![(-4, 0), (-3, 0), (-3, 0), (0, 5)]; - // assert_eq!(EstateModule::verify_land_unit_in_bound(&(0, 0), &coordinates), true); - - let second_coordinates: Vec<(i32, i32)> = vec![(-204, 25), (-203, 24), (-195, 20), (-197, 16)]; - assert_eq!( - EstateModule::verify_land_unit_in_bound(&(-20, 2), &second_coordinates), - true - ); - - let third_coordinates: Vec<(i32, i32)> = vec![(-64, 5), (-64, 4), (-64, 4), (-55, -4)]; - assert_eq!( - EstateModule::verify_land_unit_in_bound(&(-6, 0), &third_coordinates), - true - ); - - // Combined in and out bound should fail - let fourth_coordinates: Vec<(i32, i32)> = vec![(-5, 3), (-4, 6), (-5, 4)]; - assert_eq!( - EstateModule::verify_land_unit_in_bound(&(0, 0), &fourth_coordinates), - false - ); -} - -#[test] -fn ensure_land_unit_out_of_land_block_bound_should_fail() { - let coordinates: Vec<(i32, i32)> = vec![(-51, 0), (-48, 0), (-47, 0), (0, 51)]; - assert_eq!(EstateModule::verify_land_unit_in_bound(&(0, 0), &coordinates), false); - - let second_coordinates: Vec<(i32, i32)> = vec![(-250, 2), (-248, 2), (-150, 2), (-151, 6)]; - assert_eq!( - EstateModule::verify_land_unit_in_bound(&(-200, 2), &second_coordinates), - false - ); -} - -#[test] -fn issue_land_block_and_create_estate_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::issue_undeployed_land_blocks( - RuntimeOrigin::root(), - BOB, - 1, - 2, - UndeployedLandBlockType::BoundToAddress - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::UndeployedLandBlockIssued(BOB, 0)) - ); - - assert_eq!(EstateModule::get_undeployed_land_block_owner(BOB, 0), Some(())); - - let issued_undeployed_land_block = EstateModule::get_undeployed_land_block(0); - match issued_undeployed_land_block { - Some(a) => { - // Verify details of UndeployedLandBlock - assert_eq!(a.owner, BOB); - assert_eq!(a.number_land_units, 2); - assert_eq!(a.undeployed_land_block_type, UndeployedLandBlockType::BoundToAddress); - assert_eq!(a.is_locked, false); - } - _ => { - // Should fail test - assert_eq!(0, 1); - } - } - - // Bob can deploy raw land block to his metaverse - assert_ok!(EstateModule::deploy_land_block( - RuntimeOrigin::signed(BOB), - 0, - BOB_METAVERSE_ID, - LANDBLOCK_COORDINATE, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - assert_eq!(Balances::free_balance(BOB), 99998); - - assert_eq!( - EstateModule::get_land_units(BOB_METAVERSE_ID, COORDINATE_IN_1), - Some(OwnerId::Token(METAVERSE_LAND_CLASS, 2)) - ); - - assert_eq!( - EstateModule::get_land_units(BOB_METAVERSE_ID, COORDINATE_IN_2), - Some(OwnerId::Token(METAVERSE_LAND_CLASS, 2)) - ); - }); -} - -#[test] -fn create_estate_lease_offer_should_fail() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - )); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 1u64, 10u128, 8u32), - Error::::EstateDoesNotExist - ); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, 10u128, 8u32), - Error::::NoPermission - ); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, 0u128, 8u32), - Error::::LeaseOfferPriceBelowMinimum - ); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, 2u128, 1000u32), - Error::::LeaseOfferDurationAboveMaximum - ); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, 2u128, 7u32), - Error::::LeaseOfferAlreadyExists - ); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(CHARLIE), 0u64, 12u128, 8u32), - Error::::EstateIsAlreadyLeased - ); - - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - AUCTION_BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_2] - )); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(BOB), 1u64, 100u128, 8u32), - Error::::EstateAlreadyInAuction - ); - - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_3] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(BOB), - 2u64, - 12u128, - 8u32 - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 2u64, - 13u128, - 8u32 - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(CHARLIE), - 2u64, - 14u128, - 8u32 - )); - - assert_noop!( - EstateModule::create_lease_offer(RuntimeOrigin::signed(DOM), 2u64, 15u128, 8u32), - Error::::EstateLeaseOffersQueueLimitIsReached - ); - }); -} - -#[test] -fn create_estate_lease_offer_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseOfferCreated(ALICE, 0, 80)) - ); - - let lease_contract = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 7, - start_block: 8, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); - - assert_eq!(Balances::free_balance(ALICE), 99920); - }); -} - -#[test] -fn accept_estate_lease_offer_should_fail() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(BOB), - 0u64, - 10u128, - 8u32 - )); - - assert_noop!( - EstateModule::accept_lease_offer(RuntimeOrigin::signed(ALICE), 0u64, BOB), - Error::::NoPermission - ); - //TO DO: Offer cannot be accepted after asset is listed on auction - - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(BOB), - 1u64, - 10u128, - 8u32 - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 1u64, - 10u128, - 8u32 - )); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 1u64, - ALICE - )); - - assert_noop!( - EstateModule::accept_lease_offer(RuntimeOrigin::signed(BENEFICIARY_ID), 1u64, BOB), - Error::::EstateIsAlreadyLeased - ); - - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_3] - )); - - assert_noop!( - EstateModule::accept_lease_offer(RuntimeOrigin::signed(BENEFICIARY_ID), 2u64, BOB), - Error::::LeaseOfferDoesNotExist - ); - }); -} - -#[test] -fn accept_estate_lease_offer_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - let lease_contract = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 7, - start_block: 8, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) - ); - - let lease = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 9, - start_block: 1, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::leases(0u64), Some(lease)); - - assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); - - assert_eq!(Balances::free_balance(ALICE), 99920); - }); -} - -#[test] -fn cancel_lease_should_fail() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - )); - - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_noop!( - EstateModule::cancel_lease(RuntimeOrigin::signed(BOB), BENEFICIARY_ID, 0u64, ALICE), - BadOrigin - ); - - assert_noop!( - EstateModule::cancel_lease(RuntimeOrigin::root(), BENEFICIARY_ID, 1u64, ALICE), - Error::::LeaseDoesNotExist - ); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_noop!( - EstateModule::cancel_lease(RuntimeOrigin::root(), BENEFICIARY_ID, 0u64, BOB), - Error::::LeaseDoesNotExist - ); - assert_noop!( - EstateModule::cancel_lease(RuntimeOrigin::root(), BOB, 0u64, ALICE), - Error::::NoPermission - ); - - run_to_block(22); - - assert_noop!( - EstateModule::cancel_lease(RuntimeOrigin::root(), BENEFICIARY_ID, 0u64, ALICE), - Error::::LeaseIsExpired - ); - }); -} - -#[test] -fn cancel_lease_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - let lease_contract = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 7, - start_block: 8, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) - ); - - let lease = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 9, - start_block: 1, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::leases(0u64), Some(lease)); - - assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - run_to_block(5); - - assert_ok!(EstateModule::cancel_lease( - RuntimeOrigin::root(), - BENEFICIARY_ID, - 0u64, - ALICE - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseContractCancelled(0)) - ); - - assert_eq!(EstateModule::leases(0u64), None); - - assert_eq!(EstateModule::leasors(ALICE, 0u64), None); - - assert_eq!(Balances::free_balance(ALICE), 99960); - assert_eq!(Balances::free_balance(BENEFICIARY_ID), 1000039); - }); -} - -#[test] -fn remove_expired_lease_should_fail() { - ExtBuilder::default().build().execute_with(|| { - // Mint estate - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_noop!( - EstateModule::remove_expired_lease(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), - Error::::LeaseDoesNotExist - ); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - run_to_block(3); - - assert_noop!( - EstateModule::remove_expired_lease(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), - Error::::LeaseIsNotExpired - ); - - run_to_block(22); - - assert_noop!( - EstateModule::remove_expired_lease(RuntimeOrigin::signed(BOB), 0u64, ALICE), - Error::::NoPermission - ); - }); -} - -#[test] -fn remove_expired_lease_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - let lease_contract = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 7, - start_block: 8, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) - ); - - let lease = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 9, - start_block: 1, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::leases(0u64), Some(lease)); - - assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - run_to_block(10); - - assert_ok!(EstateModule::remove_expired_lease( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseContractEnded(0u64)) - ); - - assert_eq!(EstateModule::leases(0u64), None); - - assert_eq!(EstateModule::leasors(ALICE, 0u64), None); - - assert_eq!(Balances::free_balance(ALICE), 99920); - assert_eq!(Balances::free_balance(BENEFICIARY_ID), 1000079); - }); -} - -#[test] -fn remove_lease_offer_should_fail() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1] - )); - - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_noop!( - EstateModule::remove_lease_offer(RuntimeOrigin::signed(BOB), 0u64), - Error::::LeaseOfferDoesNotExist - ); - - assert_noop!( - EstateModule::remove_lease_offer(RuntimeOrigin::signed(ALICE), 1u64), - Error::::LeaseOfferDoesNotExist - ); - }); -} - -#[test] -fn remove_lease_offer_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - let lease_contract = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 7, - start_block: 8, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); - - assert_ok!(EstateModule::remove_lease_offer(RuntimeOrigin::signed(ALICE), 0u64)); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseOfferRemoved(ALICE, 0u64)) - ); - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); - assert_eq!(Balances::free_balance(ALICE), 100000); - }); -} - -#[test] -fn collect_rent_should_fail() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_noop!( - EstateModule::collect_rent(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), - Error::::LeaseDoesNotExist - ); - - assert_noop!( - EstateModule::collect_rent(RuntimeOrigin::signed(ALICE), 0u64, BENEFICIARY_ID), - Error::::NoPermission - ); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_noop!( - EstateModule::collect_rent(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, BOB), - Error::::LeaseDoesNotExist - ); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - run_to_block(22); - - assert_noop!( - EstateModule::collect_rent(RuntimeOrigin::signed(BENEFICIARY_ID), 0u64, ALICE), - Error::::LeaseIsExpired - ); - }); -} - -#[test] -fn collect_rent_should_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EstateModule::mint_estate( - RuntimeOrigin::root(), - BENEFICIARY_ID, - METAVERSE_ID, - vec![COORDINATE_IN_1, COORDINATE_IN_2] - )); - - assert_ok!(EstateModule::create_lease_offer( - RuntimeOrigin::signed(ALICE), - 0u64, - 10u128, - 8u32 - )); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - let lease_contract = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 7, - start_block: 8, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), Some(lease_contract)); - - assert_ok!(EstateModule::accept_lease_offer( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateLeaseOfferAccepted(0, ALICE, 9)) - ); - - let mut lease = LeaseContract { - price_per_block: 10u128, - duration: 8u32, - end_block: 9, - start_block: 1, - unclaimed_rent: 80u128, - }; - - assert_eq!(EstateModule::leases(0u64), Some(lease.clone())); - - assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); - - assert_eq!(EstateModule::lease_offers(0u64, ALICE), None); - - assert_eq!(Balances::free_balance(ALICE), 99920); - - run_to_block(4); - - assert_ok!(EstateModule::collect_rent( - RuntimeOrigin::signed(BENEFICIARY_ID), - 0u64, - ALICE - )); - - assert_eq!( - last_event(), - RuntimeEvent::Estate(crate::Event::EstateRentCollected(0, 30)) - ); - - lease.unclaimed_rent = 50u128; - - assert_eq!(EstateModule::leases(0u64), Some(lease)); - - assert_eq!(EstateModule::leasors(ALICE, 0u64), Some(())); - - assert_eq!(Balances::free_balance(ALICE), 99920); - assert_eq!(Balances::free_balance(BENEFICIARY_ID), 1000029); - }); +fn test_one() { + ExtBuilder::default().build().execute_with(|| assert_eq!(1, 1)); } From 15486b934e0fee712e047d1fb4416e217360a857 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 13 Nov 2023 17:33:24 +1300 Subject: [PATCH 035/114] WIP - Unit test - complete the first test run successful --- Cargo.lock | 1 + pallets/spp/Cargo.toml | 4 +++- pallets/spp/src/mock.rs | 18 +++++++++++++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6a4f98e6..39faaaf1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8025,6 +8025,7 @@ dependencies = [ name = "pallet-spp" version = "2.0.0-rc6" dependencies = [ + "asset-manager", "auction-manager", "bit-country-primitives", "core-primitives", diff --git a/pallets/spp/Cargo.toml b/pallets/spp/Cargo.toml index e6009caf7..917fae3f0 100644 --- a/pallets/spp/Cargo.toml +++ b/pallets/spp/Cargo.toml @@ -32,6 +32,7 @@ orml-tokens = { workspace = true } core-primitives = { path = "../../traits/core-primitives", default-features = false } primitives = { package = "bit-country-primitives", path = "../../primitives/metaverse", default-features = false } currencies = { package = "currencies", path = "../currencies", default-features = false } +asset-manager = { package = "asset-manager", path = "../asset-manager", default-features = false } [dependencies.auction-manager] default-features = false @@ -63,5 +64,6 @@ std = [ 'auction-manager/std', 'frame-benchmarking/std', 'orml-tokens/std', - "currencies/std" + "currencies/std", + "asset-manager/std" ] \ No newline at end of file diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index 04adc6cce..608a255f5 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -14,6 +14,7 @@ use sp_std::collections::btree_map::BTreeMap; use sp_std::default::Default; use sp_std::vec::Vec; +use asset_manager::ForeignAssetMapping; use auction_manager::{Auction, AuctionInfo, AuctionItem, AuctionType, CheckAuctionItemHandler, ListingLevel}; use core_primitives::{CollectionType, NftClassData, TokenType}; use primitives::{ @@ -133,7 +134,7 @@ impl pallet_balances::Config for Runtime { parameter_types! { pub const GetNativeCurrencyId: FungibleTokenId = FungibleTokenId::NativeToken(0); pub const LandTreasuryPalletId: PalletId = PalletId(*b"bit/land"); - pub const LandTreasuryPalletId: PalletId = PalletId(*b"bit/land"); + pub const PoolAccountPalletId: PalletId = PalletId(*b"bit/pool"); pub const MinimumLandPrice: Balance = 10 * DOLLARS; } @@ -178,6 +179,12 @@ impl currencies::Config for Runtime { type WeightInfo = (); } +impl asset_manager::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type RegisterOrigin = EnsureSignedBy; +} + parameter_types! { pub const MinBlocksPerRound: u32 = 10; pub const MinimumStake: Balance = 200; @@ -190,6 +197,7 @@ parameter_types! { pub const MaxLeasePeriod: u32 = 9; pub const LeaseOfferExpiryPeriod: u32 = 6; pub StorageDepositFee: Balance = 1; + pub const MaximumQueue: u32 = 50; } impl Config for Runtime { @@ -203,9 +211,9 @@ impl Config for Runtime { type BlockNumberToBalance = ConvertInto; type StorageDepositFee = StorageDepositFee; type MultiCurrency = Currencies; - type PoolAccount = (); - type MaximumQueue = (); - type CurrencyIdConversion = (); + type PoolAccount = PoolAccountPalletId; + type MaximumQueue = MaximumQueue; + type CurrencyIdConversion = ForeignAssetMapping; } construct_runtime!( @@ -216,6 +224,7 @@ construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + AssetManager: asset_manager::{ Pallet, Storage, Call, Event}, Currencies: currencies::{ Pallet, Storage, Call, Event}, Tokens: orml_tokens::{Pallet, Call, Storage, Config, Event}, Spp: spp:: {Pallet, Call, Storage, Event}, @@ -263,7 +272,6 @@ pub fn last_event() -> RuntimeEvent { fn next_block() { SppModule::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); - SppModule::on_initialize(System::block_number()).unwrap(); } pub fn run_to_block(n: u64) { From 04deac1c3234487039600f1174d7af32d85719c2 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 10:45:15 +1300 Subject: [PATCH 036/114] WIP - Unit test - add ksm/rksm set up for alice and bob --- pallets/spp/src/mock.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index 608a255f5..5754e0c85 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -236,7 +236,9 @@ pub type SppModule = Pallet; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -pub struct ExtBuilder; +pub struct ExtBuilder { + endowed_accounts: Vec<(AccountId, FungibleTokenId, Balance)>, +} impl Default for ExtBuilder { fn default() -> Self { @@ -260,6 +262,18 @@ impl ExtBuilder { ext.execute_with(|| System::set_block_number(1)); ext } + + pub fn balances(mut self, endowed_accounts: Vec<(AccountId, FungibleTokenId, Balance)>) -> Self { + self.endowed_accounts = endowed_accounts; + self + } + + pub fn ksm_setup_for_alice_and_bob(self) -> Self { + self.balances(vec![ + (ALICE, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM + (BOB, FungibleTokenId::FungibleToken(1), 1000), //rKSM + ]) + } } pub fn last_event() -> RuntimeEvent { From e53dfbc4d90b89f31312b343b53db8707ff6052a Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 10:46:26 +1300 Subject: [PATCH 037/114] WIP - Unit test - add missing default endowed account --- pallets/spp/src/mock.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index 5754e0c85..7a53632e8 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -242,7 +242,9 @@ pub struct ExtBuilder { impl Default for ExtBuilder { fn default() -> Self { - ExtBuilder + Self { + endowed_accounts: vec![], + } } } From 2ce040e99fee5b66579ded97f4eabb9a9312c00e Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 11:02:28 +1300 Subject: [PATCH 038/114] WIP - Unit test - create ksm pool works --- pallets/spp/src/mock.rs | 8 +++++++- pallets/spp/src/tests.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index 7a53632e8..d6dcfc4a0 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -255,7 +255,13 @@ impl ExtBuilder { .unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, 100000), (BOB, 100000), (BENEFICIARY_ID, 1000000)], + balances: vec![(ALICE, 1000000000), (BOB, 100000), (BENEFICIARY_ID, 1000000)], + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.endowed_accounts.into_iter().collect::>(), } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 95eef7654..8ceff4d59 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -19,12 +19,42 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_runtime::traits::BadOrigin; +use sp_runtime::{Perbill, Permill}; use mock::{RuntimeEvent, *}; +use crate::utils::PoolInfo; + use super::*; #[test] fn test_one() { ExtBuilder::default().build().execute_with(|| assert_eq!(1, 1)); } + +#[test] +fn create_ksm_pool_works() { + ExtBuilder::default() + .ksm_setup_for_alice_and_bob() + .build() + .execute_with(|| { + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(ALICE), + FungibleTokenId::NativeToken(1), + 50, + Permill::from_percent(5) + )); + + let next_pool_id = NextPoolId::::get(); + assert_eq!(next_pool_id, 1); + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: ALICE, + commission: Permill::from_percent(5), + currency_id: FungibleTokenId::NativeToken(1), + max: 50 + } + ) + }); +} From d1d01b05a47ee9de0e27a07098c63850ad38264e Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 11:28:29 +1300 Subject: [PATCH 039/114] WIP - Implement logic of first deposit and second deposit scenario --- pallets/spp/src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 323cc4216..3ffdcbda2 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -341,12 +341,16 @@ pub mod pallet { // Calculate rAmount as receipt of amount locked. The formula based on rAmount = (amount * rAmount // total issuance)/network ledger balance let r_amount_total_issuance = T::MultiCurrency::total_issuance(r_currency_id); - let r_amount = U256::from(amount_after_fee.saturated_into::()) - .saturating_mul(r_amount_total_issuance.saturated_into::().into()) - .checked_div(network_ledger_balance.saturated_into::().into()) - .ok_or(Error::::ArithmeticOverflow)? - .as_u128() - .saturated_into(); + let mut r_amount = amount_after_fee; + // This will apply for subsequence deposits - the first deposit r_amount = amount_after_fee + if network_ledger_balance != BalanceOf::::zero() { + r_amount = U256::from(amount_after_fee.saturated_into::()) + .saturating_mul(r_amount_total_issuance.saturated_into::().into()) + .checked_div(network_ledger_balance.saturated_into::().into()) + .ok_or(Error::::ArithmeticOverflow)? + .as_u128() + .saturated_into(); + } // Deposit rAmount to user using T::MultiCurrency::deposit T::MultiCurrency::deposit(currency_id, &who, r_amount)?; From bb9f9a17be0bdf88e704d2428cbf4128246986bf Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 11:46:14 +1300 Subject: [PATCH 040/114] WIP - Update correct reciept currency id --- pallets/spp/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 3ffdcbda2..4069227d2 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -353,7 +353,7 @@ pub mod pallet { } // Deposit rAmount to user using T::MultiCurrency::deposit - T::MultiCurrency::deposit(currency_id, &who, r_amount)?; + T::MultiCurrency::deposit(r_currency_id, &who, r_amount)?; // Update this specific pool ledger to keep track of pool balance PoolLedger::::mutate(&pool_id, |pool| -> Result<(), Error> { From adc62fab9530f5f0b3bec3f38fb3acd60af12c48 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 11:46:28 +1300 Subject: [PATCH 041/114] WIP - Unit test - Deposit works --- pallets/spp/src/mock.rs | 2 +- pallets/spp/src/tests.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index d6dcfc4a0..ca2bd5f1f 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -279,7 +279,7 @@ impl ExtBuilder { pub fn ksm_setup_for_alice_and_bob(self) -> Self { self.balances(vec![ (ALICE, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM - (BOB, FungibleTokenId::FungibleToken(1), 1000), //rKSM + (BOB, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM//rKSM ]) } } diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 8ceff4d59..4df4d4236 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -58,3 +58,35 @@ fn create_ksm_pool_works() { ) }); } + +#[test] +fn deposit_ksm_works() { + ExtBuilder::default() + .ksm_setup_for_alice_and_bob() + .build() + .execute_with(|| { + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(ALICE), + FungibleTokenId::NativeToken(1), + 50, + Permill::from_percent(5) + )); + + let next_pool_id = NextPoolId::::get(); + assert_eq!(next_pool_id, 1); + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: ALICE, + commission: Permill::from_percent(5), + currency_id: FungibleTokenId::NativeToken(1), + max: 50 + } + ); + + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + let r_ksm_amount = Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free; + // This is true because fee hasn't been set up. + assert_eq!(r_ksm_amount, 10000) + }); +} From 6b442d01b58cdd13808e406840ffae12a33690d1 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 12:43:30 +1300 Subject: [PATCH 042/114] WIP - Unit test - All deposit works --- pallets/spp/src/mock.rs | 2 +- pallets/spp/src/tests.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index ca2bd5f1f..b90291f6b 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -279,7 +279,7 @@ impl ExtBuilder { pub fn ksm_setup_for_alice_and_bob(self) -> Self { self.balances(vec![ (ALICE, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM - (BOB, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM//rKSM + (BOB, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM ]) } } diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 4df4d4236..bc60ae553 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -85,8 +85,17 @@ fn deposit_ksm_works() { ); assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); - let r_ksm_amount = Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free; // This is true because fee hasn't been set up. - assert_eq!(r_ksm_amount, 10000) + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); + + assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); + + // Deposit another 10000 KSM + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); + + assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); }); } From 137ce7b51e7b7d7229c48a7655856229d54ad7cc Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 14:07:34 +1300 Subject: [PATCH 043/114] WIP - Unit test - Redeem rksm works. --- pallets/spp/src/tests.rs | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index bc60ae553..29d37709a 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -99,3 +99,69 @@ fn deposit_ksm_works() { assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); }); } + +#[test] +fn redeem_rksm_request_works() { + ExtBuilder::default() + .ksm_setup_for_alice_and_bob() + .build() + .execute_with(|| { + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(ALICE), + FungibleTokenId::NativeToken(1), + 50, + Permill::from_percent(5) + )); + + let next_pool_id = NextPoolId::::get(); + assert_eq!(next_pool_id, 1); + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: ALICE, + commission: Permill::from_percent(5), + currency_id: FungibleTokenId::NativeToken(1), + max: 50 + } + ); + + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + // This is true because fee hasn't been set up. + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); + + assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); + + // Deposit another 10000 KSM + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); + + assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); + + assert_noop!( + SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(1), 10000), + Error::::PoolDoesNotExist + ); + + assert_noop!( + SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(0), 10000), + Error::::CurrencyIsNotSupported + ); + + assert_noop!( + SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(1), 10000), + Error::::NoCurrentStakingRound + ); + + UnlockDuration::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(1)); + // Bump current staking round to 1 + CurrentStakingRound::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(1)); + assert_ok!(SppModule::redeem( + RuntimeOrigin::signed(BOB), + 0, + FungibleTokenId::FungibleToken(1), + 10000 + )); + }); +} From 593a12e15dafdf9cc5da2faf056f9555f6c8da85 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 18:44:08 +1300 Subject: [PATCH 044/114] WIP - Unit test - ledger reduce after user redeem --- pallets/spp/src/tests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 29d37709a..c3b246520 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -163,5 +163,8 @@ fn redeem_rksm_request_works() { FungibleTokenId::FungibleToken(1), 10000 )); + + // After Bob redeems, pool ledger 0 should have only 10000 + assert_eq!(PoolLedger::::get(0), 10000); }); } From 10b905f37c244f256f6523da3fc864ee49474be8 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 18:44:18 +1300 Subject: [PATCH 045/114] WIP - Update correct redeem logic --- pallets/spp/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 4069227d2..e66f96763 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -417,11 +417,8 @@ pub mod pallet { let network_ledger_balance = Self::network_ledger(currency_id); // Collect deposit fee for protocol - // Assuming there's a function `collect_redeem_fee` that deducts a fee for deposits. let amount_after_fee = Self::collect_redeem_fee(&who, v_currency_id, r_amount)?; - let r_amount = r_amount - .checked_sub(&amount_after_fee) - .ok_or(Error::::ArithmeticOverflow)?; + let r_amount = amount_after_fee; // Calculate rAmount as receipt of amount locked. The formula based on rAmount = (amount * rAmount // total issuance)/network ledger balance let r_amount_total_issuance = T::MultiCurrency::total_issuance(v_currency_id); From 8e7fc4eab87656ee94f3d7f4682f7daaf52d45d8 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 19:14:39 +1300 Subject: [PATCH 046/114] WIP - Unit test - redeem queue system works --- pallets/spp/src/tests.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index c3b246520..97b0692bb 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -166,5 +166,18 @@ fn redeem_rksm_request_works() { // After Bob redeems, pool ledger 0 should have only 10000 assert_eq!(PoolLedger::::get(0), 10000); + + // Verify if redeem queue has requests + + let queue_id = QueueNextId::::get(FungibleTokenId::NativeToken(1)); + assert_eq!(queue_id, 1); + let mut queue_items = BoundedVec::default(); + assert_ok!(queue_items.try_push(0)); + let user_redeem_queue = UserCurrencyRedeemQueue::::get(BOB, FungibleTokenId::NativeToken(1)); + let currency_redeem_queue = CurrencyRedeemQueue::::get(FungibleTokenId::NativeToken(1), queue_id); + let staking_round_redeem_queue = + StakingRoundRedeemQueue::::get(StakingRound::Era(1), FungibleTokenId::NativeToken(1)); + assert_eq!(user_redeem_queue, Some(10000, queue_items)); + assert_eq!(currency_redeem_queue, Some(BOB, 10000, StakingRound::Era(1))); }); } From a4923d8345e4a4ca4d7141fd187152ca8843b6fa Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 21:31:24 +1300 Subject: [PATCH 047/114] WIP - Unit test - redeem queues added as expected --- pallets/spp/src/tests.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 97b0692bb..9cb1b9e86 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -174,10 +174,17 @@ fn redeem_rksm_request_works() { let mut queue_items = BoundedVec::default(); assert_ok!(queue_items.try_push(0)); let user_redeem_queue = UserCurrencyRedeemQueue::::get(BOB, FungibleTokenId::NativeToken(1)); - let currency_redeem_queue = CurrencyRedeemQueue::::get(FungibleTokenId::NativeToken(1), queue_id); + let currency_redeem_queue = CurrencyRedeemQueue::::get(FungibleTokenId::NativeToken(1), 0); let staking_round_redeem_queue = - StakingRoundRedeemQueue::::get(StakingRound::Era(1), FungibleTokenId::NativeToken(1)); - assert_eq!(user_redeem_queue, Some(10000, queue_items)); - assert_eq!(currency_redeem_queue, Some(BOB, 10000, StakingRound::Era(1))); + StakingRoundRedeemQueue::::get(StakingRound::Era(2), FungibleTokenId::NativeToken(1)); + // Verify if user redeem queue has total unlocked and queue items + assert_eq!(user_redeem_queue, Some((10000, queue_items.clone()))); + // If user redeem of Era 1, fund will be released at Era 2 + assert_eq!(currency_redeem_queue, Some((BOB, 10000, StakingRound::Era(2)))); + // Redeem added into staking round redeem queue for Era 2 + assert_eq!( + staking_round_redeem_queue, + Some((10000, queue_items.clone(), FungibleTokenId::NativeToken(1))) + ); }); } From d93a13ca4cf7a5c68f2eedfbccb4e0d491759309 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 23:00:09 +1300 Subject: [PATCH 048/114] WIP - Add relaychain block number provider trait to update relaychain block every hook --- pallets/spp/src/lib.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index e66f96763..51ba6b407 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -56,7 +56,7 @@ pub mod pallet { use frame_support::traits::{Currency, ReservableCurrency}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; - use sp_runtime::traits::{CheckedAdd, CheckedSub}; + use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedSub}; use sp_runtime::Permill; use primitives::{PoolId, StakingRound}; @@ -109,6 +109,9 @@ pub mod pallet { /// Allows converting block numbers into balance type BlockNumberToBalance: Convert>; + /// Block number provider for the relaychain. + type RelayChainBlockNumber: BlockNumberProvider>; + #[pallet::constant] type PoolAccount: Get; @@ -198,6 +201,16 @@ pub mod pallet { #[pallet::getter(fn last_staking_round)] pub type LastStakingRound = StorageMap<_, Twox64Concat, FungibleTokenId, StakingRound, ValueQuery>; + /// The relaychain block number of last staking round + #[pallet::storage] + #[pallet::getter(fn last_staking_round_updated_block)] + pub type LastEraUpdatedBlock = StorageValue<_, BlockNumberFor, ValueQuery>; + + /// The internal of relaychain block number between era. + #[pallet::storage] + #[pallet::getter(fn update_staking_round_frequency)] + pub type UpdateEraFrequency = StorageValue<_, BlockNumberFor, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn queue_next_id)] pub type QueueNextId = StorageMap<_, Twox64Concat, FungibleTokenId, u32, ValueQuery>; From 4b51aa37442f704b8f171f534292ae6af63bbc1a Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 23:23:20 +1300 Subject: [PATCH 049/114] WIP - Implement relaychain hooks to update era index and handle redeem requests --- pallets/spp/src/lib.rs | 68 ++++++++++++++++++++++++++------- primitives/metaverse/src/lib.rs | 2 + 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 51ba6b407..758609711 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -27,7 +27,7 @@ use frame_support::{ use frame_system::ensure_signed; use frame_system::pallet_prelude::*; use orml_traits::MultiCurrency; -use sp_runtime::traits::{CheckedAdd, CheckedSub}; +use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedDiv, CheckedSub}; use sp_runtime::{ traits::{AccountIdConversion, Convert, Saturating, Zero}, ArithmeticError, DispatchError, SaturatedConversion, @@ -35,7 +35,7 @@ use sp_runtime::{ use core_primitives::*; pub use pallet::*; -use primitives::{ClassId, FungibleTokenId, StakingRound, TokenId}; +use primitives::{ClassId, EraIndex, EraIndex, FungibleTokenId, StakingRound, TokenId}; pub use weights::WeightInfo; pub type QueueId = u32; @@ -197,18 +197,25 @@ pub mod pallet { #[pallet::getter(fn current_staking_round)] pub type CurrentStakingRound = StorageMap<_, Twox64Concat, FungibleTokenId, StakingRound>; + /// The current era of relaychain + /// + /// RelayChainCurrentEra : EraIndex + #[pallet::storage] + #[pallet::getter(fn relay_chain_current_era)] + pub type RelayChainCurrentEra = StorageValue<_, EraIndex, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn last_staking_round)] pub type LastStakingRound = StorageMap<_, Twox64Concat, FungibleTokenId, StakingRound, ValueQuery>; /// The relaychain block number of last staking round #[pallet::storage] - #[pallet::getter(fn last_staking_round_updated_block)] + #[pallet::getter(fn last_era_updated_block)] pub type LastEraUpdatedBlock = StorageValue<_, BlockNumberFor, ValueQuery>; /// The internal of relaychain block number between era. #[pallet::storage] - #[pallet::getter(fn update_staking_round_frequency)] + #[pallet::getter(fn update_era_frequency)] pub type UpdateEraFrequency = StorageValue<_, BlockNumberFor, ValueQuery>; #[pallet::storage] @@ -248,6 +255,7 @@ pub mod pallet { token_amount: BalanceOf, }, } + #[pallet::error] pub enum Error { /// No permission @@ -284,6 +292,19 @@ pub mod pallet { CurrencyRedeemQueueNotFound, } + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + let era_number = Self::get_era_index(T::RelayChainBlockNumber::current_block_number()); + if !era_number.is_zero() { + let _ = Self::update_current_era(era_number); + Self::handle_redeem_requests().map_err(|err| {}).ok(); + } + + T::WeightInfo::on_initialize() + } + } + #[pallet::call] impl Pallet { #[pallet::weight(T::WeightInfo::mint_land())] @@ -630,15 +651,7 @@ impl Pallet { ) } - #[transactional] - fn on_initialize() -> DispatchResult { - for currency in CurrentStakingRound::::iter_keys() { - Self::handle_redeem_staking_round_hook(currency)?; - } - Ok(()) - } - - fn handle_redeem_staking_round_hook(currency: FungibleTokenId) -> DispatchResult { + fn handle_update_staking_round(currency: FungibleTokenId) -> DispatchResult { let last_staking_round = LastStakingRound::::get(currency); let unlock_duration = match UnlockDuration::::get(currency) { Some(StakingRound::Era(unlock_duration_era)) => unlock_duration_era, @@ -873,4 +886,33 @@ impl Pallet { }); Ok(()) } + + pub fn get_era_index(relaychain_block_number: BlockNumberFor) -> EraIndex { + relaychain_block_number + .checked_sub(&Self::last_era_updated_block()) + .and_then(|n| n.checked_div(&Self::update_era_frequency())) + .and_then(|n| TryInto::::try_into(n).ok()) + .unwrap_or_else(Zero::zero) + } + + #[transactional] + fn handle_redeem_requests() -> DispatchResult { + for currency in CurrentStakingRound::::iter_keys() { + Self::handle_update_staking_round(currency)?; + } + Ok(()) + } + + pub fn update_current_era(era_index: EraIndex) -> DispatchResult { + let previous_era = Self::relay_chain_current_era(); + let new_era = previous_era.saturating_add(era_index); + + RelayChainCurrentEra::::put(new_era); + LastEraUpdatedBlock::::put(T::RelayChainBlockNumber::current_block_number()); + Self::deposit_event(Event::::CurrentEraUpdated { new_era_index: new_era }); + + Self::handle_redeem_requests()?; + + Ok(()) + } } diff --git a/primitives/metaverse/src/lib.rs b/primitives/metaverse/src/lib.rs index 5b2e39ea3..49fe3ce07 100644 --- a/primitives/metaverse/src/lib.rs +++ b/primitives/metaverse/src/lib.rs @@ -104,6 +104,8 @@ pub type ReferendumId = u64; pub type LandId = u64; /// EstateId pub type EstateId = u64; +/// Number of era on relaychain +pub type EraIndex = u32; /// Social Token Id type pub type TokenId = u64; /// Undeployed LandBlock Id type From 5a59cc238a17355b85544ba4823ebc338bea1294 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Nov 2023 23:35:27 +1300 Subject: [PATCH 050/114] WIP - Fix build issue --- pallets/spp/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 758609711..43acad2bd 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -35,7 +35,7 @@ use sp_runtime::{ use core_primitives::*; pub use pallet::*; -use primitives::{ClassId, EraIndex, EraIndex, FungibleTokenId, StakingRound, TokenId}; +use primitives::{ClassId, EraIndex, FungibleTokenId, StakingRound, TokenId}; pub use weights::WeightInfo; pub type QueueId = u32; @@ -254,6 +254,8 @@ pub mod pallet { to: T::AccountId, token_amount: BalanceOf, }, + /// Current era updated + CurrentEraUpdated { new_era_index: EraIndex }, } #[pallet::error] From a5b961371d4bea2b580cbf34b5e7b5428dec40b8 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 15 Nov 2023 10:23:46 +1300 Subject: [PATCH 051/114] WIP - Add era config set up function allow council set up the first era config --- pallets/spp/src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++++ pallets/spp/src/tests.rs | 3 +++ 2 files changed, 43 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 43acad2bd..d22c369cf 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -119,6 +119,9 @@ pub mod pallet { type MaximumQueue: Get; type CurrencyIdConversion: CurrencyIdManagement; + + /// Origin represented Governance + type GovernanceOrigin: EnsureOrigin<::RuntimeOrigin>; } pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -256,6 +259,10 @@ pub mod pallet { }, /// Current era updated CurrentEraUpdated { new_era_index: EraIndex }, + /// Last era updated + LastEraUpdated { last_era_block: BlockNumberFor }, + /// Update era frequency + UpdateEraFrequency { frequency: BlockNumberFor }, } #[pallet::error] @@ -292,6 +299,8 @@ pub mod pallet { UserCurrencyRedeemQueueNotFound, /// Redeem queue per currency not found CurrencyRedeemQueueNotFound, + /// The last era updated block is invalid + InvalidLastEraUpdatedBlock, } #[pallet::hooks] @@ -571,6 +580,37 @@ pub mod pallet { }); Ok(().into()) } + + #[pallet::weight(< T as Config >::WeightInfo::mint_land())] + pub fn update_era_config( + origin: OriginFor, + last_era_updated_block: Option>, + frequency: Option>, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + if let Some(change) = frequency { + UpdateEraFrequency::::put(change); + Self::deposit_event(Event::::UpdateEraFrequency { frequency: change }); + } + + if let Some(change) = last_era_updated_block { + let update_era_frequency = UpdateEraFrequency::::get(); + let current_relay_chain_block = T::RelayChainBlockNumber::current_block_number(); + if !update_era_frequency.is_zero() { + ensure!( + change > current_relay_chain_block.saturating_sub(update_era_frequency) + && change <= current_relay_chain_block, + Error::::InvalidLastEraUpdatedBlock + ); + + LastEraUpdatedBlock::::put(change); + Self::deposit_event(Event::::LastEraUpdated { last_era_block: change }); + } + } + + Ok(()) + } } } diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 9cb1b9e86..1610e0b6a 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -188,3 +188,6 @@ fn redeem_rksm_request_works() { ); }); } + +#[test] +fn current_era_update_works() {} From 38761a7f1824e16b64181dab0580c1b4e902abf9 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 15 Nov 2023 10:50:16 +1300 Subject: [PATCH 052/114] WIP - Fix add missing library into command node setup. --- node/src/command.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/src/command.rs b/node/src/command.rs index 6c77ee62c..c2be223b2 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -19,7 +19,7 @@ use std::{io::Write, net::SocketAddr}; use codec::Encode; use cumulus_client_cli::generate_genesis_block; - +use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; use sc_cli::{ @@ -29,6 +29,7 @@ use sc_cli::{ use sc_service::config::{BasePath, PrometheusConfig}; use sc_service::PartialComponents; use sp_core::hexdisplay::HexDisplay; +use sp_runtime::traits::AccountIdConversion; use sp_runtime::traits::Block as BlockT; #[cfg(feature = "with-continuum-runtime")] From 41c466f7f1cfce0c6eee0cb0321606fb4258c4f4 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 15 Nov 2023 10:50:48 +1300 Subject: [PATCH 053/114] WIP - Implement mock relaychain block number --- pallets/spp/src/lib.rs | 2 ++ pallets/spp/src/mock.rs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index d22c369cf..a28d007b2 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -581,6 +581,8 @@ pub mod pallet { Ok(().into()) } + /// This function only for governance origin to execute when starting the protocol or + /// changes of era duration. #[pallet::weight(< T as Config >::WeightInfo::mint_land())] pub fn update_era_config( origin: OriginFor, diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index b90291f6b..c980d0bf4 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -74,6 +74,7 @@ pub const GENERAL_METAVERSE_FUND: AccountId = 102; ord_parameter_types! { pub const One: AccountId = ALICE; + pub const Admin: AccountId = ALICE; } // Configure a mock runtime to test the pallet. @@ -185,6 +186,14 @@ impl asset_manager::Config for Runtime { type RegisterOrigin = EnsureSignedBy; } +impl BlockNumberProvider for MockRelayBlockNumberProvider { + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + Self::get() + } +} + parameter_types! { pub const MinBlocksPerRound: u32 = 10; pub const MinimumStake: Balance = 200; @@ -198,6 +207,7 @@ parameter_types! { pub const LeaseOfferExpiryPeriod: u32 = 6; pub StorageDepositFee: Balance = 1; pub const MaximumQueue: u32 = 50; + pub static MockRelayBlockNumberProvider: BlockNumber = 0; } impl Config for Runtime { @@ -214,6 +224,8 @@ impl Config for Runtime { type PoolAccount = PoolAccountPalletId; type MaximumQueue = MaximumQueue; type CurrencyIdConversion = ForeignAssetMapping; + type RelayChainBlockNumber = MockRelayBlockNumberProvider; + type GovernanceOrigin = EnsureSignedBy; } construct_runtime!( From 70dd1625b083cff29a9e6659fc9f22c1659c6bc1 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 15 Nov 2023 21:32:52 +1300 Subject: [PATCH 054/114] WIP - Implement redeem request distribute funds correctly every era --- pallets/spp/src/lib.rs | 24 ++++----- pallets/spp/src/tests.rs | 114 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 15 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index a28d007b2..f146430df 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -301,6 +301,8 @@ pub mod pallet { CurrencyRedeemQueueNotFound, /// The last era updated block is invalid InvalidLastEraUpdatedBlock, + /// Fail to process redeem requests + FailedToProcessRedemption, } #[pallet::hooks] @@ -309,7 +311,7 @@ pub mod pallet { let era_number = Self::get_era_index(T::RelayChainBlockNumber::current_block_number()); if !era_number.is_zero() { let _ = Self::update_current_era(era_number); - Self::handle_redeem_requests().map_err(|err| {}).ok(); + Self::handle_redeem_requests(era_number).map_err(|err| {}).ok(); } T::WeightInfo::on_initialize() @@ -588,6 +590,7 @@ pub mod pallet { origin: OriginFor, last_era_updated_block: Option>, frequency: Option>, + last_staking_round: StakingRound, ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; @@ -607,6 +610,7 @@ pub mod pallet { ); LastEraUpdatedBlock::::put(change); + LastStakingRound::::insert(FungibleTokenId::NativeToken(1), last_staking_round); Self::deposit_event(Event::::LastEraUpdated { last_era_block: change }); } } @@ -695,7 +699,7 @@ impl Pallet { ) } - fn handle_update_staking_round(currency: FungibleTokenId) -> DispatchResult { + fn handle_update_staking_round(era_index: EraIndex, currency: FungibleTokenId) -> DispatchResult { let last_staking_round = LastStakingRound::::get(currency); let unlock_duration = match UnlockDuration::::get(currency) { Some(StakingRound::Era(unlock_duration_era)) => unlock_duration_era, @@ -705,13 +709,7 @@ impl Pallet { _ => 0, }; - let current_staking_round = match CurrentStakingRound::::get(currency) { - Some(StakingRound::Era(unlock_duration_era)) => unlock_duration_era, - Some(StakingRound::Round(unlock_duration_round)) => unlock_duration_round, - Some(StakingRound::Epoch(unlock_duration_epoch)) => unlock_duration_epoch, - Some(StakingRound::Hour(unlock_duration_hour)) => unlock_duration_hour, - _ => 0, - }; + let current_staking_round = era_index; // Check current staking round queue with last staking round if there is any pending redeem requests if let Some((_total_locked, existing_queue, currency_id)) = @@ -940,9 +938,9 @@ impl Pallet { } #[transactional] - fn handle_redeem_requests() -> DispatchResult { + fn handle_redeem_requests(era_index: EraIndex) -> DispatchResult { for currency in CurrentStakingRound::::iter_keys() { - Self::handle_update_staking_round(currency)?; + Self::handle_update_staking_round(era_index, currency)?; } Ok(()) } @@ -953,10 +951,8 @@ impl Pallet { RelayChainCurrentEra::::put(new_era); LastEraUpdatedBlock::::put(T::RelayChainBlockNumber::current_block_number()); + Self::handle_redeem_requests(new_era)?; Self::deposit_event(Event::::CurrentEraUpdated { new_era_index: new_era }); - - Self::handle_redeem_requests()?; - Ok(()) } } diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 1610e0b6a..026a3e318 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -190,4 +190,116 @@ fn redeem_rksm_request_works() { } #[test] -fn current_era_update_works() {} +fn current_era_update_works() { + ExtBuilder::default() + .ksm_setup_for_alice_and_bob() + .build() + .execute_with(|| { + assert_eq!(SppModule::last_era_updated_block(), 0); + assert_eq!(SppModule::update_era_frequency(), 0); + assert_eq!(MockRelayBlockNumberProvider::current_block_number(), 0); + // Current relaychain block is 102. + MockRelayBlockNumberProvider::set(102); + RelayChainCurrentEra::::put(1); + IterationLimit::::put(50); + // The correct set up era config is the last era block records is 101 with duration is 100 blocks + assert_ok!(SppModule::update_era_config( + RuntimeOrigin::signed(Admin::get()), + Some(101), + Some(100), + StakingRound::Era(1), + )); + + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(ALICE), + FungibleTokenId::NativeToken(1), + 50, + Permill::from_percent(5) + )); + + let next_pool_id = NextPoolId::::get(); + assert_eq!(next_pool_id, 1); + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: ALICE, + commission: Permill::from_percent(5), + currency_id: FungibleTokenId::NativeToken(1), + max: 50, + } + ); + + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + // This is true because fee hasn't been set up. + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); + + assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); + + // Deposit another 10000 KSM + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); + + assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); + + // Pool summary + // Pool Total deposited: 20000 + // Network deposited: 20000, NativeToken(1) + + // Bob summary + // Holding: 20000 FungibleToken(1) reciept token of NativeToken(1) + + assert_noop!( + SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(1), 10000), + Error::::PoolDoesNotExist + ); + + assert_noop!( + SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(0), 10000), + Error::::CurrencyIsNotSupported + ); + + assert_noop!( + SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(1), 10000), + Error::::NoCurrentStakingRound + ); + + UnlockDuration::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(1)); // Bump current staking round to 1 + CurrentStakingRound::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(1)); + + // Bob successfully redeemed + assert_ok!(SppModule::redeem( + RuntimeOrigin::signed(BOB), + 0, + FungibleTokenId::FungibleToken(1), + 10000 + )); + + // After Bob redeems, pool ledger 0 should have only 10000 + assert_eq!(PoolLedger::::get(0), 10000); + + // Verify if redeem queue has requests + let queue_id = QueueNextId::::get(FungibleTokenId::NativeToken(1)); + assert_eq!(queue_id, 1); + let mut queue_items = BoundedVec::default(); + assert_ok!(queue_items.try_push(0)); + let user_redeem_queue = UserCurrencyRedeemQueue::::get(BOB, FungibleTokenId::NativeToken(1)); + let currency_redeem_queue = CurrencyRedeemQueue::::get(FungibleTokenId::NativeToken(1), 0); + let staking_round_redeem_queue = + StakingRoundRedeemQueue::::get(StakingRound::Era(2), FungibleTokenId::NativeToken(1)); + // Verify if user redeem queue has total unlocked and queue items + assert_eq!(user_redeem_queue, Some((10000, queue_items.clone()))); + // If user redeem of Era 1, fund will be released at Era 2 + assert_eq!(currency_redeem_queue, Some((BOB, 10000, StakingRound::Era(2)))); + // Redeem added into staking round redeem queue for Era 2 + assert_eq!( + staking_round_redeem_queue, + Some((10000, queue_items.clone(), FungibleTokenId::NativeToken(1))) + ); + + // Move to era 2 to allow user redeem token successfully + MockRelayBlockNumberProvider::set(202); + SppModule::on_initialize(100); + }); +} From 2a761558f01952dc926256ebbc7076747220a810 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 16 Nov 2023 17:38:20 +1300 Subject: [PATCH 055/114] WIP - Fix duplicate queue processing requests --- pallets/spp/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index f146430df..01a786b79 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -310,8 +310,7 @@ pub mod pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { let era_number = Self::get_era_index(T::RelayChainBlockNumber::current_block_number()); if !era_number.is_zero() { - let _ = Self::update_current_era(era_number); - Self::handle_redeem_requests(era_number).map_err(|err| {}).ok(); + let _ = Self::update_current_era(era_number).map_err(|err| err).ok(); } T::WeightInfo::on_initialize() @@ -700,7 +699,7 @@ impl Pallet { } fn handle_update_staking_round(era_index: EraIndex, currency: FungibleTokenId) -> DispatchResult { - let last_staking_round = LastStakingRound::::get(currency); + let last_staking_round = StakingRound::Era(era_index as u32); let unlock_duration = match UnlockDuration::::get(currency) { Some(StakingRound::Era(unlock_duration_era)) => unlock_duration_era, Some(StakingRound::Round(unlock_duration_round)) => unlock_duration_round, @@ -937,7 +936,6 @@ impl Pallet { .unwrap_or_else(Zero::zero) } - #[transactional] fn handle_redeem_requests(era_index: EraIndex) -> DispatchResult { for currency in CurrentStakingRound::::iter_keys() { Self::handle_update_staking_round(era_index, currency)?; @@ -945,6 +943,7 @@ impl Pallet { Ok(()) } + #[transactional] pub fn update_current_era(era_index: EraIndex) -> DispatchResult { let previous_era = Self::relay_chain_current_era(); let new_era = previous_era.saturating_add(era_index); @@ -955,4 +954,8 @@ impl Pallet { Self::deposit_event(Event::::CurrentEraUpdated { new_era_index: new_era }); Ok(()) } + + pub fn get_pool_account() -> T::AccountId { + (T::PoolAccount::get().into_account_truncating(),) + } } From a756d00df1a4a46ea324855db53990aee9020805 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 16 Nov 2023 18:09:26 +1300 Subject: [PATCH 056/114] WIP - Add function get pool account --- pallets/spp/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 01a786b79..bb8909ae4 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -955,7 +955,7 @@ impl Pallet { Ok(()) } - pub fn get_pool_account() -> T::AccountId { - (T::PoolAccount::get().into_account_truncating(),) + pub fn get_pool_account() -> T::AccountId { + T::PoolAccount::get().into_account_truncating() } } From be05516d4dd69fa15e41315691bd2eb73fbc8a0c Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 16 Nov 2023 18:09:53 +1300 Subject: [PATCH 057/114] Complete unit test on successfully redeem native token after era changes --- pallets/spp/src/mock.rs | 4 ++-- pallets/spp/src/tests.rs | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index c980d0bf4..d2ed479bf 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -290,8 +290,8 @@ impl ExtBuilder { pub fn ksm_setup_for_alice_and_bob(self) -> Self { self.balances(vec![ - (ALICE, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM - (BOB, FungibleTokenId::NativeToken(1), 1000000000000000000000), //KSM + (ALICE, FungibleTokenId::NativeToken(1), 20000), //KSM + (BOB, FungibleTokenId::NativeToken(1), 20000), //KSM ]) } } diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 026a3e318..2001395a1 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -228,10 +228,13 @@ fn current_era_update_works() { max: 50, } ); - + // Verify BOB account with 20000 KSM + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 20000); assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); // This is true because fee hasn't been set up. assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); + // Bob KSM balance become 10000 + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 10000); assert_eq!(PoolLedger::::get(0), 10000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); @@ -239,6 +242,8 @@ fn current_era_update_works() { // Deposit another 10000 KSM assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); + // Bob KSM now is 0 + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 0); assert_eq!(PoolLedger::::get(0), 20000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); @@ -279,6 +284,9 @@ fn current_era_update_works() { // After Bob redeems, pool ledger 0 should have only 10000 assert_eq!(PoolLedger::::get(0), 10000); + // After Bob redeem, make sure BOB KSM balance remains the same as it will only released next era + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 0); + // Verify if redeem queue has requests let queue_id = QueueNextId::::get(FungibleTokenId::NativeToken(1)); assert_eq!(queue_id, 1); @@ -299,7 +307,29 @@ fn current_era_update_works() { ); // Move to era 2 to allow user redeem token successfully + LastStakingRound::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(0)); MockRelayBlockNumberProvider::set(202); - SppModule::on_initialize(100); + SppModule::on_initialize(200); + + let pool_account = SppModule::get_pool_account(); + assert_eq!( + Tokens::accounts(pool_account, FungibleTokenId::NativeToken(1)).free, + 10001 + ); + + // After KSM released, BOB balance now is + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 10000); + assert_eq!( + CurrencyRedeemQueue::::get(FungibleTokenId::NativeToken(1), 0), + None + ); + assert_eq!( + UserCurrencyRedeemQueue::::get(BOB, FungibleTokenId::NativeToken(1)), + None + ); + assert_eq!( + StakingRoundRedeemQueue::::get(StakingRound::Era(2), FungibleTokenId::NativeToken(1)), + None + ); }); } From 7b9f67a211cea96f0f48934656eb52b87dfa4ac2 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 16 Nov 2023 18:19:54 +1300 Subject: [PATCH 058/114] More test cases to avoid double distribution --- pallets/spp/src/tests.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 2001395a1..d5c383807 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -307,7 +307,6 @@ fn current_era_update_works() { ); // Move to era 2 to allow user redeem token successfully - LastStakingRound::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(0)); MockRelayBlockNumberProvider::set(202); SppModule::on_initialize(200); @@ -331,5 +330,34 @@ fn current_era_update_works() { StakingRoundRedeemQueue::::get(StakingRound::Era(2), FungibleTokenId::NativeToken(1)), None ); + + // Move to era 3, make sure no double redeem process + MockRelayBlockNumberProvider::set(302); + SppModule::on_initialize(300); + + // Pool account remain the same + assert_eq!( + Tokens::accounts(pool_account, FungibleTokenId::NativeToken(1)).free, + 10001 + ); + + // BOB balance remain the same + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 10000); + assert_eq!( + CurrencyRedeemQueue::::get(FungibleTokenId::NativeToken(1), 0), + None + ); + assert_eq!( + UserCurrencyRedeemQueue::::get(BOB, FungibleTokenId::NativeToken(1)), + None + ); + assert_eq!( + StakingRoundRedeemQueue::::get(StakingRound::Era(2), FungibleTokenId::NativeToken(1)), + None + ); + assert_eq!( + StakingRoundRedeemQueue::::get(StakingRound::Era(3), FungibleTokenId::NativeToken(1)), + None + ); }); } From baf901acc97e53c465b976bd069618d13ac1aecf Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 17 Nov 2023 12:54:12 +1300 Subject: [PATCH 059/114] WIP - add boosting conviction and boost info data type --- pallets/spp/src/utils.rs | 120 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/utils.rs b/pallets/spp/src/utils.rs index bec2e5c66..e8fb8fc59 100644 --- a/pallets/spp/src/utils.rs +++ b/pallets/spp/src/utils.rs @@ -19,7 +19,10 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_runtime::{Permill, RuntimeDebug}; +use sp_runtime::{ + traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, IntegerSquareRoot, Saturating, Zero}, + Permill, RuntimeDebug, +}; use primitives::FungibleTokenId; @@ -35,3 +38,118 @@ pub struct PoolInfo { /// Max nft rewards pub max: u32, } + +/// Amount of votes and capital placed in delegation for an account. +#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct BoostingDelegations { + /// The number of votes (this is post-conviction). + pub votes: Balance, + /// The amount of raw capital, used for the turnout. + pub capital: Balance, +} + +/// A value denoting the strength of conviction of a vote. +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo)] +pub enum BoostingConviction { + /// 0.1x votes, unlocked. + None, + /// 1x votes, locked for an enactment period following a successful vote. + Locked1x, + /// 2x votes, locked for 2x enactment periods following a successful vote. + Locked2x, + /// 3x votes, locked for 4x... + Locked3x, + /// 4x votes, locked for 8x... + Locked4x, + /// 5x votes, locked for 16x... + Locked5x, + /// 6x votes, locked for 32x... + Locked6x, +} + +impl Default for BoostingConviction { + fn default() -> Self { + BoostingConviction::None + } +} + +impl From for u8 { + fn from(c: BoostingConviction) -> u8 { + match c { + BoostingConviction::None => 0, + BoostingConviction::Locked1x => 1, + BoostingConviction::Locked2x => 2, + BoostingConviction::Locked3x => 3, + BoostingConviction::Locked4x => 4, + BoostingConviction::Locked5x => 5, + BoostingConviction::Locked6x => 6, + } + } +} + +impl TryFrom for BoostingConviction { + type Error = (); + fn try_from(i: u8) -> Result { + Ok(match i { + 0 => BoostingConviction::None, + 1 => BoostingConviction::Locked1x, + 2 => BoostingConviction::Locked2x, + 3 => BoostingConviction::Locked3x, + 4 => BoostingConviction::Locked4x, + 5 => BoostingConviction::Locked5x, + 6 => BoostingConviction::Locked6x, + _ => return Err(()), + }) + } +} + +impl BoostingConviction { + /// The amount of time (in number of periods) that our conviction implies a successful voter's + /// balance should be locked for. + pub fn lock_periods(self) -> u32 { + match self { + BoostingConviction::None => 0, + BoostingConviction::Locked1x => 1, + BoostingConviction::Locked2x => 2, + BoostingConviction::Locked3x => 4, + BoostingConviction::Locked4x => 8, + BoostingConviction::Locked5x => 16, + BoostingConviction::Locked6x => 32, + } + } + + /// The votes of a voter of the given `balance` with our conviction. + pub fn votes + Zero + Copy + CheckedMul + CheckedDiv + Bounded>( + self, + capital: B, + ) -> BoostingDelegations { + let votes = match self { + BoostingConviction::None => capital.checked_div(&10u8.into()).unwrap_or_else(Zero::zero), + x => capital.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value), + }; + BoostingDelegations { votes, capital } + } +} + +impl Bounded for BoostingConviction { + fn min_value() -> Self { + BoostingConviction::None + } + fn max_value() -> Self { + BoostingConviction::Locked6x + } +} + +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct BoostInfo { + pub(crate) balance: Balance, + pub(crate) conviction: BoostingConviction, +} + +impl BoostInfo { + /// Returns `Some` of the lock periods that the account is locked for, assuming that the + /// referendum passed if `approved` is `true`. + pub fn get_locked_period(self) -> (u32, Balance) { + return (self.conviction.lock_periods(), self.balance); + } +} From 72612ee06b4e790c05b49b31d6eea47c3016476e Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 17 Nov 2023 12:56:32 +1300 Subject: [PATCH 060/114] WIP - integrate boosting mechanism --- Cargo.lock | 18 +++++++++ Cargo.toml | 89 +++++++++++++++++++++--------------------- pallets/spp/Cargo.toml | 4 +- pallets/spp/src/lib.rs | 8 +++- 4 files changed, 73 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39faaaf1c..fc2087ec6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6637,6 +6637,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "orml-rewards" +version = "0.4.1-dev" +source = "git+https://github.com/bit-country/open-runtime-module-library?branch=v-9.38#5861b6707c69031e8d70515b0aea6d222f5a5053" +dependencies = [ + "frame-support", + "frame-system", + "orml-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "orml-tokens" version = "0.4.1-dev" @@ -8033,6 +8050,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "orml-rewards", "orml-tokens", "orml-traits", "pallet-balances", diff --git a/Cargo.toml b/Cargo.toml index 41220719b..3f959a986 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ url = "2.3.1" tracing-core = "0.1.28" impl-trait-for-tuples = "0.2.2" num_enum = { version = "0.5.3", default-features = false } -getrandom = { version = "0.2.7", features = ["js"]} +getrandom = { version = "0.2.7", features = ["js"] } blake2-rfc = { version = "0.2.18", default-features = false } hex = { version = "0.4", default-features = false } funty = { version = "=1.1.0", default-features = false } @@ -38,13 +38,13 @@ similar-asserts = { version = "1.1.0" } # General (ethereum) ethabi = { version = "18.0.0", default-features = false } -evm = { git = "https://github.com/rust-blockchain/evm", rev = "51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c", default-features = false, features = [ "with-codec" ] } +evm = { git = "https://github.com/rust-blockchain/evm", rev = "51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c", default-features = false, features = ["with-codec"] } # General (precompile macro) proc-macro2 = "1.0" quote = "1.0" sha3 = "0.8" -syn = { version = "1.0", features = [ "extra-traits", "fold", "full", "visit" ] } +syn = { version = "1.0", features = ["extra-traits", "fold", "full", "visit"] } # General (node only) parking_lot = "0.12.1" @@ -60,7 +60,7 @@ frame-executive = { git = 'https://github.com/paritytech/substrate', branch = 'p frame-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } frame-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } frame-system = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -frame-system-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false} +frame-system-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } frame-system-rpc-runtime-api = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } frame-try-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false } @@ -85,7 +85,7 @@ sc-consensus-epochs = { git = "https://github.com/paritytech/substrate", branch sc-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sc-consensus-manual-seal = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -sp-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38"} +sp-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sc-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sc-finality-grandpa-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } @@ -101,22 +101,22 @@ sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkad # Substrate Primitive Dependencies (general) sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } sp-block-builder = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } -sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } +sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } sp-api = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-consensus-aura = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-inherents = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-io = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-runtime = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-std = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-version = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } -sp-application-crypto = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -sp-arithmetic = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-consensus-aura = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-inherents = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-io = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-runtime = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-std = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-version = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } +sp-application-crypto = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } +sp-arithmetic = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } # Substrate Utilities -node-primitives = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38"} +node-primitives = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } try-runtime-cli = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38" } substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } @@ -128,13 +128,13 @@ sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "polkad sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -pallet-im-online = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38"} +pallet-im-online = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } # Substrate Pallets pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } pallet-balances = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } -pallet-collective = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false} +pallet-collective = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } pallet-democracy = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } pallet-randomness-collective-flip = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } pallet-sudo = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.38', default-features = false } @@ -186,38 +186,39 @@ pallet-collator-selection = { git = 'https://github.com/paritytech/cumulus', bra # Polkadot polkadot-cli = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" } polkadot-service = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" } -polkadot-parachain = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" , default-features = false } -polkadot-runtime-parachains = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" , default-features = false } -polkadot-primitives = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" , default-features = false } -xcm = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" , default-features = false } -xcm-builder = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" , default-features = false } -xcm-executor = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" , default-features = false } -pallet-xcm = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38" , default-features = false } +polkadot-parachain = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } +polkadot-runtime-parachains = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } +polkadot-primitives = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } +xcm = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } +xcm-builder = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } +xcm-executor = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } +pallet-xcm = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } polkadot-runtime-common = { git = 'https://github.com/paritytech/polkadot', branch = "release-v0.9.38", default-features = false } kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.38", default-features = false } # ORML -orml-currencies = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-tokens = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-traits = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-nft = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-unknown-tokens = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-xtokens = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-xcm = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-xcm-support = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } -orml-oracle = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-currencies = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-tokens = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-traits = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-nft = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-unknown-tokens = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-xtokens = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-xcm = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-xcm-support = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-oracle = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } orml-benchmarking = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } +orml-rewards = { git = "https://github.com/bit-country/open-runtime-module-library", branch = "v-9.38", default-features = false } # EVM pallet-dynamic-fee = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } -fp-consensus = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} -fp-storage = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} -fc-cli = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} -fc-rpc = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} -fc-rpc-core = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} -fc-consensus = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} -fc-mapping-sync = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} -fc-db = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38"} +fp-consensus = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } +fp-storage = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } +fc-cli = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } +fc-rpc = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } +fc-rpc-core = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } +fc-consensus = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } +fc-mapping-sync = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } +fc-db = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38" } fp-self-contained = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38", default-features = false } fp-rpc = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38", default-features = false } fp-evm = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38", default-features = false } @@ -231,7 +232,7 @@ pallet-evm-precompile-ed25519 = { git = "https://github.com/justinphamnz/frontie pallet-evm-precompile-modexp = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38", default-features = false } pallet-evm-precompile-sha3fips = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38", default-features = false } pallet-evm-precompile-simple = { git = "https://github.com/justinphamnz/frontier", branch = "polkadot-v0.9.38", default-features = false } -libsecp256k1 = { version = "0.6", default-features = false, features = ["hmac", "static-context"]} +libsecp256k1 = { version = "0.6", default-features = false, features = ["hmac", "static-context"] } # ink! pallet-contracts = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } diff --git a/pallets/spp/Cargo.toml b/pallets/spp/Cargo.toml index 917fae3f0..30082b8de 100644 --- a/pallets/spp/Cargo.toml +++ b/pallets/spp/Cargo.toml @@ -27,6 +27,7 @@ substrate-fixed = { workspace = true } pallet-balances = { workspace = true, optional = true } orml-traits = { workspace = true } orml-tokens = { workspace = true } +orml-rewards = { workspace = true } # local packages core-primitives = { path = "../../traits/core-primitives", default-features = false } @@ -65,5 +66,6 @@ std = [ 'frame-benchmarking/std', 'orml-tokens/std', "currencies/std", - "asset-manager/std" + "asset-manager/std", + "orml-rewards/std" ] \ No newline at end of file diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index bb8909ae4..2211da5eb 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -61,7 +61,7 @@ pub mod pallet { use primitives::{PoolId, StakingRound}; - use crate::utils::PoolInfo; + use crate::utils::{BoostInfo, PoolInfo}; use super::*; @@ -616,6 +616,12 @@ pub mod pallet { Ok(()) } + + /// This function allow reward voting for the pool + #[pallet::weight(< T as Config >::WeightInfo::mint_land())] + pub fn boost(origin: OriginFor, pool_id: PoolId, vote: BoostInfo>) -> DispatchResult { + Ok(()) + } } } From 776281fc4d8a200c28591c26b67e1c30ed208a5e Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 17 Nov 2023 21:19:41 +1300 Subject: [PATCH 061/114] WIP - Add boost mechanism that calculates boosting weights --- pallets/economy/src/lib.rs | 2 -- pallets/spp/src/lib.rs | 32 +++++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pallets/economy/src/lib.rs b/pallets/economy/src/lib.rs index ea06898a0..124e7997a 100644 --- a/pallets/economy/src/lib.rs +++ b/pallets/economy/src/lib.rs @@ -18,7 +18,6 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Encode; - use frame_support::{ ensure, pallet_prelude::*, @@ -54,7 +53,6 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { - use sp_runtime::traits::{CheckedAdd, CheckedSub, Saturating}; use sp_runtime::ArithmeticError; diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 2211da5eb..353e95eba 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -53,10 +53,10 @@ pub mod weights; #[frame_support::pallet] pub mod pallet { - use frame_support::traits::{Currency, ReservableCurrency}; + use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; - use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedSub}; + use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub}; use sp_runtime::Permill; use primitives::{PoolId, StakingRound}; @@ -75,7 +75,9 @@ pub mod pallet { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Currency type - type Currency: Currency + ReservableCurrency; + type Currency: Currency + + ReservableCurrency + + LockableCurrency; /// Multi currencies type that handles different currency type in auction type MultiCurrency: MultiReservableCurrency< Self::AccountId, @@ -303,6 +305,8 @@ pub mod pallet { InvalidLastEraUpdatedBlock, /// Fail to process redeem requests FailedToProcessRedemption, + /// Insufficient Fund + InsufficientFund, } #[pallet::hooks] @@ -620,6 +624,28 @@ pub mod pallet { /// This function allow reward voting for the pool #[pallet::weight(< T as Config >::WeightInfo::mint_land())] pub fn boost(origin: OriginFor, pool_id: PoolId, vote: BoostInfo>) -> DispatchResult { + // Ensure user is signed + let who = ensure_signed(origin)?; + + // Ensure user has balance to vote + ensure!( + vote.balance <= T::Currency::free_balance(&who), + Error::::InsufficientFund + ); + + // Check if pool exists + let pool_instance = Pool::::get(pool_id).ok_or(Error::::PoolDoesNotExist)?; + // Convert boost conviction into shares + let vote_conviction = vote.conviction.lock_periods(); + // Calculate lock period from UnlockDuration block number x conviction + let current_block: T::BlockNumber = >::block_number(); + + let mut unlock_at = current_block.saturating_add(UpdateEraFrequency::::get()); + if !vote_conviction.is_zero() { + unlock_at.saturating_mul(vote_conviction.into()); + } + // Locked token + // Add shares into the rewards pool Ok(()) } } From 9c00cd0986494650c427517e038a5596520aef2e Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 17 Nov 2023 21:55:09 +1300 Subject: [PATCH 062/114] WIP - Add locking type that allow accumulating balance and locks --- pallets/spp/src/utils.rs | 45 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/utils.rs b/pallets/spp/src/utils.rs index e8fb8fc59..45edf1b95 100644 --- a/pallets/spp/src/utils.rs +++ b/pallets/spp/src/utils.rs @@ -24,7 +24,7 @@ use sp_runtime::{ Permill, RuntimeDebug, }; -use primitives::FungibleTokenId; +use primitives::{FungibleTokenId, PoolId}; // Helper methods to compute the issuance rate for undeployed land. @@ -153,3 +153,46 @@ impl BoostInfo { return (self.conviction.lock_periods(), self.balance); } } + +/// A "prior" lock, i.e. a lock for some now-forgotten reason. +#[derive(Encode, Decode, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo)] +pub struct PriorLock(BlockNumber, Balance); + +impl PriorLock { + /// Accumulates an additional lock. + pub fn accumulate(&mut self, until: BlockNumber, amount: Balance) { + self.0 = self.0.max(until); + self.1 = self.1.max(amount); + } + + pub fn locked(&self) -> Balance { + self.1 + } + + pub fn update(&mut self, now: BlockNumber) { + if now >= self.0 { + self.0 = Zero::zero(); + self.1 = Zero::zero(); + } + } +} + +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct BoostingRecord { + pub(crate) votes: Vec<(PoolId, Vote)>, + pub(crate) prior: PriorLock, +} + +impl BoostingRecord { + pub fn update(&mut self, now: BlockNumber) { + self.prior.update(now); + } + + /// The amount of this account's balance that much currently be locked due to voting. + pub fn locked_balance(&self) -> Balance { + self.votes + .iter() + .map(|i| i.1.balance) + .fold(self.prior.locked(), |a, i| a.max(i)) + } +} From 3d0d5cb951b7cb99c4546a414c7655f599f9aea1 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 17 Nov 2023 21:55:52 +1300 Subject: [PATCH 063/114] WIP - Implement boosting info per account storage --- pallets/spp/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 353e95eba..cadd3b278 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -61,7 +61,7 @@ pub mod pallet { use primitives::{PoolId, StakingRound}; - use crate::utils::{BoostInfo, PoolInfo}; + use crate::utils::{BoostInfo, BoostingRecord, PoolInfo}; use super::*; @@ -231,6 +231,12 @@ pub mod pallet { #[pallet::getter(fn iteration_limit)] pub type IterationLimit = StorageValue<_, u32, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn boosting_record)] + /// Store boosting records for each account + pub type BoostingOf = + StorageMap<_, Twox64Concat, T::AccountId, BoostingRecord, T::BlockNumber>, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { From 0441ea8b47e588597ea388be5929421011fce0bd Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 18 Nov 2023 22:04:43 +1300 Subject: [PATCH 064/114] WIP - Add boost calculation and locking logic for existing boost and new boost --- pallets/spp/src/lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index cadd3b278..4c8eef690 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -313,6 +313,8 @@ pub mod pallet { FailedToProcessRedemption, /// Insufficient Fund InsufficientFund, + /// Error while adding new boost + MaxVotesReached, } #[pallet::hooks] @@ -647,10 +649,34 @@ pub mod pallet { let current_block: T::BlockNumber = >::block_number(); let mut unlock_at = current_block.saturating_add(UpdateEraFrequency::::get()); + let mut total_balance = vote.balance; if !vote_conviction.is_zero() { unlock_at.saturating_mul(vote_conviction.into()); + total_balance.saturating_mul(vote_conviction.into()); } // Locked token + + BoostingOf::::try_mutate(who, |voting| -> DispatchResult { + let votes = &mut voting.votes; + let prior_lock = &mut voting.prior; + match votes.binary_search_by_key(&pool_id, |i| i.0) { + Ok(i) => { + // User already boosted, this is adding up their boosting weight + votes[i] + .1 + .add(total_balance.clone()) + .ok_or(Error::::ArithmeticOverflow)?; + voting + .prior + .accumulate(unlock_at, votes[i].1.balance.saturating_add(total_balance)) + } + Err(i) => { + votes.insert(i, (pool_id, vote)); + voting.prior.accumulate(unlock_at, total_balance); + } + } + Ok(()) + })?; // Add shares into the rewards pool Ok(()) } From 6b737b4e2c88b2298ef94290d4ff21fe189f2533 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 18 Nov 2023 22:05:11 +1300 Subject: [PATCH 065/114] WIP - Implement Copy trait and add logic to calculate boost info --- pallets/spp/src/utils.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/utils.rs b/pallets/spp/src/utils.rs index 45edf1b95..83e1ec37f 100644 --- a/pallets/spp/src/utils.rs +++ b/pallets/spp/src/utils.rs @@ -146,12 +146,17 @@ pub struct BoostInfo { pub(crate) conviction: BoostingConviction, } -impl BoostInfo { +impl BoostInfo { /// Returns `Some` of the lock periods that the account is locked for, assuming that the /// referendum passed if `approved` is `true`. pub fn get_locked_period(self) -> (u32, Balance) { return (self.conviction.lock_periods(), self.balance); } + + pub fn add(&mut self, balance: Balance) -> Option<()> { + self.balance.saturating_add(balance.into()); + Some(()) + } } /// A "prior" lock, i.e. a lock for some now-forgotten reason. @@ -179,7 +184,7 @@ impl PriorLock { - pub(crate) votes: Vec<(PoolId, Vote)>, + pub(crate) votes: Vec<(PoolId, BoostInfo)>, pub(crate) prior: PriorLock, } From 9679d14e6d59aaf7a6d8d16aa5bbe1b36867d27a Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 19 Nov 2023 10:57:17 +1300 Subject: [PATCH 066/114] WIP - Implement reward share calculation when user boosted --- pallets/spp/src/lib.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 4c8eef690..2c6fd1a5f 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -56,7 +56,7 @@ pub mod pallet { use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; - use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub}; + use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub, UniqueSaturatedInto}; use sp_runtime::Permill; use primitives::{PoolId, StakingRound}; @@ -71,7 +71,15 @@ pub mod pallet { pub struct Pallet(PhantomData); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: + frame_system::Config + + orml_rewards::Config< + Share = BalanceOf, + Balance = BalanceOf, + PoolId = PoolId, + CurrencyId = FungibleTokenId, + > + { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Currency type @@ -271,6 +279,12 @@ pub mod pallet { LastEraUpdated { last_era_block: BlockNumberFor }, /// Update era frequency UpdateEraFrequency { frequency: BlockNumberFor }, + /// Boosted successful + Boosted { + booster: T::AccountId, + pool_id: PoolId, + boost_info: BoostInfo>, + }, } #[pallet::error] @@ -656,9 +670,8 @@ pub mod pallet { } // Locked token - BoostingOf::::try_mutate(who, |voting| -> DispatchResult { + BoostingOf::::try_mutate(who.clone(), |voting| -> DispatchResult { let votes = &mut voting.votes; - let prior_lock = &mut voting.prior; match votes.binary_search_by_key(&pool_id, |i| i.0) { Ok(i) => { // User already boosted, this is adding up their boosting weight @@ -671,13 +684,21 @@ pub mod pallet { .accumulate(unlock_at, votes[i].1.balance.saturating_add(total_balance)) } Err(i) => { - votes.insert(i, (pool_id, vote)); + votes.insert(i, (pool_id, vote.clone())); voting.prior.accumulate(unlock_at, total_balance); } } Ok(()) })?; // Add shares into the rewards pool + >::add_share(&who, &pool_id, total_balance.unique_saturated_into()); + // Emit Boosted event + Self::deposit_event(Event::::Boosted { + booster: who.clone(), + pool_id, + boost_info: vote.clone(), + }); + Ok(()) } } From b90650c2b39538698e323ac0a47466d9d4827cb7 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 19 Nov 2023 12:17:11 +1300 Subject: [PATCH 067/114] WIP - Apply extend lock to currency with amount of boosted --- pallets/spp/src/lib.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 2c6fd1a5f..a69ef0ac2 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -18,6 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::*; +use frame_support::traits::LockIdentifier; use frame_support::{ dispatch::DispatchResult, ensure, @@ -26,7 +27,7 @@ use frame_support::{ }; use frame_system::ensure_signed; use frame_system::pallet_prelude::*; -use orml_traits::MultiCurrency; +use orml_traits::{MultiCurrency, RewardHandler}; use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedDiv, CheckedSub}; use sp_runtime::{ traits::{AccountIdConversion, Convert, Saturating, Zero}, @@ -35,7 +36,7 @@ use sp_runtime::{ use core_primitives::*; pub use pallet::*; -use primitives::{ClassId, EraIndex, FungibleTokenId, StakingRound, TokenId}; +use primitives::{ClassId, EraIndex, FungibleTokenId, PoolId, StakingRound, TokenId}; pub use weights::WeightInfo; pub type QueueId = u32; @@ -51,9 +52,11 @@ mod tests; pub mod weights; +const BOOSTING_ID: LockIdentifier = *b"bc/boost"; + #[frame_support::pallet] pub mod pallet { - use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency}; + use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency, WithdrawReasons}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub, UniqueSaturatedInto}; @@ -656,7 +659,8 @@ pub mod pallet { ); // Check if pool exists - let pool_instance = Pool::::get(pool_id).ok_or(Error::::PoolDoesNotExist)?; + ensure!(Pool::::get(pool_id).is_some(), Error::::PoolDoesNotExist); + // Still need to work out some // Convert boost conviction into shares let vote_conviction = vote.conviction.lock_periods(); // Calculate lock period from UnlockDuration block number x conviction @@ -690,6 +694,8 @@ pub mod pallet { } Ok(()) })?; + T::Currency::extend_lock(BOOSTING_ID, &who, vote.balance, WithdrawReasons::TRANSFER); + // Add shares into the rewards pool >::add_share(&who, &pool_id, total_balance.unique_saturated_into()); // Emit Boosted event @@ -1044,3 +1050,15 @@ impl Pallet { T::PoolAccount::get().into_account_truncating() } } + +impl RewardHandler for Pallet { + type Balance = BalanceOf; + type PoolId = PoolId; + + fn payout(who: &T::AccountId, pool_id: &Self::PoolId, currency_id: FungibleTokenId, payout_amount: Self::Balance) { + if payout_amount.is_zero() { + return; + } + // TODO implement payout logic + } +} From 6889610e3e9086780b00a3ad0275a5aa59e0a54f Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 19 Nov 2023 12:17:40 +1300 Subject: [PATCH 068/114] WIP - exposed block number and balance of prior lock for test --- pallets/spp/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/spp/src/utils.rs b/pallets/spp/src/utils.rs index 83e1ec37f..367303352 100644 --- a/pallets/spp/src/utils.rs +++ b/pallets/spp/src/utils.rs @@ -161,7 +161,7 @@ impl BoostInfo { /// A "prior" lock, i.e. a lock for some now-forgotten reason. #[derive(Encode, Decode, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo)] -pub struct PriorLock(BlockNumber, Balance); +pub struct PriorLock(pub(crate) BlockNumber, pub(crate) Balance); impl PriorLock { /// Accumulates an additional lock. From d4df72a48da44a3daa77b6227034ce63ee1dfd34 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 19 Nov 2023 12:17:59 +1300 Subject: [PATCH 069/114] WIP - Unit test - boosting works --- pallets/spp/src/mock.rs | 11 ++++++- pallets/spp/src/tests.rs | 65 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index d2ed479bf..77283e38f 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -18,7 +18,7 @@ use asset_manager::ForeignAssetMapping; use auction_manager::{Auction, AuctionInfo, AuctionItem, AuctionType, CheckAuctionItemHandler, ListingLevel}; use core_primitives::{CollectionType, NftClassData, TokenType}; use primitives::{ - Amount, AssetId, Attributes, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, NftMetadata, TokenId, + Amount, AssetId, Attributes, AuctionId, ClassId, FungibleTokenId, GroupCollectionId, NftMetadata, PoolId, TokenId, LAND_CLASS_ID, }; @@ -194,6 +194,14 @@ impl BlockNumberProvider for MockRelayBlockNumberProvider { } } +impl orml_rewards::Config for Runtime { + type Share = Balance; + type Balance = Balance; + type PoolId = PoolId; + type CurrencyId = FungibleTokenId; + type Handler = SppModule; +} + parameter_types! { pub const MinBlocksPerRound: u32 = 10; pub const MinimumStake: Balance = 200; @@ -240,6 +248,7 @@ construct_runtime!( Currencies: currencies::{ Pallet, Storage, Call, Event}, Tokens: orml_tokens::{Pallet, Call, Storage, Config, Event}, Spp: spp:: {Pallet, Call, Storage, Event}, + RewardsModule: orml_rewards } ); diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index d5c383807..15442cd22 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -23,7 +23,7 @@ use sp_runtime::{Perbill, Permill}; use mock::{RuntimeEvent, *}; -use crate::utils::PoolInfo; +use crate::utils::{BoostInfo, BoostingConviction, BoostingRecord, PoolInfo, PriorLock}; use super::*; @@ -361,3 +361,66 @@ fn current_era_update_works() { ); }); } + +#[test] +fn boosting_works() { + ExtBuilder::default() + .ksm_setup_for_alice_and_bob() + .build() + .execute_with(|| { + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(ALICE), + FungibleTokenId::NativeToken(1), + 50, + Permill::from_percent(5) + )); + + let next_pool_id = NextPoolId::::get(); + assert_eq!(next_pool_id, 1); + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: ALICE, + commission: Permill::from_percent(5), + currency_id: FungibleTokenId::NativeToken(1), + max: 50 + } + ); + + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + // This is true because fee hasn't been set up. + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); + + assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); + + // Deposit another 10000 KSM + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); + + assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); + + // Boosting works + assert_ok!(SppModule::boost( + RuntimeOrigin::signed(BOB), + 0, + BoostInfo { + balance: 1000, + conviction: BoostingConviction::None + } + )); + let boosting_of = BoostingOf::::get(BOB); + let some_record = BoostingRecord { + votes: vec![( + 0, + BoostInfo { + balance: 1000, + conviction: BoostingConviction::None, + }, + )], + prior: PriorLock(1, 1000), + }; + assert_eq!(boosting_of, some_record) + }); +} From 21a345a177a2bd00e78a37c0c3251b29010946ef Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 19 Nov 2023 21:08:06 +1300 Subject: [PATCH 070/114] WIP - Unit test - complete boosting test that ensure balance is not usable after boosted. --- pallets/spp/src/lib.rs | 7 ++++++- pallets/spp/src/tests.rs | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index a69ef0ac2..3f2b24471 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -694,7 +694,12 @@ pub mod pallet { } Ok(()) })?; - T::Currency::extend_lock(BOOSTING_ID, &who, vote.balance, WithdrawReasons::TRANSFER); + T::Currency::extend_lock( + BOOSTING_ID, + &who, + vote.balance, + frame_support::traits::WithdrawReasons::TRANSFER, + ); // Add shares into the rewards pool >::add_share(&who, &pool_id, total_balance.unique_saturated_into()); diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 15442cd22..4f63e1363 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -18,6 +18,7 @@ #![cfg(test)] use frame_support::{assert_err, assert_noop, assert_ok}; +use pallet_balances::BalanceLock; use sp_runtime::traits::BadOrigin; use sp_runtime::{Perbill, Permill}; @@ -32,6 +33,14 @@ fn test_one() { ExtBuilder::default().build().execute_with(|| assert_eq!(1, 1)); } +fn the_lock(amount: Balance) -> BalanceLock { + BalanceLock { + id: BOOSTING_ID, + amount, + reasons: pallet_balances::Reasons::Misc, + } +} + #[test] fn create_ksm_pool_works() { ExtBuilder::default() @@ -402,11 +411,12 @@ fn boosting_works() { assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); // Boosting works + let bob_free_balance = Balances::free_balance(BOB); assert_ok!(SppModule::boost( RuntimeOrigin::signed(BOB), 0, BoostInfo { - balance: 1000, + balance: bob_free_balance, conviction: BoostingConviction::None } )); @@ -415,12 +425,13 @@ fn boosting_works() { votes: vec![( 0, BoostInfo { - balance: 1000, + balance: bob_free_balance, conviction: BoostingConviction::None, }, )], - prior: PriorLock(1, 1000), + prior: PriorLock(1, bob_free_balance), }; - assert_eq!(boosting_of, some_record) + assert_eq!(boosting_of, some_record); + assert_eq!(Balances::usable_balance(&BOB), 0); }); } From ff743c3de448dd6a207303910b00c705f04ba2c2 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 20 Nov 2023 15:21:39 +1300 Subject: [PATCH 071/114] WIP - Reserve pool id 0 as network pool --- pallets/spp/src/lib.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 3f2b24471..ba35a81e4 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -59,7 +59,7 @@ pub mod pallet { use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency, WithdrawReasons}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; - use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub, UniqueSaturatedInto}; + use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub, One, UniqueSaturatedInto}; use sp_runtime::Permill; use primitives::{PoolId, StakingRound}; @@ -248,6 +248,11 @@ pub mod pallet { pub type BoostingOf = StorageMap<_, Twox64Concat, T::AccountId, BoostingRecord, T::BlockNumber>, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn network_boost_info)] + /// Store boosting records for each account + pub type NetworkBoostingInfo = StorageValue<_, BoostingRecord, T::BlockNumber>, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { @@ -363,11 +368,15 @@ pub mod pallet { Error::::CurrencyIsNotSupported ); - // TODO Check commission below threshold - // Collect pool creation fee Self::collect_pool_creation_fee(&who, currency_id)?; + // Ensure no pool id is zero + let current_pool_id = NextPoolId::::get(); + if current_pool_id.is_zero() { + NextPoolId::::put(1u32); + } + // Next pool id let next_pool_id = NextPoolId::::try_mutate(|id| -> Result { let current_id = *id; From e253806e92b72c89b23a93fdae8a86540687e9b2 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 20 Nov 2023 15:21:56 +1300 Subject: [PATCH 072/114] WIP - Unit test - verify all pool id 0 logic works --- pallets/spp/src/tests.rs | 66 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 4f63e1363..b7e50ecf1 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -55,7 +55,7 @@ fn create_ksm_pool_works() { )); let next_pool_id = NextPoolId::::get(); - assert_eq!(next_pool_id, 1); + assert_eq!(next_pool_id, 2); assert_eq!( Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { @@ -82,7 +82,7 @@ fn deposit_ksm_works() { )); let next_pool_id = NextPoolId::::get(); - assert_eq!(next_pool_id, 1); + assert_eq!(next_pool_id, 2); assert_eq!( Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { @@ -93,18 +93,18 @@ fn deposit_ksm_works() { } ); - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); // This is true because fee hasn't been set up. assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); - assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(PoolLedger::::get(1), 10000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); // Deposit another 10000 KSM - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); - assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(PoolLedger::::get(1), 20000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); }); } @@ -123,7 +123,7 @@ fn redeem_rksm_request_works() { )); let next_pool_id = NextPoolId::::get(); - assert_eq!(next_pool_id, 1); + assert_eq!(next_pool_id, 2); assert_eq!( Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { @@ -134,32 +134,32 @@ fn redeem_rksm_request_works() { } ); - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); // This is true because fee hasn't been set up. assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); - assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(PoolLedger::::get(1), 10000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); // Deposit another 10000 KSM - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); - assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(PoolLedger::::get(1), 20000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); assert_noop!( - SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(1), 10000), + SppModule::redeem(RuntimeOrigin::signed(BOB), 2, FungibleTokenId::FungibleToken(1), 10000), Error::::PoolDoesNotExist ); assert_noop!( - SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(0), 10000), + SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(0), 10000), Error::::CurrencyIsNotSupported ); assert_noop!( - SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(1), 10000), + SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(1), 10000), Error::::NoCurrentStakingRound ); @@ -168,13 +168,13 @@ fn redeem_rksm_request_works() { CurrentStakingRound::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(1)); assert_ok!(SppModule::redeem( RuntimeOrigin::signed(BOB), - 0, + 1, FungibleTokenId::FungibleToken(1), 10000 )); // After Bob redeems, pool ledger 0 should have only 10000 - assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(PoolLedger::::get(1), 10000); // Verify if redeem queue has requests @@ -227,7 +227,7 @@ fn current_era_update_works() { )); let next_pool_id = NextPoolId::::get(); - assert_eq!(next_pool_id, 1); + assert_eq!(next_pool_id, 2); assert_eq!( Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { @@ -239,22 +239,22 @@ fn current_era_update_works() { ); // Verify BOB account with 20000 KSM assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 20000); - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); // This is true because fee hasn't been set up. assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); // Bob KSM balance become 10000 assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 10000); - assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(PoolLedger::::get(1), 10000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); // Deposit another 10000 KSM - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); // Bob KSM now is 0 assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 0); - assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(PoolLedger::::get(1), 20000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); // Pool summary @@ -265,17 +265,17 @@ fn current_era_update_works() { // Holding: 20000 FungibleToken(1) reciept token of NativeToken(1) assert_noop!( - SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(1), 10000), + SppModule::redeem(RuntimeOrigin::signed(BOB), 2, FungibleTokenId::FungibleToken(1), 10000), Error::::PoolDoesNotExist ); assert_noop!( - SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(0), 10000), + SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(0), 10000), Error::::CurrencyIsNotSupported ); assert_noop!( - SppModule::redeem(RuntimeOrigin::signed(BOB), 0, FungibleTokenId::FungibleToken(1), 10000), + SppModule::redeem(RuntimeOrigin::signed(BOB), 1, FungibleTokenId::FungibleToken(1), 10000), Error::::NoCurrentStakingRound ); @@ -285,13 +285,13 @@ fn current_era_update_works() { // Bob successfully redeemed assert_ok!(SppModule::redeem( RuntimeOrigin::signed(BOB), - 0, + 1, FungibleTokenId::FungibleToken(1), 10000 )); // After Bob redeems, pool ledger 0 should have only 10000 - assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(PoolLedger::::get(1), 10000); // After Bob redeem, make sure BOB KSM balance remains the same as it will only released next era assert_eq!(Tokens::accounts(BOB, FungibleTokenId::NativeToken(1)).free, 0); @@ -385,7 +385,7 @@ fn boosting_works() { )); let next_pool_id = NextPoolId::::get(); - assert_eq!(next_pool_id, 1); + assert_eq!(next_pool_id, 2); assert_eq!( Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { @@ -396,25 +396,25 @@ fn boosting_works() { } ); - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); // This is true because fee hasn't been set up. assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); - assert_eq!(PoolLedger::::get(0), 10000); + assert_eq!(PoolLedger::::get(1), 10000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); // Deposit another 10000 KSM - assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 0, 10000)); + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); - assert_eq!(PoolLedger::::get(0), 20000); + assert_eq!(PoolLedger::::get(1), 20000); assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); // Boosting works let bob_free_balance = Balances::free_balance(BOB); assert_ok!(SppModule::boost( RuntimeOrigin::signed(BOB), - 0, + 1, BoostInfo { balance: bob_free_balance, conviction: BoostingConviction::None @@ -423,7 +423,7 @@ fn boosting_works() { let boosting_of = BoostingOf::::get(BOB); let some_record = BoostingRecord { votes: vec![( - 0, + 1, BoostInfo { balance: bob_free_balance, conviction: BoostingConviction::None, From 47afe4a2a72f970bb56e5107746cfbb642b79b0a Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 20 Nov 2023 16:01:16 +1300 Subject: [PATCH 073/114] WIP - Add network reward ledger when user boosted --- pallets/spp/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index ba35a81e4..dbec98d8b 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -712,6 +712,9 @@ pub mod pallet { // Add shares into the rewards pool >::add_share(&who, &pool_id, total_balance.unique_saturated_into()); + // Add shares into the network pool + >::add_share(&who, &Zero::zero(), total_balance.unique_saturated_into()); + // Emit Boosted event Self::deposit_event(Event::::Boosted { booster: who.clone(), From 56245b39042d6a52e0dcb467b1b9daa2716aa99e Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 20 Nov 2023 16:01:37 +1300 Subject: [PATCH 074/114] WIP - Unit test - verify if all share pools work --- pallets/spp/src/tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index b7e50ecf1..a9b9d913d 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -433,5 +433,9 @@ fn boosting_works() { }; assert_eq!(boosting_of, some_record); assert_eq!(Balances::usable_balance(&BOB), 0); + let pool_1_shared_rewards = RewardsModule::shares_and_withdrawn_rewards(1, BOB); + let network_shared_rewards = RewardsModule::shares_and_withdrawn_rewards(0, BOB); + assert_eq!(pool_1_shared_rewards, (bob_free_balance, Default::default())); + assert_eq!(network_shared_rewards, (bob_free_balance, Default::default())); }); } From 1de4a4a07a7b3d56cb8b64816583469d4cf5bd3e Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 20 Nov 2023 22:55:57 +1300 Subject: [PATCH 075/114] WIP - Implement claim reward and reward ledger storage --- pallets/spp/src/lib.rs | 79 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index dbec98d8b..7ede5d5cf 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -56,6 +56,8 @@ const BOOSTING_ID: LockIdentifier = *b"bc/boost"; #[frame_support::pallet] pub mod pallet { + use std::collections::BTreeMap; + use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency, WithdrawReasons}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; @@ -250,8 +252,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn network_boost_info)] - /// Store boosting records for each account - pub type NetworkBoostingInfo = StorageValue<_, BoostingRecord, T::BlockNumber>, ValueQuery>; + /// Store boosting records for each pool + pub type NetworkBoostingInfo = StorageMap<_, Twox64Concat, PoolId, BalanceOf, ValueQuery>; + + /// PoolRewardAmountPerEra: double_map Pool, FungibleTokenId => RewardAmountPerEra + #[pallet::storage] + #[pallet::getter(fn incentive_reward_amounts)] + pub type PoolRewardAmountPerEra = + StorageDoubleMap<_, Twox64Concat, PoolId, Twox64Concat, FungibleTokenId, BalanceOf, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] @@ -724,6 +732,73 @@ pub mod pallet { Ok(()) } + + /// This function allow reward voting for the pool + #[pallet::weight(< T as Config >::WeightInfo::mint_land())] + pub fn claim_reward(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + // Ensure user is signed + let who = ensure_signed(origin)?; + + // orml_rewards will claim rewards for all currencies rewards + >::claim_rewards(&who, &pool_id); + + // Check if pool exists + ensure!(Pool::::get(pool_id).is_some(), Error::::PoolDoesNotExist); + // Still need to work out some + // Convert boost conviction into shares + let vote_conviction = vote.conviction.lock_periods(); + // Calculate lock period from UnlockDuration block number x conviction + let current_block: T::BlockNumber = >::block_number(); + + let mut unlock_at = current_block.saturating_add(UpdateEraFrequency::::get()); + let mut total_balance = vote.balance; + if !vote_conviction.is_zero() { + unlock_at.saturating_mul(vote_conviction.into()); + total_balance.saturating_mul(vote_conviction.into()); + } + // Locked token + + BoostingOf::::try_mutate(who.clone(), |voting| -> DispatchResult { + let votes = &mut voting.votes; + match votes.binary_search_by_key(&pool_id, |i| i.0) { + Ok(i) => { + // User already boosted, this is adding up their boosting weight + votes[i] + .1 + .add(total_balance.clone()) + .ok_or(Error::::ArithmeticOverflow)?; + voting + .prior + .accumulate(unlock_at, votes[i].1.balance.saturating_add(total_balance)) + } + Err(i) => { + votes.insert(i, (pool_id, vote.clone())); + voting.prior.accumulate(unlock_at, total_balance); + } + } + Ok(()) + })?; + T::Currency::extend_lock( + BOOSTING_ID, + &who, + vote.balance, + frame_support::traits::WithdrawReasons::TRANSFER, + ); + + // Add shares into the rewards pool + >::add_share(&who, &pool_id, total_balance.unique_saturated_into()); + // Add shares into the network pool + >::add_share(&who, &Zero::zero(), total_balance.unique_saturated_into()); + + // Emit Boosted event + Self::deposit_event(Event::::Boosted { + booster: who.clone(), + pool_id, + boost_info: vote.clone(), + }); + + Ok(()) + } } } From 6cf87ed5da77e8e10a6df221cf30174bcef0008b Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 10:35:06 +1300 Subject: [PATCH 076/114] WIP - Add function to handle pool reward distribution per era --- pallets/spp/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 7ede5d5cf..a37a1e5e7 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -951,6 +951,8 @@ impl Pallet { Ok(()) } + fn handle_reward_distribution_to_each_pool(pool_id: PoolId) -> DispatchResult {} + #[transactional] fn update_queue_request( currency_id: FungibleTokenId, @@ -1126,6 +1128,13 @@ impl Pallet { Ok(()) } + fn handle_boosting_reward_per_era(era_index: EraIndex) -> DispatchResult { + for boosted_pool_keys in NetworkBoostingInfo::::iter_keys() { + Self::handle_reward_distribution_to_each_pool(boosted_pool_keys)?; + } + Ok(()) + } + #[transactional] pub fn update_current_era(era_index: EraIndex) -> DispatchResult { let previous_era = Self::relay_chain_current_era(); From 6483bf357e43e301981529af55ebd92e47445fdb Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 13:23:48 +1300 Subject: [PATCH 077/114] WIP - Set up pool treasury account --- pallets/spp/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index a37a1e5e7..365b3b685 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -261,6 +261,10 @@ pub mod pallet { pub type PoolRewardAmountPerEra = StorageDoubleMap<_, Twox64Concat, PoolId, Twox64Concat, FungibleTokenId, BalanceOf, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn reward_frequency_per_era)] + pub type RewardEraFrequency = StorageValue<_, (BlockNumberFor, BalanceOf), ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { @@ -1150,6 +1154,10 @@ impl Pallet { pub fn get_pool_account() -> T::AccountId { T::PoolAccount::get().into_account_truncating() } + + pub fn get_pool_treasury(pool_id: PoolId) -> T::AccountId { + return T::PoolAccount::get().into_sub_account_truncating(pool_id); + } } impl RewardHandler for Pallet { From b7e098807d305bdf1e4c68c8534d7171244c10ce Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 14:13:32 +1300 Subject: [PATCH 078/114] WIP - Accumulating reward of network pool for all boosters --- pallets/spp/src/lib.rs | 81 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 365b3b685..3fa13da53 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -130,6 +130,9 @@ pub mod pallet { #[pallet::constant] type PoolAccount: Get; + #[pallet::constant] + type RewardPayoutAccount: Get; + #[pallet::constant] type MaximumQueue: Get; @@ -263,7 +266,11 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn reward_frequency_per_era)] - pub type RewardEraFrequency = StorageValue<_, (BlockNumberFor, BalanceOf), ValueQuery>; + pub type RewardEraFrequency = StorageValue<_, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn is_reward_distribution_origin)] + pub type RewardDistributionOrigin = StorageValue<_, T::AccountId, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] @@ -305,6 +312,10 @@ pub mod pallet { pool_id: PoolId, boost_info: BoostInfo>, }, + /// Reward distribution added + RewardDistributionAdded { who: T::AccountId }, + /// Reward distribution removed + RewardDistributionRemoved { who: T::AccountId }, } #[pallet::error] @@ -349,6 +360,10 @@ pub mod pallet { InsufficientFund, /// Error while adding new boost MaxVotesReached, + /// Reward distribution origin already exists + OriginsAlreadyExist, + /// Origin doesn't exists + OriginDoesNotExists, } #[pallet::hooks] @@ -804,6 +819,31 @@ pub mod pallet { Ok(()) } } + + #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] + pub fn add_reward_distribution_origin(origin: OriginFor, who: T::AccountId) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + ensure!( + Self::is_reward_distribution_origin() != who, + Error::::OriginsAlreadyExist + ); + + RewardDistributionOrigin::::put(who.clone()); + Self::deposit_event(Event::RewardDistributionAdded { who }); + Ok(()) + } + + #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] + pub fn remove_reward_distribution_origin(origin: OriginFor, who: T::AccountId) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + ensure!(is_reward_distribution_origin == who, Error::::OriginDoesNotExists); + + RewardDistributionOrigin::::remove(who.clone()); + Self::deposit_event(Event::RewardDistributionRemoved { who }); + Ok(()) + } } impl Pallet { @@ -955,7 +995,32 @@ impl Pallet { Ok(()) } - fn handle_reward_distribution_to_each_pool(pool_id: PoolId) -> DispatchResult {} + fn handle_reward_distribution_to_network_pool() -> DispatchResult { + // Get reward per era + // Accumulate reward to pool_id + let reward_per_era = RewardEraFrequency::::get(); + let reward_distribution_origin = RewardDistributionOrigin::::get(); + let reward_distribution_balance = T::Currency::free_balance(&RewardDistributionOrigin::::get()); + + if reward_distribution_balance.is_zero() || !reward_distribution_origin.is_some() { + // Ignore if reward distributor balance is zero + Ok(()) + } + + let mut amount_to_send = reward_per_era.clone(); + // Make user distributor account has enough balance + if amount_to_send > reward_distribution_balance { + amount_to_send = reward_distribution_balance + } + + T::Currency::transfer( + reward_distribution_origin, + Self::get_reward_payout_account_id(), + amount_to_send, + )?; + >::accumulate_reward(&Zero::zero(), FungibleTokenId::NativeToken(0), amount_to_send)?; + Ok(()) + } #[transactional] fn update_queue_request( @@ -1132,13 +1197,6 @@ impl Pallet { Ok(()) } - fn handle_boosting_reward_per_era(era_index: EraIndex) -> DispatchResult { - for boosted_pool_keys in NetworkBoostingInfo::::iter_keys() { - Self::handle_reward_distribution_to_each_pool(boosted_pool_keys)?; - } - Ok(()) - } - #[transactional] pub fn update_current_era(era_index: EraIndex) -> DispatchResult { let previous_era = Self::relay_chain_current_era(); @@ -1147,6 +1205,7 @@ impl Pallet { RelayChainCurrentEra::::put(new_era); LastEraUpdatedBlock::::put(T::RelayChainBlockNumber::current_block_number()); Self::handle_redeem_requests(new_era)?; + Self::handle_reward_distribution_to_network_pool()?; Self::deposit_event(Event::::CurrentEraUpdated { new_era_index: new_era }); Ok(()) } @@ -1158,6 +1217,10 @@ impl Pallet { pub fn get_pool_treasury(pool_id: PoolId) -> T::AccountId { return T::PoolAccount::get().into_sub_account_truncating(pool_id); } + + pub fn get_reward_payout_account_id() -> T::AccountId { + T::RewardPayoutAccount::get().into_account_truncating() + } } impl RewardHandler for Pallet { From 9d86b1fdee0f226a3bd59ae12a06fe0cc193fd42 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 15:29:02 +1300 Subject: [PATCH 079/114] WIP - Claim reward logic that allow rewards distributed can be claimed by booster --- pallets/spp/src/lib.rs | 152 +++++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 67 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 3fa13da53..ea47d2dda 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -21,7 +21,7 @@ use frame_support::pallet_prelude::*; use frame_support::traits::LockIdentifier; use frame_support::{ dispatch::DispatchResult, - ensure, + ensure, log, traits::{Currency, Get}, transactional, PalletId, }; @@ -272,6 +272,21 @@ pub mod pallet { #[pallet::getter(fn is_reward_distribution_origin)] pub type RewardDistributionOrigin = StorageValue<_, T::AccountId, OptionQuery>; + /// The pending rewards amount, actual available rewards amount may be deducted + /// + /// PendingRewards: double_map PoolId, AccountId => BTreeMap + #[pallet::storage] + #[pallet::getter(fn pending_multi_rewards)] + pub type PendingRewards = StorageDoubleMap< + _, + Twox64Concat, + PoolId, + Twox64Concat, + T::AccountId, + BTreeMap>, + ValueQuery, + >; + #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { @@ -751,73 +766,13 @@ pub mod pallet { Ok(()) } + } - /// This function allow reward voting for the pool - #[pallet::weight(< T as Config >::WeightInfo::mint_land())] - pub fn claim_reward(origin: OriginFor, pool_id: PoolId) -> DispatchResult { - // Ensure user is signed - let who = ensure_signed(origin)?; - - // orml_rewards will claim rewards for all currencies rewards - >::claim_rewards(&who, &pool_id); - - // Check if pool exists - ensure!(Pool::::get(pool_id).is_some(), Error::::PoolDoesNotExist); - // Still need to work out some - // Convert boost conviction into shares - let vote_conviction = vote.conviction.lock_periods(); - // Calculate lock period from UnlockDuration block number x conviction - let current_block: T::BlockNumber = >::block_number(); - - let mut unlock_at = current_block.saturating_add(UpdateEraFrequency::::get()); - let mut total_balance = vote.balance; - if !vote_conviction.is_zero() { - unlock_at.saturating_mul(vote_conviction.into()); - total_balance.saturating_mul(vote_conviction.into()); - } - // Locked token - - BoostingOf::::try_mutate(who.clone(), |voting| -> DispatchResult { - let votes = &mut voting.votes; - match votes.binary_search_by_key(&pool_id, |i| i.0) { - Ok(i) => { - // User already boosted, this is adding up their boosting weight - votes[i] - .1 - .add(total_balance.clone()) - .ok_or(Error::::ArithmeticOverflow)?; - voting - .prior - .accumulate(unlock_at, votes[i].1.balance.saturating_add(total_balance)) - } - Err(i) => { - votes.insert(i, (pool_id, vote.clone())); - voting.prior.accumulate(unlock_at, total_balance); - } - } - Ok(()) - })?; - T::Currency::extend_lock( - BOOSTING_ID, - &who, - vote.balance, - frame_support::traits::WithdrawReasons::TRANSFER, - ); - - // Add shares into the rewards pool - >::add_share(&who, &pool_id, total_balance.unique_saturated_into()); - // Add shares into the network pool - >::add_share(&who, &Zero::zero(), total_balance.unique_saturated_into()); - - // Emit Boosted event - Self::deposit_event(Event::::Boosted { - booster: who.clone(), - pool_id, - boost_info: vote.clone(), - }); + #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] + pub fn claim_rewards(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; - Ok(()) - } + Self::do_claim_rewards(who, pool_id) } #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] @@ -1221,16 +1176,79 @@ impl Pallet { pub fn get_reward_payout_account_id() -> T::AccountId { T::RewardPayoutAccount::get().into_account_truncating() } + + fn do_claim_rewards(who: T::AccountId, pool_id: PoolId) -> DispatchResult { + if pool_id == Zero::zero() { + >::claim_rewards(&who, &pool_id); + + PendingRewards::::mutate_exists(pool_id, &who, |maybe_pending_multi_rewards| { + if let Some(pending_multi_rewards) = maybe_pending_multi_rewards { + for (currency_id, pending_reward) in pending_multi_rewards.iter_mut() { + if pending_reward.is_zero() { + continue; + } + + match Self::payout_reward(pool_id, &who, *currency_id, payout_amount) { + Ok(_) => { + // update state + *pending_reward = Zero::zero(); + + Self::deposit_event(Event::ClaimRewards { + who: who.clone(), + pool: pool_id, + reward_currency_id: FungibleTokenId::NativeToken(0), + actual_amount: payout_amount, + }); + } + Err(e) => { + log::error!( + target: "spp", + "payout_reward: failed to payout {:?} to {:?} to pool {:?}: {:?}", + payout_amount, who, pool_id, e + ); + } + } + } + } + }) + } + + Ok(()) + } + + /// Ensure atomic + #[transactional] + fn payout_reward( + pool_id: PoolId, + who: &T::AccountId, + reward_currency_id: FungibleTokenId, + payout_amount: BalanceOf, + ) -> DispatchResult { + T::MultiCurrency::transfer( + reward_currency_id, + &Self::get_reward_payout_account_id(), + who, + payout_amount, + )?; + Ok(()) + } } impl RewardHandler for Pallet { type Balance = BalanceOf; type PoolId = PoolId; + /// This function trigger by orml_reward claim_rewards, it will modify and add pending reward + /// into PendingRewards for users to claim fn payout(who: &T::AccountId, pool_id: &Self::PoolId, currency_id: FungibleTokenId, payout_amount: Self::Balance) { if payout_amount.is_zero() { return; } - // TODO implement payout logic + PendingRewards::::mutate(pool_id, who, |rewards| { + rewards + .entry(currency_id) + .and_modify(|current| *current = current.saturating_add(payout_amount)) + .or_insert(payout_amount); + }); } } From 1494a41a60cb4385c1bd222b7f77309041b251c1 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 18:07:25 +1300 Subject: [PATCH 080/114] WIP - Fix build compilation errors of handle reward distribution logic --- pallets/spp/src/lib.rs | 76 +++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index ea47d2dda..5cb1ef496 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -18,7 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::*; -use frame_support::traits::LockIdentifier; +use frame_support::traits::{ExistenceRequirement, LockIdentifier}; use frame_support::{ dispatch::DispatchResult, ensure, log, @@ -133,6 +133,9 @@ pub mod pallet { #[pallet::constant] type RewardPayoutAccount: Get; + #[pallet::constant] + type RewardHoldingAccount: Get; + #[pallet::constant] type MaximumQueue: Get; @@ -268,10 +271,6 @@ pub mod pallet { #[pallet::getter(fn reward_frequency_per_era)] pub type RewardEraFrequency = StorageValue<_, BalanceOf, ValueQuery>; - #[pallet::storage] - #[pallet::getter(fn is_reward_distribution_origin)] - pub type RewardDistributionOrigin = StorageValue<_, T::AccountId, OptionQuery>; - /// The pending rewards amount, actual available rewards amount may be deducted /// /// PendingRewards: double_map PoolId, AccountId => BTreeMap @@ -327,10 +326,13 @@ pub mod pallet { pool_id: PoolId, boost_info: BoostInfo>, }, - /// Reward distribution added - RewardDistributionAdded { who: T::AccountId }, - /// Reward distribution removed - RewardDistributionRemoved { who: T::AccountId }, + /// Claim rewards. + ClaimRewards { + who: T::AccountId, + pool: PoolId, + reward_currency_id: FungibleTokenId, + claimed_amount: BalanceOf, + }, } #[pallet::error] @@ -766,38 +768,13 @@ pub mod pallet { Ok(()) } - } - - #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] - pub fn claim_rewards(origin: OriginFor, pool_id: PoolId) -> DispatchResult { - let who = ensure_signed(origin)?; - - Self::do_claim_rewards(who, pool_id) - } - - #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] - pub fn add_reward_distribution_origin(origin: OriginFor, who: T::AccountId) -> DispatchResult { - T::GovernanceOrigin::ensure_origin(origin)?; - ensure!( - Self::is_reward_distribution_origin() != who, - Error::::OriginsAlreadyExist - ); - - RewardDistributionOrigin::::put(who.clone()); - Self::deposit_event(Event::RewardDistributionAdded { who }); - Ok(()) - } - - #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] - pub fn remove_reward_distribution_origin(origin: OriginFor, who: T::AccountId) -> DispatchResult { - T::GovernanceOrigin::ensure_origin(origin)?; - - ensure!(is_reward_distribution_origin == who, Error::::OriginDoesNotExists); + #[pallet::weight(< T as pallet::Config >::WeightInfo::mint_land())] + pub fn claim_rewards(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; - RewardDistributionOrigin::::remove(who.clone()); - Self::deposit_event(Event::RewardDistributionRemoved { who }); - Ok(()) + Self::do_claim_rewards(who, pool_id) + } } } @@ -954,12 +931,12 @@ impl Pallet { // Get reward per era // Accumulate reward to pool_id let reward_per_era = RewardEraFrequency::::get(); - let reward_distribution_origin = RewardDistributionOrigin::::get(); - let reward_distribution_balance = T::Currency::free_balance(&RewardDistributionOrigin::::get()); + let reward_distribution_origin = T::RewardHoldingAccount::get().into_account_truncating(); + let reward_distribution_balance = T::Currency::free_balance(&reward_distribution_origin); - if reward_distribution_balance.is_zero() || !reward_distribution_origin.is_some() { + if reward_distribution_balance.is_zero() { // Ignore if reward distributor balance is zero - Ok(()) + return Ok(()); } let mut amount_to_send = reward_per_era.clone(); @@ -969,9 +946,10 @@ impl Pallet { } T::Currency::transfer( - reward_distribution_origin, - Self::get_reward_payout_account_id(), + &reward_distribution_origin, + &Self::get_reward_payout_account_id(), amount_to_send, + ExistenceRequirement::KeepAlive, )?; >::accumulate_reward(&Zero::zero(), FungibleTokenId::NativeToken(0), amount_to_send)?; Ok(()) @@ -1178,7 +1156,7 @@ impl Pallet { } fn do_claim_rewards(who: T::AccountId, pool_id: PoolId) -> DispatchResult { - if pool_id == Zero::zero() { + if pool_id.is_zero() { >::claim_rewards(&who, &pool_id); PendingRewards::::mutate_exists(pool_id, &who, |maybe_pending_multi_rewards| { @@ -1188,7 +1166,7 @@ impl Pallet { continue; } - match Self::payout_reward(pool_id, &who, *currency_id, payout_amount) { + match Self::payout_reward(pool_id, &who, *currency_id, *pending_reward) { Ok(_) => { // update state *pending_reward = Zero::zero(); @@ -1197,14 +1175,14 @@ impl Pallet { who: who.clone(), pool: pool_id, reward_currency_id: FungibleTokenId::NativeToken(0), - actual_amount: payout_amount, + claimed_amount: *pending_reward, }); } Err(e) => { log::error!( target: "spp", "payout_reward: failed to payout {:?} to {:?} to pool {:?}: {:?}", - payout_amount, who, pool_id, e + pending_reward, who, pool_id, e ); } } From 519f92edb9108033e6b90ed62c27c93f62ebd762 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 18:08:46 +1300 Subject: [PATCH 081/114] WIP - Update reward distribution logic comment --- pallets/spp/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 5cb1ef496..a15cbcde8 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -928,25 +928,25 @@ impl Pallet { } fn handle_reward_distribution_to_network_pool() -> DispatchResult { - // Get reward per era - // Accumulate reward to pool_id + // Get reward per era that set up Governance let reward_per_era = RewardEraFrequency::::get(); - let reward_distribution_origin = T::RewardHoldingAccount::get().into_account_truncating(); - let reward_distribution_balance = T::Currency::free_balance(&reward_distribution_origin); + // Get reward holding account + let reward_holding_origin = T::RewardHoldingAccount::get().into_account_truncating(); + let reward_holding_balance = T::Currency::free_balance(&reward_distribution_origin); - if reward_distribution_balance.is_zero() { + if reward_holding_balance.is_zero() { // Ignore if reward distributor balance is zero return Ok(()); } let mut amount_to_send = reward_per_era.clone(); - // Make user distributor account has enough balance - if amount_to_send > reward_distribution_balance { - amount_to_send = reward_distribution_balance + // Make sure user distributor account has enough balance + if amount_to_send > reward_holding_balance { + amount_to_send = reward_holding_balance } T::Currency::transfer( - &reward_distribution_origin, + &reward_holding_origin, &Self::get_reward_payout_account_id(), amount_to_send, ExistenceRequirement::KeepAlive, From 1d52db7886576c2e35664208653e88c7358c5d66 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 19:08:16 +1300 Subject: [PATCH 082/114] WIP - Remove max bound --- pallets/spp/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index a15cbcde8..41c74c37f 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -109,9 +109,6 @@ pub mod pallet { #[pallet::constant] type RewardPaymentDelay: Get; - /// Default max bound for each metaverse mapping system, this could change through proposal - type DefaultMaxBound: Get<(i32, i32)>; - /// Network fee charged on pool creation #[pallet::constant] type NetworkFee: Get>; @@ -932,7 +929,7 @@ impl Pallet { let reward_per_era = RewardEraFrequency::::get(); // Get reward holding account let reward_holding_origin = T::RewardHoldingAccount::get().into_account_truncating(); - let reward_holding_balance = T::Currency::free_balance(&reward_distribution_origin); + let reward_holding_balance = T::Currency::free_balance(&reward_holding_origin); if reward_holding_balance.is_zero() { // Ignore if reward distributor balance is zero From 964e7290f8c8d6083e0a65b94a9a3976fb35441a Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 19:08:36 +1300 Subject: [PATCH 083/114] WIP - Implement extra config for mock runtime --- pallets/spp/src/mock.rs | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index 77283e38f..1dbbd347f 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -41,37 +41,12 @@ pub const AUCTION_BENEFICIARY_ID: AccountId = 100; pub const CLASS_FUND_ID: AccountId = 123; pub const METAVERSE_ID: MetaverseId = 0; pub const DOLLARS: Balance = 1_000_000_000_000_000_000; -pub const ALICE_METAVERSE_ID: MetaverseId = 1; -pub const BOB_METAVERSE_ID: MetaverseId = 2; -pub const MAX_BOUND: (i32, i32) = (-100, 100); -pub const LANDBLOCK_COORDINATE: (i32, i32) = (0, 0); -pub const COORDINATE_IN_1: (i32, i32) = (-4, 4); -pub const COORDINATE_IN_2: (i32, i32) = (-4, 5); -pub const COORDINATE_IN_3: (i32, i32) = (-4, 6); -pub const COORDINATE_IN_4: (i32, i32) = (-4, 8); -pub const COORDINATE_OUT: (i32, i32) = (0, 101); -pub const COORDINATE_IN_AUCTION: (i32, i32) = (-4, 7); -pub const ESTATE_IN_AUCTION: EstateId = 3; pub const BOND_AMOUNT_1: Balance = 1000; pub const BOND_AMOUNT_2: Balance = 2000; pub const BOND_AMOUNT_BELOW_MINIMUM: Balance = 100; pub const BOND_LESS_AMOUNT_1: Balance = 100; -pub const ESTATE_ID: EstateId = 0; - -pub const ASSET_ID_1: TokenId = 101; -pub const ASSET_ID_2: TokenId = 100; -pub const ASSET_CLASS_ID: ClassId = 5; -pub const ASSET_TOKEN_ID: TokenId = 6; -pub const ASSET_COLLECTION_ID: GroupCollectionId = 7; -pub const METAVERSE_LAND_CLASS: ClassId = 15; -pub const METAVERSE_LAND_IN_AUCTION_TOKEN: TokenId = 4; -pub const METAVERSE_ESTATE_CLASS: ClassId = 16; -pub const METAVERSE_ESTATE_IN_AUCTION_TOKEN: TokenId = 3; - -pub const GENERAL_METAVERSE_FUND: AccountId = 102; - ord_parameter_types! { pub const One: AccountId = ALICE; pub const Admin: AccountId = ALICE; @@ -134,9 +109,9 @@ impl pallet_balances::Config for Runtime { parameter_types! { pub const GetNativeCurrencyId: FungibleTokenId = FungibleTokenId::NativeToken(0); - pub const LandTreasuryPalletId: PalletId = PalletId(*b"bit/land"); pub const PoolAccountPalletId: PalletId = PalletId(*b"bit/pool"); - pub const MinimumLandPrice: Balance = 10 * DOLLARS; + pub const RewardPayoutAccount: PalletId = PalletId(*b"bit/payo"); + pub const RewardHoldingAccount: PalletId = PalletId(*b"bit/hold"); } fn test_attributes(x: u8) -> Attributes { @@ -207,7 +182,6 @@ parameter_types! { pub const MinimumStake: Balance = 200; /// Reward payments are delayed by 2 hours (2 * 300 * block_time) pub const RewardPaymentDelay: u32 = 2; - pub const DefaultMaxBound: (i32,i32) = MAX_BOUND; pub const NetworkFee: Balance = 1; // Network fee pub const MaxOffersPerEstate: u32 = 2; pub const MinLeasePricePerBlock: Balance = 1u128; @@ -224,7 +198,6 @@ impl Config for Runtime { type WeightInfo = (); type MinimumStake = MinimumStake; type RewardPaymentDelay = RewardPaymentDelay; - type DefaultMaxBound = DefaultMaxBound; type NetworkFee = NetworkFee; type BlockNumberToBalance = ConvertInto; type StorageDepositFee = StorageDepositFee; @@ -234,6 +207,8 @@ impl Config for Runtime { type CurrencyIdConversion = ForeignAssetMapping; type RelayChainBlockNumber = MockRelayBlockNumberProvider; type GovernanceOrigin = EnsureSignedBy; + type RewardPayoutAccount = RewardPayoutAccount; + type RewardHoldingAccount = RewardHoldingAccount; } construct_runtime!( From 37a0d4eaceb634b008120452adad47409c5916c3 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 23:20:04 +1300 Subject: [PATCH 084/114] WIP - Implement extra reward holding account id --- pallets/spp/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 41c74c37f..f4cac11fa 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -1152,6 +1152,10 @@ impl Pallet { T::RewardPayoutAccount::get().into_account_truncating() } + pub fn get_reward_holding_account_id() -> T::AccountId { + T::RewardHoldingAccount::get().into_account_truncating() + } + fn do_claim_rewards(who: T::AccountId, pool_id: PoolId) -> DispatchResult { if pool_id.is_zero() { >::claim_rewards(&who, &pool_id); From b0c1982756bdd05e26f7a51b53406f77aa6f5ea9 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 21 Nov 2023 23:20:29 +1300 Subject: [PATCH 085/114] WIP - Unit test - boosting and reward distribution works unit tests --- pallets/spp/src/tests.rs | 115 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index a9b9d913d..e39f948bf 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -173,7 +173,7 @@ fn redeem_rksm_request_works() { 10000 )); - // After Bob redeems, pool ledger 0 should have only 10000 + // After Bob redeems, pool ledger 1 should have only 10000 assert_eq!(PoolLedger::::get(1), 10000); // Verify if redeem queue has requests @@ -439,3 +439,116 @@ fn boosting_works() { assert_eq!(network_shared_rewards, (bob_free_balance, Default::default())); }); } + +#[test] +fn boosting_and_claim_reward_works() { + ExtBuilder::default() + .ksm_setup_for_alice_and_bob() + .build() + .execute_with(|| { + // Era config set up + // Current relaychain block is 102. + MockRelayBlockNumberProvider::set(102); + RelayChainCurrentEra::::put(1); + IterationLimit::::put(50); + // The correct set up era config is the last era block records is 101 with duration is 100 blocks + assert_ok!(SppModule::update_era_config( + RuntimeOrigin::signed(Admin::get()), + Some(101), + Some(100), + StakingRound::Era(1), + )); + + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(ALICE), + FungibleTokenId::NativeToken(1), + 50, + Permill::from_percent(5) + )); + + let next_pool_id = NextPoolId::::get(); + assert_eq!(next_pool_id, 2); + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: ALICE, + commission: Permill::from_percent(5), + currency_id: FungibleTokenId::NativeToken(1), + max: 50 + } + ); + + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); + // This is true because fee hasn't been set up. + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); + + assert_eq!(PoolLedger::::get(1), 10000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); + + // Deposit another 10000 KSM + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 20000); + + assert_eq!(PoolLedger::::get(1), 20000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 20000); + + // Boosting works + let bob_free_balance = Balances::free_balance(BOB); + assert_ok!(SppModule::boost( + RuntimeOrigin::signed(BOB), + 1, + BoostInfo { + balance: 15000, + conviction: BoostingConviction::None + } + )); + let boosting_of = BoostingOf::::get(BOB); + let some_record = BoostingRecord { + votes: vec![( + 1, + BoostInfo { + balance: 15000, + conviction: BoostingConviction::None, + }, + )], + prior: PriorLock(101, 15000), + }; + assert_eq!(boosting_of, some_record); + assert_eq!(Balances::usable_balance(&BOB), bob_free_balance - 15000); + let pool_1_shared_rewards = RewardsModule::shares_and_withdrawn_rewards(1, BOB); + let network_shared_rewards = RewardsModule::shares_and_withdrawn_rewards(0, BOB); + assert_eq!(pool_1_shared_rewards, (15000, Default::default())); + assert_eq!(network_shared_rewards, (15000, Default::default())); + + // Set reward per era. - 1000 NativeToken(0) per 100 blocks + RewardEraFrequency::::put(1000); + // Simulate Council transfer 10000 NativeToken to reward_payout_account so that account has + // sufficient balance for reward distribution + let reward_holding_account = SppModule::get_reward_holding_account_id(); + assert_ok!(Balances::transfer( + RuntimeOrigin::signed(ALICE), + reward_holding_account.clone(), + 10000 + )); + + // Move to era 2, now protocol distribute 1000 NEER to incentivise boosters + MockRelayBlockNumberProvider::set(202); + SppModule::on_initialize(200); + + let network_reward_pool = RewardsModule::pool_infos(0u32); + let reward_accumulated = RewardsModule::shares_and_withdrawn_rewards(0, BOB); + + // Verify after 1 era, total rewards should have 1000 NEER and 0 claimed + assert_eq!( + network_reward_pool, + orml_rewards::PoolInfo { + total_shares: 15000, + rewards: vec![(FungibleTokenId::NativeToken(0), (1000, 0))].into_iter().collect() + } + ); + + // Reward records of BOB holding 15000 shares and 0 claimed + assert_eq!(reward_accumulated, (15000, Default::default())); + // Reward distribution works, now let's do claim rewards + }); +} From 42d0fb11a426d68271a7404e40fb796372c6d920 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 22 Nov 2023 10:46:09 +1300 Subject: [PATCH 086/114] WIP - Copy pending reward into payout to avoid variable changes --- pallets/spp/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index f4cac11fa..0303c34ea 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -1167,7 +1167,9 @@ impl Pallet { continue; } - match Self::payout_reward(pool_id, &who, *currency_id, *pending_reward) { + let payout_amount = pending_reward.clone(); + + match Self::payout_reward(pool_id, &who, *currency_id, payout_amount) { Ok(_) => { // update state *pending_reward = Zero::zero(); @@ -1176,7 +1178,7 @@ impl Pallet { who: who.clone(), pool: pool_id, reward_currency_id: FungibleTokenId::NativeToken(0), - claimed_amount: *pending_reward, + claimed_amount: payout_amount, }); } Err(e) => { From 8b548c0a2a5be48566e33d5fcdabc6e0f5668fca Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 22 Nov 2023 10:46:28 +1300 Subject: [PATCH 087/114] WIP - Unit test - ensure claim reward successful --- pallets/spp/src/tests.rs | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index e39f948bf..858ad3da6 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -549,6 +549,44 @@ fn boosting_and_claim_reward_works() { // Reward records of BOB holding 15000 shares and 0 claimed assert_eq!(reward_accumulated, (15000, Default::default())); - // Reward distribution works, now let's do claim rewards + // Reward distribution works, now claim rewards + let bob_balance_before_claiming_boosting_reward = Balances::free_balance(BOB); + // Bob claim rewards + assert_ok!(SppModule::claim_rewards(RuntimeOrigin::signed(BOB), 0)); + assert_eq!( + last_event(), + mock::RuntimeEvent::Spp(crate::Event::ClaimRewards { + who: BOB, + pool: 0, + reward_currency_id: FungibleTokenId::NativeToken(0), + claimed_amount: 1000, + }) + ); + + // Bob free balance now will be bob_balance_before_claiming_boosting_reward + 1000 as claimed reward + assert_eq!( + Balances::free_balance(BOB), + bob_balance_before_claiming_boosting_reward + 1000 + ); + + // Bob try to claim again but getting no reward + assert_ok!(SppModule::claim_rewards(RuntimeOrigin::signed(BOB), 0)); + // Bob balance doesn't increase + assert_eq!( + Balances::free_balance(BOB), + bob_balance_before_claiming_boosting_reward + 1000 + ); + + // Move to era 3, now protocol distribute another 1000 NEER to incentivise boosters + MockRelayBlockNumberProvider::set(302); + SppModule::on_initialize(300); + + // Bob try to claim reward for new era + assert_ok!(SppModule::claim_rewards(RuntimeOrigin::signed(BOB), 0)); + // Bob balance should increase 2000 + assert_eq!( + Balances::free_balance(BOB), + bob_balance_before_claiming_boosting_reward + 2000 + ); }); } From cddb58eddf6eb3d4c66b2dcf9e9c884c74b2e64a Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 22 Nov 2023 11:17:58 +1300 Subject: [PATCH 088/114] WIP - Unit test - add more cases of another booster --- pallets/spp/src/mock.rs | 2 +- pallets/spp/src/tests.rs | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/mock.rs b/pallets/spp/src/mock.rs index 1dbbd347f..ae1304727 100644 --- a/pallets/spp/src/mock.rs +++ b/pallets/spp/src/mock.rs @@ -251,7 +251,7 @@ impl ExtBuilder { .unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, 1000000000), (BOB, 100000), (BENEFICIARY_ID, 1000000)], + balances: vec![(ALICE, 1000000000), (BOB, 100000), (CHARLIE, 100000), (DOM, 100000)], } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 858ad3da6..480b20b4b 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -588,5 +588,55 @@ fn boosting_and_claim_reward_works() { Balances::free_balance(BOB), bob_balance_before_claiming_boosting_reward + 2000 ); + + // Charlie now boost pool 1 with 15000 (share 50% of reward with Bob) + assert_ok!(SppModule::boost( + RuntimeOrigin::signed(CHARLIE), + 1, + BoostInfo { + balance: 15000, + conviction: BoostingConviction::None + } + )); + // Charlie now should have 15000 shares in the pool + assert_eq!( + RewardsModule::shares_and_withdrawn_rewards(1, CHARLIE), + (15000, Default::default()) + ); + + // Network pool ledger should have total shares of 30,000 , 2000 total reward and claimed 2000 by + // Bob. However, as Charlie boosted, network pool inflate 15,000 shares, added 50% reward and 50% + // claimed reward to avoid dilution. + assert_eq!( + RewardsModule::pool_infos(0u32), + orml_rewards::PoolInfo { + total_shares: 30000, + rewards: vec![(FungibleTokenId::NativeToken(0), (4000, 4000))] + .into_iter() + .collect() + } + ); + + let charlie_balance_before_claiming_boosting_reward = Balances::free_balance(CHARLIE); + + // Move to era 4, now protocol distribute another 1000 NEER to incentivise boosters + MockRelayBlockNumberProvider::set(402); + SppModule::on_initialize(400); + + // Bob try to claim reward for new era + assert_ok!(SppModule::claim_rewards(RuntimeOrigin::signed(BOB), 0)); + // Bob balance should increase 500 as Charlie shares 50% rewards + assert_eq!( + Balances::free_balance(BOB), + bob_balance_before_claiming_boosting_reward + 2500 + ); + + // Charlie try to claim reward for new era + assert_ok!(SppModule::claim_rewards(RuntimeOrigin::signed(CHARLIE), 0)); + // Charlie balance should increase 500 + assert_eq!( + Balances::free_balance(CHARLIE), + charlie_balance_before_claiming_boosting_reward + 500 + ); }); } From 68a504aae32eadea03d24d4975fd10046815308a Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 22 Nov 2023 20:32:36 +1300 Subject: [PATCH 089/114] WIP - Handle reward distribution to all pool treasury --- pallets/spp/src/lib.rs | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 0303c34ea..e9d39b222 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -28,10 +28,10 @@ use frame_support::{ use frame_system::ensure_signed; use frame_system::pallet_prelude::*; use orml_traits::{MultiCurrency, RewardHandler}; -use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedDiv, CheckedSub}; +use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedDiv, CheckedSub, UniqueSaturatedInto}; use sp_runtime::{ traits::{AccountIdConversion, Convert, Saturating, Zero}, - ArithmeticError, DispatchError, SaturatedConversion, + ArithmeticError, DispatchError, Perbill, Permill, SaturatedConversion, }; use core_primitives::*; @@ -270,7 +270,7 @@ pub mod pallet { /// The pending rewards amount, actual available rewards amount may be deducted /// - /// PendingRewards: double_map PoolId, AccountId => BTreeMap + /// PendingRewards: double_map PoolId, AccountId => BTreeMap #[pallet::storage] #[pallet::getter(fn pending_multi_rewards)] pub type PendingRewards = StorageDoubleMap< @@ -283,6 +283,12 @@ pub mod pallet { ValueQuery, >; + /// The estimated staking reward rate per era on relaychain. + /// + /// EstimatedRewardRatePerEra: value: Rate + #[pallet::storage] + pub type EstimatedRewardRatePerEra = StorageValue<_, Permill, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] pub enum Event { @@ -952,6 +958,31 @@ impl Pallet { Ok(()) } + fn handle_reward_distribution_to_pool_treasury(previous_era: EraIndex, new_era: EraIndex) -> DispatchResult { + // Get reward per era for pool treasury + let reward_rate_per_era = EstimatedRewardRatePerEra::::get(); + // Get total compound reward rate based on number of era. + let reward_rate = reward_rate_per_era + .saturating_add(Permill::one()) + .saturating_pow(new_era.saturating_sub(previous_era).unique_saturated_into()) + .saturating_sub(Permill::one()); + + if !reward_rate.is_zero() { + let mut total_reward_staking: BalanceOf = Zero::zero(); + + // iterate all pool ledgers + for (pool_id, pool_ledgers) in PoolLedger::::iter() { + let reward_staking = reward_rate.saturating_mul_int(pool_ledgers); + + if !reward_staking.is_zero() { + total_reward_staking = total_reward_staking.saturating_add(reward_staking); + } + } + } + + Ok(()) + } + #[transactional] fn update_queue_request( currency_id: FungibleTokenId, From 73c096a13729e15496842c563f7d925b3c4aed13 Mon Sep 17 00:00:00 2001 From: Ray Lu Date: Wed, 22 Nov 2023 21:17:39 +1300 Subject: [PATCH 090/114] Update tests.rs Added second pool in the pool test. --- pallets/spp/src/tests.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 480b20b4b..188d08d73 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -47,6 +47,8 @@ fn create_ksm_pool_works() { .ksm_setup_for_alice_and_bob() .build() .execute_with(|| { + + // Create the first pool assert_ok!(SppModule::create_pool( RuntimeOrigin::signed(ALICE), FungibleTokenId::NativeToken(1), @@ -54,8 +56,11 @@ fn create_ksm_pool_works() { Permill::from_percent(5) )); - let next_pool_id = NextPoolId::::get(); + // Check the next pool id will increment + let next_pool_id: u32 = NextPoolId::::get(); assert_eq!(next_pool_id, 2); + + // Check if the pool details as expected. assert_eq!( Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { @@ -64,7 +69,29 @@ fn create_ksm_pool_works() { currency_id: FungibleTokenId::NativeToken(1), max: 50 } - ) + ); + + // Create a second pool + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(BOB), + FungibleTokenId::NativeToken(1), + 10, + Permill::from_percent(1) + )); + + // Check Id will increment + let next_pool_id: u32 = NextPoolId::::get(); + assert_eq!(next_pool_id, 3); + // Check the second pool has the right information as expected + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: BOB, + commission: Permill::from_percent(1), + currency_id: FungibleTokenId::NativeToken(1), + max: 10 + } + ); }); } From 29e2a9314ea53e1ef3acf4b2c0e47ff8b207b1a9 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 22 Nov 2023 23:31:05 +1300 Subject: [PATCH 091/114] WIP - Inflate more share of commission reward into pool treasury --- pallets/spp/src/lib.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index e9d39b222..7a8df6afd 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -28,7 +28,9 @@ use frame_support::{ use frame_system::ensure_signed; use frame_system::pallet_prelude::*; use orml_traits::{MultiCurrency, RewardHandler}; -use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedDiv, CheckedSub, UniqueSaturatedInto}; +use sp_runtime::traits::{ + BlockNumberProvider, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, UniqueSaturatedInto, +}; use sp_runtime::{ traits::{AccountIdConversion, Convert, Saturating, Zero}, ArithmeticError, DispatchError, Perbill, Permill, SaturatedConversion, @@ -968,14 +970,22 @@ impl Pallet { .saturating_sub(Permill::one()); if !reward_rate.is_zero() { - let mut total_reward_staking: BalanceOf = Zero::zero(); - // iterate all pool ledgers - for (pool_id, pool_ledgers) in PoolLedger::::iter() { - let reward_staking = reward_rate.saturating_mul_int(pool_ledgers); + for (pool_id, pool_amount) in PoolLedger::::iter() { + let reward_staking = reward_rate * Permill::from_percent(1u32) * pool_amount; if !reward_staking.is_zero() { - total_reward_staking = total_reward_staking.saturating_add(reward_staking); + let pool_treasury_account = Self::get_pool_treasury(pool_id); + T::MultiCurrency::deposit( + FungibleTokenId::FungibleToken(1), + &pool_treasury_account, + reward_staking, + ); + >::accumulate_reward( + &pool_id, + FungibleTokenId::FungibleToken(1), + reward_staking, + )?; } } } From 1c72db4e83d24f5ac40193073a91485e87e9594d Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 10:23:41 +1300 Subject: [PATCH 092/114] WIP - Add safe bounded check for fractional avoid precision loss. --- primitives/metaverse/src/bounded.rs | 240 ++++++++++++++++++++++++++++ primitives/metaverse/src/lib.rs | 1 + 2 files changed, 241 insertions(+) create mode 100644 primitives/metaverse/src/bounded.rs diff --git a/primitives/metaverse/src/bounded.rs b/primitives/metaverse/src/bounded.rs new file mode 100644 index 000000000..0e900f738 --- /dev/null +++ b/primitives/metaverse/src/bounded.rs @@ -0,0 +1,240 @@ +// This file is part of Metaverse.Network & Bit.Country. + +// Copyright (C) 2020-2022 Metaverse.Network & Bit.Country . +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::Get; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize}; +use sp_runtime::{ + traits::{CheckedSub, One, Zero}, + FixedPointNumber, FixedU128, RuntimeDebug, +}; +use sp_std::{marker::PhantomData, prelude::*, result::Result}; + +use primitives::{Balance, BlockNumber}; + +pub type Rate = FixedU128; + +/// The bounded type errors. +#[derive(RuntimeDebug, PartialEq, Eq)] +pub enum Error { + /// The value is out of bound. + OutOfBounds, + /// The change diff exceeds the max absolute value. + ExceedMaxChangeAbs, +} +/// An abstract definition of bounded type. The type is within the range of `Range` +/// and while update the inner value, the max absolute value of the diff is `MaxChangeAbs`. +/// The `Default` value is minimum value of the range. +#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] +#[derive(Encode, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +#[scale_info(skip_type_params(Range, MaxChangeAbs))] +pub struct BoundedType( + T, + #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData<(Range, MaxChangeAbs)>, +); + +impl, MaxChangeAbs: Get> Decode + for BoundedType +{ + fn decode(input: &mut I) -> Result { + let inner = T::decode(input)?; + Self::try_from(inner).map_err(|_| "BoundedType: value out of bounds".into()) + } +} + +#[cfg(feature = "std")] +impl<'de, T, Range, MaxChangeAbs> Deserialize<'de> for BoundedType +where + T: Encode + Decode + CheckedSub + PartialOrd + Deserialize<'de>, + Range: Get<(T, T)>, + MaxChangeAbs: Get, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value: T = T::deserialize(deserializer)?; + Self::try_from(value).map_err(|_| SerdeError::custom("out of bounds")) + } +} + +impl, MaxChangeAbs: Get> Default + for BoundedType +{ + fn default() -> Self { + let (min, _) = Range::get(); + Self(min, PhantomData) + } +} + +impl BoundedType +where + T: Encode + Decode + CheckedSub + PartialOrd, + Range: Get<(T, T)>, + MaxChangeAbs: Get, +{ + /// Try to create a new instance of `BoundedType`. Returns `Err` if out of bound. + pub fn try_from(value: T) -> Result { + let (min, max) = Range::get(); + if value < min || value > max { + return Err(Error::OutOfBounds); + } + Ok(Self(value, PhantomData)) + } + + /// Set the inner value. Returns `Err` if out of bound or the diff with current value exceeds + /// the max absolute value. + pub fn try_set(&mut self, value: T) -> Result<(), Error> { + let (min, max) = Range::get(); + let max_change_abs = MaxChangeAbs::get(); + let old_value = &self.0; + if value < min || value > max { + return Err(Error::OutOfBounds); + } + + let abs = if value > *old_value { + value + .checked_sub(old_value) + .expect("greater number subtracting smaller one can't underflow; qed") + } else { + old_value + .checked_sub(&value) + .expect("greater number subtracting smaller one can't underflow; qed") + }; + if abs > max_change_abs { + return Err(Error::ExceedMaxChangeAbs); + } + + self.0 = value; + Ok(()) + } + + pub fn into_inner(self) -> T { + self.0 + } + + pub fn inner(&self) -> &T { + &self.0 + } +} + +/// Fractional range between `Rate::zero()` and `Rate::one()`. +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] +pub struct Fractional; +impl Get<(Rate, Rate)> for Fractional { + fn get() -> (Rate, Rate) { + (Rate::zero(), Rate::one()) + } +} + +/// Maximum absolute change is 1/5. +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] +pub struct OneFifth; +impl Get for OneFifth { + fn get() -> Rate { + Rate::saturating_from_rational(1, 5) + } +} + +pub type BoundedRate = BoundedType; + +/// Fractional rate. +/// +/// The range is between 0 to 1, and max absolute value of change diff is 1/5. +pub type FractionalRate = BoundedRate; + +pub type BoundedBalance = BoundedType; + +pub type BoundedBlockNumber = BoundedType; + +#[cfg(test)] +mod tests { + use frame_support::{assert_err, assert_ok}; + + use super::*; + + #[test] + fn fractional_rate_works() { + assert_err!( + FractionalRate::try_from(Rate::from_rational(11, 10)), + Error::OutOfBounds + ); + + let mut rate = FractionalRate::try_from(Rate::from_rational(8, 10)).unwrap(); + assert_ok!(rate.try_set(Rate::from_rational(10, 10))); + assert_err!(rate.try_set(Rate::from_rational(11, 10)), Error::OutOfBounds); + assert_err!(rate.try_set(Rate::from_rational(79, 100)), Error::ExceedMaxChangeAbs); + + assert_eq!(FractionalRate::default().into_inner(), Rate::zero()); + } + + #[test] + fn encode_decode_works() { + let rate = FractionalRate::try_from(Rate::from_rational(8, 10)).unwrap(); + let encoded = rate.encode(); + assert_eq!(FractionalRate::decode(&mut &encoded[..]).unwrap(), rate); + + assert_eq!(encoded, Rate::from_rational(8, 10).encode()); + } + + #[test] + fn decode_fails_if_out_of_bounds() { + let bad_rate = BoundedType::(Rate::from_rational(11, 10), PhantomData); + let bad_rate_encoded = bad_rate.encode(); + assert_err!( + FractionalRate::decode(&mut &bad_rate_encoded[..]), + "BoundedType: value out of bounds" + ); + } + + #[test] + fn ser_de_works() { + let rate = FractionalRate::try_from(Rate::from_rational(8, 10)).unwrap(); + assert_eq!(serde_json::json!(&rate).to_string(), r#""800000000000000000""#); + + let deserialized: FractionalRate = serde_json::from_str(r#""800000000000000000""#).unwrap(); + assert_eq!(deserialized, rate); + } + + #[test] + fn deserialize_fails_if_out_of_bounds() { + let failed: Result = serde_json::from_str(r#""1100000000000000000""#); + match failed { + Err(msg) => assert_eq!(msg.to_string(), "out of bounds"), + _ => panic!("should fail"), + } + } + + #[test] + fn bounded_type_default_is_range_min() { + #[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] + pub struct OneToTwo; + impl Get<(Rate, Rate)> for OneToTwo { + fn get() -> (Rate, Rate) { + (Rate::one(), Rate::from_rational(2, 1)) + } + } + + type BoundedRateOneToTwo = BoundedRate; + + assert_eq!(BoundedRateOneToTwo::default().into_inner(), Rate::one()); + } +} diff --git a/primitives/metaverse/src/lib.rs b/primitives/metaverse/src/lib.rs index 49fe3ce07..ad1544bc9 100644 --- a/primitives/metaverse/src/lib.rs +++ b/primitives/metaverse/src/lib.rs @@ -34,6 +34,7 @@ use sp_std::prelude::*; use sp_std::vec::Vec; use xcm::v3::MultiLocation; +pub mod bounded; pub mod continuum; pub mod estate; pub mod evm; From ca55483d72ee30987f236135d99e0e9fb448a3c1 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 10:24:30 +1300 Subject: [PATCH 093/114] WIP - Update type path. --- pallets/spp/src/lib.rs | 2 +- primitives/metaverse/src/bounded.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 7a8df6afd..678f1cebd 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -980,7 +980,7 @@ impl Pallet { FungibleTokenId::FungibleToken(1), &pool_treasury_account, reward_staking, - ); + )?; >::accumulate_reward( &pool_id, FungibleTokenId::FungibleToken(1), diff --git a/primitives/metaverse/src/bounded.rs b/primitives/metaverse/src/bounded.rs index 0e900f738..991640223 100644 --- a/primitives/metaverse/src/bounded.rs +++ b/primitives/metaverse/src/bounded.rs @@ -28,7 +28,7 @@ use sp_runtime::{ }; use sp_std::{marker::PhantomData, prelude::*, result::Result}; -use primitives::{Balance, BlockNumber}; +use crate::{Balance, BlockNumber}; pub type Rate = FixedU128; From 76fcbd136392e9d5e64c2a79e5978a214b8740e4 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 12:18:56 +1300 Subject: [PATCH 094/114] WIP - Update reward rate into fixedu128 to avoid precision loss --- pallets/spp/src/lib.rs | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 678f1cebd..58f137b6a 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -29,16 +29,17 @@ use frame_system::ensure_signed; use frame_system::pallet_prelude::*; use orml_traits::{MultiCurrency, RewardHandler}; use sp_runtime::traits::{ - BlockNumberProvider, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, UniqueSaturatedInto, + BlockNumberProvider, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, UniqueSaturatedInto, }; use sp_runtime::{ traits::{AccountIdConversion, Convert, Saturating, Zero}, - ArithmeticError, DispatchError, Perbill, Permill, SaturatedConversion, + ArithmeticError, DispatchError, FixedPointNumber, Perbill, Permill, SaturatedConversion, }; use core_primitives::*; pub use pallet::*; -use primitives::{ClassId, EraIndex, FungibleTokenId, PoolId, StakingRound, TokenId}; +use primitives::bounded::Rate; +use primitives::{ClassId, EraIndex, FungibleTokenId, PoolId, Ratio, StakingRound, TokenId}; pub use weights::WeightInfo; pub type QueueId = u32; @@ -66,6 +67,7 @@ pub mod pallet { use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub, One, UniqueSaturatedInto}; use sp_runtime::Permill; + use primitives::bounded::FractionalRate; use primitives::{PoolId, StakingRound}; use crate::utils::{BoostInfo, BoostingRecord, PoolInfo}; @@ -289,7 +291,7 @@ pub mod pallet { /// /// EstimatedRewardRatePerEra: value: Rate #[pallet::storage] - pub type EstimatedRewardRatePerEra = StorageValue<_, Permill, ValueQuery>; + pub type EstimatedRewardRatePerEra = StorageValue<_, FractionalRate, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub (crate) fn deposit_event)] @@ -960,31 +962,44 @@ impl Pallet { Ok(()) } + pub(crate) fn estimated_reward_rate_per_era() -> Rate { + EstimatedRewardRatePerEra::::get().into_inner() + } + fn handle_reward_distribution_to_pool_treasury(previous_era: EraIndex, new_era: EraIndex) -> DispatchResult { + let era_changes = new_era.saturating_sub(previous_era); + ensure!(!era_changes.is_zero(), Error::::Unexpected); // Get reward per era for pool treasury - let reward_rate_per_era = EstimatedRewardRatePerEra::::get(); + let reward_rate_per_era = Self::estimated_reward_rate_per_era(); // Get total compound reward rate based on number of era. let reward_rate = reward_rate_per_era - .saturating_add(Permill::one()) - .saturating_pow(new_era.saturating_sub(previous_era).unique_saturated_into()) - .saturating_sub(Permill::one()); + .saturating_add(Rate::one()) + .saturating_pow(era_changes.unique_saturated_into()) + .saturating_sub(Rate::one()); if !reward_rate.is_zero() { // iterate all pool ledgers for (pool_id, pool_amount) in PoolLedger::::iter() { - let reward_staking = reward_rate * Permill::from_percent(1u32) * pool_amount; + let mut total_reward_staking: BalanceOf = Zero::zero(); + let mut reward_staking = reward_rate.saturating_mul_int(pool_amount); if !reward_staking.is_zero() { let pool_treasury_account = Self::get_pool_treasury(pool_id); + total_reward_staking = total_reward_staking.saturating_add(reward_staking); + + let reward_commission_amount = Ratio::checked_from_rational(1, 100) + .unwrap_or_default() + .saturating_mul_int(total_reward_staking); + T::MultiCurrency::deposit( FungibleTokenId::FungibleToken(1), &pool_treasury_account, - reward_staking, + reward_commission_amount, )?; >::accumulate_reward( &pool_id, FungibleTokenId::FungibleToken(1), - reward_staking, + reward_commission_amount, )?; } } From c161cefaac6411a597c186fcaaf803ba2c9d2d37 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 12:52:46 +1300 Subject: [PATCH 095/114] WIP - Update fees from permill to rate --- pallets/spp/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 58f137b6a..b3c6e6200 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -154,7 +154,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn fees)] - pub type Fees = StorageValue<_, (Permill, Permill), ValueQuery>; + pub type Fees = StorageValue<_, (FractionalRate, FractionalRate), ValueQuery>; /// Keep track of Pool detail #[pallet::storage] @@ -409,7 +409,7 @@ pub mod pallet { origin: OriginFor, currency_id: FungibleTokenId, max_nft_reward: u32, - commission: Permill, + commission: Rate, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -824,7 +824,7 @@ impl Pallet { ) -> Result, DispatchError> { let (deposit_rate, _redeem_rate) = Fees::::get(); - let deposit_fee = deposit_rate * amount; + let deposit_fee = deposit_rate.into_inner().saturating_mul_int(amount); let amount_exclude_fee = amount.checked_sub(&deposit_fee).ok_or(Error::::ArithmeticOverflow)?; T::MultiCurrency::transfer( currency_id, @@ -842,7 +842,7 @@ impl Pallet { amount: BalanceOf, ) -> Result, DispatchError> { let (_mint_rate, redeem_rate) = Fees::::get(); - let redeem_fee = redeem_rate * amount; + let redeem_fee = redeem_rate.into_inner().saturating_mul_int(amount); let amount_exclude_fee = amount.checked_sub(&redeem_fee).ok_or(Error::::ArithmeticOverflow)?; T::MultiCurrency::transfer( currency_id, @@ -987,7 +987,7 @@ impl Pallet { let pool_treasury_account = Self::get_pool_treasury(pool_id); total_reward_staking = total_reward_staking.saturating_add(reward_staking); - let reward_commission_amount = Ratio::checked_from_rational(1, 100) + let reward_commission_amount = Rate::checked_from_rational(1, 100) .unwrap_or_default() .saturating_mul_int(total_reward_staking); From 655f8d12a291452d13a8c809f54b2692a4b6bc07 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 12:52:59 +1300 Subject: [PATCH 096/114] WIP - Update pool info struct property --- pallets/spp/src/utils.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/utils.rs b/pallets/spp/src/utils.rs index 367303352..d9f893956 100644 --- a/pallets/spp/src/utils.rs +++ b/pallets/spp/src/utils.rs @@ -24,6 +24,7 @@ use sp_runtime::{ Permill, RuntimeDebug, }; +use primitives::bounded::{FractionalRate, Rate}; use primitives::{FungibleTokenId, PoolId}; // Helper methods to compute the issuance rate for undeployed land. @@ -32,7 +33,7 @@ use primitives::{FungibleTokenId, PoolId}; #[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct PoolInfo { pub creator: AccountId, - pub commission: Permill, + pub commission: Rate, /// Currency id of the pool pub currency_id: FungibleTokenId, /// Max nft rewards From 467b0cdf171307245deb0f9bce001b060925f71e Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 12:53:22 +1300 Subject: [PATCH 097/114] WIP - Fix unit tests to ensure all changes applied correctly --- pallets/auction/src/lib.rs | 2 -- pallets/spp/src/tests.rs | 25 +++++++++++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pallets/auction/src/lib.rs b/pallets/auction/src/lib.rs index 67403e434..e338a1cf7 100644 --- a/pallets/auction/src/lib.rs +++ b/pallets/auction/src/lib.rs @@ -58,7 +58,6 @@ pub mod weights; pub struct AuctionLogicHandler; pub mod migration_v2 { - use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] @@ -103,7 +102,6 @@ pub mod pallet { use frame_support::dispatch::DispatchResultWithPostInfo; use frame_support::log; use frame_support::sp_runtime::traits::CheckedSub; - use frame_system::pallet_prelude::OriginFor; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_runtime::traits::CheckedAdd; diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 480b20b4b..2ef804e66 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -23,6 +23,7 @@ use sp_runtime::traits::BadOrigin; use sp_runtime::{Perbill, Permill}; use mock::{RuntimeEvent, *}; +use primitives::bounded::FractionalRate; use crate::utils::{BoostInfo, BoostingConviction, BoostingRecord, PoolInfo, PriorLock}; @@ -51,7 +52,7 @@ fn create_ksm_pool_works() { RuntimeOrigin::signed(ALICE), FungibleTokenId::NativeToken(1), 50, - Permill::from_percent(5) + Rate::saturating_from_rational(5, 100) )); let next_pool_id = NextPoolId::::get(); @@ -60,7 +61,7 @@ fn create_ksm_pool_works() { Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { creator: ALICE, - commission: Permill::from_percent(5), + commission: Rate::saturating_from_rational(5, 100), currency_id: FungibleTokenId::NativeToken(1), max: 50 } @@ -78,7 +79,7 @@ fn deposit_ksm_works() { RuntimeOrigin::signed(ALICE), FungibleTokenId::NativeToken(1), 50, - Permill::from_percent(5) + Rate::saturating_from_rational(5, 100) )); let next_pool_id = NextPoolId::::get(); @@ -87,7 +88,7 @@ fn deposit_ksm_works() { Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { creator: ALICE, - commission: Permill::from_percent(5), + commission: Rate::saturating_from_rational(5, 100), currency_id: FungibleTokenId::NativeToken(1), max: 50 } @@ -119,7 +120,7 @@ fn redeem_rksm_request_works() { RuntimeOrigin::signed(ALICE), FungibleTokenId::NativeToken(1), 50, - Permill::from_percent(5) + Rate::saturating_from_rational(5, 100) )); let next_pool_id = NextPoolId::::get(); @@ -128,7 +129,7 @@ fn redeem_rksm_request_works() { Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { creator: ALICE, - commission: Permill::from_percent(5), + commission: Rate::saturating_from_rational(5, 100), currency_id: FungibleTokenId::NativeToken(1), max: 50 } @@ -223,7 +224,7 @@ fn current_era_update_works() { RuntimeOrigin::signed(ALICE), FungibleTokenId::NativeToken(1), 50, - Permill::from_percent(5) + Rate::saturating_from_rational(5, 100) )); let next_pool_id = NextPoolId::::get(); @@ -232,7 +233,7 @@ fn current_era_update_works() { Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { creator: ALICE, - commission: Permill::from_percent(5), + commission: Rate::saturating_from_rational(5, 100), currency_id: FungibleTokenId::NativeToken(1), max: 50, } @@ -381,7 +382,7 @@ fn boosting_works() { RuntimeOrigin::signed(ALICE), FungibleTokenId::NativeToken(1), 50, - Permill::from_percent(5) + Rate::saturating_from_rational(5, 100) )); let next_pool_id = NextPoolId::::get(); @@ -390,7 +391,7 @@ fn boosting_works() { Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { creator: ALICE, - commission: Permill::from_percent(5), + commission: Rate::saturating_from_rational(5, 100), currency_id: FungibleTokenId::NativeToken(1), max: 50 } @@ -463,7 +464,7 @@ fn boosting_and_claim_reward_works() { RuntimeOrigin::signed(ALICE), FungibleTokenId::NativeToken(1), 50, - Permill::from_percent(5) + Rate::saturating_from_rational(5, 100) )); let next_pool_id = NextPoolId::::get(); @@ -472,7 +473,7 @@ fn boosting_and_claim_reward_works() { Pool::::get(next_pool_id - 1).unwrap(), PoolInfo:: { creator: ALICE, - commission: Permill::from_percent(5), + commission: Rate::saturating_from_rational(5, 100), currency_id: FungibleTokenId::NativeToken(1), max: 50 } From cceec466c9d4aeba8e5c79036571a85451067160 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 13:26:10 +1300 Subject: [PATCH 098/114] WIP - Include estimated rate per era config update --- pallets/spp/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index b3c6e6200..6da76747a 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -340,6 +340,8 @@ pub mod pallet { reward_currency_id: FungibleTokenId, claimed_amount: BalanceOf, }, + /// Reward rate per era updated. + EstimatedRewardRatePerEraUpdated { reward_rate_per_era: Rate }, } #[pallet::error] @@ -388,6 +390,8 @@ pub mod pallet { OriginsAlreadyExist, /// Origin doesn't exists OriginDoesNotExists, + /// Invalid rate input + InvalidRate, } #[pallet::hooks] @@ -679,6 +683,7 @@ pub mod pallet { last_era_updated_block: Option>, frequency: Option>, last_staking_round: StakingRound, + estimated_reward_rate_per_era: Option, ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; @@ -703,6 +708,13 @@ pub mod pallet { } } + if let Some(reward_rate_per_era) = estimated_reward_rate_per_era { + EstimatedRewardRatePerEra::::mutate(|rate| -> DispatchResult { + rate.try_set(reward_rate_per_era) + .map_err(|_| Error::::InvalidRate.into()) + })?; + Self::deposit_event(Event::::EstimatedRewardRatePerEraUpdated { reward_rate_per_era }); + } Ok(()) } From 5f46fd6de7ca4f5db0d15d0249b903b8b28ffa73 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 13:28:09 +1300 Subject: [PATCH 099/114] WIP - Unit test - update unit test of updating era config --- pallets/spp/src/tests.rs | 97 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 2ef804e66..43d08b6f2 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -218,6 +218,7 @@ fn current_era_update_works() { Some(101), Some(100), StakingRound::Era(1), + Some(Rate::saturating_from_rational(35, 100000)) )); assert_ok!(SppModule::create_pool( @@ -458,6 +459,7 @@ fn boosting_and_claim_reward_works() { Some(101), Some(100), StakingRound::Era(1), + Some(Rate::saturating_from_rational(35, 100000)) )); assert_ok!(SppModule::create_pool( @@ -641,3 +643,98 @@ fn boosting_and_claim_reward_works() { ); }); } + +#[test] +fn pool_treasury_distribution_works() { + ExtBuilder::default() + .ksm_setup_for_alice_and_bob() + .build() + .execute_with(|| { + // Era config set up + // Current relaychain block is 102. + MockRelayBlockNumberProvider::set(102); + RelayChainCurrentEra::::put(1); + IterationLimit::::put(50); + // The correct set up era config is the last era block records is 101 with duration is 100 blocks + assert_ok!(SppModule::update_era_config( + RuntimeOrigin::signed(Admin::get()), + Some(101), + Some(100), + StakingRound::Era(1), + Some(Rate::saturating_from_rational(35, 100000)) + )); + + assert_ok!(SppModule::create_pool( + RuntimeOrigin::signed(ALICE), + FungibleTokenId::NativeToken(1), + 50, + Rate::saturating_from_rational(5, 100) + )); + + let next_pool_id = NextPoolId::::get(); + assert_eq!(next_pool_id, 2); + assert_eq!( + Pool::::get(next_pool_id - 1).unwrap(), + PoolInfo:: { + creator: ALICE, + commission: Rate::saturating_from_rational(5, 100), + currency_id: FungibleTokenId::NativeToken(1), + max: 50 + } + ); + + assert_ok!(SppModule::deposit(RuntimeOrigin::signed(BOB), 1, 10000)); + // This is true because fee hasn't been set up. + assert_eq!(Tokens::accounts(BOB, FungibleTokenId::FungibleToken(1)).free, 10000); + + assert_eq!(PoolLedger::::get(1), 10000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 10000); + + // Boosting works + let bob_free_balance = Balances::free_balance(BOB); + assert_ok!(SppModule::boost( + RuntimeOrigin::signed(BOB), + 1, + BoostInfo { + balance: 15000, + conviction: BoostingConviction::None + } + )); + let boosting_of = BoostingOf::::get(BOB); + let some_record = BoostingRecord { + votes: vec![( + 1, + BoostInfo { + balance: 15000, + conviction: BoostingConviction::None, + }, + )], + prior: PriorLock(101, 15000), + }; + assert_eq!(boosting_of, some_record); + assert_eq!(Balances::usable_balance(&BOB), bob_free_balance - 15000); + let pool_1_shared_rewards = RewardsModule::shares_and_withdrawn_rewards(1, BOB); + let network_shared_rewards = RewardsModule::shares_and_withdrawn_rewards(0, BOB); + assert_eq!(pool_1_shared_rewards, (15000, Default::default())); + assert_eq!(network_shared_rewards, (15000, Default::default())); + + // Charlie boosted with 15000 Native token + assert_ok!(SppModule::boost( + RuntimeOrigin::signed(CHARLIE), + 1, + BoostInfo { + balance: 15000, + conviction: BoostingConviction::None + } + )); + // Charlie now should have 15000 shares in the pool + assert_eq!( + RewardsModule::shares_and_withdrawn_rewards(1, CHARLIE), + (15000, Default::default()) + ); + + // Move to era 2 + MockRelayBlockNumberProvider::set(202); + SppModule::on_initialize(200); + }); +} From 359665ef9ebb119a2e4260d8a4f7c4228b47c71c Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 14:45:04 +1300 Subject: [PATCH 100/114] WIP - Increase network ledger with amount of staked rewards --- pallets/spp/src/lib.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 6da76747a..1784ccfdc 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -988,33 +988,54 @@ impl Pallet { .saturating_add(Rate::one()) .saturating_pow(era_changes.unique_saturated_into()) .saturating_sub(Rate::one()); + let mut total_reward_staking: BalanceOf = Zero::zero(); if !reward_rate.is_zero() { // iterate all pool ledgers for (pool_id, pool_amount) in PoolLedger::::iter() { - let mut total_reward_staking: BalanceOf = Zero::zero(); let mut reward_staking = reward_rate.saturating_mul_int(pool_amount); if !reward_staking.is_zero() { let pool_treasury_account = Self::get_pool_treasury(pool_id); total_reward_staking = total_reward_staking.saturating_add(reward_staking); - let reward_commission_amount = Rate::checked_from_rational(1, 100) - .unwrap_or_default() - .saturating_mul_int(total_reward_staking); + let pool_treasury_commission = Rate::checked_from_rational(1, 100).unwrap_or_default(); + let pool_treasury_reward_commission_amount = + pool_treasury_commission.saturating_mul_int(reward_staking); + + // Increase reward staking of pool ledger + PoolLedger::::mutate(pool_id, |total_staked| -> Result<(), Error> { + *total_staked = total_staked + .checked_add(&reward_staking) + .ok_or(Error::::ArithmeticOverflow)?; + + Ok(()) + })?; T::MultiCurrency::deposit( FungibleTokenId::FungibleToken(1), &pool_treasury_account, - reward_commission_amount, + pool_treasury_reward_commission_amount, )?; >::accumulate_reward( &pool_id, FungibleTokenId::FungibleToken(1), - reward_commission_amount, + pool_treasury_reward_commission_amount, )?; } } + + if !total_reward_staking.is_zero() { + NetworkLedger::::mutate( + &FungibleTokenId::NativeToken(1), + |total_staked| -> Result<(), Error> { + *total_staked = total_staked + .checked_add(&total_reward_staking) + .ok_or(Error::::ArithmeticOverflow)?; + Ok(()) + }, + )?; + } } Ok(()) From 1d0ef8a451719b9d0b991731e4d10f3c8433d1c5 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 24 Nov 2023 15:53:30 +1300 Subject: [PATCH 101/114] WIP - Unit test Reward distribution works --- pallets/spp/src/tests.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/tests.rs b/pallets/spp/src/tests.rs index 43d08b6f2..450c28b33 100644 --- a/pallets/spp/src/tests.rs +++ b/pallets/spp/src/tests.rs @@ -645,7 +645,7 @@ fn boosting_and_claim_reward_works() { } #[test] -fn pool_treasury_distribution_works() { +fn reward_distribution_works() { ExtBuilder::default() .ksm_setup_for_alice_and_bob() .build() @@ -655,13 +655,15 @@ fn pool_treasury_distribution_works() { MockRelayBlockNumberProvider::set(102); RelayChainCurrentEra::::put(1); IterationLimit::::put(50); + UnlockDuration::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(1)); // Bump current staking round to 1 + CurrentStakingRound::::insert(FungibleTokenId::NativeToken(1), StakingRound::Era(1)); // The correct set up era config is the last era block records is 101 with duration is 100 blocks assert_ok!(SppModule::update_era_config( RuntimeOrigin::signed(Admin::get()), Some(101), Some(100), StakingRound::Era(1), - Some(Rate::saturating_from_rational(35, 100000)) + Some(Rate::saturating_from_rational(20, 100)) // Set reward rate per era is 20%. )); assert_ok!(SppModule::create_pool( @@ -736,5 +738,17 @@ fn pool_treasury_distribution_works() { // Move to era 2 MockRelayBlockNumberProvider::set(202); SppModule::on_initialize(200); + + assert_ok!(SppModule::handle_reward_distribution_to_pool_treasury(1, 2)); + let pool_treasury = SppModule::get_pool_treasury(1); + + assert_eq!( + Currencies::free_balance(FungibleTokenId::FungibleToken(1), &pool_treasury), + 20 + ); + + assert_eq!(Currencies::total_issuance(FungibleTokenId::FungibleToken(1)), 10020); + assert_eq!(PoolLedger::::get(1), 12000); + assert_eq!(NetworkLedger::::get(FungibleTokenId::NativeToken(1)), 12000); }); } From 3a79bdeb96fd6939dd7ab3708a5c55374d97bf2c Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 12:14:13 +1300 Subject: [PATCH 102/114] WIP - Add spp into runtime --- Cargo.lock | 1 + runtime/metaverse/Cargo.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index fc2087ec6..a3bb61f9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5823,6 +5823,7 @@ dependencies = [ "pallet-reward", "pallet-scheduler", "pallet-session", + "pallet-spp", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", diff --git a/runtime/metaverse/Cargo.toml b/runtime/metaverse/Cargo.toml index 03fe82227..85e6ed61a 100644 --- a/runtime/metaverse/Cargo.toml +++ b/runtime/metaverse/Cargo.toml @@ -104,6 +104,7 @@ core-primitives = { path = "../../traits/core-primitives", default-features = fa metaverse-runtime-common = { path = "../common", default-features = false } asset-manager = { path = "../../pallets/asset-manager", default-features = false } evm-mapping = { package = "pallet-evm-mapping", path = "../../pallets/evm-mapping", default-features = false } +spp = { package = "pallet-spp", path = "../../pallets/spp", default-features = false } modules-bridge = { path = "../../modules/bridge", default-features = false } @@ -176,6 +177,7 @@ std = [ "economy/std", "emergency/std", "evm-mapping/std", + "spp/std", "pallet-utility/std", "pallet-collator-selection/std", "orml-benchmarking/std", From 603295bc1dbe9f7b5cfe38e72f6091f70484269f Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 12:14:43 +1300 Subject: [PATCH 103/114] WIP - Remove unused config type for spp pallet --- pallets/spp/src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 1784ccfdc..2431047d6 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -109,10 +109,6 @@ pub mod pallet { #[pallet::constant] type MinimumStake: Get>; - /// Delay of staking reward payment (in number of rounds) - #[pallet::constant] - type RewardPaymentDelay: Get; - /// Network fee charged on pool creation #[pallet::constant] type NetworkFee: Get>; @@ -122,9 +118,6 @@ pub mod pallet { #[pallet::constant] type StorageDepositFee: Get>; - /// Allows converting block numbers into balance - type BlockNumberToBalance: Convert>; - /// Block number provider for the relaychain. type RelayChainBlockNumber: BlockNumberProvider>; From 8fb4966433f2e0b1913e1c16476b4591b8dfe62f Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 12:15:40 +1300 Subject: [PATCH 104/114] WIP - Include spp into metaverse runtime --- runtime/metaverse/src/lib.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/runtime/metaverse/src/lib.rs b/runtime/metaverse/src/lib.rs index 66fda6b2c..a522b48f9 100644 --- a/runtime/metaverse/src/lib.rs +++ b/runtime/metaverse/src/lib.rs @@ -60,7 +60,6 @@ use pallet_evm::{ }; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; -//use pallet_evm::{EnsureAddressTruncated, HashedAddressMapping}; use polkadot_primitives::v2::MAX_POV_SIZE; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -87,6 +86,8 @@ use sp_std::prelude::*; use sp_version::NativeVersion; use sp_version::RuntimeVersion; +//use pallet_evm::{EnsureAddressTruncated, HashedAddressMapping}; +use asset_manager::ForeignAssetMapping; pub use constants::{currency::*, time::*}; use core_primitives::{NftAssetData, NftClassData}; // External imports @@ -364,6 +365,10 @@ parameter_types! { pub const EconomyTreasury: PalletId = PalletId(*b"bit/econ"); pub const LocalMetaverseFundPalletId: PalletId = PalletId(*b"bit/meta"); pub const BridgeSovereignPalletId: PalletId = PalletId(*b"bit/brgd"); + pub const PoolAccountPalletId: PalletId = PalletId(*b"bit/pool"); + pub const RewardPayoutAccountPalletId: PalletId = PalletId(*b"bit/pout"); + pub const RewardHoldingAccountPalletId: PalletId = PalletId(*b"bit/hold"); + pub const MaxAuthorities: u32 = 50; pub const MaxSetIdSessionEntries: u64 = u64::MAX; } @@ -1437,6 +1442,27 @@ impl modules_bridge::Config for Runtime { type PalletId = BridgeSovereignPalletId; } +parameter_types! { + pub const MaximumQueue: u32 = 50; +} + +impl spp::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type MultiCurrency = Currencies; + type WeightInfo = weights::module_estate::WeightInfo; + type MinimumStake = MinimumStake; + type NetworkFee = NetworkFee; + type StorageDepositFee = StorageDepositFee; + type RelayChainBlockNumber = (); + type PoolAccount = PoolAccountPalletId; + type RewardPayoutAccount = RewardPayoutAccountPalletId; + type RewardHoldingAccount = RewardHoldingAccountPalletId; + type MaximumQueue = MaximumQueue; + type CurrencyIdConversion = ForeignAssetMapping; + type GovernanceOrigin = EnsureRootOrTwoThirdsCouncilCollective; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime where @@ -1510,6 +1536,9 @@ construct_runtime!( // Bridge BridgeSupport: modules_bridge::{Pallet, Call, Storage, Event}, + + // Spp + Spp: spp::{Pallet, Call, Storage, Event} } ); From 3f186a9b525bb0eef822c504c1f8430fa7d85a94 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 12:25:21 +1300 Subject: [PATCH 105/114] WIP - Add mock module_spp weight --- runtime/metaverse/src/weights/mod.rs | 1 + runtime/metaverse/src/weights/module_spp.rs | 168 ++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 runtime/metaverse/src/weights/module_spp.rs diff --git a/runtime/metaverse/src/weights/mod.rs b/runtime/metaverse/src/weights/mod.rs index 8d70a266d..f934833ce 100644 --- a/runtime/metaverse/src/weights/mod.rs +++ b/runtime/metaverse/src/weights/mod.rs @@ -9,3 +9,4 @@ pub mod module_metaverse; pub mod module_mining; pub mod module_nft; pub mod module_reward; +pub mod module_spp; diff --git a/runtime/metaverse/src/weights/module_spp.rs b/runtime/metaverse/src/weights/module_spp.rs new file mode 100644 index 000000000..8e4985b6e --- /dev/null +++ b/runtime/metaverse/src/weights/module_spp.rs @@ -0,0 +1,168 @@ +// This file is part of Metaverse.Network & Bit.Country. + +// Copyright (C) 2020-2022 Metaverse.Network & Bit.Country . +// 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. + +//! Autogenerated weights for estate +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-11-03, STEPS: `20`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// ./target/release/metaverse-node +// benchmark +// --chain=dev +// --pallet=estate +// --extrinsic=* +// --steps=20 +// --repeat=10 +// --execution=wasm +// --wasm-execution=compiled +// --template=./template/runtime-weight-template.hbs +// --output +// ./pallets/estate/src/weights.rs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for estate. +pub struct WeightInfo(PhantomData); + +impl spp::WeightInfo for WeightInfo { + fn mint_land() -> Weight { + Weight::from_parts(59_273_000, 36660) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) + } + fn mint_lands() -> Weight { + Weight::from_parts(83_541_000, 41610) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } + fn transfer_land() -> Weight { + Weight::from_parts(47_423_000, 28255) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + fn mint_estate() -> Weight { + Weight::from_parts(64_479_000, 47210) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(11)) + } + fn dissolve_estate() -> Weight { + Weight::from_parts(110_008_000, 67224) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(13)) + } + fn add_land_unit_to_estate() -> Weight { + Weight::from_parts(71_706_000, 38079) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) + } + fn remove_land_unit_from_estate() -> Weight { + Weight::from_parts(90_257_000, 60226) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(8)) + } + fn create_estate() -> Weight { + Weight::from_parts(131_741_000, 66058) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(17)) + } + fn transfer_estate() -> Weight { + Weight::from_parts(54_808_000, 38025) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + fn issue_undeployed_land_blocks() -> Weight { + Weight::from_parts(162_875_000, 9825) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(43)) + } + fn freeze_undeployed_land_blocks() -> Weight { + Weight::from_parts(22_833_000, 7834) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn unfreeze_undeployed_land_blocks() -> Weight { + Weight::from_parts(21_423_000, 7834) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn approve_undeployed_land_blocks() -> Weight { + Weight::from_parts(21_819_000, 7834) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn unapprove_undeployed_land_blocks() -> Weight { + Weight::from_parts(21_237_000, 7900) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn transfer_undeployed_land_blocks() -> Weight { + Weight::from_parts(41_173_000, 13673) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } + fn deploy_land_block() -> Weight { + Weight::from_parts(100_659_000, 64897) + .saturating_add(T::DbWeight::get().reads(13)) + .saturating_add(T::DbWeight::get().writes(11)) + } + fn burn_undeployed_land_blocks() -> Weight { + Weight::from_parts(32_196_000, 10661) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + fn create_lease_offer() -> Weight { + Weight::from_parts(98_902_000, 27383) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn accept_lease_offer() -> Weight { + Weight::from_parts(57_019_000, 28844) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) + } + fn cancel_lease() -> Weight { + Weight::from_parts(57_720_000, 28571) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + fn remove_expired_lease() -> Weight { + Weight::from_parts(57_681_000, 28571) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + fn remove_lease_offer() -> Weight { + Weight::from_parts(41_923_000, 8328) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn collect_rent() -> Weight { + Weight::from_parts(53_171_000, 28571) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn on_initialize() -> Weight { + Weight::from_parts(191_000, 0) + } +} \ No newline at end of file From 1fd74b84818845162ac048d68ad4e0cce80db5ff Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 12:25:38 +1300 Subject: [PATCH 106/114] WIP - Implement cumulus parachain info into metaverse runtime --- Cargo.lock | 11 +++++++++++ runtime/metaverse/Cargo.toml | 23 ++++++++++++++++++++++- runtime/metaverse/src/lib.rs | 18 ++++++++++++++---- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3bb61f9b..030695fb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5772,6 +5772,15 @@ dependencies = [ "auction-manager", "bit-country-primitives", "core-primitives", + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-timestamp", + "cumulus-primitives-utility", "currencies", "fp-evm", "fp-rpc", @@ -5791,6 +5800,7 @@ dependencies = [ "orml-currencies", "orml-nft", "orml-oracle", + "orml-rewards", "orml-tokens", "orml-traits", "pallet-auction", @@ -5831,6 +5841,7 @@ dependencies = [ "pallet-treasury", "pallet-utility", "pallet-vesting", + "parachain-info", "parity-scale-codec", "polkadot-primitives", "scale-info", diff --git a/runtime/metaverse/Cargo.toml b/runtime/metaverse/Cargo.toml index 85e6ed61a..216dca109 100644 --- a/runtime/metaverse/Cargo.toml +++ b/runtime/metaverse/Cargo.toml @@ -84,6 +84,7 @@ orml-tokens = { workspace = true } orml-traits = { workspace = true } orml-nft = { workspace = true } orml-oracle = { workspace = true } +orml-rewards = { workspace = true } orml-benchmarking = { workspace = true, optional = true } # metaverse dependencies primitives = { package = "bit-country-primitives", path = "../../primitives/metaverse", default-features = false } @@ -115,6 +116,18 @@ pallet-contracts-primitives = { workspace = true } # XCM builder ( need to be used to build the runtime benchmarking correctly) xcm-builder = { workspace = true } +# Cumulus Dependencies +cumulus-pallet-aura-ext = { workspace = true } +cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-dmp-queue = { workspace = true } +cumulus-pallet-xcm = { workspace = true } +cumulus-pallet-xcmp-queue = { workspace = true } +cumulus-primitives-core = { workspace = true } +cumulus-primitives-timestamp = { workspace = true } +cumulus-primitives-utility = { workspace = true } +cumulus-pallet-session-benchmarking = { workspace = true } +parachain-info = { workspace = true } + [build-dependencies] substrate-wasm-builder = { workspace = true } @@ -163,6 +176,7 @@ std = [ "orml-tokens/std", "orml-nft/std", "orml-oracle/std", + "orml-rewards/std", "primitives/std", "metaverse/std", "auction/std", @@ -191,7 +205,14 @@ std = [ "pallet-contracts-primitives/std", "frame-try-runtime/std", "metaverse-runtime-common/std", - "modules-bridge/std" + "modules-bridge/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-pallet-xcm/std", + "cumulus-primitives-core/std", + "cumulus-primitives-timestamp/std", + "cumulus-primitives-utility/std", ] runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", diff --git a/runtime/metaverse/src/lib.rs b/runtime/metaverse/src/lib.rs index a522b48f9..0103d1725 100644 --- a/runtime/metaverse/src/lib.rs +++ b/runtime/metaverse/src/lib.rs @@ -24,6 +24,7 @@ extern crate orml_benchmarking; use codec::{Decode, Encode, MaxEncodedLen}; +use cumulus_pallet_parachain_system::RelaychainDataProvider; // pub use this so we can import it in the chain spec. #[cfg(feature = "std")] pub use fp_evm::GenesisAccount; @@ -99,7 +100,7 @@ use primitives::evm::{ CurrencyIdType, Erc20Mapping, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_FUNGIBLE_TOKEN, H160_POSITION_MINING_RESOURCE, H160_POSITION_TOKEN, H160_POSITION_TOKEN_NFT, H160_POSITION_TOKEN_NFT_CLASS_ID_END, }; -use primitives::{Amount, Balance, BlockNumber, ClassId, FungibleTokenId, Moment, NftId, RoundIndex, TokenId}; +use primitives::{Amount, Balance, BlockNumber, ClassId, FungibleTokenId, Moment, NftId, PoolId, RoundIndex, TokenId}; // primitives imports use crate::opaque::SessionKeys; @@ -1442,6 +1443,14 @@ impl modules_bridge::Config for Runtime { type PalletId = BridgeSovereignPalletId; } +impl orml_rewards::Config for Runtime { + type Share = Balance; + type Balance = Balance; + type PoolId = PoolId; + type CurrencyId = FungibleTokenId; + type Handler = Spp; +} + parameter_types! { pub const MaximumQueue: u32 = 50; } @@ -1450,11 +1459,11 @@ impl spp::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type MultiCurrency = Currencies; - type WeightInfo = weights::module_estate::WeightInfo; + type WeightInfo = weights::module_spp::WeightInfo; type MinimumStake = MinimumStake; type NetworkFee = NetworkFee; type StorageDepositFee = StorageDepositFee; - type RelayChainBlockNumber = (); + type RelayChainBlockNumber = RelaychainDataProvider; type PoolAccount = PoolAccountPalletId; type RewardPayoutAccount = RewardPayoutAccountPalletId; type RewardHoldingAccount = RewardHoldingAccountPalletId; @@ -1538,7 +1547,8 @@ construct_runtime!( BridgeSupport: modules_bridge::{Pallet, Call, Storage, Event}, // Spp - Spp: spp::{Pallet, Call, Storage, Event} + Spp: spp::{Pallet, Call, Storage, Event}, + Rewards: orml_rewards::{Pallet, Storage} } ); From d6eda759e4ac2a948fda16f983fd8e4e5920b8c8 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 18:35:36 +1300 Subject: [PATCH 107/114] WIP - Update correct relaychain block number provider --- runtime/metaverse/src/lib.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/runtime/metaverse/src/lib.rs b/runtime/metaverse/src/lib.rs index 0103d1725..115ca8177 100644 --- a/runtime/metaverse/src/lib.rs +++ b/runtime/metaverse/src/lib.rs @@ -24,7 +24,7 @@ extern crate orml_benchmarking; use codec::{Decode, Encode, MaxEncodedLen}; -use cumulus_pallet_parachain_system::RelaychainDataProvider; +use cumulus_pallet_parachain_system::RelaychainBlockNumberProvider; // pub use this so we can import it in the chain spec. #[cfg(feature = "std")] pub use fp_evm::GenesisAccount; @@ -70,7 +70,7 @@ use sp_core::{ sp_std::marker::PhantomData, ConstBool, OpaqueMetadata, H160, H256, U256, }; -use sp_runtime::traits::DispatchInfoOf; +use sp_runtime::traits::{BlockNumberProvider, DispatchInfoOf}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; use sp_runtime::{ @@ -1454,6 +1454,15 @@ impl orml_rewards::Config for Runtime { parameter_types! { pub const MaximumQueue: u32 = 50; } +pub static MockRelayBlockNumberProvider: BlockNumber = 0; + +impl BlockNumberProvider for MockRelayBlockNumberProvider { + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + Self::get() + } +} impl spp::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -1463,7 +1472,7 @@ impl spp::Config for Runtime { type MinimumStake = MinimumStake; type NetworkFee = NetworkFee; type StorageDepositFee = StorageDepositFee; - type RelayChainBlockNumber = RelaychainDataProvider; + type RelayChainBlockNumber = RelaychainBlockNumberProvider; type PoolAccount = PoolAccountPalletId; type RewardPayoutAccount = RewardPayoutAccountPalletId; type RewardHoldingAccount = RewardHoldingAccountPalletId; From 53373ca7ea9f32a7a78d5d289ed7e64ce814d049 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 18:37:14 +1300 Subject: [PATCH 108/114] WIP - Temp use current chain block number as index --- pallets/spp/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 2431047d6..3d603cfda 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -390,7 +390,9 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - let era_number = Self::get_era_index(T::RelayChainBlockNumber::current_block_number()); + // let era_number = Self::get_era_index(T::RelayChainBlockNumber::current_block_number()); + let era_number = Self::get_era_index(>::block_number()); + if !era_number.is_zero() { let _ = Self::update_current_era(era_number).map_err(|err| err).ok(); } From 5d6730d14fff415a37a460f5306b7279bb3f519b Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 21:10:27 +1300 Subject: [PATCH 109/114] WIP - Add missing BTreemap and Vec --- pallets/spp/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 3d603cfda..5672ab9de 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -59,13 +59,12 @@ const BOOSTING_ID: LockIdentifier = *b"bc/boost"; #[frame_support::pallet] pub mod pallet { - use std::collections::BTreeMap; - use frame_support::traits::{Currency, LockableCurrency, ReservableCurrency, WithdrawReasons}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_core::U256; use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedMul, CheckedSub, One, UniqueSaturatedInto}; use sp_runtime::Permill; + use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; use primitives::bounded::FractionalRate; use primitives::{PoolId, StakingRound}; From 59ab0caab4b33bf0cd08dcf264d4b997b630c50a Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 Nov 2023 21:10:50 +1300 Subject: [PATCH 110/114] WIP - Complete adding metaverse runtime for testing --- pallets/spp/src/utils.rs | 1 + runtime/metaverse/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pallets/spp/src/utils.rs b/pallets/spp/src/utils.rs index d9f893956..f5c70a688 100644 --- a/pallets/spp/src/utils.rs +++ b/pallets/spp/src/utils.rs @@ -23,6 +23,7 @@ use sp_runtime::{ traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, IntegerSquareRoot, Saturating, Zero}, Permill, RuntimeDebug, }; +use sp_std::vec::Vec; use primitives::bounded::{FractionalRate, Rate}; use primitives::{FungibleTokenId, PoolId}; diff --git a/runtime/metaverse/src/lib.rs b/runtime/metaverse/src/lib.rs index 115ca8177..e5a6f0c73 100644 --- a/runtime/metaverse/src/lib.rs +++ b/runtime/metaverse/src/lib.rs @@ -1453,8 +1453,8 @@ impl orml_rewards::Config for Runtime { parameter_types! { pub const MaximumQueue: u32 = 50; + pub const MockRelayBlockNumberProvider: BlockNumber = 0; } -pub static MockRelayBlockNumberProvider: BlockNumber = 0; impl BlockNumberProvider for MockRelayBlockNumberProvider { type BlockNumber = BlockNumber; @@ -1472,7 +1472,7 @@ impl spp::Config for Runtime { type MinimumStake = MinimumStake; type NetworkFee = NetworkFee; type StorageDepositFee = StorageDepositFee; - type RelayChainBlockNumber = RelaychainBlockNumberProvider; + type RelayChainBlockNumber = MockRelayBlockNumberProvider; type PoolAccount = PoolAccountPalletId; type RewardPayoutAccount = RewardPayoutAccountPalletId; type RewardHoldingAccount = RewardHoldingAccountPalletId; From 4754983e25e8ec7daee55686456fcc532ddd0e00 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 28 Nov 2023 11:34:46 +1300 Subject: [PATCH 111/114] Add signature expired block for presigned mint for safer revoke permission --- pallets/nft/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pallets/nft/src/lib.rs b/pallets/nft/src/lib.rs index b45d145e2..fc1b14d09 100644 --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -156,6 +156,7 @@ pub mod pallet { ::ClassId, ::TokenId, ::AccountId, + BlockNumberFor, BalanceOf, >; @@ -403,6 +404,8 @@ pub mod pallet { InvalidCurrentTotalIssuance, /// Wrong signature WrongSignature, + /// Signature expired + SignatureExpired, } #[pallet::call] @@ -1174,6 +1177,7 @@ impl Pallet { attributes, metadata, only_account, + expired, mint_price, } = mint_data; @@ -1191,6 +1195,9 @@ impl Pallet { ensure!(account == mint_to, Error::::NoPermission); } + let now = frame_system::Pallet::::block_number(); + ensure!(expired >= now, Error::::SignatureExpired); + // Get class info of the collection let class_info = NftModule::::classes(class_id).ok_or(Error::::ClassIdNotFound)?; From 6a1de47bc223874b52c2f3561b2633b0bcb2ac80 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 28 Nov 2023 11:35:00 +1300 Subject: [PATCH 112/114] Implement expired into presigned mint struct --- traits/core-primitives/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/traits/core-primitives/src/lib.rs b/traits/core-primitives/src/lib.rs index 72f15aa0d..5aa8b55df 100644 --- a/traits/core-primitives/src/lib.rs +++ b/traits/core-primitives/src/lib.rs @@ -163,7 +163,7 @@ pub struct MetaverseFund { } #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct PreSignedMint { +pub struct PreSignedMint { /// A collection of the item to be minted. pub class_id: ClassId, /// TokenId. @@ -174,6 +174,8 @@ pub struct PreSignedMint { pub metadata: NftMetadata, /// Restrict the claim to a particular account. pub only_account: Option, + /// A deadline for the signature. + pub expired: BlockNumber, /// An optional price the claimer would need to pay for the mint. pub mint_price: Option, } From 0cc75f7b3f0446ac4c346ddec69722a6566e5d8f Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 28 Nov 2023 11:35:10 +1300 Subject: [PATCH 113/114] Bump runtime --- runtime/metaverse/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/metaverse/src/lib.rs b/runtime/metaverse/src/lib.rs index e5a6f0c73..f90678a72 100644 --- a/runtime/metaverse/src/lib.rs +++ b/runtime/metaverse/src/lib.rs @@ -182,7 +182,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 92, + spec_version: 93, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From e035dda21f9cf00be31bd5ad855110dc29dac5a9 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 28 Nov 2023 11:48:42 +1300 Subject: [PATCH 114/114] Update era config to include more initial era setup --- pallets/spp/src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pallets/spp/src/lib.rs b/pallets/spp/src/lib.rs index 5672ab9de..460888b2d 100644 --- a/pallets/spp/src/lib.rs +++ b/pallets/spp/src/lib.rs @@ -334,6 +334,13 @@ pub mod pallet { }, /// Reward rate per era updated. EstimatedRewardRatePerEraUpdated { reward_rate_per_era: Rate }, + /// Unlock duration updated. + UnlockDurationUpdated { + currency_id: FungibleTokenId, + unlock_duration: StakingRound, + }, + /// Iterator limit updated. + IterationLimitUpdated { new_limit: u32 }, } #[pallet::error] @@ -678,6 +685,8 @@ pub mod pallet { frequency: Option>, last_staking_round: StakingRound, estimated_reward_rate_per_era: Option, + unlock_duration: Option<(FungibleTokenId, StakingRound)>, + iteration_limit: Option, ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; @@ -709,6 +718,20 @@ pub mod pallet { })?; Self::deposit_event(Event::::EstimatedRewardRatePerEraUpdated { reward_rate_per_era }); } + + if let Some((currency_id, staking_round)) = unlock_duration { + UnlockDuration::::insert(currency_id, staking_round); + Self::deposit_event(Event::::UnlockDurationUpdated { + currency_id, + unlock_duration: staking_round, + }) + } + + if let Some(new_limit) = iteration_limit { + IterationLimit::::put(new_limit); + Self::deposit_event(Event::::IterationLimitUpdated { new_limit }) + } + Ok(()) }