Skip to content

Commit

Permalink
Add dedicated Sender and Beneficiary origins + reorder payments extri…
Browse files Browse the repository at this point in the history
…nsics by sender/beneficiary/resolver
  • Loading branch information
olanod committed Feb 15, 2024
1 parent ac58bf4 commit 2321b5f
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 90 deletions.
181 changes: 92 additions & 89 deletions pallets/payments/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ pub mod pallet {

type FeeHandler: FeeHandler<Self>;

type SenderOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;

type BeneficiaryOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;

type DisputeResolver: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;

type PaymentId: PaymentId<Self> + Member + Parameter + MaxEncodedLen;
Expand Down Expand Up @@ -266,7 +270,7 @@ pub mod pallet {
#[pallet::compact] amount: BalanceOf<T>,
remark: Option<BoundedDataOf<T>>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let sender = T::SenderOrigin::ensure_origin(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?;

// create PaymentDetail and add to storage
Expand Down Expand Up @@ -297,7 +301,7 @@ pub mod pallet {
#[pallet::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::release())]
pub fn release(origin: OriginFor<T>, payment_id: T::PaymentId) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let sender = T::SenderOrigin::ensure_origin(origin)?;

// ensure the payment is in Created state
let payment = Payment::<T>::get(&sender, &payment_id).map_err(|_| Error::<T>::InvalidPayment)?;
Expand All @@ -308,43 +312,13 @@ pub mod pallet {
Ok(().into())
}

/// Cancel a payment in created state, this will release the reserved
/// back to creator of the payment. This extrinsic can only be called by
/// the recipient of the payment
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::cancel())]
pub fn cancel(origin: OriginFor<T>, payment_id: T::PaymentId) -> DispatchResultWithPostInfo {
let beneficiary = ensure_signed(origin)?;
let (sender, b) = PaymentParties::<T>::get(&payment_id)?;
ensure!(beneficiary == b, Error::<T>::InvalidBeneficiary);

let payment = Payment::<T>::get(&sender, &payment_id).map_err(|_| Error::<T>::InvalidPayment)?;

match payment.state {
PaymentState::Created => {
Self::cancel_payment(&sender, payment)?;
Self::deposit_event(Event::PaymentCancelled { payment_id });
}
PaymentState::RefundRequested { cancel_block: _ } => {
Self::cancel_payment(&sender, payment)?;
Self::deposit_event(Event::PaymentRefunded { payment_id });
}
_ => fail!(Error::<T>::InvalidAction),
}

Payment::<T>::remove(&sender, &payment_id);
PaymentParties::<T>::remove(payment_id);

Ok(().into())
}

/// Allow the creator of a payment to initiate a refund that will return
/// the funds after a configured amount of time that the receiver has to
/// react and opose the request
#[pallet::call_index(3)]
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::request_refund())]
pub fn request_refund(origin: OriginFor<T>, payment_id: T::PaymentId) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin.clone())?;
let sender = T::SenderOrigin::ensure_origin(origin)?;

let expiry = Payment::<T>::try_mutate(&sender, &payment_id, |maybe_payment| -> Result<_, DispatchError> {
// ensure the payment exists
Expand Down Expand Up @@ -378,15 +352,79 @@ pub mod pallet {
Ok(().into())
}

#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::accept_and_pay())]
pub fn accept_and_pay(origin: OriginFor<T>, payment_id: T::PaymentId) -> DispatchResultWithPostInfo {
let sender = T::SenderOrigin::ensure_origin(origin)?;
let (_, beneficiary) = PaymentParties::<T>::get(&payment_id)?;

Payment::<T>::try_mutate(&sender, payment_id, |maybe_payment| -> Result<_, DispatchError> {
let payment = maybe_payment.as_mut().map_err(|_| Error::<T>::InvalidPayment)?;
const IS_DISPUTE: bool = false;

// Release sender fees recipients
let (fee_sender_recipients, _total_sender_fee_amount_mandatory, _total_sender_fee_amount_optional) =
payment.fees.summary_for(Role::Sender, IS_DISPUTE)?;

let (
fee_beneficiary_recipients,
_total_beneficiary_fee_amount_mandatory,
_total_beneficiary_fee_amount_optional,
) = payment.fees.summary_for(Role::Beneficiary, IS_DISPUTE)?;

Self::try_transfer_fees(&sender, payment, fee_sender_recipients, IS_DISPUTE)?;

T::Assets::transfer(payment.asset.clone(), &sender, &beneficiary, payment.amount, Expendable)
.map_err(|_| Error::<T>::TransferFailed)?;

Self::try_transfer_fees(&beneficiary, payment, fee_beneficiary_recipients, IS_DISPUTE)?;

payment.state = PaymentState::Finished;
Ok(())
})?;

Self::deposit_event(Event::PaymentRequestCreated { payment_id });
Ok(().into())
}

/// Cancel a payment in created state, this will release the reserved
/// back to creator of the payment. This extrinsic can only be called by
/// the recipient of the payment
#[pallet::call_index(10)]
#[pallet::weight(<T as Config>::WeightInfo::cancel())]
pub fn cancel(origin: OriginFor<T>, payment_id: T::PaymentId) -> DispatchResultWithPostInfo {
let beneficiary = T::BeneficiaryOrigin::ensure_origin(origin)?;
let (sender, b) = PaymentParties::<T>::get(&payment_id)?;
ensure!(beneficiary == b, Error::<T>::InvalidBeneficiary);

let payment = Payment::<T>::get(&sender, &payment_id).map_err(|_| Error::<T>::InvalidPayment)?;

match payment.state {
PaymentState::Created => {
Self::cancel_payment(&sender, payment)?;
Self::deposit_event(Event::PaymentCancelled { payment_id });
}
PaymentState::RefundRequested { cancel_block: _ } => {
Self::cancel_payment(&sender, payment)?;
Self::deposit_event(Event::PaymentRefunded { payment_id });
}
_ => fail!(Error::<T>::InvalidAction),
}

Payment::<T>::remove(&sender, &payment_id);
PaymentParties::<T>::remove(payment_id);

Ok(().into())
}

/// Allow payment beneficiary to dispute the refund request from the
/// payment creator This does not cancel the request, instead sends the
/// payment to a NeedsReview state The assigned resolver account can
/// then change the state of the payment after review.
#[pallet::call_index(4)]
#[pallet::call_index(11)]
#[pallet::weight(<T as Config>::WeightInfo::dispute_refund())]
pub fn dispute_refund(origin: OriginFor<T>, payment_id: T::PaymentId) -> DispatchResultWithPostInfo {
use PaymentState::*;
let beneficiary = ensure_signed(origin)?;
let beneficiary = T::BeneficiaryOrigin::ensure_origin(origin)?;
let (sender, b) = PaymentParties::<T>::get(&payment_id)?;
ensure!(beneficiary == b, Error::<T>::InvalidBeneficiary);

Expand All @@ -395,7 +433,7 @@ pub mod pallet {
let payment = maybe_payment.as_mut().map_err(|_| Error::<T>::InvalidPayment)?;

// ensure the payment is in Requested Refund state
let RefundRequested { cancel_block } = payment.state else {
let PaymentState::RefundRequested { cancel_block } = payment.state else {
fail!(Error::<T>::InvalidAction);
};
ensure!(
Expand All @@ -417,40 +455,20 @@ pub mod pallet {
Ok(().into())
}

#[pallet::call_index(5)]
#[pallet::weight(<T as Config>::WeightInfo::resolve_dispute())]
pub fn resolve_dispute(
origin: OriginFor<T>,
payment_id: T::PaymentId,
dispute_result: DisputeResult,
) -> DispatchResultWithPostInfo {
let dispute_resolver = T::DisputeResolver::ensure_origin(origin)?;
let (sender, beneficiary) = PaymentParties::<T>::get(&payment_id)?;

let payment = Payment::<T>::get(&sender, &payment_id).map_err(|_| Error::<T>::InvalidPayment)?;
ensure!(payment.state == PaymentState::NeedsReview, Error::<T>::InvalidAction);

let dispute = Some((dispute_result, dispute_resolver));
Self::settle_payment(&sender, &beneficiary, &payment_id, dispute)?;

Self::deposit_event(Event::PaymentDisputeResolved { payment_id });
Ok(().into())
}

// Creates a new payment with the given details. This can be called by the
// recipient of the payment to create a payment and then completed by the sender
// using the `accept_and_pay` extrinsic. The payment will be in
// PaymentRequested State and can only be modified by the `accept_and_pay`
// extrinsic.
#[pallet::call_index(6)]
#[pallet::call_index(12)]
#[pallet::weight(<T as Config>::WeightInfo::request_payment())]
pub fn request_payment(
origin: OriginFor<T>,
sender: AccountIdLookupOf<T>,
asset: AssetIdOf<T>,
#[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let beneficiary = ensure_signed(origin)?;
let beneficiary = T::BeneficiaryOrigin::ensure_origin(origin)?;
let sender = T::Lookup::lookup(sender)?;
// create PaymentDetail and add to storage
let (payment_id, _) = Self::create_payment(
Expand All @@ -468,38 +486,23 @@ pub mod pallet {
Ok(().into())
}

#[pallet::call_index(7)]
#[pallet::weight(<T as Config>::WeightInfo::accept_and_pay())]
pub fn accept_and_pay(origin: OriginFor<T>, payment_id: T::PaymentId) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let (_, beneficiary) = PaymentParties::<T>::get(&payment_id)?;

Payment::<T>::try_mutate(&sender, payment_id, |maybe_payment| -> Result<_, DispatchError> {
let payment = maybe_payment.as_mut().map_err(|_| Error::<T>::InvalidPayment)?;
const IS_DISPUTE: bool = false;

// Release sender fees recipients
let (fee_sender_recipients, _total_sender_fee_amount_mandatory, _total_sender_fee_amount_optional) =
payment.fees.summary_for(Role::Sender, IS_DISPUTE)?;

let (
fee_beneficiary_recipients,
_total_beneficiary_fee_amount_mandatory,
_total_beneficiary_fee_amount_optional,
) = payment.fees.summary_for(Role::Beneficiary, IS_DISPUTE)?;

Self::try_transfer_fees(&sender, payment, fee_sender_recipients, IS_DISPUTE)?;

T::Assets::transfer(payment.asset.clone(), &sender, &beneficiary, payment.amount, Expendable)
.map_err(|_| Error::<T>::TransferFailed)?;
#[pallet::call_index(20)]
#[pallet::weight(<T as Config>::WeightInfo::resolve_dispute())]
pub fn resolve_dispute(
origin: OriginFor<T>,
payment_id: T::PaymentId,
dispute_result: DisputeResult,
) -> DispatchResultWithPostInfo {
let dispute_resolver = T::DisputeResolver::ensure_origin(origin)?;
let (sender, beneficiary) = PaymentParties::<T>::get(&payment_id)?;

Self::try_transfer_fees(&beneficiary, payment, fee_beneficiary_recipients, IS_DISPUTE)?;
let payment = Payment::<T>::get(&sender, &payment_id).map_err(|_| Error::<T>::InvalidPayment)?;
ensure!(payment.state == PaymentState::NeedsReview, Error::<T>::InvalidAction);

payment.state = PaymentState::Finished;
Ok(())
})?;
let dispute = Some((dispute_result, dispute_resolver));
Self::settle_payment(&sender, &beneficiary, &payment_id, dispute)?;

Self::deposit_event(Event::PaymentRequestCreated { payment_id });
Self::deposit_event(Event::PaymentDisputeResolved { payment_id });
Ok(().into())
}
}
Expand Down
4 changes: 3 additions & 1 deletion pallets/payments/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use frame_support::{
PalletId,
};

use frame_system::EnsureRoot;
use frame_system::{EnsureRoot, EnsureSigned};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_core::H256;
Expand Down Expand Up @@ -250,6 +250,8 @@ impl pallet_payments::Config for Test {
type FeeHandler = MockFeeHandler;
type IncentivePercentage = IncentivePercentage;
type MaxRemarkLength = MaxRemarkLength;
type SenderOrigin = EnsureSigned<AccountId>;
type BeneficiaryOrigin = EnsureSigned<AccountId>;
type DisputeResolver = frame_system::EnsureRootWithSuccess<u64, ConstU64<ROOT_ACCOUNT>>;
type PalletId = PaymentPalletId;
type RuntimeHoldReason = RuntimeHoldReason;
Expand Down
3 changes: 3 additions & 0 deletions runtime/kreivo/src/payments.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use frame_system::EnsureSigned;
use parity_scale_codec::Encode;

parameter_types! {
Expand Down Expand Up @@ -75,6 +76,8 @@ impl pallet_payments::Config for Runtime {
type FeeHandler = KreivoFeeHandler;
type IncentivePercentage = IncentivePercentage;
type MaxRemarkLength = MaxRemarkLength;
type SenderOrigin = EnsureSigned<AccountId>;
type BeneficiaryOrigin = EnsureSigned<AccountId>;
type DisputeResolver = frame_system::EnsureRootWithSuccess<AccountId, TreasuryAccount>;
type PalletId = PaymentPalletId;
type RuntimeHoldReason = RuntimeHoldReason;
Expand Down

0 comments on commit 2321b5f

Please sign in to comment.