@@ -303,7 +303,7 @@ func (st *programState) runSendStatement(statement parser.SendStatement) ([]Post
303303 return nil , err
304304 }
305305 st .CurrentAsset = * asset
306- sentAmt , err := st .trySendingAll (statement .Source )
306+ sentAmt , err := st .sendAll (statement .Source )
307307 if err != nil {
308308 return nil , err
309309 }
@@ -325,22 +325,11 @@ func (st *programState) runSendStatement(statement parser.SendStatement) ([]Post
325325 return nil , NegativeAmountErr {Amount : monetary .Amount }
326326 }
327327
328- sentTotal , err : = st .trySending (statement .Source , monetaryAmt )
328+ err = st .trySendingExact (statement .Source , monetaryAmt )
329329 if err != nil {
330330 return nil , err
331331 }
332332
333- // sentTotal < monetary.Amount
334- if sentTotal .Cmp (& monetaryAmt ) == - 1 {
335- var missing big.Int
336- missing .Sub (& monetaryAmt , sentTotal )
337- return nil , MissingFundsErr {
338- Asset : string (monetary .Asset ),
339- Missing : missing ,
340- Sent : * sentTotal ,
341- }
342- }
343-
344333 // TODO simplify pointers
345334 amt := big .Int (monetary .Amount )
346335 err = st .receiveFrom (statement .Destination , & amt )
@@ -373,56 +362,52 @@ func (s *programState) getBalance(account string, asset string) *big.Int {
373362 return assetBalance
374363}
375364
376- func (s * programState ) trySendingAccount (name string , amount big.Int ) (* big.Int , error ) {
377- var monetaryAmount big.Int
378- monetaryAmount .Set (& amount )
379-
380- if name != "world" {
381- balance := s .getBalance (name , s .CurrentAsset )
365+ func (s * programState ) sendAllToAccount (accountLiteral parser.Literal , ovedraft * big.Int ) (* big.Int , error ) {
366+ account , err := evaluateLitExpecting (s , accountLiteral , expectAccount )
367+ if err != nil {
368+ return nil , err
369+ }
382370
383- // monetary = min(balance, monetary)
384- if balance . Cmp ( & monetaryAmount ) == - 1 /* balance < monetary */ {
385- monetaryAmount . Set ( balance )
371+ if * account == "world" || ovedraft == nil {
372+ return nil , InvalidUnboundedInSendAll {
373+ Name : * account ,
386374 }
387375 }
388376
377+ balance := s .getBalance (* account , s .CurrentAsset )
378+
379+ // we sent balance+overdraft
380+ var sentAmt big.Int
381+ sentAmt .Add (balance , ovedraft )
382+
389383 s .Senders = append (s .Senders , Sender {
390- Name : name ,
391- Monetary : & monetaryAmount ,
384+ Name : * account ,
385+ Monetary : & sentAmt ,
392386 })
393-
394- return & monetaryAmount , nil
387+ return & sentAmt , nil
395388}
396389
397- func (s * programState ) trySendingAll (source parser.Source ) (* big.Int , error ) {
390+ // Send as much as possible (and return the sent amt)
391+ func (s * programState ) sendAll (source parser.Source ) (* big.Int , error ) {
398392 switch source := source .(type ) {
399393 case * parser.SourceAccount :
400- account , err := evaluateLitExpecting (s , source .Literal , expectAccount )
401- if err != nil {
402- return nil , err
403- }
404- name := string (* account )
394+ return s .sendAllToAccount (source .Literal , big .NewInt (0 ))
405395
406- if name == "world" {
407- return nil , InvalidUnboundedInSendAll {
408- Name : name ,
396+ case * parser.SourceOverdraft :
397+ var cap * big.Int
398+ if source .Bounded != nil {
399+ bounded , err := evaluateLitExpecting (s , * source .Bounded , expectMonetaryOfAsset (s .CurrentAsset ))
400+ if err != nil {
401+ return nil , err
409402 }
403+ cap = bounded
410404 }
411-
412- var balanceClone big.Int
413- // TODO err empty balance?
414- balance := s .getBalance (name , s .CurrentAsset )
415- s .Senders = append (s .Senders , Sender {
416- Name : name ,
417- Monetary : balanceClone .Set (balance ),
418- })
419-
420- return & balanceClone , nil
405+ return s .sendAllToAccount (source .Address , cap )
421406
422407 case * parser.SourceInorder :
423408 totalSent := big .NewInt (0 )
424409 for _ , subSource := range source .Sources {
425- sent , err := s .trySendingAll (subSource )
410+ sent , err := s .sendAll (subSource )
426411 if err != nil {
427412 return nil , err
428413 }
@@ -437,96 +422,97 @@ func (s *programState) trySendingAll(source parser.Source) (*big.Int, error) {
437422 }
438423
439424 // We switch to the default sending evaluation for this subsource
440- return s .trySending (source .From , * monetary )
425+ return s .trySendingUpTo (source .From , * monetary )
441426
442427 case * parser.SourceAllotment :
443428 return nil , InvalidAllotmentInSendAll {}
444429
445- case * parser. SourceOverdraft :
446- account , err := evaluateLitExpecting ( s , source . Address , expectAccount )
447- if err != nil {
448- return nil , err
449- }
430+ default :
431+ utils. NonExhaustiveMatchPanic [ error ]( source )
432+ return nil , nil
433+ }
434+ }
450435
451- if source .Bounded == nil {
452- return nil , InvalidUnboundedInSendAll {
453- Name : * account ,
454- }
436+ // Fails if it doesn't manage to send exactly "amount"
437+ func (s * programState ) trySendingExact (source parser.Source , amount big.Int ) error {
438+ sentAmt , err := s .trySendingUpTo (source , amount )
439+ if err != nil {
440+ return err
441+ }
442+ if sentAmt .Cmp (& amount ) != 0 {
443+ return MissingFundsErr {
444+ Asset : s .CurrentAsset ,
445+ Needed : amount ,
446+ Available : * sentAmt ,
455447 }
448+ }
449+ return nil
450+ }
456451
457- amount , err := evaluateLitExpecting (s , * source .Bounded , expectMonetaryOfAsset (s .CurrentAsset ))
458- if err != nil {
459- return nil , err
460- }
452+ func (s * programState ) trySendingToAccount (accountLiteral parser.Literal , amount big.Int , overdraft * big.Int ) (* big.Int , error ) {
453+ account , err := evaluateLitExpecting (s , accountLiteral , expectAccount )
454+ if err != nil {
455+ return nil , err
456+ }
457+ if * account == "world" {
458+ overdraft = nil
459+ }
461460
462- return s .trySendingAccount (* account , * amount )
461+ var actuallySentAmt big.Int
462+ if overdraft == nil {
463+ // unbounded overdraft: we send the required amount
464+ actuallySentAmt .Set (& amount )
465+ } else {
466+ balance := s .getBalance (* account , s .CurrentAsset )
463467
464- default :
465- utils.NonExhaustiveMatchPanic [error ](source )
466- return nil , nil
468+ // that's the amount we are allowed to send (balance + overdraft)
469+ var safeSendAmt big.Int
470+ safeSendAmt .Add (balance , overdraft )
471+
472+ actuallySentAmt = * utils .MinBigInt (& safeSendAmt , & amount )
467473 }
474+
475+ s .Senders = append (s .Senders , Sender {
476+ Name : * account ,
477+ Monetary : & actuallySentAmt ,
478+ })
479+ return & actuallySentAmt , nil
468480}
469481
470- func (s * programState ) trySending (source parser.Source , amount big.Int ) (* big.Int , error ) {
482+ // Tries sending "amount" and returns the actually sent amt.
483+ // Doesn't fail (unless nested sources fail)
484+ func (s * programState ) trySendingUpTo (source parser.Source , amount big.Int ) (* big.Int , error ) {
471485 switch source := source .(type ) {
472486 case * parser.SourceAccount :
473- account , err := evaluateLitExpecting (s , source .Literal , expectAccount )
474- if err != nil {
475- return nil , err
476- }
477- return s .trySendingAccount (string (* account ), amount )
487+ return s .trySendingToAccount (source .Literal , amount , big .NewInt (0 ))
478488
479489 case * parser.SourceOverdraft :
480- name , err := evaluateLitExpecting (s , source .Address , expectAccount )
481- if err != nil {
482- return nil , err
483- }
484-
485- balance := s .getBalance (* name , s .CurrentAsset )
486- // "overdraft up to `source.Bounded`"
490+ var cap * big.Int
487491 if source .Bounded != nil {
488492 upTo , err := evaluateLitExpecting (s , * source .Bounded , expectMonetaryOfAsset (s .CurrentAsset ))
489493 if err != nil {
490494 return nil , err
491495 }
492-
493- var balancePlusOverdraft big.Int
494- balancePlusOverdraft .Add (balance , upTo )
495- // check that amount > balance + overdraft
496- if amount .Cmp (& balancePlusOverdraft ) == 1 {
497- var missing big.Int
498-
499- return nil , MissingFundsErr {
500- Asset : s .CurrentAsset ,
501- Missing : * missing .Sub (& amount , & balancePlusOverdraft ),
502- Sent : balancePlusOverdraft ,
503- }
504- }
505-
496+ cap = upTo
506497 }
507-
508- s .Senders = append (s .Senders , Sender {
509- Name : * name ,
510- Monetary : & amount ,
511- })
512-
513- return & amount , nil
498+ return s .trySendingToAccount (source .Address , amount , cap )
514499
515500 case * parser.SourceInorder :
516- sentTotal := big .NewInt (0 )
501+ var totalLeft big.Int
502+ totalLeft .Set (& amount )
517503 for _ , source := range source .Sources {
518- var sendingMonetary big.Int
519- sendingMonetary .Sub (& amount , sentTotal )
520- sentAmt , err := s .trySending (source , sendingMonetary )
504+ sentAmt , err := s .trySendingUpTo (source , totalLeft )
521505 if err != nil {
522506 return nil , err
523507 }
524- sentTotal . Add ( sentTotal , sentAmt )
508+ totalLeft . Sub ( & totalLeft , sentAmt )
525509 }
526- return sentTotal , nil
510+
511+ var sentAmt big.Int
512+ sentAmt .Sub (& amount , & totalLeft )
513+ return & sentAmt , nil
527514
528515 case * parser.SourceAllotment :
529- receivedTotal := big .NewInt (0 )
530516 var items []parser.AllotmentValue
531517 for _ , i := range source .Items {
532518 items = append (items , i .Allotment )
@@ -536,29 +522,20 @@ func (s *programState) trySending(source parser.Source, amount big.Int) (*big.In
536522 return nil , err
537523 }
538524 for i , allotmentItem := range source .Items {
539- source := allotmentItem .From
540- received , err := s .trySending (source , * big .NewInt (allot [i ]))
525+ err := s .trySendingExact (allotmentItem .From , * big .NewInt (allot [i ]))
541526 if err != nil {
542527 return nil , err
543528 }
544- receivedTotal .Add (receivedTotal , received )
545529 }
546- return receivedTotal , nil
530+ return & amount , nil
547531
548532 case * parser.SourceCapped :
549533 cap , err := evaluateLitExpecting (s , source .Cap , expectMonetaryOfAsset (s .CurrentAsset ))
550534 if err != nil {
551535 return nil , err
552536 }
553-
554- // TODO use utils.min
555- var cappedAmount big.Int
556- if amount .Cmp (cap ) == - 1 /* monetary < cap */ {
557- cappedAmount .Set (& amount )
558- } else {
559- cappedAmount .Set (cap )
560- }
561- return s .trySending (source .From , cappedAmount )
537+ cappedAmount := utils .MinBigInt (& amount , cap )
538+ return s .trySendingUpTo (source .From , * cappedAmount )
562539
563540 default :
564541 utils.NonExhaustiveMatchPanic [any ](source )
0 commit comments