diff --git a/pallets/dex/src/lib.rs b/pallets/dex/src/lib.rs index 9076197e..d730bd10 100644 --- a/pallets/dex/src/lib.rs +++ b/pallets/dex/src/lib.rs @@ -248,7 +248,10 @@ pub mod pallet { owner: T::AccountId, }, /// A sell order was cancelled - SellOrderCancelled { order_id: OrderId, seller: T::AccountId }, + SellOrderCancelled { + order_id: OrderId, + seller: T::AccountId, + }, /// A buy order was processed successfully BuyOrderCreated { order_id: OrderId, @@ -263,11 +266,19 @@ pub mod pallet { buyer: T::AccountId, }, /// A new ValidatorAccount has been added - ValidatorAccountAdded { account_id: T::AccountId }, + ValidatorAccountAdded { + account_id: T::AccountId, + }, /// An ValidatorAccount has been removed - ValidatorAccountRemoved { account_id: T::AccountId }, + ValidatorAccountRemoved { + account_id: T::AccountId, + }, /// A buy order payment was validated - BuyOrderPaymentValidated { order_id: BuyOrderId, chain_id: u32, validator: T::AccountId }, + BuyOrderPaymentValidated { + order_id: BuyOrderId, + chain_id: u32, + validator: T::AccountId, + }, /// A buy order was completed successfully BuyOrderFilled { order_id: BuyOrderId, @@ -286,9 +297,14 @@ pub mod pallet { preference: Option>, }, /// Authority to validate seller payout has been set - SellerPayoutAuthoritySet { authority: T::AccountId }, + SellerPayoutAuthoritySet { + authority: T::AccountId, + }, /// A seller was paid - SellerPayoutExecuted { seller: T::AccountId, payout: PayoutExecutedToSellerOf }, + SellerPayoutExecuted { + seller: T::AccountId, + payout: PayoutExecutedToSellerOf, + }, /// A buy order was expired and removed BuyOrderExpired { order_id: BuyOrderId, @@ -297,9 +313,17 @@ pub mod pallet { buyer: T::AccountId, }, /// User open order units limit set - UserOpenOrderUnitsLimitUpdated { level: UserLevel, limit: AssetBalanceOf }, + UserOpenOrderUnitsLimitUpdated { + level: UserLevel, + limit: AssetBalanceOf, + }, /// BuyOrdersByUser storage was cleard - BuyOrdersByUserCleared { user: T::AccountId }, + BuyOrdersByUserCleared { + user: T::AccountId, + }, + BankWireInProcessSet { + order_id: BuyOrderId, + }, } // Errors inform users that something went wrong. @@ -364,7 +388,7 @@ pub mod pallet { /// Min validators cannot be zero MinValidatorsCannotBeZero, /// User kyc level cannot use bank wire - UserCannotUseBankWire + UserCannotUseBankWire, } #[pallet::hooks] @@ -546,7 +570,7 @@ pub mod pallet { asset_id: AssetIdOf, units: AssetBalanceOf, max_fee: CurrencyBalanceOf, - is_bank_wire: bool + is_bank_wire: bool, ) -> DispatchResult { let buyer = ensure_signed(origin)?; @@ -614,12 +638,12 @@ pub mod pallet { let current_block_number = >::block_number(); let expiry_time = if is_bank_wire { current_block_number - .checked_add(&T::BuyOrderExpiryTime::get().saturating_mul(8_u32.into())) - .ok_or(Error::::OrderIdOverflow)? + .checked_add(&T::BuyOrderExpiryTime::get().saturating_mul(2_u32.into())) + .ok_or(Error::::OrderIdOverflow)? } else { current_block_number - .checked_add(&T::BuyOrderExpiryTime::get()) - .ok_or(Error::::OrderIdOverflow)? + .checked_add(&T::BuyOrderExpiryTime::get()) + .ok_or(Error::::OrderIdOverflow)? }; // ensure the user does not have open buy orders more than limit @@ -1074,6 +1098,56 @@ pub mod pallet { Self::deposit_event(Event::BuyOrdersByUserCleared { user }); Ok(()) } + + /// This action will extend the expiration time of the order to + /// allow for the additional time typically required for bank wire transfers to complete. + /// + /// # Arguments + /// + /// * `origin` - The account initiating the request. The caller must be a valid validator + /// account. + /// * `order_id` - The unique identifier of the buy order for which the payment status is + /// being set to "pending". + /// + /// # Effects + /// + /// * This function fetches the buy order associated with the given `order_id`. + /// * If the order exists, it extends the order's expiry time by a duration determined by + /// the `BuyOrderExpiryTime` constant multiplied by 8. + /// * This is useful in scenarios where a bank wire transfer is the chosen payment method, + /// as these transfers generally require more time to process. + /// + /// # Errors + /// + /// This function will return an error in the following scenarios: + /// + /// * If the `origin` is not a valid, signed account. + /// * If the account initiating the call is not a validator. + /// * If the provided `order_id` does not correspond to an existing buy order. + /// * If extending the expiry time results in an overflow of the `expiry_time` field. + #[pallet::call_index(14)] + #[pallet::weight(T::WeightInfo::force_set_purchase_fee())] + pub fn set_bank_wire_in_process( + origin: OriginFor, + order_id: BuyOrderId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + Self::check_validator_account(&sender)?; + + // fetch the buy order + BuyOrders::::try_mutate(order_id, |maybe_order| -> DispatchResult { + let mut order = maybe_order.take().ok_or(Error::::InvalidOrderId)?; + + order.expiry_time = order + .expiry_time + .checked_add(&T::BuyOrderExpiryTime::get().saturating_mul(8_u32.into())) + .ok_or(Error::::OrderIdOverflow)?; + + Self::deposit_event(Event::BankWireInProcessSet { order_id }); + + Ok(()) + }) + } } impl Pallet { @@ -1113,19 +1187,16 @@ pub mod pallet { } /// Checks if given account is allowed to use bank wire - pub fn check_allowed_to_use_bank_wire( - account_id: &T::AccountId, - ) -> DispatchResult { + pub fn check_allowed_to_use_bank_wire(account_id: &T::AccountId) -> DispatchResult { use primitives::CarbonAssetType::*; if let Some(kyc_level) = T::KYCProvider::get_kyc_level(account_id.clone()) { - match kyc_level { - primitives::UserLevel::KYCLevel3 => return Ok(()), - primitives::UserLevel::KYCLevel4 => return Ok(()), - _ => return Err(Error::::UserCannotUseBankWire.into()), - } + match kyc_level { + primitives::UserLevel::KYCLevel3 => return Ok(()), + primitives::UserLevel::KYCLevel4 => return Ok(()), + _ => return Err(Error::::UserCannotUseBankWire.into()), } - else { + } else { Err(Error::::UserCannotUseBankWire.into()) } } diff --git a/pallets/dex/src/tests.rs b/pallets/dex/src/tests.rs index 0a054b84..e66b7b68 100644 --- a/pallets/dex/src/tests.rs +++ b/pallets/dex/src/tests.rs @@ -186,48 +186,48 @@ fn buy_order_should_work() { // non existing order should fail assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 10, 0, 1, 100), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 10, 0, 1, 100, false), Error::::InvalidOrderId ); // non kyc buyer should fail assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(20), 0, 10, 1, 100), + Dex::create_buy_order(RuntimeOrigin::signed(20), 0, 10, 1, 100, false), Error::::KYCAuthorisationFailed ); // non matching asset_id should fail assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, 10, 1, 100), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, 10, 1, 100, false), Error::::InvalidAssetId ); // more than listed volume should fail assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1000, 100), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1000, 100, false), Error::::OrderUnitsOverflow ); // should fail if the buyer and seller are same assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(seller), 0, asset_id, 1, 100), + Dex::create_buy_order(RuntimeOrigin::signed(seller), 0, asset_id, 1, 100, false), Error::::SellerAndBuyerCannotBeSame ); // should fail if the fee is zero assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 0), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 0, false), Error::::FeeExceedsUserLimit ); // should fail if the fee is less than expected assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 0), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 0, false), Error::::FeeExceedsUserLimit ); // use should be able to purchase - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); // sell order storage should be updated correctly let sell_order_storage = Orders::::get(0).unwrap(); @@ -299,7 +299,7 @@ fn validate_buy_order_should_work() { assert_ok!(Dex::create_sell_order(RuntimeOrigin::signed(seller), asset_id, 5, 10)); // create a new buy order - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); let tx_proof: BoundedVec<_, _> = vec![].try_into().unwrap(); @@ -380,7 +380,7 @@ fn payment_is_processed_after_validator_threshold_reached() { add_validator_account(validator_two); // create a new buy order - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); let tx_proof: BoundedVec<_, _> = vec![].try_into().unwrap(); @@ -602,7 +602,7 @@ fn buy_order_handle_expiry_should_work() { add_validator_account(validator); // use should be able to purchase - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); // sell order storage should be updated correctly let sell_order_storage = Orders::::get(0).unwrap(); @@ -775,7 +775,7 @@ fn buy_order_limits_should_work() { // should fail if the limits are not set assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false), Error::::UserOpenOrderUnitsLimtNotFound ); @@ -788,26 +788,26 @@ fn buy_order_limits_should_work() { // use should not be able to purchase above limit assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 11, 110), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 11, 110, false), Error::::UserOpenOrderUnitsAllowedExceeded ); // use should be able to purchase below limit - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); // use should not be able exceed purchase limit with another order assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 10, 110), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 10, 110, false), Error::::UserOpenOrderUnitsAllowedExceeded ); // use should be able to create another order if its below limit - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 5, 110),); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 5, 110, false),); // use should not be able to create another order if its above number of total open orders // allowed assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 110), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 110, false), Error::::OpenOrderLimitExceeded ); @@ -838,7 +838,7 @@ fn buy_order_limits_are_reset_correctly() { // should fail if the limits are not set assert_noop!( - Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11), + Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false), Error::::UserOpenOrderUnitsLimtNotFound ); @@ -850,10 +850,10 @@ fn buy_order_limits_are_reset_correctly() { )); // use should be able to purchase below limit - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); // use should be able to create another order if its below limit - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 5, 110),); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 5, 110, false),); // ensure the storage is updated correctly let order_storage_by_user = BuyOrdersByUser::::get(buyer); @@ -912,7 +912,7 @@ fn purchase_is_retired_if_payment_is_stripe() { add_validator_account(validator_two); // create a new buy order - assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); let tx_proof: BoundedVec<_, _> = vec![].try_into().unwrap(); @@ -1013,3 +1013,123 @@ fn purchase_is_retired_if_payment_is_stripe() { assert_eq!(Assets::balance(asset_id, dex_account), 4); }); } + +#[test] +fn set_bank_wire_in_process_should_work() { + new_test_ext().execute_with(|| { + let asset_id = 0; + let seller = 1; + let buyer = 4; + let validator = 10; + let buy_order_id = 0; + + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(seller), asset_id, 1, 100)); + assert_eq!(Assets::balance(asset_id, seller), 100); + + // set fee values + assert_ok!(Dex::force_set_payment_fee(RuntimeOrigin::root(), Percent::from_percent(10))); + assert_ok!(Dex::force_set_purchase_fee(RuntimeOrigin::root(), 10u32.into())); + + // configure limit to avoid failure + assert_ok!(Dex::force_set_open_order_allowed_limits( + RuntimeOrigin::root(), + UserLevel::KYCLevel1, + 1000 + )); + + // should be able to create a sell order + assert_ok!(Dex::create_sell_order(RuntimeOrigin::signed(seller), asset_id, 5, 10)); + + // create a new buy order + assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11, false)); + + let tx_proof: BoundedVec<_, _> = vec![].try_into().unwrap(); + + // non validator cannot validate + assert_noop!( + Dex::validate_buy_order( + RuntimeOrigin::signed(buyer), + buy_order_id, + 0u32, + vec![].try_into().unwrap(), + None + ), + Error::::NotAuthorised + ); + + // validator can validate a payment order + add_validator_account(validator); + // Test: Set the bank wire in process + assert_ok!(Dex::set_bank_wire_in_process(RuntimeOrigin::signed(validator), buy_order_id)); + + // Fetch the updated buy order from storage + let updated_buy_order = BuyOrders::::get(buy_order_id).unwrap(); + + // Calculate the expected expiry time + let expected_expiry_time = updated_buy_order.expiry_time.checked_add(4 * 8).unwrap(); + + // Assert that the expiry time has been correctly extended + assert_eq!(updated_buy_order.expiry_time, expected_expiry_time); + }); +} + +// #[test] +// fn set_bank_wire_in_process_should_work() { +// new_test_ext().execute_with(|| { +// let asset_id = 0; +// let seller = 1; +// let buyer = 4; +// let validator = 10; +// let buy_order_id = 0; + +// // Setup: Create asset, mint to seller, and create buy order +// assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, 1, true, 1)); +// assert_ok!(Assets::mint(RuntimeOrigin::signed(seller), asset_id, 1, 100)); +// assert_eq!(Assets::balance(asset_id, seller), 100); + +// // Set required fees and limits +// assert_ok!(Dex::force_set_payment_fee(RuntimeOrigin::root(), Percent::from_percent(10))); +// assert_ok!(Dex::force_set_purchase_fee(RuntimeOrigin::root(), 10u32.into())); +// assert_ok!(Dex::force_set_open_order_allowed_limits( +// RuntimeOrigin::root(), +// UserLevel::KYCLevel1, +// 1000 +// )); + +// // Seller creates a sell order, and buyer creates a buy order +// assert_ok!(Dex::create_sell_order(RuntimeOrigin::signed(seller), asset_id, 5, 10)); +// assert_ok!(Dex::create_buy_order(RuntimeOrigin::signed(buyer), 0, asset_id, 1, 11)); + +// // Add validator and validate the buy order +// add_validator_account(validator); +// assert_ok!(Dex::validate_buy_order( +// RuntimeOrigin::signed(validator), +// buy_order_id, +// 0u32, +// vec![].try_into().unwrap(), +// None +// )); + +// // Test: Set the bank wire in process +// assert_ok!(Dex::set_bank_wire_in_process(RuntimeOrigin::signed(validator), buy_order_id)); + +// // Fetch the updated buy order from storage +// let updated_buy_order = BuyOrders::::get(buy_order_id).unwrap(); + +// // Calculate the expected expiry time +// let expected_expiry_time = updated_buy_order +// .expiry_time +// .checked_add(&Test::BuyOrderExpiryTime::get().saturating_mul(8_u32.into())) +// .unwrap(); + +// // Assert that the expiry time has been correctly extended +// assert_eq!(updated_buy_order.expiry_time, expected_expiry_time); + +// // Assert that the event was emitted correctly +// assert_eq!( +// last_event(), +// Event::BankWireInProcessSet { order_id: buy_order_id }.into() +// ); +// }); +// } diff --git a/runtime/bitgreen/src/lib.rs b/runtime/bitgreen/src/lib.rs index 9e193cf7..d91ed24e 100644 --- a/runtime/bitgreen/src/lib.rs +++ b/runtime/bitgreen/src/lib.rs @@ -172,7 +172,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bitgreen-parachain"), impl_name: create_runtime_str!("bitgreen-parachain"), authoring_version: 1, - spec_version: 1401, // v1.4.1 + spec_version: 1402, // v1.4.2 impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index ec24ab81..850e1df4 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -172,7 +172,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bitgreen-rococo"), impl_name: create_runtime_str!("bitgreen-rococo"), authoring_version: 1, - spec_version: 1401, // v1.4.1 + spec_version: 1402, // v1.4.2 impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2,