@@ -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) ;
@@ -262,8 +266,15 @@ impl SharpyContract {
262266 append_audit ( & env, p. invoice_id , symbol_short ! ( "pool_pay" ) , & payer) ;
263267 events:: payment_received ( & env, p. invoice_id , & payer, p. amount ) ;
264268 let inv_total: i128 = inv. amounts . iter ( ) . sum ( ) ;
265- if inv. funded >= inv_total && !inv. escrow_enabled {
266- Self :: _release ( & env, p. invoice_id , & mut inv, & payer) ;
269+ if inv. funded >= inv_total {
270+ if inv. escrow_enabled {
271+ let release_at = env. ledger ( ) . timestamp ( ) + inv. escrow_release_delay ;
272+ let state = DisputeState { release_at, disputed : false , disputed_at : 0 } ;
273+ env. storage ( ) . persistent ( ) . set ( & escrow_state_key ( p. invoice_id ) , & state) ;
274+ save_invoice ( & env, p. invoice_id , & inv) ;
275+ } else {
276+ Self :: _release ( & env, p. invoice_id , & mut inv, & payer) ;
277+ }
267278 } else {
268279 save_invoice ( & env, p. invoice_id , & inv) ;
269280 }
@@ -274,14 +285,71 @@ impl SharpyContract {
274285 require_not_paused ( & env) ;
275286 let mut invoice = load_invoice ( & env, invoice_id) ;
276287 assert ! ( invoice. escrow_enabled, "escrow not enabled on this invoice" ) ;
277- let release_at : u64 = env. storage ( ) . persistent ( )
288+ let state : DisputeState = env. storage ( ) . persistent ( )
278289 . get ( & escrow_state_key ( invoice_id) ) . expect ( "escrow not found" ) ;
279- assert ! ( env. ledger( ) . timestamp( ) >= release_at, "escrow delay not yet met" ) ;
290+ assert ! ( !state. disputed, "release is disputed, use resolve_dispute" ) ;
291+ assert ! ( env. ledger( ) . timestamp( ) >= state. release_at, "escrow delay not yet met" ) ;
280292 let caller = env. current_contract_address ( ) ;
281293 Self :: _release ( & env, invoice_id, & mut invoice, & caller) ;
282294 env. storage ( ) . persistent ( ) . remove ( & escrow_state_key ( invoice_id) ) ;
283295 }
284296
297+ pub fn dispute_release ( env : Env , invoice_id : u64 ) {
298+ require_not_paused ( & env) ;
299+ let invoice = load_invoice ( & env, invoice_id) ;
300+ assert ! ( invoice. status == InvoiceStatus :: Pending , "invoice is not pending" ) ;
301+ assert ! ( invoice. escrow_enabled, "escrow not enabled on this invoice" ) ;
302+ invoice. creator . require_auth ( ) ;
303+
304+ let state: DisputeState = env. storage ( ) . persistent ( )
305+ . get ( & escrow_state_key ( invoice_id) ) . expect ( "escrow not found" ) ;
306+ assert ! ( !state. disputed, "dispute already raised" ) ;
307+ assert ! ( env. ledger( ) . timestamp( ) < state. release_at, "escrow delay has passed, cannot dispute" ) ;
308+
309+ let new_state = DisputeState { disputed : true , disputed_at : env. ledger ( ) . timestamp ( ) , ..state } ;
310+ env. storage ( ) . persistent ( ) . set ( & escrow_state_key ( invoice_id) , & new_state) ;
311+ append_audit ( & env, invoice_id, symbol_short ! ( "dispute" ) , & invoice. creator ) ;
312+ events:: dispute_raised ( & env, invoice_id, & invoice. creator ) ;
313+ }
314+
315+ pub fn resolve_dispute ( env : Env , invoice_id : u64 , release : bool ) {
316+ require_not_paused ( & env) ;
317+ let mut invoice = load_invoice ( & env, invoice_id) ;
318+ assert ! ( invoice. status == InvoiceStatus :: Pending , "invoice is not pending" ) ;
319+
320+ let state: DisputeState = env. storage ( ) . persistent ( )
321+ . get ( & escrow_state_key ( invoice_id) ) . expect ( "escrow not found" ) ;
322+ assert ! ( state. disputed, "no active dispute" ) ;
323+
324+ let resolver = invoice. arbitrator . clone ( ) . unwrap_or_else ( || invoice. creator . clone ( ) ) ;
325+ resolver. require_auth ( ) ;
326+
327+ env. storage ( ) . persistent ( ) . remove ( & escrow_state_key ( invoice_id) ) ;
328+
329+ if release {
330+ Self :: _release ( & env, invoice_id, & mut invoice, & resolver) ;
331+ } else {
332+ let token_client = token:: Client :: new ( & env, & invoice. tokens . get ( 0 ) . expect ( "no token" ) ) ;
333+ let mut totals: Map < Address , i128 > = Map :: new ( & env) ;
334+ for payment in invoice. payments . iter ( ) {
335+ let prev = totals. get ( payment. payer . clone ( ) ) . unwrap_or ( 0 ) ;
336+ totals. set ( payment. payer . clone ( ) , prev + payment. amount ) ;
337+ }
338+ for ( payer, amount) in totals. iter ( ) {
339+ token_client. transfer ( & env. current_contract_address ( ) , & payer, & amount) ;
340+ events:: payer_refunded ( & env, invoice_id, & payer, amount) ;
341+ }
342+
343+ invoice. status = InvoiceStatus :: Refunded ;
344+ invoice. completion_time = Some ( env. ledger ( ) . timestamp ( ) ) ;
345+ save_invoice ( & env, invoice_id, & invoice) ;
346+ append_audit ( & env, invoice_id, symbol_short ! ( "resolve" ) , & resolver) ;
347+ events:: invoice_refunded ( & env, invoice_id) ;
348+ }
349+
350+ events:: dispute_resolved ( & env, invoice_id, & resolver, release) ;
351+ }
352+
285353 fn _release ( env : & Env , invoice_id : u64 , invoice : & mut Invoice , actor : & Address ) {
286354 assert ! ( invoice. status == InvoiceStatus :: Pending , "invoice is not pending" ) ;
287355
@@ -336,7 +404,7 @@ impl SharpyContract {
336404
337405 let next_invoice = build_invoice (
338406 env, params. creator . clone ( ) , params. recipients . clone ( ) ,
339- params. amounts . clone ( ) , params. tokens . clone ( ) , next_deadline, false , 0 , Vec :: new ( env) ,
407+ params. amounts . clone ( ) , params. tokens . clone ( ) , next_deadline, false , 0 , Vec :: new ( env) , None ,
340408 ) ;
341409 save_invoice ( env, next_id, & next_invoice) ;
342410
@@ -422,4 +490,8 @@ impl SharpyContract {
422490 pub fn get_next_recurring ( env : Env , invoice_id : u64 ) -> Option < u64 > {
423491 env. storage ( ) . persistent ( ) . get ( & next_invoice_key ( invoice_id) )
424492 }
493+
494+ pub fn get_escrow_state ( env : Env , invoice_id : u64 ) -> Option < DisputeState > {
495+ env. storage ( ) . persistent ( ) . get ( & escrow_state_key ( invoice_id) )
496+ }
425497}
0 commit comments