diff --git a/internal/interpreter/interpreter.go b/internal/interpreter/interpreter.go index 69350ac..989b303 100644 --- a/internal/interpreter/interpreter.go +++ b/internal/interpreter/interpreter.go @@ -247,6 +247,20 @@ type programState struct { CurrentBalanceQuery BalanceQuery } +func (st *programState) pushSender(name string, monetary *big.Int) { + if monetary.Cmp(big.NewInt(0)) == 0 { + return + } + st.Senders = append(st.Senders, Sender{Name: name, Monetary: monetary}) +} + +func (st *programState) pushReceiver(name string, monetary *big.Int) { + if monetary.Cmp(big.NewInt(0)) == 0 { + return + } + st.Receivers = append(st.Receivers, Receiver{Name: name, Monetary: monetary}) +} + func (st *programState) runStatement(statement parser.Statement) ([]Posting, InterpreterError) { st.Senders = nil st.Receivers = nil @@ -413,10 +427,7 @@ func (s *programState) sendAllToAccount(accountLiteral parser.ValueExpr, ovedraf // we sent balance+overdraft sentAmt := new(big.Int).Add(balance, ovedraft) - s.Senders = append(s.Senders, Sender{ - Name: *account, - Monetary: sentAmt, - }) + s.pushSender(*account, sentAmt) return sentAmt, nil } @@ -506,10 +517,7 @@ func (s *programState) trySendingToAccount(accountLiteral parser.ValueExpr, amou actuallySentAmt = utils.MinBigInt(safeSendAmt, amount) } - s.Senders = append(s.Senders, Sender{ - Name: *account, - Monetary: actuallySentAmt, - }) + s.pushSender(*account, actuallySentAmt) return actuallySentAmt, nil } @@ -585,10 +593,7 @@ func (s *programState) receiveFrom(destination parser.Destination, amount *big.I if err != nil { return err } - s.Receivers = append(s.Receivers, Receiver{ - Name: *account, - Monetary: amount, - }) + s.pushReceiver(*account, amount) return nil case *parser.DestinationAllotment: @@ -659,13 +664,12 @@ func (s *programState) receiveFrom(destination parser.Destination, amount *big.I } } +const KEPT_ADDR = "" + func (s *programState) receiveFromKeptOrDest(keptOrDest parser.KeptOrDestination, amount *big.Int) InterpreterError { switch destinationTarget := keptOrDest.(type) { case *parser.DestinationKept: - s.Receivers = append(s.Receivers, Receiver{ - Name: "", - Monetary: amount, - }) + s.pushReceiver(KEPT_ADDR, amount) return nil case *parser.DestinationTo: diff --git a/internal/interpreter/interpreter_test.go b/internal/interpreter/interpreter_test.go index e1b7cb1..7d068dc 100644 --- a/internal/interpreter/interpreter_test.go +++ b/internal/interpreter/interpreter_test.go @@ -1731,7 +1731,6 @@ func TestDestinationComplex(t *testing.T) { // TODO TestNeededBalances, TestSetTxMeta, TestSetAccountMeta func TestSendZero(t *testing.T) { - // TODO double check tc := NewTestCase() tc.compile(t, ` send [COIN 0] ( @@ -1740,6 +1739,7 @@ func TestSendZero(t *testing.T) { )`) tc.expected = CaseResult{ Postings: []Posting{ + // Zero posting is omitted // { // Asset: "COIN", // Amount: big.NewInt(0), @@ -1815,7 +1815,6 @@ func TestNegativeBalanceLiteral(t *testing.T) { } func TestBalanceNotFound(t *testing.T) { - // TODO double check tc := NewTestCase() tc.compile(t, ` vars { @@ -1828,6 +1827,7 @@ func TestBalanceNotFound(t *testing.T) { )`) tc.expected = CaseResult{ Postings: []Posting{ + // Zero posting is omitted // { // Asset: "EUR/2", // Amount: big.NewInt(0), @@ -2683,6 +2683,41 @@ func TestZeroPostingsDestination(t *testing.T) { test(t, tc) } +func TestZeroPostingsExplicitInorder(t *testing.T) { + tc := NewTestCase() + tc.compile(t, ` + send [COIN 0] ( + source = { + @a + @b + @c + } + destination = @dest + ) + `) + tc.expected = CaseResult{ + Postings: []machine.Posting{}, + } + test(t, tc) +} + +func TestZeroPostingsExplicitAllotment(t *testing.T) { + tc := NewTestCase() + tc.compile(t, ` + send [COIN 0] ( + source = { + 1/2 from @a + 1/2 from @b + } + destination = @dest + ) + `) + tc.expected = CaseResult{ + Postings: []machine.Posting{}, + } + test(t, tc) +} + func TestUnboundedOverdraftWhenNotEnoughFunds(t *testing.T) { tc := NewTestCase() tc.setBalance("users:2345:main", "USD/2", 8000) diff --git a/internal/interpreter/reconciler.go b/internal/interpreter/reconciler.go index 49ab8e4..7f97f75 100644 --- a/internal/interpreter/reconciler.go +++ b/internal/interpreter/reconciler.go @@ -33,13 +33,6 @@ type Receiver struct { } func Reconcile(asset string, senders []Sender, receivers []Receiver) ([]Posting, InterpreterError) { - var sendersFiltered []Sender - for _, e := range senders { - if e.Monetary.Cmp(big.NewInt(0)) != 0 { - sendersFiltered = append(sendersFiltered, e) - } - } - senders = sendersFiltered // We reverse senders and receivers once so that we can // treat them as stack and push/pop in O(1) @@ -54,7 +47,7 @@ func Reconcile(asset string, senders []Sender, receivers []Receiver) ([]Posting, } // Ugly workaround - if receiver.Name == "" { + if receiver.Name == KEPT_ADDR { sender, empty := popStack(&senders) if !empty { var newMon big.Int diff --git a/internal/interpreter/reconciler_test.go b/internal/interpreter/reconciler_test.go index d4b41dc..cb09227 100644 --- a/internal/interpreter/reconciler_test.go +++ b/internal/interpreter/reconciler_test.go @@ -43,15 +43,14 @@ func TestReconcileSingletonExactMatch(t *testing.T) { } func TestReconcileZero(t *testing.T) { - // TODO double check runReconcileTestCase(t, ReconcileTestCase{ Currency: "COIN", Senders: []Sender{{"src", big.NewInt(0)}}, Receivers: []Receiver{{"dest", big.NewInt(0)}}, - Expected: nil, - // []Posting{ - // // {"src", "dest", big.NewInt(0), "COIN"} - // }, + Expected: []Posting{ + {"src", "dest", big.NewInt(0), "COIN"}, + }, + ExpectedErr: nil, }) }