diff --git a/pallets/housing_fund/src/functions.rs b/pallets/housing_fund/src/functions.rs index fb047cca..d6ddac05 100644 --- a/pallets/housing_fund/src/functions.rs +++ b/pallets/housing_fund/src/functions.rs @@ -84,7 +84,7 @@ impl Pallet { } // The amount is tagged as reserved in the fund for the account_id - T::LocalCurrency::reserve(&T::PalletId::get().into_account_truncating(), amount)?; + T::LocalCurrency::reserve(&Self::fund_account_id(), amount)?; fund.reserve(amount); // The amount is reserved in the pot @@ -120,4 +120,48 @@ impl Pallet { pub fn get_contributions() -> Vec<(AccountIdOf, Contribution)> { Contributions::::iter().map(|elt| (elt.0, elt.1)).collect() } + + pub fn cancel_house_bidding(nft_collection_id: NftCollectionId, nft_item_id: NftItemId) -> DispatchResultWithPostInfo { + let reservation_wrap = Reservations::::get((nft_collection_id, nft_item_id)); + + ensure!(reservation_wrap.is_some(), Error::::NoFundReservationFound); + + let reservation = reservation_wrap.unwrap(); + + let contributions_iter = reservation.contributions.iter(); + + for item in contributions_iter { + let entry = Contributions::::get(item.0.clone()); + Contributions::::mutate(item.0.clone(), |val| { + let mut unwrap_val = val.clone().unwrap(); + unwrap_val.unreserve_amount(item.1); + let contribution = unwrap_val.clone(); + *val = Some(contribution); + }); + } + + let mut fund = FundBalance::::get(); + T::LocalCurrency::unreserve(&Self::fund_account_id(), reservation.amount); + fund.unreserve(reservation.amount); + + Reservations::::remove((nft_collection_id, nft_item_id)); + + // The amount is reserved in the pot + FundBalance::::mutate(|val| { + *val = fund.clone(); + }); + + // Get the block number for timestamp + let block_number = >::block_number(); + + // Emit an event. + Self::deposit_event(Event::FundReservationCancelled( + nft_collection_id, + nft_item_id, + reservation.amount, + block_number, + )); + + Ok(().into()) + } } diff --git a/pallets/housing_fund/src/lib.rs b/pallets/housing_fund/src/lib.rs index 1a4e3502..f8afdca5 100644 --- a/pallets/housing_fund/src/lib.rs +++ b/pallets/housing_fund/src/lib.rs @@ -117,6 +117,7 @@ pub mod pallet { ), /// Fund reservation succeded FundReservationSucceeded(T::NftCollectionId, T::NftItemId, BalanceOf, BlockNumberOf), + FundReservationCancelled(T::NftCollectionId, T::NftItemId, BalanceOf, BlockNumberOf), } // Errors inform users that something went wrong. @@ -144,6 +145,8 @@ pub mod pallet { NotAnInvestor, /// Must not have more investor than the max acceppted NotMoreThanMaxInvestorPerHouse, + /// The reservation doesn't exist in the storage + NoFundReservationFound, } #[pallet::call] diff --git a/pallets/housing_fund/src/structs.rs b/pallets/housing_fund/src/structs.rs index eefbcdc8..ec275e9a 100644 --- a/pallets/housing_fund/src/structs.rs +++ b/pallets/housing_fund/src/structs.rs @@ -63,6 +63,13 @@ impl FundInfo { // add the amount to reserved self.reserved += amount; } + + pub fn unreserve(&mut self, amount: BalanceOf) { + // remove the amount to reserved + self.reserved -= amount; + // add the amount to transferable + self.transferable += amount; + } } // Contains amount and timestamp of an account diff --git a/pallets/housing_fund/src/tests.rs b/pallets/housing_fund/src/tests.rs index 1d321e92..b5c27065 100644 --- a/pallets/housing_fund/src/tests.rs +++ b/pallets/housing_fund/src/tests.rs @@ -618,6 +618,101 @@ fn house_bidding_with_valid_values_should_succeed() { }); } +#[test] +fn cancel_house_bidding_with_invalid_values_should_fail() { + new_test_ext().execute_with(|| { + // Try to cancel a bidding that doesn't exist + assert_noop!( + HousingFundModule::cancel_house_bidding(1,1), + Error::::NoFundReservationFound + ); + }); +} + +#[test] +fn cancel_house_bidding_with_valid_values_should_succeed() { + new_test_ext().execute_with(|| { + let fund_account_id = HousingFundModule::fund_account_id(); + + // Give the investor role to the accounts + assert_ok!(RoleModule::set_role(Origin::signed(1), 1, crate::ROLES::Accounts::INVESTOR)); + assert_ok!(RoleModule::set_role(Origin::signed(2), 2, crate::ROLES::Accounts::INVESTOR)); + + assert_ok!(HousingFundModule::contribute_to_fund(Origin::signed(1), 40)); + assert_ok!(HousingFundModule::contribute_to_fund(Origin::signed(2), 40)); + + assert_ok!(HousingFundModule::house_bidding( + 1, + 1, + 60, + vec![(1, 30), (2, 30)] + )); + + assert_ok!(HousingFundModule::cancel_house_bidding(1,1)); + + assert_eq!( + HousingFundModule::fund_balance(), + FundInfo { + total: HousingFundModule::u64_to_balance_option(80).unwrap(), + transferable: HousingFundModule::u64_to_balance_option(80).unwrap(), + reserved: HousingFundModule::u64_to_balance_option(0).unwrap(), + } + ); + + assert_eq!( + HousingFundModule::contributions(1), + Some(Contribution { + account_id: 1, + available_balance: HousingFundModule::u64_to_balance_option(40).unwrap(), + reserved_balance: HousingFundModule::u64_to_balance_option(0).unwrap(), + contributed_balance: HousingFundModule::u64_to_balance_option(0).unwrap(), + has_withdrawn: false, + block_number: 1, + contributions: vec![ContributionLog { + amount: HousingFundModule::u64_to_balance_option(40).unwrap(), + block_number: 1 + }], + withdraws: Vec::new() + }) + ); + + assert_eq!( + HousingFundModule::contributions(2), + Some(Contribution { + account_id: 2, + available_balance: HousingFundModule::u64_to_balance_option(40).unwrap(), + reserved_balance: HousingFundModule::u64_to_balance_option(0).unwrap(), + contributed_balance: HousingFundModule::u64_to_balance_option(0).unwrap(), + has_withdrawn: false, + block_number: 1, + contributions: vec![ContributionLog { + amount: HousingFundModule::u64_to_balance_option(40).unwrap(), + block_number: 1 + }], + withdraws: Vec::new() + }) + ); + + assert_eq!(HousingFundModule::reservations((1,1)).is_none(), true); + + // Check the amount reserved for the account + assert_eq!( + Balances::reserved_balance(&fund_account_id), + HousingFundModule::u64_to_balance_option(0).unwrap() + ); + + let event = >::events() + .pop() + .expect("Expected at least one EventRecord to be found") + .event; + + assert_eq!( + event, + mock::Event::HousingFundModule(crate::Event::FundReservationCancelled(1, 1, 60, 1)) + ); + }); +} + #[test] fn fund_info_contribute_transferable_should_succeed() { new_test_ext().execute_with(|| {