@@ -10,7 +10,7 @@ mod test;
1010
1111use soroban_sdk:: { contract, contractimpl, symbol_short, token, Address , Env , Map , Symbol , Vec } ;
1212use 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