diff --git a/interpreter/interpreter.go b/interpreter/interpreter.go index b11fe82..4ae9975 100644 --- a/interpreter/interpreter.go +++ b/interpreter/interpreter.go @@ -687,12 +687,16 @@ func (s *programState) receiveFrom(destination parser.Destination, amount *big.I case *parser.DestinationTo: var remainingAmount big.Int remainingAmount.Sub(&amountToReceive, receivedTotal) - // receivedTotal += destination.receive(monetary-receivedTotal, ctx) - received, err := s.receiveFrom(destinationTarget.Destination, &remainingAmount) - if err != nil { - return err + + if remainingAmount.Cmp(big.NewInt(0)) != 0 { + // receivedTotal += destination.receive(monetary-receivedTotal, ctx) + received, err := s.receiveFrom(destinationTarget.Destination, &remainingAmount) + if err != nil { + return err + } + receivedTotal.Add(receivedTotal, received) } - receivedTotal.Add(receivedTotal, received) + return nil default: @@ -815,6 +819,13 @@ func balance( // body balance := s.getBalance(*account, *asset) + if balance.Cmp(big.NewInt(0)) == -1 { + return nil, NegativeBalanceError{ + Account: *account, + Amount: *balance, + } + } + var balanceCopy big.Int balanceCopy.Set(balance) diff --git a/interpreter/interpreter_error.go b/interpreter/interpreter_error.go index 70236a9..0d0c404 100644 --- a/interpreter/interpreter_error.go +++ b/interpreter/interpreter_error.go @@ -64,6 +64,15 @@ func (e InvalidTypeErr) Error() string { return fmt.Sprintf("This type does not exist: %s", e.Name) } +type NegativeBalanceError struct { + Account string + Amount big.Int +} + +func (e NegativeBalanceError) Error() string { + return fmt.Sprintf("Cannot fetch negative balance from account @%s", e.Account) +} + type NegativeAmountErr struct{ Amount MonetaryInt } func (e NegativeAmountErr) Error() string { diff --git a/interpreter/interpreter_test.go b/interpreter/interpreter_test.go index 7ce86db..a325f99 100644 --- a/interpreter/interpreter_test.go +++ b/interpreter/interpreter_test.go @@ -1459,6 +1459,27 @@ func TestDestinationComplex(t *testing.T) { // TODO TestNeededBalances, TestSetTxMeta, TestSetAccountMeta +func TestSendZero(t *testing.T) { + tc := NewTestCase() + tc.compile(t, ` + send [COIN 0] ( + source = @src + destination = @dest + )`) + tc.expected = CaseResult{ + Postings: []Posting{ + { + Asset: "COIN", + Amount: big.NewInt(0), + Source: "src", + Destination: "dest", + }, + }, + Error: nil, + } + test(t, tc) +} + func TestBalance(t *testing.T) { tc := NewTestCase() tc.compile(t, ` @@ -1485,6 +1506,52 @@ func TestBalance(t *testing.T) { test(t, tc) } +func TestNegativeBalance(t *testing.T) { + tc := NewTestCase() + tc.compile(t, ` + vars { + monetary $balance = balance(@a, EUR/2) + } + + send $balance ( + source = @world + destination = @dest + )`) + tc.setBalance("a", "EUR/2", -100) + tc.expected = CaseResult{ + Error: machine.NegativeBalanceError{ + Account: "a", + Amount: *big.NewInt(-100), + }, + } + test(t, tc) +} + +func TestBalanceNotFound(t *testing.T) { + tc := NewTestCase() + tc.compile(t, ` + vars { + monetary $balance = balance(@a, EUR/2) + } + + send $balance ( + source = @world + destination = @dest + )`) + tc.expected = CaseResult{ + Postings: []Posting{ + { + Asset: "EUR/2", + Amount: big.NewInt(0), + Source: "world", + Destination: "dest", + }, + }, + Error: nil, + } + test(t, tc) +} + func TestInoderDestination(t *testing.T) { tc := NewTestCase() tc.compile(t, `send [COIN 100] ( @@ -1695,16 +1762,19 @@ func TestVariableBalance(t *testing.T) { tc := NewTestCase() script = ` vars { - monetary $amount = balance(@world, USD/2) + monetary $amount = balance(@src, USD/2) } send $amount ( source = @A destination = @B )` tc.compile(t, script) - tc.setBalance("world", "USD/2", -40) + tc.setBalance("src", "USD/2", -40) tc.expected = CaseResult{ - Error: machine.NegativeAmountErr{Amount: machine.NewMonetaryInt(-40)}, + Error: machine.NegativeBalanceError{ + Account: "src", + Amount: *big.NewInt(-40), + }, } test(t, tc) }) diff --git a/interpreter/reconciler.go b/interpreter/reconciler.go index 1b81e52..74a9f91 100644 --- a/interpreter/reconciler.go +++ b/interpreter/reconciler.go @@ -105,10 +105,6 @@ func Reconcile(asset string, senders []Sender, receivers []Receiver) ([]Posting, postingAmount = *receiver.Monetary } - if postingAmt := big.Int(postingAmount); postingAmt.BitLen() == 0 { - continue - } - var postingToMerge *Posting if len(postings) != 0 { posting := &postings[len(postings)-1] diff --git a/interpreter/reconciler_test.go b/interpreter/reconciler_test.go index dcaf1f1..35e00c5 100644 --- a/interpreter/reconciler_test.go +++ b/interpreter/reconciler_test.go @@ -41,6 +41,15 @@ func TestReconcileSingletonExactMatch(t *testing.T) { }) } +func TestReconcileZero(t *testing.T) { + runReconcileTestCase(t, ReconcileTestCase{ + Currency: "COIN", + Senders: []Sender{{"src", big.NewInt(0)}}, + Receivers: []Receiver{{"dest", big.NewInt(0)}}, + Expected: []Posting{{"src", "dest", big.NewInt(0), "COIN"}}, + }) +} + func TestNoReceiversLeft(t *testing.T) { runReconcileTestCase(t, ReconcileTestCase{ Senders: []Sender{{