Skip to content

Commit 670f0b9

Browse files
authored
Merge branch 'main' into feat/pool-pay-multi-token
2 parents 2ec6b11 + 82fbb39 commit 670f0b9

4 files changed

Lines changed: 200 additions & 104 deletions

File tree

contracts/sharpy/src/events.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@ pub struct PaymentReceivedEvent {
1919
#[derive(Clone)]
2020
pub struct InvoiceReleasedEvent {
2121
pub id: u64,
22+
pub funded: i128,
23+
pub recipient_count: u32,
24+
pub creator: Address,
2225
}
2326

2427
#[contracttype]
2528
#[derive(Clone)]
2629
pub struct InvoiceRefundedEvent {
2730
pub id: u64,
31+
pub funded: i128,
32+
pub recipient_count: u32,
33+
pub creator: Address,
2834
}
2935

3036
#[contracttype]
@@ -43,14 +49,37 @@ pub fn payment_received(env: &Env, invoice_id: u64, payer: &Address, amount: i12
4349
env.events().publish((symbol_short!("payment"),), PaymentReceivedEvent { invoice_id, payer: payer.clone(), amount });
4450
}
4551

46-
pub fn invoice_released(env: &Env, id: u64, _recipients: &Vec<Address>) {
47-
env.events().publish((symbol_short!("released"),), InvoiceReleasedEvent { id });
52+
pub fn invoice_released(env: &Env, id: u64, funded: i128, recipient_count: u32, creator: &Address) {
53+
env.events().publish((symbol_short!("released"),), InvoiceReleasedEvent { id, funded, recipient_count, creator: creator.clone() });
4854
}
4955

50-
pub fn invoice_refunded(env: &Env, id: u64) {
51-
env.events().publish((symbol_short!("refunded"),), InvoiceRefundedEvent { id });
56+
pub fn invoice_refunded(env: &Env, id: u64, funded: i128, recipient_count: u32, creator: &Address) {
57+
env.events().publish((symbol_short!("refunded"),), InvoiceRefundedEvent { id, funded, recipient_count, creator: creator.clone() });
5258
}
5359

5460
pub fn payer_refunded(env: &Env, invoice_id: u64, payer: &Address, amount: i128) {
5561
env.events().publish((symbol_short!("pyr"),), PayerRefundedEvent { invoice_id, payer: payer.clone(), amount });
5662
}
63+
64+
#[contracttype]
65+
#[derive(Clone)]
66+
pub struct DisputeRaisedEvent {
67+
pub invoice_id: u64,
68+
pub creator: Address,
69+
}
70+
71+
#[contracttype]
72+
#[derive(Clone)]
73+
pub struct DisputeResolvedEvent {
74+
pub invoice_id: u64,
75+
pub resolver: Address,
76+
pub release: bool,
77+
}
78+
79+
pub fn dispute_raised(env: &Env, invoice_id: u64, creator: &Address) {
80+
env.events().publish((symbol_short!("dispute"),), DisputeRaisedEvent { invoice_id, creator: creator.clone() });
81+
}
82+
83+
pub fn dispute_resolved(env: &Env, invoice_id: u64, resolver: &Address, release: bool) {
84+
env.events().publish((symbol_short!("dsprslv"),), DisputeResolvedEvent { invoice_id, resolver: resolver.clone(), release });
85+
}

contracts/sharpy/src/lib.rs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod test;
1010

1111
use soroban_sdk::{contract, contractimpl, symbol_short, token, Address, Env, Map, Symbol, Vec};
1212
use types::{
13-
AuditEntry, CreateInvoiceParams, Invoice, InvoiceOptions, InvoicePayment,
13+
AuditEntry, CreateInvoiceParams, DisputeState, Invoice, InvoiceOptions, InvoicePayment,
1414
InvoiceStatus, Payment, SplitRule, SubscriptionParams,
1515
};
1616

@@ -70,6 +70,7 @@ fn build_invoice(
7070
escrow_enabled: bool,
7171
escrow_release_delay: u64,
7272
split_rules: Vec<SplitRule>,
73+
arbitrator: Option<Address>,
7374
) -> Invoice {
7475
let mut claimed: Vec<i128> = Vec::new(env);
7576
for _ in recipients.iter() {
@@ -92,6 +93,7 @@ fn build_invoice(
9293
escrow_release_delay,
9394
split_rules,
9495
auto_resolve_rules: Vec::new(env),
96+
arbitrator,
9597
}
9698
}
9799

@@ -140,6 +142,7 @@ impl SharpyContract {
140142
let invoice = build_invoice(
141143
&env, creator.clone(), recipients, amounts, tokens, deadline,
142144
options.escrow_enabled, options.escrow_release_delay.unwrap_or(0), options.split_rules,
145+
options.arbitrator,
143146
);
144147
save_invoice(&env, id, &invoice);
145148
events::invoice_created(&env, id, &creator);
@@ -157,7 +160,7 @@ impl SharpyContract {
157160
let id = bump_counter(&env);
158161
let invoice = build_invoice(
159162
&env, creator.clone(), params.recipients.clone(), params.amounts.clone(),
160-
params.tokens.clone(), params.deadline, false, 0, Vec::new(&env),
163+
params.tokens.clone(), params.deadline, false, 0, Vec::new(&env), None,
161164
);
162165
save_invoice(&env, id, &invoice);
163166
events::invoice_created(&env, id, &creator);
@@ -185,7 +188,7 @@ impl SharpyContract {
185188
let id = bump_counter(&env);
186189
let invoice = build_invoice(
187190
&env, creator.clone(), recipients.clone(), amounts.clone(),
188-
tokens.clone(), deadline, false, 0, Vec::new(&env),
191+
tokens.clone(), deadline, false, 0, Vec::new(&env), None,
189192
);
190193
save_invoice(&env, id, &invoice);
191194

@@ -226,7 +229,8 @@ impl SharpyContract {
226229
if invoice.funded >= total {
227230
if invoice.escrow_enabled {
228231
let release_at = env.ledger().timestamp() + invoice.escrow_release_delay;
229-
env.storage().persistent().set(&escrow_state_key(invoice_id), &release_at);
232+
let state = DisputeState { release_at, disputed: false, disputed_at: 0 };
233+
env.storage().persistent().set(&escrow_state_key(invoice_id), &state);
230234
save_invoice(&env, invoice_id, &invoice);
231235
} else {
232236
Self::_release(&env, invoice_id, &mut invoice, &payer);
@@ -271,7 +275,8 @@ impl SharpyContract {
271275
if inv.funded >= inv_total {
272276
if inv.escrow_enabled {
273277
let release_at = env.ledger().timestamp() + inv.escrow_release_delay;
274-
env.storage().persistent().set(&escrow_state_key(p.invoice_id), &release_at);
278+
let state = DisputeState { release_at, disputed: false, disputed_at: 0 };
279+
env.storage().persistent().set(&escrow_state_key(p.invoice_id), &state);
275280
save_invoice(&env, p.invoice_id, &inv);
276281
} else {
277282
Self::_release(&env, p.invoice_id, &mut inv, &payer);
@@ -286,14 +291,71 @@ impl SharpyContract {
286291
require_not_paused(&env);
287292
let mut invoice = load_invoice(&env, invoice_id);
288293
assert!(invoice.escrow_enabled, "escrow not enabled on this invoice");
289-
let release_at: u64 = env.storage().persistent()
294+
let state: DisputeState = env.storage().persistent()
290295
.get(&escrow_state_key(invoice_id)).expect("escrow not found");
291-
assert!(env.ledger().timestamp() >= release_at, "escrow delay not yet met");
296+
assert!(!state.disputed, "release is disputed, use resolve_dispute");
297+
assert!(env.ledger().timestamp() >= state.release_at, "escrow delay not yet met");
292298
let caller = env.current_contract_address();
293299
Self::_release(&env, invoice_id, &mut invoice, &caller);
294300
env.storage().persistent().remove(&escrow_state_key(invoice_id));
295301
}
296302

303+
pub fn dispute_release(env: Env, invoice_id: u64) {
304+
require_not_paused(&env);
305+
let invoice = load_invoice(&env, invoice_id);
306+
assert!(invoice.status == InvoiceStatus::Pending, "invoice is not pending");
307+
assert!(invoice.escrow_enabled, "escrow not enabled on this invoice");
308+
invoice.creator.require_auth();
309+
310+
let state: DisputeState = env.storage().persistent()
311+
.get(&escrow_state_key(invoice_id)).expect("escrow not found");
312+
assert!(!state.disputed, "dispute already raised");
313+
assert!(env.ledger().timestamp() < state.release_at, "escrow delay has passed, cannot dispute");
314+
315+
let new_state = DisputeState { disputed: true, disputed_at: env.ledger().timestamp(), ..state };
316+
env.storage().persistent().set(&escrow_state_key(invoice_id), &new_state);
317+
append_audit(&env, invoice_id, symbol_short!("dispute"), &invoice.creator);
318+
events::dispute_raised(&env, invoice_id, &invoice.creator);
319+
}
320+
321+
pub fn resolve_dispute(env: Env, invoice_id: u64, release: bool) {
322+
require_not_paused(&env);
323+
let mut invoice = load_invoice(&env, invoice_id);
324+
assert!(invoice.status == InvoiceStatus::Pending, "invoice is not pending");
325+
326+
let state: DisputeState = env.storage().persistent()
327+
.get(&escrow_state_key(invoice_id)).expect("escrow not found");
328+
assert!(state.disputed, "no active dispute");
329+
330+
let resolver = invoice.arbitrator.clone().unwrap_or_else(|| invoice.creator.clone());
331+
resolver.require_auth();
332+
333+
env.storage().persistent().remove(&escrow_state_key(invoice_id));
334+
335+
if release {
336+
Self::_release(&env, invoice_id, &mut invoice, &resolver);
337+
} else {
338+
let token_client = token::Client::new(&env, &invoice.tokens.get(0).expect("no token"));
339+
let mut totals: Map<Address, i128> = Map::new(&env);
340+
for payment in invoice.payments.iter() {
341+
let prev = totals.get(payment.payer.clone()).unwrap_or(0);
342+
totals.set(payment.payer.clone(), prev + payment.amount);
343+
}
344+
for (payer, amount) in totals.iter() {
345+
token_client.transfer(&env.current_contract_address(), &payer, &amount);
346+
events::payer_refunded(&env, invoice_id, &payer, amount);
347+
}
348+
349+
invoice.status = InvoiceStatus::Refunded;
350+
invoice.completion_time = Some(env.ledger().timestamp());
351+
save_invoice(&env, invoice_id, &invoice);
352+
append_audit(&env, invoice_id, symbol_short!("resolve"), &resolver);
353+
events::invoice_refunded(&env, invoice_id);
354+
}
355+
356+
events::dispute_resolved(&env, invoice_id, &resolver, release);
357+
}
358+
297359
fn _release(env: &Env, invoice_id: u64, invoice: &mut Invoice, actor: &Address) {
298360
assert!(invoice.status == InvoiceStatus::Pending, "invoice is not pending");
299361

@@ -336,7 +398,7 @@ impl SharpyContract {
336398
invoice.completion_time = Some(env.ledger().timestamp());
337399
save_invoice(env, invoice_id, invoice);
338400
append_audit(env, invoice_id, symbol_short!("release"), actor);
339-
events::invoice_released(env, invoice_id, &invoice.recipients);
401+
events::invoice_released(env, invoice_id, invoice.funded, n as u32, &invoice.creator);
340402

341403
// Spin up next recurring invoice if configured
342404
if let Some(params) = env.storage().persistent()
@@ -348,7 +410,7 @@ impl SharpyContract {
348410

349411
let next_invoice = build_invoice(
350412
env, params.creator.clone(), params.recipients.clone(),
351-
params.amounts.clone(), params.tokens.clone(), next_deadline, false, 0, Vec::new(env),
413+
params.amounts.clone(), params.tokens.clone(), next_deadline, false, 0, Vec::new(env), None,
352414
);
353415
save_invoice(env, next_id, &next_invoice);
354416

@@ -389,7 +451,8 @@ impl SharpyContract {
389451
invoice.completion_time = Some(env.ledger().timestamp());
390452
save_invoice(&env, invoice_id, &invoice);
391453
append_audit(&env, invoice_id, symbol_short!("refund"), &env.current_contract_address());
392-
events::invoice_refunded(&env, invoice_id);
454+
let recipient_count = invoice.recipients.len() as u32;
455+
events::invoice_refunded(&env, invoice_id, invoice.funded, recipient_count, &invoice.creator);
393456
}
394457

395458
pub fn cancel_invoice(env: Env, caller: Address, invoice_id: u64) {
@@ -434,4 +497,8 @@ impl SharpyContract {
434497
pub fn get_next_recurring(env: Env, invoice_id: u64) -> Option<u64> {
435498
env.storage().persistent().get(&next_invoice_key(invoice_id))
436499
}
500+
501+
pub fn get_escrow_state(env: Env, invoice_id: u64) -> Option<DisputeState> {
502+
env.storage().persistent().get(&escrow_state_key(invoice_id))
503+
}
437504
}

0 commit comments

Comments
 (0)