From 77cd087eca4f3bc3d44a8b30c6337036fbf58ad2 Mon Sep 17 00:00:00 2001 From: Kazunobu Ndong Date: Thu, 2 Feb 2023 11:54:08 +0900 Subject: [PATCH 1/3] Pallet_payment tests, part 1 --- pallets/payment/src/tests.rs | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/pallets/payment/src/tests.rs b/pallets/payment/src/tests.rs index deedb341..3e1b6d3e 100644 --- a/pallets/payment/src/tests.rs +++ b/pallets/payment/src/tests.rs @@ -363,3 +363,114 @@ fn test_charging_fee_payment_works() { ); }); } + +#[test] +fn test_charging_fee_payment_works_when_canceled() { + new_test_ext().execute_with(|| { + let creator_initial_balance = 100_000_000_000; + let payment_amount = 40; + let expected_incentive_amount = payment_amount / INCENTIVE_PERCENTAGE as u64; + let expected_fee_amount = payment_amount / MARKETPLACE_FEE_PERCENTAGE as u64; + + // should be able to create a payment with available balance + assert_ok!(PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT_FEE_CHARGED, + payment_amount, + None + )); + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT_FEE_CHARGED), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::Created, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, expected_fee_amount)), + }) + ); + // the payment amount should be reserved + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR), + creator_initial_balance - payment_amount - expected_fee_amount - expected_incentive_amount + ); + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT_FEE_CHARGED), 1); + + // should succeed for valid payment + assert_ok!(PaymentModule::cancel( + Origin::signed(PAYMENT_RECIPENT_FEE_CHARGED), + PAYMENT_CREATOR + )); + // the payment amount should be transferred + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR), + creator_initial_balance + ); + assert_eq!(Balances::free_balance(&PAYMENT_CREATOR).saturating_add(Balances::reserved_balance(&PAYMENT_CREATOR)), 100_000_000_000); + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT_FEE_CHARGED), 1); + assert_eq!(Balances::free_balance(&FEE_RECIPIENT_ACCOUNT), 1); + }); +} + + +#[test] +fn test_pay_with_remark_works() { + new_test_ext().execute_with(|| { + let creator_initial_balance = 100_000_000_000; + let payment_amount = 40; + let expected_incentive_amount = payment_amount / INCENTIVE_PERCENTAGE as u64; + + // should be able to create a payment with available balance + assert_ok!(PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + Some(vec![1u8; 10].try_into().unwrap()) + )); + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::Created, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, 0)), + }) + ); + // the payment amount should be reserved correctly + // the amount + incentive should be removed from the sender account + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR), + creator_initial_balance - payment_amount - expected_incentive_amount + ); + // the incentive amount should be reserved in the sender account + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR).saturating_add(Balances::reserved_balance(&PAYMENT_CREATOR)), + creator_initial_balance - payment_amount + ); + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT), 1); + // the transferred amount should be reserved in the recipent account + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT).saturating_add(Balances::reserved_balance(&PAYMENT_RECIPENT)), Balances::free_balance(&PAYMENT_RECIPENT).saturating_add(payment_amount)); + + // the payment should not be overwritten + assert_noop!( + PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + None + ), + crate::Error::::PaymentAlreadyInProcess + ); + + assert_eq!( + last_event(), + crate::Event::::PaymentCreated { + from: PAYMENT_CREATOR, + amount: payment_amount, + remark: Some(vec![1u8; 10].try_into().unwrap()) + } + .into() + ); + }); +} From 3c3ac02e73ceeef8ca1d3f2807cc34164215a683 Mon Sep 17 00:00:00 2001 From: Kazunobu Ndong Date: Thu, 2 Feb 2023 12:07:38 +0900 Subject: [PATCH 2/3] pallet_payment test, part 2 --- pallets/payment/src/tests.rs | 317 +++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) diff --git a/pallets/payment/src/tests.rs b/pallets/payment/src/tests.rs index 3e1b6d3e..f1b59afd 100644 --- a/pallets/payment/src/tests.rs +++ b/pallets/payment/src/tests.rs @@ -474,3 +474,320 @@ fn test_pay_with_remark_works() { ); }); } + + +#[test] +fn test_do_not_overwrite_logic_works() { + new_test_ext().execute_with(|| { + let payment_amount = 40; + let expected_incentive_amount = payment_amount / INCENTIVE_PERCENTAGE as u64; + + assert_ok!(PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + None + )); + + assert_noop!( + PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + None + ), + crate::Error::::PaymentAlreadyInProcess + ); + + // set payment state to NeedsReview + PaymentStore::::insert( + PAYMENT_CREATOR, + PAYMENT_RECIPENT, + PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::NeedsReview, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, 0)), + }, + ); + + // the payment should not be overwritten + assert_noop!( + PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + None + ), + crate::Error::::PaymentAlreadyInProcess + ); + }); +} + +#[test] +fn test_request_refund() { + new_test_ext().execute_with(|| { + let payment_amount = 20; + let expected_incentive_amount = payment_amount / INCENTIVE_PERCENTAGE as u64; + let expected_cancel_block = CANCEL_BLOCK_BUFFER + 1; + + assert_ok!(PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + None + )); + + assert_ok!(PaymentModule::request_refund( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT + )); + + // do not overwrite payment + assert_noop!( + PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + None + ), + crate::Error::::PaymentAlreadyInProcess + ); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::RefundRequested { + cancel_block: expected_cancel_block + }, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, 0)), + }) + ); + + assert_eq!( + last_event(), + crate::Event::::PaymentCreatorRequestedRefund { + from: PAYMENT_CREATOR, + to: PAYMENT_RECIPENT, + expiry: expected_cancel_block + } + .into() + ); + }); +} + +#[test] +fn test_dispute_refund() { + new_test_ext().execute_with(|| { + let payment_amount = 20; + let expected_incentive_amount = payment_amount / INCENTIVE_PERCENTAGE as u64; + let expected_cancel_block = CANCEL_BLOCK_BUFFER + 1; + + assert_ok!(PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + payment_amount, + None + )); + + // cannot dispute if refund is not requested + assert_noop!( + PaymentModule::dispute_refund(Origin::signed(PAYMENT_RECIPENT), PAYMENT_CREATOR), + Error::InvalidAction + ); + // creator requests a refund + assert_ok!(PaymentModule::request_refund( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT + )); + // ensure the request is added to the refund queue + let scheduled_tasks_list = ScheduledTasks::::get(); + assert_eq!( + scheduled_tasks_list.get(&(PAYMENT_CREATOR, PAYMENT_RECIPENT)).unwrap(), + &ScheduledTask { + task: Task::Cancel, + when: expected_cancel_block + } + ); + + // recipient disputes the refund request + assert_ok!(PaymentModule::dispute_refund( + Origin::signed(PAYMENT_RECIPENT), + PAYMENT_CREATOR + )); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::NeedsReview, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, 0)), + }) + ); + + assert_eq!( + last_event(), + crate::Event::::PaymentRefundDisputed { + from: PAYMENT_CREATOR, + to: PAYMENT_RECIPENT, + } + .into() + ); + + // ensure the request is removed from the refund queue + let scheduled_tasks_list = ScheduledTasks::::get(); + assert_eq!(scheduled_tasks_list.get(&(PAYMENT_CREATOR, PAYMENT_RECIPENT)), None); + }); +} + + +#[test] +fn test_request_payment() { + new_test_ext().execute_with(|| { + let payment_amount = 20; + let expected_incentive_amount = 0; + + assert_ok!(PaymentModule::request_payment( + Origin::signed(PAYMENT_RECIPENT), + PAYMENT_CREATOR, + payment_amount, + )); + + assert_noop!( + PaymentModule::request_refund(Origin::signed(PAYMENT_CREATOR), PAYMENT_RECIPENT), + crate::Error::::InvalidAction + ); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::PaymentRequested, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, 0)), + }) + ); + + assert_eq!( + last_event(), + crate::Event::::PaymentRequestCreated { + from: PAYMENT_CREATOR, + to: PAYMENT_RECIPENT, + } + .into() + ); + }); +} + +#[test] +fn test_requested_payment_cannot_be_released() { + new_test_ext().execute_with(|| { + let payment_amount = 20; + + assert_ok!(PaymentModule::request_payment( + Origin::signed(PAYMENT_RECIPENT), + PAYMENT_CREATOR, + payment_amount, + )); + + // requested payment cannot be released + assert_noop!( + PaymentModule::release(Origin::signed(PAYMENT_CREATOR), PAYMENT_RECIPENT), + Error::InvalidAction + ); + }); +} + +#[test] +fn test_requested_payment_can_be_cancelled_by_requestor() { + new_test_ext().execute_with(|| { + let payment_amount = 20; + + assert_ok!(PaymentModule::request_payment( + Origin::signed(PAYMENT_RECIPENT), + PAYMENT_CREATOR, + payment_amount, + )); + + assert_ok!(PaymentModule::cancel(Origin::signed(PAYMENT_RECIPENT), PAYMENT_CREATOR)); + + // the request should be removed from storage + assert_eq!(PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), None); + }); +} + +#[test] +fn test_accept_and_pay() { + new_test_ext().execute_with(|| { + let creator_initial_balance = 100_000_000_000; + let payment_amount = 20; + let expected_incentive_amount = 0; + let recipient_initial_balance = 1; + + assert_ok!(PaymentModule::request_payment( + Origin::signed(PAYMENT_RECIPENT), + PAYMENT_CREATOR, + payment_amount, + )); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::PaymentRequested, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, 0)), + }) + ); + + assert_ok!(PaymentModule::accept_and_pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + )); + + // the payment amount should be transferred + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR), + creator_initial_balance - payment_amount + ); + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT), payment_amount.saturating_add(recipient_initial_balance)); + + // should be deleted from storage + assert_eq!(PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), None); + + assert_eq!( + last_event(), + crate::Event::::PaymentRequestCompleted { + from: PAYMENT_CREATOR, + to: PAYMENT_RECIPENT, + } + .into() + ); + }); +} + +#[test] +fn test_accept_and_pay_should_fail_for_non_payment_requested() { + new_test_ext().execute_with(|| { + assert_ok!(PaymentModule::pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT, + 20, + None + )); + + assert_noop!( + PaymentModule::accept_and_pay(Origin::signed(PAYMENT_CREATOR), PAYMENT_RECIPENT,), + Error::InvalidAction + ); + }); +} + From da729c5fe8d7c33b96d1f2e206a9e3c2becdab5b Mon Sep 17 00:00:00 2001 From: Kazunobu Ndong Date: Thu, 2 Feb 2023 14:17:16 +0900 Subject: [PATCH 3/3] pallet_payment test, part 3 --- pallets/payment/src/tests.rs | 214 +++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/pallets/payment/src/tests.rs b/pallets/payment/src/tests.rs index f1b59afd..e5f19976 100644 --- a/pallets/payment/src/tests.rs +++ b/pallets/payment/src/tests.rs @@ -791,3 +791,217 @@ fn test_accept_and_pay_should_fail_for_non_payment_requested() { }); } +#[test] +fn test_accept_and_pay_should_charge_fee_correctly() { + new_test_ext().execute_with(|| { + let creator_initial_balance = 100_000_000_000; + let recipient_initial_balance = 1; + let payment_amount = 20; + let expected_incentive_amount = 0; + let expected_fee_amount = payment_amount / MARKETPLACE_FEE_PERCENTAGE as u64; + + assert_ok!(PaymentModule::request_payment( + Origin::signed(PAYMENT_RECIPENT_FEE_CHARGED), + PAYMENT_CREATOR, + payment_amount, + )); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT_FEE_CHARGED), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::PaymentRequested, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, expected_fee_amount)), + }) + ); + + assert_ok!(PaymentModule::accept_and_pay( + Origin::signed(PAYMENT_CREATOR), + PAYMENT_RECIPENT_FEE_CHARGED, + )); + + // the payment amount should be transferred + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR), + creator_initial_balance - payment_amount - expected_fee_amount + ); + assert_eq!( + Balances::free_balance(&PAYMENT_RECIPENT_FEE_CHARGED), + payment_amount.saturating_add(recipient_initial_balance) + ); + assert_eq!( + Balances::free_balance(&FEE_RECIPIENT_ACCOUNT), + expected_fee_amount.saturating_add(recipient_initial_balance) + ); + + // should be deleted from storage + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT_FEE_CHARGED), + None + ); + + assert_eq!( + last_event(), + crate::Event::::PaymentRequestCompleted { + from: PAYMENT_CREATOR, + to: PAYMENT_RECIPENT_FEE_CHARGED, + } + .into() + ); + }); +} + +#[test] +fn test_create_payment_works() { + new_test_ext().execute_with(|| { + let creator_initial_balance = 100_000_000_000; + let payment_amount = 20; + let expected_incentive_amount = payment_amount / INCENTIVE_PERCENTAGE as u64; + let expected_fee_amount = 0; + + // the payment amount should not be reserved + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR), + creator_initial_balance + ); + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT), 1); + + // should be able to create a payment with available balance within a + // transaction + assert_ok!(with_transaction(|| TransactionOutcome::Commit({ + >::create_payment( + &PAYMENT_CREATOR, + &PAYMENT_RECIPENT, + payment_amount, + PaymentState::Created, + Percent::from_percent(INCENTIVE_PERCENTAGE), + Some(&[1u8; 10]), + ) + }))); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::Created, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, expected_fee_amount)), + }) + ); + + // the payment should not be overwritten + assert_noop!( + with_transaction(|| TransactionOutcome::Commit({ + >::create_payment( + &PAYMENT_CREATOR, + &PAYMENT_RECIPENT, + payment_amount, + PaymentState::Created, + Percent::from_percent(INCENTIVE_PERCENTAGE), + Some(&[1u8; 10]), + ) + })), + Error::PaymentAlreadyInProcess + ); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::Created, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, expected_fee_amount)), + }) + ); + }); +} + +#[test] +fn test_reserve_payment_amount_works() { + new_test_ext().execute_with(|| { + let creator_initial_balance = 100_000_000_000; + let payment_amount = 20; + let expected_incentive_amount = payment_amount / INCENTIVE_PERCENTAGE as u64; + let expected_fee_amount = 0; + + // the payment amount should not be reserved + assert_eq!(Balances::free_balance(&PAYMENT_CREATOR), 100_000_000_000); + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT), 1); + + // should be able to create a payment with available balance within a + // transaction + assert_ok!(with_transaction(|| TransactionOutcome::Commit({ + >::create_payment( + &PAYMENT_CREATOR, + &PAYMENT_RECIPENT, + payment_amount, + PaymentState::Created, + Percent::from_percent(INCENTIVE_PERCENTAGE), + Some(&[1u8; 10]), + ) + }))); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::Created, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, expected_fee_amount)), + }) + ); + + assert_ok!(with_transaction(|| TransactionOutcome::Commit({ + >::reserve_payment_amount( + &PAYMENT_CREATOR, + &PAYMENT_RECIPENT, + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT).unwrap(), + ) + }))); + // the payment amount should be reserved correctly + // the amount + incentive should be removed from the sender account + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR), + creator_initial_balance - payment_amount - expected_incentive_amount + ); + // the incentive amount should be reserved in the sender account + assert_eq!( + Balances::free_balance(&PAYMENT_CREATOR).saturating_add(Balances::reserved_balance(&PAYMENT_CREATOR)), + creator_initial_balance - payment_amount + ); + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT), 1); + // the transferred amount should be reserved in the recipent account + assert_eq!(Balances::free_balance(&PAYMENT_RECIPENT).saturating_add(Balances::reserved_balance(&PAYMENT_RECIPENT)), payment_amount.saturating_add(Balances::free_balance(&PAYMENT_RECIPENT))); + + // the payment should not be overwritten + assert_noop!( + with_transaction(|| TransactionOutcome::Commit({ + >::create_payment( + &PAYMENT_CREATOR, + &PAYMENT_RECIPENT, + payment_amount, + PaymentState::Created, + Percent::from_percent(INCENTIVE_PERCENTAGE), + Some(&[1u8; 10]), + ) + })), + Error::PaymentAlreadyInProcess + ); + + assert_eq!( + PaymentStore::::get(PAYMENT_CREATOR, PAYMENT_RECIPENT), + Some(PaymentDetail { + amount: payment_amount, + incentive_amount: expected_incentive_amount, + state: PaymentState::Created, + resolver_account: RESOLVER_ACCOUNT, + fee_detail: Some((FEE_RECIPIENT_ACCOUNT, expected_fee_amount)), + }) + ); + }); +} \ No newline at end of file