diff --git a/pallets/marketplace/src/lib.rs b/pallets/marketplace/src/lib.rs index 2cd33fd3..616402f5 100644 --- a/pallets/marketplace/src/lib.rs +++ b/pallets/marketplace/src/lib.rs @@ -744,7 +744,8 @@ pub mod pallet { match requirements.instant_match { Some(sources) => { - Self::process_matching(once(&Match { + // ignore remaining rewards; do not pay out the matcher which is the same as the one registering + let _ = Self::process_matching(once(&Match { job_id: job_id.clone(), sources, }))?; diff --git a/pallets/marketplace/src/mock.rs b/pallets/marketplace/src/mock.rs index 18ea9886..d4e5fdd9 100644 --- a/pallets/marketplace/src/mock.rs +++ b/pallets/marketplace/src/mock.rs @@ -266,16 +266,22 @@ impl> RewardManager } fn pay_matcher_reward( - rewards: Vec<(JobId, ::Balance)>, + remaining_rewards: Vec<(JobId, ::Balance)>, matcher: &T::AccountId, ) -> Result<(), DispatchError> { mock_pallet::Pallet::deposit_event(mock_pallet::Event::::PayMatcherReward(( - rewards.clone(), + remaining_rewards.clone(), matcher.clone(), ))); - for (job_id, remaining_reward) in rewards.into_iter() { - Budget::unreserve(&job_id, remaining_reward).unwrap(); + + let mut matcher_reward: T::Balance = 0u8.into(); + for (job_id, remaining_reward) in remaining_rewards.into_iter() { + let matcher_fee = FeeManagerImpl::get_matcher_percentage().mul_floor(remaining_reward); + Budget::unreserve(&job_id, matcher_fee) + .map_err(|_| DispatchError::Other("Severe Error: JobBudget::unreserve failed"))?; + matcher_reward += matcher_fee; } + Ok(()) } diff --git a/pallets/marketplace/src/payments.rs b/pallets/marketplace/src/payments.rs index 3d89a7c5..16c8b1cb 100644 --- a/pallets/marketplace/src/payments.rs +++ b/pallets/marketplace/src/payments.rs @@ -28,7 +28,7 @@ pub trait RewardManager { target: &T::AccountId, ) -> Result<(), DispatchError>; fn pay_matcher_reward( - rewards: Vec<(JobId, ::Balance)>, + remaining_rewards: Vec<(JobId, ::Balance)>, matcher: &T::AccountId, ) -> Result<(), DispatchError>; fn refund(job_id: &JobId) -> T::Balance; @@ -51,7 +51,7 @@ impl RewardManager for () { } fn pay_matcher_reward( - _rewards: Vec<(JobId, ::Balance)>, + _remaining_rewards: Vec<(JobId, ::Balance)>, _matcher: &T::AccountId, ) -> Result<(), DispatchError> { Ok(()) @@ -157,26 +157,27 @@ where } fn pay_matcher_reward( - rewards: Vec<(JobId, T::Balance)>, + remaining_rewards: Vec<(JobId, T::Balance)>, matcher: &T::AccountId, ) -> Result<(), DispatchError> { let matcher_fee_percentage = AssetSplit::get_matcher_percentage(); // TODO: fee will be indexed by version in the future - let mut reward: T::Balance = 0u8.into(); - for (job_id, remaining_reward) in rewards.into_iter() { - Budget::unreserve(&job_id, remaining_reward) + let mut matcher_reward: T::Balance = 0u8.into(); + for (job_id, remaining_reward) in remaining_rewards.into_iter() { + let matcher_fee = matcher_fee_percentage.mul_floor(remaining_reward); + Budget::unreserve(&job_id, matcher_fee) .map_err(|_| DispatchError::Other("Severe Error: JobBudget::unreserve failed"))?; - reward += matcher_fee_percentage.mul_floor(remaining_reward); + matcher_reward += matcher_fee; } let pallet_account: T::AccountId = ::PalletId::get().into_account_truncating(); // Extract fee from the matcher reward let fee_percentage = AssetSplit::get_fee_percentage(); // TODO: fee will be indexed by version in the future - let fee = fee_percentage.mul_floor(reward); + let fee = fee_percentage.mul_floor(matcher_reward); // Subtract the fee from the reward - let reward_after_fee = reward - fee; + let reward_after_fee = matcher_reward - fee; // Transfer fees to Acurast fees manager account let fee_pallet_account: T::AccountId = AssetSplit::pallet_id().into_account_truncating(); diff --git a/pallets/marketplace/src/tests.rs b/pallets/marketplace/src/tests.rs index b4a84913..2f2db4c3 100644 --- a/pallets/marketplace/src/tests.rs +++ b/pallets/marketplace/src/tests.rs @@ -1,14 +1,15 @@ #![cfg(test)] use frame_support::{assert_err, assert_ok, traits::Hooks}; -use pallet_acurast::MultiOrigin; use sp_runtime::Permill; +use pallet_acurast::MultiOrigin; use pallet_acurast::{ utils::validate_and_extract_attestation, JobModules, JobRegistrationFor, Schedule, }; use reputation::{BetaReputation, ReputationEngine}; +use crate::payments::JobBudget; use crate::{ mock::*, AdvertisementRestriction, Assignment, Error, ExecutionResult, JobStatus, Match, SLA, }; @@ -99,14 +100,19 @@ fn test_match() { AcurastMarketplace::stored_advertisement_pricing(processor_account_id()) ); + let job_id1 = (MultiOrigin::Acurast(alice_account_id()), initial_job_id + 1); + let job_id2 = (MultiOrigin::Acurast(alice_account_id()), initial_job_id + 2); + assert_ok!(Acurast::register( RuntimeOrigin::signed(alice_account_id()).into(), registration1.clone(), )); + assert_eq!(12_000_000, AcurastMarketplace::reserved(&job_id1)); assert_ok!(Acurast::register( RuntimeOrigin::signed(alice_account_id()).into(), registration2.clone(), )); + assert_eq!(12_000_000, AcurastMarketplace::reserved(&job_id2)); assert_eq!( Some(JobStatus::Open), AcurastMarketplace::stored_job_status( @@ -119,7 +125,6 @@ fn test_match() { AcurastMarketplace::stored_storage_capacity(processor_account_id()) ); - let job_id1 = (MultiOrigin::Acurast(alice_account_id()), initial_job_id + 1); let job_match1 = Match { job_id: job_id1.clone(), sources: vec![PlannedExecution { @@ -127,7 +132,6 @@ fn test_match() { start_delay: 0, }], }; - let job_id2 = (MultiOrigin::Acurast(alice_account_id()), initial_job_id + 2); let job_match2 = Match { job_id: job_id2.clone(), sources: vec![PlannedExecution { @@ -150,6 +154,9 @@ fn test_match() { Some(60_000), AcurastMarketplace::stored_storage_capacity(processor_account_id()) ); + // matcher got payed out already so job budget decreased + assert_eq!(11804000, AcurastMarketplace::reserved(&job_id1)); + assert_eq!(11804000, AcurastMarketplace::reserved(&job_id2)); assert_ok!(AcurastMarketplace::acknowledge_match( RuntimeOrigin::signed(processor_account_id()).into(), @@ -171,6 +178,8 @@ fn test_match() { job_id1.clone(), ExecutionResult::Success(operation_hash()) )); + // job budget decreased by reward worth one execution + assert_eq!(6784000, AcurastMarketplace::reserved(&job_id1)); // average reward only updated at end of job assert_eq!(None, AcurastMarketplace::average_reward()); // reputation still ~50% @@ -211,11 +220,15 @@ fn test_match() { job_id1.clone(), ExecutionResult::Success(operation_hash()) )); + // job budget decreased by reward worth one execution + assert_eq!(1764000, AcurastMarketplace::reserved(&job_id1)); // pretend time moved on later(registration1.schedule.end_time + 1); assert_eq!(4, System::block_number()); + assert_eq!(1764000, AcurastMarketplace::reserved(&job_id1)); + assert_ok!(AcurastMarketplace::finalize_job( RuntimeOrigin::signed(processor_account_id()).into(), job_id1.clone() @@ -257,6 +270,10 @@ fn test_match() { None, AcurastMarketplace::stored_job_status(&job_id1.0, &job_id1.1), ); + // the remaining budget got refunded + assert_eq!(0, AcurastMarketplace::reserved(&job_id1)); + // but job2 still have full budget + assert_eq!(11804000, AcurastMarketplace::reserved(&job_id2)); assert_eq!( events(), @@ -346,7 +363,10 @@ fn test_match() { } )), RuntimeEvent::AcurastMarketplace(crate::Event::JobFinalized(job_id1.clone())), - RuntimeEvent::MockPallet(mock_pallet::Event::RefundReward((job_id1.clone(), 0,))), + RuntimeEvent::MockPallet(mock_pallet::Event::RefundReward(( + job_id1.clone(), + 1764000 + ))), RuntimeEvent::AcurastMarketplace(crate::Event::JobFinalized(job_id1.clone(),)), ] );