Skip to content

Commit 00ca3ff

Browse files
authored
Merge pull request #47 from walterthesmart/fix-issues-5-32
feat(contract): Implement pausable functionality and advanced querying for merchants/invoices
2 parents 56d7637 + 983be76 commit 00ca3ff

9 files changed

Lines changed: 233 additions & 14 deletions

File tree

‎contracts/shade/src/components/invoice.rs‎

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::components::merchant;
22
use crate::errors::ContractError;
33
use crate::events;
4-
use crate::types::{DataKey, Invoice, InvoiceStatus};
5-
use soroban_sdk::{panic_with_error, Address, Env, String};
4+
use crate::types::{DataKey, Invoice, InvoiceFilter, InvoiceStatus};
5+
use soroban_sdk::{panic_with_error, Address, Env, String, Vec};
66

77
pub fn create_invoice(
88
env: &Env,
@@ -71,3 +71,62 @@ pub fn get_invoice(env: &Env, invoice_id: u64) -> Invoice {
7171
.get(&DataKey::Invoice(invoice_id))
7272
.unwrap_or_else(|| panic_with_error!(env, ContractError::InvoiceNotFound))
7373
}
74+
75+
pub fn get_invoices(env: &Env, filter: InvoiceFilter) -> Vec<Invoice> {
76+
let invoice_count: u64 = env
77+
.storage()
78+
.persistent()
79+
.get(&DataKey::InvoiceCount)
80+
.unwrap_or(0);
81+
82+
let mut invoices: Vec<Invoice> = Vec::new(env);
83+
84+
for i in 1..=invoice_count {
85+
if let Some(invoice) = env
86+
.storage()
87+
.persistent()
88+
.get::<_, Invoice>(&DataKey::Invoice(i))
89+
{
90+
let mut matches = true;
91+
92+
if let Some(status) = filter.status {
93+
if invoice.status as u32 != status {
94+
matches = false;
95+
}
96+
}
97+
98+
if let Some(merchant) = &filter.merchant {
99+
if let Some(merchant_id) = env
100+
.storage()
101+
.persistent()
102+
.get::<_, u64>(&DataKey::MerchantId(merchant.clone()))
103+
{
104+
if invoice.merchant_id != merchant_id {
105+
matches = false;
106+
}
107+
} else {
108+
// Merchant not found means no invoices for it
109+
matches = false;
110+
}
111+
}
112+
113+
if let Some(min_amount) = filter.min_amount {
114+
if invoice.amount < min_amount as i128 {
115+
matches = false;
116+
}
117+
}
118+
119+
if let Some(max_amount) = filter.max_amount {
120+
if invoice.amount > max_amount as i128 {
121+
matches = false;
122+
}
123+
}
124+
125+
if matches {
126+
invoices.push_back(invoice);
127+
}
128+
}
129+
}
130+
131+
invoices
132+
}

‎contracts/shade/src/components/merchant.rs‎

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::errors::ContractError;
22
use crate::events;
3-
use crate::types::{DataKey, Merchant};
4-
use soroban_sdk::{panic_with_error, Address, Env};
3+
use crate::types::{DataKey, Merchant, MerchantFilter};
4+
use soroban_sdk::{panic_with_error, Address, Env, Vec};
55

66
pub fn register_merchant(env: &Env, merchant: &Address) {
77
merchant.require_auth();
@@ -74,3 +74,41 @@ pub fn is_merchant(env: &Env, merchant: &Address) -> bool {
7474
.persistent()
7575
.has(&DataKey::MerchantId(merchant.clone()))
7676
}
77+
78+
pub fn get_merchants(env: &Env, filter: MerchantFilter) -> Vec<Merchant> {
79+
let merchant_count: u64 = env
80+
.storage()
81+
.persistent()
82+
.get(&DataKey::MerchantCount)
83+
.unwrap_or(0);
84+
85+
let mut merchants: Vec<Merchant> = Vec::new(env);
86+
87+
for i in 1..=merchant_count {
88+
if let Some(merchant) = env
89+
.storage()
90+
.persistent()
91+
.get::<_, Merchant>(&DataKey::Merchant(i))
92+
{
93+
let mut matches = true;
94+
95+
if let Some(active) = filter.is_active {
96+
if merchant.active != active {
97+
matches = false;
98+
}
99+
}
100+
101+
if let Some(verified) = filter.is_verified {
102+
if merchant.verified != verified {
103+
matches = false;
104+
}
105+
}
106+
107+
if matches {
108+
merchants.push_back(merchant);
109+
}
110+
}
111+
}
112+
113+
merchants
114+
}

‎contracts/shade/src/components/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ pub mod admin;
22
pub mod core;
33
pub mod invoice;
44
pub mod merchant;
5+
pub mod pausable;
56
pub mod reentrancy;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use crate::components::core;
2+
use crate::errors::ContractError;
3+
use crate::events;
4+
use crate::types::DataKey;
5+
use soroban_sdk::{panic_with_error, Address, Env};
6+
7+
pub fn pause(env: &Env, admin: &Address) {
8+
admin.require_auth();
9+
10+
if core::get_admin(env) != admin.clone() {
11+
panic_with_error!(env, ContractError::NotAuthorized);
12+
}
13+
14+
assert_not_paused(env);
15+
16+
env.storage().persistent().set(&DataKey::Paused, &true);
17+
18+
events::publish_contract_paused_event(env, admin.clone(), env.ledger().timestamp());
19+
}
20+
21+
pub fn unpause(env: &Env, admin: &Address) {
22+
admin.require_auth();
23+
24+
if core::get_admin(env) != admin.clone() {
25+
panic_with_error!(env, ContractError::NotAuthorized);
26+
}
27+
28+
assert_paused(env);
29+
30+
env.storage().persistent().set(&DataKey::Paused, &false);
31+
32+
events::publish_contract_unpaused_event(env, admin.clone(), env.ledger().timestamp());
33+
}
34+
35+
pub fn is_paused(env: &Env) -> bool {
36+
env.storage()
37+
.persistent()
38+
.get(&DataKey::Paused)
39+
.unwrap_or(false)
40+
}
41+
42+
pub fn assert_paused(env: &Env) {
43+
if !is_paused(env) {
44+
panic_with_error!(env, ContractError::ContractNotPaused);
45+
}
46+
}
47+
48+
pub fn assert_not_paused(env: &Env) {
49+
if is_paused(env) {
50+
panic_with_error!(env, ContractError::ContractPaused);
51+
}
52+
}

‎contracts/shade/src/errors.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ pub enum ContractError {
1212
MerchantNotFound = 6,
1313
InvalidAmount = 7,
1414
InvoiceNotFound = 8,
15+
ContractPaused = 9,
16+
ContractNotPaused = 10,
1517
}

‎contracts/shade/src/events.rs‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,23 @@ pub fn publish_invoice_created_event(
7474
}
7575
.publish(env);
7676
}
77+
78+
#[contractevent]
79+
pub struct ContractPausedEvent {
80+
pub admin: Address,
81+
pub timestamp: u64,
82+
}
83+
84+
pub fn publish_contract_paused_event(env: &Env, admin: Address, timestamp: u64) {
85+
ContractPausedEvent { admin, timestamp }.publish(env);
86+
}
87+
88+
#[contractevent]
89+
pub struct ContractUnpausedEvent {
90+
pub admin: Address,
91+
pub timestamp: u64,
92+
}
93+
94+
pub fn publish_contract_unpaused_event(env: &Env, admin: Address, timestamp: u64) {
95+
ContractUnpausedEvent { admin, timestamp }.publish(env);
96+
}

‎contracts/shade/src/interface.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::types::{Invoice, Merchant};
2-
use soroban_sdk::{contracttrait, Address, Env, String};
1+
use crate::types::{Invoice, InvoiceFilter, Merchant, MerchantFilter};
2+
use soroban_sdk::{contracttrait, Address, Env, String, Vec};
33

44
#[contracttrait]
55
pub trait ShadeTrait {
@@ -10,6 +10,7 @@ pub trait ShadeTrait {
1010
fn is_accepted_token(env: Env, token: Address) -> bool;
1111
fn register_merchant(env: Env, merchant: Address);
1212
fn get_merchant(env: Env, merchant_id: u64) -> Merchant;
13+
fn get_merchants(env: Env, filter: MerchantFilter) -> Vec<Merchant>;
1314
fn is_merchant(env: Env, merchant: Address) -> bool;
1415
fn create_invoice(
1516
env: Env,
@@ -19,4 +20,9 @@ pub trait ShadeTrait {
1920
token: Address,
2021
) -> u64;
2122
fn get_invoice(env: Env, invoice_id: u64) -> Invoice;
23+
fn get_invoices(env: Env, filter: InvoiceFilter) -> Vec<Invoice>;
24+
25+
fn pause(env: Env, admin: Address);
26+
fn unpause(env: Env, admin: Address);
27+
fn is_paused(env: Env) -> bool;
2228
}

‎contracts/shade/src/shade.rs‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use crate::components::{
22
admin as admin_component, core as core_component, invoice as invoice_component,
3-
merchant as merchant_component,
3+
merchant as merchant_component, pausable as pausable_component,
44
};
55
use crate::errors::ContractError;
66
use crate::events;
77
use crate::interface::ShadeTrait;
8-
use crate::types::{ContractInfo, DataKey, Invoice, Merchant};
9-
use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Env, String};
8+
use crate::types::{ContractInfo, DataKey, Invoice, InvoiceFilter, Merchant, MerchantFilter};
9+
use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Env, String, Vec};
1010

1111
#[contract]
1212
pub struct Shade;
@@ -32,10 +32,12 @@ impl ShadeTrait for Shade {
3232
}
3333

3434
fn add_accepted_token(env: Env, admin: Address, token: Address) {
35+
pausable_component::assert_not_paused(&env);
3536
admin_component::add_accepted_token(&env, &admin, &token);
3637
}
3738

3839
fn remove_accepted_token(env: Env, admin: Address, token: Address) {
40+
pausable_component::assert_not_paused(&env);
3941
admin_component::remove_accepted_token(&env, &admin, &token);
4042
}
4143

@@ -44,13 +46,18 @@ impl ShadeTrait for Shade {
4446
}
4547

4648
fn register_merchant(env: Env, merchant: Address) {
49+
pausable_component::assert_not_paused(&env);
4750
merchant_component::register_merchant(&env, &merchant);
4851
}
4952

5053
fn get_merchant(env: Env, merchant_id: u64) -> Merchant {
5154
merchant_component::get_merchant(&env, merchant_id)
5255
}
5356

57+
fn get_merchants(env: Env, filter: MerchantFilter) -> Vec<Merchant> {
58+
merchant_component::get_merchants(&env, filter)
59+
}
60+
5461
fn is_merchant(env: Env, merchant: Address) -> bool {
5562
merchant_component::is_merchant(&env, &merchant)
5663
}
@@ -62,10 +69,27 @@ impl ShadeTrait for Shade {
6269
amount: i128,
6370
token: Address,
6471
) -> u64 {
72+
pausable_component::assert_not_paused(&env);
6573
invoice_component::create_invoice(&env, &merchant, &description, amount, &token)
6674
}
6775

6876
fn get_invoice(env: Env, invoice_id: u64) -> Invoice {
6977
invoice_component::get_invoice(&env, invoice_id)
7078
}
79+
80+
fn get_invoices(env: Env, filter: InvoiceFilter) -> Vec<Invoice> {
81+
invoice_component::get_invoices(&env, filter)
82+
}
83+
84+
fn pause(env: Env, admin: Address) {
85+
pausable_component::pause(&env, &admin);
86+
}
87+
88+
fn unpause(env: Env, admin: Address) {
89+
pausable_component::unpause(&env, &admin);
90+
}
91+
92+
fn is_paused(env: Env) -> bool {
93+
pausable_component::is_paused(&env)
94+
}
7195
}

‎contracts/shade/src/types.rs‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,27 @@ pub struct Invoice {
5050
}
5151

5252
#[contracttype]
53-
#[derive(Clone, Debug, Eq, PartialEq)]
53+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
54+
#[repr(u32)]
5455
pub enum InvoiceStatus {
55-
Pending,
56-
Paid,
57-
Cancelled,
58-
Refunded,
56+
Pending = 0,
57+
Paid = 1,
58+
Cancelled = 2,
59+
Refunded = 3,
60+
}
61+
62+
#[contracttype]
63+
#[derive(Clone, Debug, Eq, PartialEq)]
64+
pub struct MerchantFilter {
65+
pub is_active: Option<bool>,
66+
pub is_verified: Option<bool>,
67+
}
68+
69+
#[contracttype]
70+
#[derive(Clone, Debug, Eq, PartialEq)]
71+
pub struct InvoiceFilter {
72+
pub status: Option<u32>,
73+
pub merchant: Option<Address>,
74+
pub min_amount: Option<u128>,
75+
pub max_amount: Option<u128>,
5976
}

0 commit comments

Comments
 (0)