From ad1741692a7b0e5297cc7178b4bbf8d3dd812a0b Mon Sep 17 00:00:00 2001 From: Ragot Geoffrey Date: Wed, 22 Mar 2023 15:12:42 +0100 Subject: [PATCH] refactor: machine resources resolution (#162) --- pkg/api/apierrors/errors.go | 12 +- pkg/core/json.go | 26 +- pkg/core/json_test.go | 20 +- pkg/ledger/runner/runner.go | 7 +- pkg/ledger/runner/runner_test.go | 6 +- pkg/machine/examples/basic.go | 47 +- pkg/machine/machine.go | 66 +- pkg/machine/machine_test.go | 34 +- pkg/machine/{ => vm}/errors.go | 2 +- pkg/machine/vm/machine.go | 367 ++++----- pkg/machine/vm/machine_test.go | 1269 +++++++++++++++++++++++------ pkg/machine/vm/program/program.go | 6 +- pkg/machine/vm/store.go | 33 + 13 files changed, 1288 insertions(+), 607 deletions(-) rename pkg/machine/{ => vm}/errors.go (98%) create mode 100644 pkg/machine/vm/store.go diff --git a/pkg/api/apierrors/errors.go b/pkg/api/apierrors/errors.go index 6c331f772..67a273bf9 100644 --- a/pkg/api/apierrors/errors.go +++ b/pkg/api/apierrors/errors.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/formancehq/ledger/pkg/ledger/runner" - "github.com/formancehq/ledger/pkg/machine" + "github.com/formancehq/ledger/pkg/machine/vm" "github.com/formancehq/ledger/pkg/storage" "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/logging" @@ -56,11 +56,11 @@ func coreErrorToErrorCode(err error) (int, string, string) { return http.StatusBadRequest, ErrValidation, "" case runner.IsNotFoundError(err): return http.StatusNotFound, ErrNotFound, "" - case machine.IsScriptErrorWithCode(err, ErrScriptNoScript), - machine.IsScriptErrorWithCode(err, ErrInsufficientFund), - machine.IsScriptErrorWithCode(err, ErrScriptCompilationFailed), - machine.IsScriptErrorWithCode(err, ErrScriptMetadataOverride): - scriptErr := &machine.ScriptError{} + case vm.IsScriptErrorWithCode(err, ErrScriptNoScript), + vm.IsScriptErrorWithCode(err, ErrInsufficientFund), + vm.IsScriptErrorWithCode(err, ErrScriptCompilationFailed), + vm.IsScriptErrorWithCode(err, ErrScriptMetadataOverride): + scriptErr := &vm.ScriptError{} _ = errors.As(err, &scriptErr) return http.StatusBadRequest, scriptErr.Code, EncodeLink(scriptErr.Message) case errors.Is(err, context.Canceled): diff --git a/pkg/core/json.go b/pkg/core/json.go index 00fe11262..5a0076983 100644 --- a/pkg/core/json.go +++ b/pkg/core/json.go @@ -29,7 +29,7 @@ func TypeFromName(name string) (Type, bool) { } } -func NewValueFromTypedJSON(rawInput json.RawMessage) (*Value, error) { +func NewValueFromTypedJSON(rawInput json.RawMessage) (Value, error) { var input ValueJSON if err := json.Unmarshal(rawInput, &input); err != nil { return nil, err @@ -43,7 +43,7 @@ func NewValueFromTypedJSON(rawInput json.RawMessage) (*Value, error) { return NewValueFromJSON(typ, input.Value) } -func NewValueFromJSON(typ Type, data json.RawMessage) (*Value, error) { +func NewValueFromJSON(typ Type, data json.RawMessage) (Value, error) { var value Value switch typ { case TypeAccount: @@ -106,5 +106,25 @@ func NewValueFromJSON(typ Type, data json.RawMessage) (*Value, error) { return nil, fmt.Errorf("invalid type '%v'", typ) } - return &value, nil + return value, nil +} + +func NewJSONFromValue(value Value) (any, error) { + switch value.GetType() { + case TypeAccount: + return string(value.(AccountAddress)), nil + case TypeAsset: + return string(value.(Asset)), nil + case TypeString: + return string(value.(String)), nil + case TypeNumber: + return value.(*MonetaryInt).String(), nil + case TypeMonetary: + return value, nil + + case TypePortion: + return value.(Portion).String(), nil + default: + return nil, fmt.Errorf("invalid type '%v'", value.GetType()) + } } diff --git a/pkg/core/json_test.go b/pkg/core/json_test.go index 7b62c84e8..334575af3 100644 --- a/pkg/core/json_test.go +++ b/pkg/core/json_test.go @@ -17,8 +17,8 @@ func TestAccountTypedJSON(t *testing.T) { value, err := NewValueFromTypedJSON(j) require.NoError(t, err) - if !ValueEquals(*value, AccountAddress("users:001")) { - t.Fatalf("unexpected value: %v", *value) + if !ValueEquals(value, AccountAddress("users:001")) { + t.Fatalf("unexpected value: %v", value) } } @@ -30,8 +30,8 @@ func TestAssetTypedJSON(t *testing.T) { value, err := NewValueFromTypedJSON(j) require.NoError(t, err) - if !ValueEquals(*value, Asset("EUR/2")) { - t.Fatalf("unexpected value: %v", *value) + if !ValueEquals(value, Asset("EUR/2")) { + t.Fatalf("unexpected value: %v", value) } } @@ -46,8 +46,8 @@ func TestNumberTypedJSON(t *testing.T) { num, err := ParseNumber("89849865111111111111111111111111111555555555555555555555555555555555555555555555555999999999999999999999") require.NoError(t, err) - if !ValueEquals(*value, num) { - t.Fatalf("unexpected value: %v", *value) + if !ValueEquals(value, num) { + t.Fatalf("unexpected value: %v", value) } } @@ -62,11 +62,11 @@ func TestMonetaryTypedJSON(t *testing.T) { value, err := NewValueFromTypedJSON(j) require.NoError(t, err) - if !ValueEquals(*value, Monetary{ + if !ValueEquals(value, Monetary{ Asset: "EUR/2", Amount: NewMonetaryInt(123456), }) { - t.Fatalf("unexpected value: %v", *value) + t.Fatalf("unexpected value: %v", value) } } @@ -81,8 +81,8 @@ func TestPortionTypedJSON(t *testing.T) { portion, err := NewPortionSpecific(*big.NewRat(90, 100)) require.NoError(t, err) - if !ValueEquals(*value, *portion) { - t.Fatalf("unexpected value: %v", *value) + if !ValueEquals(value, *portion) { + t.Fatalf("unexpected value: %v", value) } } diff --git a/pkg/ledger/runner/runner.go b/pkg/ledger/runner/runner.go index d26033090..8d48969eb 100644 --- a/pkg/ledger/runner/runner.go +++ b/pkg/ledger/runner/runner.go @@ -14,6 +14,7 @@ import ( "github.com/formancehq/ledger/pkg/ledger/lock" "github.com/formancehq/ledger/pkg/machine" "github.com/formancehq/ledger/pkg/machine/script/compiler" + "github.com/formancehq/ledger/pkg/machine/vm" "github.com/formancehq/ledger/pkg/storage" "github.com/pkg/errors" ) @@ -115,7 +116,7 @@ func (r *Runner) acquireInflight(ctx context.Context, script core.RunScript) (*i script.WithDefaultValues() if script.Plain == "" { - return nil, machine.NewScriptError(machine.ScriptErrorNoScript, "no script to execute") + return nil, vm.NewScriptError(vm.ScriptErrorNoScript, "no script to execute") } if err := r.checkConstraints(ctx, script); err != nil { @@ -135,12 +136,12 @@ func (r *Runner) execute(ctx context.Context, script core.RunScript, dryRun bool prog, err := compiler.Compile(script.Plain) if err != nil { - return nil, nil, machine.NewScriptError(machine.ScriptErrorCompilationFailed, errors.Wrap(err, "compiling numscript").Error()) + return nil, nil, vm.NewScriptError(vm.ScriptErrorCompilationFailed, errors.Wrap(err, "compiling numscript").Error()) } involvedAccounts, err := prog.GetInvolvedAccounts(script.Vars) if err != nil { - return nil, nil, machine.NewScriptError(machine.ScriptErrorCompilationFailed, err.Error()) + return nil, nil, vm.NewScriptError(vm.ScriptErrorCompilationFailed, err.Error()) } unlock, err := r.locker.Lock(ctx, r.store.Name(), involvedAccounts...) diff --git a/pkg/ledger/runner/runner_test.go b/pkg/ledger/runner/runner_test.go index 67f156d35..6876f01a0 100644 --- a/pkg/ledger/runner/runner_test.go +++ b/pkg/ledger/runner/runner_test.go @@ -8,7 +8,7 @@ import ( "github.com/formancehq/ledger/pkg/ledger/cache" "github.com/formancehq/ledger/pkg/ledger/lock" "github.com/formancehq/ledger/pkg/ledgertesting" - "github.com/formancehq/ledger/pkg/machine" + "github.com/formancehq/ledger/pkg/machine/vm" "github.com/formancehq/stack/libs/go-libs/pgtesting" "github.com/google/uuid" "github.com/pkg/errors" @@ -74,12 +74,12 @@ var testCases = []testCase{ { name: "no script", script: ``, - expectedError: machine.NewScriptError(machine.ScriptErrorNoScript, ""), + expectedError: vm.NewScriptError(vm.ScriptErrorNoScript, ""), }, { name: "invalid script", script: `XXX`, - expectedError: machine.NewScriptError(machine.ScriptErrorCompilationFailed, ""), + expectedError: vm.NewScriptError(vm.ScriptErrorCompilationFailed, ""), }, { name: "set reference conflict", diff --git a/pkg/machine/examples/basic.go b/pkg/machine/examples/basic.go index 521e81ad1..42e23c610 100644 --- a/pkg/machine/examples/basic.go +++ b/pkg/machine/examples/basic.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/formancehq/ledger/pkg/core" @@ -38,35 +39,31 @@ func main() { panic(err) } - initialBalances := map[string]map[string]*core.MonetaryInt{ - "alice": {"COIN": core.NewMonetaryInt(10)}, - "bob": {"COIN": core.NewMonetaryInt(100)}, + initialVolumes := map[string]map[string]core.Volumes{ + "alice": { + "COIN": core.NewEmptyVolumes().WithInput(core.NewMonetaryInt(10)), + }, + "bob": { + "COIN": core.NewEmptyVolumes().WithInput(core.NewMonetaryInt(100)), + }, } - { - ch, err := m.ResolveResources() - if err != nil { - panic(err) - } - for req := range ch { - if req.Error != nil { - panic(req.Error) - } - } + err = m.ResolveResources(context.Background(), vm.EmptyStore) + if err != nil { + panic(err) } - { - ch, err := m.ResolveBalances() - if err != nil { - panic(err) - } - for req := range ch { - val := initialBalances[req.Account][req.Asset] - if req.Error != nil { - panic(req.Error) - } - req.Response <- val - } + err = m.ResolveBalances(context.Background(), vm.StoreFn(func(ctx context.Context, address string) (*core.AccountWithVolumes, error) { + return &core.AccountWithVolumes{ + Account: core.Account{ + Address: address, + Metadata: core.Metadata{}, + }, + Volumes: initialVolumes[address], + }, nil + })) + if err != nil { + panic(err) } exitCode, err := m.Execute() diff --git a/pkg/machine/machine.go b/pkg/machine/machine.go index 44b9ad2f3..6a522f42c 100644 --- a/pkg/machine/machine.go +++ b/pkg/machine/machine.go @@ -3,7 +3,6 @@ package machine import ( "context" "encoding/json" - "fmt" "github.com/formancehq/ledger/pkg/core" "github.com/formancehq/ledger/pkg/machine/vm" @@ -26,67 +25,20 @@ func Run(ctx context.Context, store Store, prog *program.Program, script core.Ru m := vm.NewMachine(*prog) if err := m.SetVarsFromJSON(script.Vars); err != nil { - return nil, NewScriptError(ScriptErrorCompilationFailed, + return nil, vm.NewScriptError(vm.ScriptErrorCompilationFailed, errors.Wrap(err, "could not set variables").Error()) } - resourcesChan, err := m.ResolveResources() + err := m.ResolveResources(ctx, store) if err != nil { - return nil, errors.Wrap(err, "could not resolve program resources") - } - for req := range resourcesChan { - if req.Error != nil { - return nil, NewScriptError(ScriptErrorCompilationFailed, - errors.Wrap(req.Error, "could not resolve program resources").Error()) - } - account, err := store.GetAccountWithVolumes(ctx, req.Account) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("could not get account %q", req.Account)) - } - if req.Key != "" { - entry, ok := account.Metadata[req.Key] - if !ok { - return nil, NewScriptError(ScriptErrorCompilationFailed, - fmt.Sprintf("missing key %v in metadata for account %v", req.Key, req.Account)) - } - data, err := json.Marshal(entry) - if err != nil { - return nil, errors.Wrap(err, "marshaling metadata") - } - value, err := core.NewValueFromTypedJSON(data) - if err != nil { - return nil, NewScriptError(ScriptErrorCompilationFailed, - errors.Wrap(err, fmt.Sprintf("invalid format for metadata at key %v for account %v", - req.Key, req.Account)).Error()) - } - req.Response <- *value - } else if req.Asset != "" { - amt := account.Volumes[req.Asset].Balance().OrZero() - resp := *amt - req.Response <- &resp - } else { - return nil, NewScriptError(ScriptErrorCompilationFailed, - errors.Wrap(err, fmt.Sprintf("invalid ResourceRequest: %+v", req)).Error()) - } + return nil, vm.NewScriptError(vm.ScriptErrorCompilationFailed, + errors.Wrap(err, "could not resolve program resources").Error()) } - balanceCh, err := m.ResolveBalances() + err = m.ResolveBalances(ctx, store) if err != nil { - return nil, errors.Wrap(err, "could not resolve balances") - } - for req := range balanceCh { - if req.Error != nil { - return nil, NewScriptError(ScriptErrorCompilationFailed, - errors.Wrap(req.Error, "could not resolve program balances").Error()) - } - var amt *core.MonetaryInt - account, err := store.GetAccountWithVolumes(ctx, req.Account) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("could not get account %q", req.Account)) - } - amt = account.Volumes[req.Asset].Balance().OrZero() - resp := *amt - req.Response <- &resp + return nil, vm.NewScriptError(vm.ScriptErrorCompilationFailed, + errors.Wrap(err, "could not resolve balances").Error()) } exitCode, err := m.Execute() @@ -103,7 +55,7 @@ func Run(ctx context.Context, store Store, prog *program.Program, script core.Ru case vm.EXIT_FAIL_INSUFFICIENT_FUNDS: // TODO: If the machine can provide the asset which is failing // we should be able to use InsufficientFundError{} instead of error code - return nil, NewScriptError(ScriptErrorInsufficientFund, + return nil, vm.NewScriptError(vm.ScriptErrorInsufficientFund, "account had insufficient funds") default: return nil, errors.New("script execution failed") @@ -136,7 +88,7 @@ func Run(ctx context.Context, store Store, prog *program.Program, script core.Ru for k, v := range script.Metadata { _, ok := result.Metadata[k] if ok { - return nil, NewScriptError(ScriptErrorMetadataOverride, "cannot override metadata from script") + return nil, vm.NewScriptError(vm.ScriptErrorMetadataOverride, "cannot override metadata from script") } result.Metadata[k] = v } diff --git a/pkg/machine/machine_test.go b/pkg/machine/machine_test.go index a0e03ad16..8a0612547 100644 --- a/pkg/machine/machine_test.go +++ b/pkg/machine/machine_test.go @@ -9,6 +9,7 @@ import ( "github.com/formancehq/ledger/pkg/ledger/cache" "github.com/formancehq/ledger/pkg/ledgertesting" "github.com/formancehq/ledger/pkg/machine/script/compiler" + "github.com/formancehq/ledger/pkg/machine/vm" "github.com/formancehq/ledger/pkg/storage" "github.com/formancehq/stack/libs/go-libs/pgtesting" "github.com/google/uuid" @@ -21,7 +22,6 @@ type testCase struct { vars map[string]json.RawMessage expectErrorCode string expectResult Result - expectSources []string setup func(t *testing.T, store storage.LedgerStore) metadata core.Metadata } @@ -41,7 +41,6 @@ var testCases = []testCase{ Metadata: map[string]any{}, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"world"}, }, { name: "not enough funds", @@ -50,8 +49,7 @@ var testCases = []testCase{ source = @bank destination = @user:001 )`, - expectErrorCode: ScriptErrorInsufficientFund, - expectSources: []string{"bank"}, + expectErrorCode: vm.ScriptErrorInsufficientFund, }, { name: "send 0$", @@ -68,7 +66,6 @@ var testCases = []testCase{ Metadata: map[string]any{}, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"world"}, }, { name: "send all available", @@ -85,7 +82,6 @@ var testCases = []testCase{ Metadata: map[string]any{}, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"alice"}, }, { name: "with variable", @@ -101,7 +97,6 @@ var testCases = []testCase{ vars: map[string]json.RawMessage{ "dest": json.RawMessage(`"user:001"`), }, - expectSources: []string{"world"}, expectResult: Result{ Postings: []core.Posting{ core.NewPosting("world", "user:001", "CAD/2", core.NewMonetaryInt(42)), @@ -122,8 +117,7 @@ var testCases = []testCase{ destination = $dest )`, vars: map[string]json.RawMessage{}, - expectErrorCode: ScriptErrorCompilationFailed, - expectSources: []string{"world"}, + expectErrorCode: vm.ScriptErrorCompilationFailed, }, { name: "use empty account", @@ -155,7 +149,6 @@ var testCases = []testCase{ Metadata: map[string]any{}, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"", "world", "bob"}, }, { name: "missing metadata", @@ -172,8 +165,7 @@ var testCases = []testCase{ vars: map[string]json.RawMessage{ "sale": json.RawMessage(`"sales:042"`), }, - expectErrorCode: ScriptErrorCompilationFailed, - expectSources: []string{"sales:042"}, + expectErrorCode: vm.ScriptErrorCompilationFailed, }, { name: "using metadata", @@ -225,7 +217,6 @@ var testCases = []testCase{ Metadata: core.Metadata{}, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"sales:001"}, }, { name: "defining metadata from input", @@ -246,7 +237,6 @@ var testCases = []testCase{ }, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"world"}, }, { name: "defining metadata from script", @@ -268,7 +258,6 @@ var testCases = []testCase{ }, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"world"}, }, { name: "override metadata from script", @@ -281,8 +270,7 @@ var testCases = []testCase{ metadata: core.Metadata{ "priority": "low", }, - expectErrorCode: ScriptErrorMetadataOverride, - expectSources: []string{"world"}, + expectErrorCode: vm.ScriptErrorMetadataOverride, }, { name: "set account meta", @@ -312,7 +300,6 @@ var testCases = []testCase{ }, }, }, - expectSources: []string{"world"}, }, { name: "balance function", @@ -342,7 +329,6 @@ var testCases = []testCase{ Metadata: core.Metadata{}, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"users:001"}, }, { name: "balance function with negative balance", @@ -365,8 +351,7 @@ var testCases = []testCase{ source = @users:001 destination = @world )`, - expectErrorCode: ScriptErrorCompilationFailed, - expectSources: []string{"users:001"}, + expectErrorCode: vm.ScriptErrorCompilationFailed, }, { name: "overdraft", @@ -382,7 +367,6 @@ var testCases = []testCase{ Metadata: core.Metadata{}, AccountMetadata: map[string]core.Metadata{}, }, - expectSources: []string{"users:001"}, }, } @@ -420,10 +404,6 @@ func TestMachine(t *testing.T) { prog, err := compiler.Compile(tc.script) require.NoError(t, err) - involvedSources, err := prog.GetInvolvedSources(tc.vars) - require.NoError(t, err) - require.Equal(t, tc.expectSources, involvedSources) - result, err := Run(context.Background(), cache, prog, core.RunScript{ Script: core.Script{ Plain: tc.script, @@ -432,7 +412,7 @@ func TestMachine(t *testing.T) { Metadata: tc.metadata, }) if tc.expectErrorCode != "" { - require.True(t, IsScriptErrorWithCode(err, tc.expectErrorCode)) + require.True(t, vm.IsScriptErrorWithCode(err, tc.expectErrorCode)) } else { require.NoError(t, err) require.NotNil(t, result) diff --git a/pkg/machine/errors.go b/pkg/machine/vm/errors.go similarity index 98% rename from pkg/machine/errors.go rename to pkg/machine/vm/errors.go index d28f7f381..4420b90cf 100644 --- a/pkg/machine/errors.go +++ b/pkg/machine/vm/errors.go @@ -1,4 +1,4 @@ -package machine +package vm import ( "fmt" diff --git a/pkg/machine/vm/machine.go b/pkg/machine/vm/machine.go index 176a728d1..7192c7739 100644 --- a/pkg/machine/vm/machine.go +++ b/pkg/machine/vm/machine.go @@ -9,15 +9,16 @@ Provides `Machine`, which executes programs and outputs postings. package vm import ( + "context" "encoding/binary" "encoding/json" - "errors" "fmt" "math/big" "github.com/formancehq/ledger/pkg/core" "github.com/formancehq/ledger/pkg/machine/vm/program" "github.com/logrusorgru/aurora" + "github.com/pkg/errors" ) const ( @@ -483,75 +484,46 @@ type BalanceRequest struct { Error error } -func (m *Machine) ResolveBalances() (chan BalanceRequest, error) { +func (m *Machine) ResolveBalances(ctx context.Context, store Store) error { if len(m.Resources) != len(m.UnresolvedResources) { - return nil, errors.New("tried to resolve balances before resources") + return errors.New("tried to resolve balances before resources") } if m.setBalanceCalled { - return nil, errors.New("tried to call ResolveBalances twice") + return errors.New("tried to call ResolveBalances twice") } m.setBalanceCalled = true - resChan := make(chan BalanceRequest) - go func() { - defer close(resChan) - m.Balances = make(map[core.AccountAddress]map[core.Asset]*core.MonetaryInt) - // for every account that we need balances of, check if it's there - for addr, neededAssets := range m.Program.NeededBalances { - account, ok := m.getResource(addr) + m.Balances = make(map[core.AccountAddress]map[core.Asset]*core.MonetaryInt) + // for every account that we need balances of, check if it's there + for addr, neededAssets := range m.Program.NeededBalances { + account, ok := m.getResource(addr) + if !ok { + return errors.New("invalid program (resolve balances: invalid address of account)") + } + accountAddress := (*account).(core.AccountAddress) + m.Balances[accountAddress] = make(map[core.Asset]*core.MonetaryInt) + // for every asset, send request + for addr := range neededAssets { + mon, ok := m.getResource(addr) if !ok { - resChan <- BalanceRequest{ - Error: errors.New("invalid program (resolve balances: invalid address of account)"), - } - return + return errors.New("invalid program (resolve balances: invalid address of monetary)") } - if account, ok := (*account).(core.AccountAddress); ok { - m.Balances[account] = make(map[core.Asset]*core.MonetaryInt) - // for every asset, send request - for addr := range neededAssets { - mon, ok := m.getResource(addr) - if !ok { - resChan <- BalanceRequest{ - Error: errors.New("invalid program (resolve balances: invalid address of monetary)"), - } - return - } - if ha, ok := (*mon).(core.HasAsset); ok { - asset := ha.GetAsset() - if string(account) == "world" { - m.Balances[account][asset] = core.NewMonetaryInt(0) - continue - } - respChan := make(chan *core.MonetaryInt) - resChan <- BalanceRequest{ - Account: string(account), - Asset: string(asset), - Response: respChan, - } - resp, ok := <-respChan - close(respChan) - if !ok { - resChan <- BalanceRequest{ - Error: errors.New("error on response channel"), - } - return - } - m.Balances[account][asset] = resp - } else { - resChan <- BalanceRequest{ - Error: errors.New("invalid program (resolve balances: not an asset)"), - } - return - } - } - } else { - resChan <- BalanceRequest{ - Error: errors.New("incorrect program (resolve balances: not an account)"), - } - return + + asset := (*mon).(core.HasAsset).GetAsset() + if string(accountAddress) == "world" { + m.Balances[accountAddress][asset] = core.NewMonetaryInt(0) + continue + } + + account, err := store.GetAccountWithVolumes(ctx, string(accountAddress)) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get account %q", account)) } + amt := account.Volumes[string(asset)].Balance().OrZero() + + m.Balances[accountAddress][asset] = amt } - }() - return resChan, nil + } + return nil } type ResourceRequest struct { @@ -562,172 +534,123 @@ type ResourceRequest struct { Error error } -func (m *Machine) ResolveResources() (chan ResourceRequest, error) { +func (m *Machine) ResolveResources(ctx context.Context, store Store) error { if m.resolveCalled { - return nil, errors.New("tried to call ResolveResources twice") + return errors.New("tried to call ResolveResources twice") } + m.resolveCalled = true - resChan := make(chan ResourceRequest) - go func() { - defer close(resChan) - for len(m.Resources) != len(m.UnresolvedResources) { - idx := len(m.Resources) - res := m.UnresolvedResources[idx] - var val core.Value - switch res := res.(type) { - case program.Constant: - val = res.Inner - case program.Variable: - var ok bool - val, ok = m.Vars[res.Name] - if !ok { - resChan <- ResourceRequest{ - Error: fmt.Errorf("missing variable '%s'", res.Name), - } - return - } - case program.VariableAccountMetadata: - sourceAccount, ok := m.getResource(res.Account) - if !ok { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request metadata of an account which has not yet been solved", - res.Name), - } - return - } - if (*sourceAccount).GetType() != core.TypeAccount { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request metadata on wrong entity: %v instead of account", - res.Name, (*sourceAccount).GetType()), - } - return - } - account := (*sourceAccount).(core.AccountAddress) - resp := make(chan core.Value) - resChan <- ResourceRequest{ - Account: string(account), - Key: res.Key, - Response: resp, - } - val = <-resp - close(resp) - if val == nil { - resChan <- ResourceRequest{ - Error: fmt.Errorf("variable '%s': tried to set nil as resource", res.Name), - } - return - } - if val.GetType() != res.Typ { - resChan <- ResourceRequest{ - Error: fmt.Errorf("variable '%s': wrong type: expected %v, got %v", - res.Name, res.Typ, val.GetType()), - } - return - } - case program.VariableAccountBalance: - acc, ok := m.getResource(res.Account) - if !ok { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request balance of an account which has not yet been solved", - res.Name), - } - return - } - if (*acc).GetType() != core.TypeAccount { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request balance on wrong entity: %v instead of account", - res.Name, (*acc).GetType()), - } - return - } - account := (*acc).(core.AccountAddress) - - ass, ok := m.getResource(res.Asset) - if !ok { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request balance of an account for an asset which has not yet been solved", - res.Name), - } - return - } - if (*ass).GetType() != core.TypeAsset { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request account balance on wrong entity: %v instead of asset", - res.Name, (*ass).GetType()), - } - return - } - asset := (*ass).(core.Asset) - resp := make(chan core.Value) - resChan <- ResourceRequest{ - Account: string(account), - Asset: string(asset), - Response: resp, - } - amount := <-resp - close(resp) - if amount == nil { - resChan <- ResourceRequest{ - Error: fmt.Errorf("variable '%s': received nil amount", res.Name), - } - return - } - if amount.GetType() != core.TypeNumber { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request balance: wrong type received: expected %v, got %v", - res.Name, core.TypeNumber, amount.GetType()), - } - return - } - amt := amount.(core.Number) - if amt.Ltz() { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "variable '%s': tried to request the balance of account %s for asset %s: received %s: monetary amounts must be non-negative", - res.Name, account, asset, amt), - } - return - } - val = core.Monetary{ - Asset: asset, - Amount: amt, - } - case program.Monetary: - ass, ok := m.getResource(res.Asset) - if !ok { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "tried to resolve an asset which has not yet been solved"), - } - return - } - if (*ass).GetType() != core.TypeAsset { - resChan <- ResourceRequest{ - Error: fmt.Errorf( - "tried to resolve an asset on wrong type '%v'", - (*ass).GetType()), - } - return - } - asset := (*ass).(core.Asset) - val = core.Monetary{ - Asset: asset, - Amount: res.Amount, - } - default: - panic(fmt.Errorf("type %T not implemented", res)) + for len(m.Resources) != len(m.UnresolvedResources) { + idx := len(m.Resources) + res := m.UnresolvedResources[idx] + var val core.Value + switch res := res.(type) { + case program.Constant: + val = res.Inner + case program.Variable: + var ok bool + val, ok = m.Vars[res.Name] + if !ok { + return fmt.Errorf("missing variable '%s'", res.Name) + } + case program.VariableAccountMetadata: + sourceAccount, ok := m.getResource(res.Account) + if !ok { + return fmt.Errorf( + "variable '%s': tried to request metadata of an account which has not yet been solved", + res.Name) + } + if (*sourceAccount).GetType() != core.TypeAccount { + return fmt.Errorf( + "variable '%s': tried to request metadata on wrong entity: %v instead of account", + res.Name, (*sourceAccount).GetType()) + } + + address := string((*sourceAccount).(core.AccountAddress)) + account, err := store.GetAccountWithVolumes(ctx, address) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get account %s", address)) + } + + entry, ok := account.Metadata[res.Key] + if !ok { + return NewScriptError(ScriptErrorCompilationFailed, + fmt.Sprintf("missing key %v in metadata for account %s", res.Key, address)) + } + + data, err := json.Marshal(entry) + if err != nil { + return errors.Wrap(err, "marshaling metadata") + } + val, err = core.NewValueFromTypedJSON(data) + if err != nil { + return NewScriptError(ScriptErrorCompilationFailed, + errors.Wrap(err, fmt.Sprintf("invalid format for metadata at key %v for account %s", + res.Key, address)).Error()) + } + case program.VariableAccountBalance: + sourceAccount, ok := m.getResource(res.Account) + if !ok { + return fmt.Errorf( + "variable '%s': tried to request metadata of an account which has not yet been solved", + res.Name) + } + if (*sourceAccount).GetType() != core.TypeAccount { + return fmt.Errorf( + "variable '%s': tried to request balance of an account which has not yet been solved", + res.Name) + } + + address := string((*sourceAccount).(core.AccountAddress)) + account, err := store.GetAccountWithVolumes(ctx, address) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get account %s", address)) + } + + ass, ok := m.getResource(res.Asset) + if !ok { + return fmt.Errorf( + "variable '%s': tried to request balance of an account for an asset which has not yet been solved", + res.Name) } - m.Resources = append(m.Resources, val) + if (*ass).GetType() != core.TypeAsset { + return fmt.Errorf( + "variable '%s': tried to request account balance on wrong entity: %v instead of asset", + res.Name, (*ass).GetType()) + } + + amt := account.Volumes[string((*ass).(core.Asset))].Balance().OrZero() + if amt.Ltz() { + return fmt.Errorf( + "variable '%s': tried to request the balance of account %s for asset %s: received %s: monetary amounts must be non-negative", + res.Name, address, string((*ass).(core.Asset)), amt) + } + + val = core.Monetary{ + Asset: (*ass).(core.Asset), + Amount: amt, + } + case program.Monetary: + ass, ok := m.getResource(res.Asset) + if !ok { + return fmt.Errorf("tried to resolve an asset which has not yet been solved") + } + if (*ass).GetType() != core.TypeAsset { + return fmt.Errorf( + "tried to resolve an asset on wrong type '%v'", + (*ass).GetType()) + } + asset := (*ass).(core.Asset) + val = core.Monetary{ + Asset: asset, + Amount: res.Amount, + } + default: + panic(fmt.Errorf("type %T not implemented", res)) } - }() - return resChan, nil + m.Resources = append(m.Resources, val) + } + return nil } func (m *Machine) SetVars(vars map[string]core.Value) error { diff --git a/pkg/machine/vm/machine_test.go b/pkg/machine/vm/machine_test.go index 054967719..184ee6085 100644 --- a/pkg/machine/vm/machine_test.go +++ b/pkg/machine/vm/machine_test.go @@ -1,7 +1,9 @@ package vm import ( + "context" "encoding/json" + "fmt" "math/big" "sync" "testing" @@ -79,37 +81,44 @@ func test(t *testing.T, testCase TestCase) { return 0, err } - { - ch, err := m.ResolveResources() - if err != nil { - return 0, err - } - for req := range ch { - if req.Key != "" { - val := testCase.meta[req.Account][req.Key] - req.Response <- val - } else if req.Asset != "" { - val := testCase.balances[req.Account][req.Asset] - req.Response <- val - } - if req.Error != nil { - return 0, req.Error + store := StoreFn(func(ctx context.Context, address string) (*core.AccountWithVolumes, error) { + m := core.Metadata{} + for s, value := range testCase.meta[address] { + json, err := core.NewJSONFromValue(value) + require.NoError(t, err) + + m[s] = map[string]any{ + "type": value.GetType().String(), + "value": json, } } + return &core.AccountWithVolumes{ + Account: core.Account{ + Address: address, + Metadata: m, + }, + Volumes: func() core.AssetsVolumes { + ret := make(core.AssetsVolumes, 0) + for asset, balance := range testCase.balances[address] { + if balance.Gt(core.NewMonetaryInt(0)) { + ret[asset] = core.NewEmptyVolumes().WithInput(balance) + } else { + ret[asset] = core.NewEmptyVolumes().WithOutput(balance.Neg()) + } + } + return ret + }(), + }, nil + }) + + err := m.ResolveResources(context.Background(), store) + if err != nil { + return 128, err } - { - ch, err := m.ResolveBalances() - if err != nil { - return 0, err - } - for req := range ch { - val := testCase.balances[req.Account][req.Asset] - req.Response <- val - if req.Error != nil { - return 0, req.Error - } - } + err = m.ResolveBalances(context.Background(), store) + if err != nil { + return 128, err } return m.Execute() @@ -136,7 +145,7 @@ func testImpl(t *testing.T, prog *program.Program, expected CaseResult, exec fun } exitCode, err := exec(m) - require.Equal(t, expected.ExitCode, exitCode, err) + require.Equal(t, expected.ExitCode, exitCode) if expected.Error != "" { require.ErrorContains(t, err, expected.Error) } else { @@ -212,22 +221,14 @@ func TestVariables(t *testing.T) { tc.compile(t, `vars { account $rider account $driver - string $description - number $nb - asset $ass } - send [$ass 999] ( + send [EUR/2 999] ( source=$rider destination=$driver - ) - set_tx_meta("description", $description) - set_tx_meta("ride", $nb)`) + )`) tc.vars = map[string]core.Value{ - "rider": core.AccountAddress("users:001"), - "driver": core.AccountAddress("users:002"), - "description": core.String("midnight ride"), - "nb": core.NewMonetaryInt(1), - "ass": core.Asset("EUR/2"), + "rider": core.AccountAddress("users:001"), + "driver": core.AccountAddress("users:002"), } tc.setBalance("users:001", "EUR/2", 1000) tc.expected = CaseResult{ @@ -240,10 +241,6 @@ func TestVariables(t *testing.T) { Destination: "users:002", }, }, - Metadata: map[string]core.Value{ - "description": core.String("midnight ride"), - "ride": core.NewMonetaryInt(1), - }, ExitCode: EXIT_OK, } } @@ -255,9 +252,8 @@ func TestVariablesJSON(t *testing.T) { account $driver string $description number $nb - asset $ass } - send [$ass 999] ( + send [EUR/2 999] ( source=$rider destination=$driver ) @@ -267,8 +263,7 @@ func TestVariablesJSON(t *testing.T) { "rider": "users:001", "driver": "users:002", "description": "midnight ride", - "nb": 1, - "ass": "EUR/2" + "nb": 1 }`) tc.setBalance("users:001", "EUR/2", 1000) tc.expected = CaseResult{ @@ -942,47 +937,22 @@ func TestNeededBalances(t *testing.T) { if err != nil { t.Fatalf("did not expect error on SetVars, got: %v", err) } - { - ch, err := m.ResolveResources() - if err != nil { - t.Fatalf("did not expect error on ResolveResources, got: %v", err) - } - for range ch { - t.Fatalf("did not expect to need any metadata") - } - } + err = m.ResolveResources(context.Background(), EmptyStore) + require.NoError(t, err) - expected := map[string]map[string]struct{}{ - "a": { - "GEM": {}, - }, - "b": { - "GEM": {}, - }, - } - { - ch, err := m.ResolveBalances() - if err != nil { - t.Fatalf("did not expect error on ResolveBalances, got: %v", err) - } - for req := range ch { - if req.Error != nil { - t.Fatalf("did not expect error in balance request: %v", req.Error) - } - if _, ok := expected[req.Account][req.Asset]; ok { - delete(expected[req.Account], req.Asset) - if len(expected[req.Account]) == 0 { - delete(expected, req.Account) - } - req.Response <- core.NewMonetaryInt(0) - } else { - t.Fatalf("did not expect to need %v balance of %v", req.Asset, req.Account) - } - } - } - if len(expected) != 0 { - t.Fatalf("some balances were not requested: %v", expected) - } + called := make(map[string]*struct{}) + err = m.ResolveBalances(context.Background(), StoreFn(func(ctx context.Context, address string) (*core.AccountWithVolumes, error) { + called[address] = &struct{}{} + return &core.AccountWithVolumes{ + Account: core.Account{ + Address: address, + Metadata: core.Metadata{}, + }, + Volumes: map[string]core.Volumes{}, + }, nil + })) + require.NotNil(t, called["a"]) + require.NotNil(t, called["b"]) } func TestSetTxMeta(t *testing.T) { @@ -998,21 +968,10 @@ func TestSetTxMeta(t *testing.T) { m := NewMachine(*p) - { - ch, err := m.ResolveResources() - require.NoError(t, err) - for req := range ch { - require.NoError(t, req.Error) - } - } - - { - ch, err := m.ResolveBalances() - require.NoError(t, err) - for req := range ch { - require.NoError(t, req.Error) - } - } + err = m.ResolveResources(context.Background(), EmptyStore) + require.NoError(t, err) + err = m.ResolveBalances(context.Background(), EmptyStore) + require.NoError(t, err) exitCode, err := m.Execute() require.NoError(t, err) @@ -1048,21 +1007,11 @@ func TestSetAccountMeta(t *testing.T) { m := NewMachine(*p) - { - ch, err := m.ResolveResources() - require.NoError(t, err) - for req := range ch { - require.NoError(t, req.Error) - } - } + err = m.ResolveResources(context.Background(), EmptyStore) + require.NoError(t, err) - { - ch, err := m.ResolveBalances() - require.NoError(t, err) - for req := range ch { - require.NoError(t, req.Error) - } - } + err = m.ResolveBalances(context.Background(), EmptyStore) + require.NoError(t, err) exitCode, err := m.Execute() require.NoError(t, err) @@ -1109,21 +1058,11 @@ func TestSetAccountMeta(t *testing.T) { "acc": core.AccountAddress("test"), })) - { - ch, err := m.ResolveResources() - require.NoError(t, err) - for req := range ch { - require.NoError(t, req.Error) - } - } + err = m.ResolveResources(context.Background(), EmptyStore) + require.NoError(t, err) - { - ch, err := m.ResolveBalances() - require.NoError(t, err) - for req := range ch { - require.NoError(t, req.Error) - } - } + err = m.ResolveBalances(context.Background(), EmptyStore) + require.NoError(t, err) exitCode, err := m.Execute() require.NoError(t, err) @@ -1341,7 +1280,8 @@ func TestVariableBalance(t *testing.T) { tc.compile(t, script) tc.setBalance("world", "USD/2", -40) tc.expected = CaseResult{ - Error: "must be non-negative", + ExitCode: 128, + Error: "must be non-negative", } test(t, tc) }) @@ -1376,34 +1316,6 @@ func TestVariablesParsing(t *testing.T) { })) }) - t.Run("asset", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - asset $ass - } - set_tx_meta("asset", $ass) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.NoError(t, m.SetVars(map[string]core.Value{ - "ass": core.Asset("USD/2"), - })) - - require.Error(t, m.SetVars(map[string]core.Value{ - "ass": core.Asset("USD-2"), - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]json.RawMessage{ - "ass": json.RawMessage(`"USD/2"`), - })) - - require.Error(t, m.SetVarsFromJSON(map[string]json.RawMessage{ - "ass": json.RawMessage(`"USD-2"`), - })) - }) - // TODO: handle properly in ledger v1.10 t.Run("account empty string", func(t *testing.T) { p, err := compiler.Compile(` @@ -1679,17 +1591,11 @@ func TestMachine(t *testing.T) { }) require.NoError(t, err) - ch1, err := m.ResolveResources() + err := m.ResolveResources(context.Background(), EmptyStore) require.NoError(t, err) - for req := range ch1 { - require.NoError(t, req.Error) - } - ch2, err := m.ResolveBalances() + err = m.ResolveBalances(context.Background(), EmptyStore) require.NoError(t, err) - for req := range ch2 { - require.NoError(t, req.Error) - } exitCode, err := m.Execute() require.NoError(t, err) @@ -1711,11 +1617,8 @@ func TestMachine(t *testing.T) { }) require.NoError(t, err) - ch1, err := m.ResolveResources() + err := m.ResolveResources(context.Background(), EmptyStore) require.NoError(t, err) - for req := range ch1 { - require.NoError(t, req.Error) - } exitCode, err := m.Execute() require.ErrorContains(t, err, "balances haven't been initialized") @@ -1730,20 +1633,17 @@ func TestMachine(t *testing.T) { }) require.NoError(t, err) - ch1, err := m.ResolveResources() + err := m.ResolveResources(context.Background(), EmptyStore) require.NoError(t, err) - for req := range ch1 { - require.NoError(t, req.Error) - } - _, err = m.ResolveResources() + err = m.ResolveResources(context.Background(), EmptyStore) require.ErrorContains(t, err, "tried to call ResolveResources twice") }) t.Run("err balances before resources", func(t *testing.T) { m := NewMachine(*p) - _, err := m.ResolveBalances() + err := m.ResolveBalances(context.Background(), EmptyStore) require.ErrorContains(t, err, "tried to resolve balances before resources") }) @@ -1755,94 +1655,969 @@ func TestMachine(t *testing.T) { }) require.NoError(t, err) - ch1, err := m.ResolveResources() + err := m.ResolveResources(context.Background(), EmptyStore) require.NoError(t, err) - for req := range ch1 { - require.NoError(t, req.Error) - } - ch2, err := m.ResolveBalances() + err = m.ResolveBalances(context.Background(), EmptyStore) require.NoError(t, err) - for req := range ch2 { - require.NoError(t, req.Error) - } - _, err = m.ResolveBalances() + err = m.ResolveBalances(context.Background(), EmptyStore) require.ErrorContains(t, err, "tried to call ResolveBalances twice") }) t.Run("err missing var", func(t *testing.T) { m := NewMachine(*p) - ch1, err := m.ResolveResources() - require.NoError(t, err) - for req := range ch1 { - require.ErrorContains(t, req.Error, "missing variable 'dest'") - } + err := m.ResolveResources(context.Background(), EmptyStore) + require.Error(t, err) }) } -func TestVariableAsset(t *testing.T) { - script := ` - vars { - asset $ass - monetary $bal = balance(@alice, $ass) - } +func TestIsScriptErrorWithCode(t *testing.T) { + type args struct { + err error + code string + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, IsScriptErrorWithCode(tt.args.err, tt.args.code), "IsScriptErrorWithCode(%v, %v)", tt.args.err, tt.args.code) + }) + } +} - send [$ass 15] ( - source = { - @alice - @bob +func TestMachine_Execute(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + tests := []struct { + name string + fields fields + want byte + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, } - destination = @swap - ) - - send [$ass *] ( - source = @swap - destination = { - max $bal to @alice_2 - remaining to @bob_2 + got, err := m.Execute() + if !tt.wantErr(t, err, "Execute()") { + return } - )` + assert.Equalf(t, tt.want, got, "Execute()") + }) + } +} - tc := NewTestCase() - tc.compile(t, script) - tc.vars = map[string]core.Value{ - "ass": core.Asset("USD"), +func TestMachine_GetAccountsMetaJSON(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool } - tc.setBalance("alice", "USD", 10) - tc.setBalance("bob", "USD", 10) - tc.expected = CaseResult{ - Printed: []core.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: core.NewMonetaryInt(10), - Source: "alice", - Destination: "swap", - }, - { - Asset: "USD", - Amount: core.NewMonetaryInt(5), - Source: "bob", - Destination: "swap", - }, - { - Asset: "USD", - Amount: core.NewMonetaryInt(10), - Source: "swap", - Destination: "alice_2", - }, - { - Asset: "USD", - Amount: core.NewMonetaryInt(5), - Source: "swap", - Destination: "bob_2", - }, - }, - ExitCode: EXIT_OK, + tests := []struct { + name string + fields fields + want Metadata + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + assert.Equalf(t, tt.want, m.GetAccountsMetaJSON(), "GetAccountsMetaJSON()") + }) + } +} + +func TestMachine_GetTxMetaJSON(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + tests := []struct { + name string + fields fields + want Metadata + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + assert.Equalf(t, tt.want, m.GetTxMetaJSON(), "GetTxMetaJSON()") + }) + } +} + +func TestMachine_ResolveBalances(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + ctx context.Context + store Store + } + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + tt.wantErr(t, m.ResolveBalances(tt.args.ctx, tt.args.store), fmt.Sprintf("ResolveBalances(%v, %v)", tt.args.ctx, tt.args.store)) + }) + } +} + +func TestMachine_ResolveResources(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + ctx context.Context + store Store + } + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + tt.wantErr(t, m.ResolveResources(tt.args.ctx, tt.args.store), fmt.Sprintf("ResolveResources(%v, %v)", tt.args.ctx, tt.args.store)) + }) + } +} + +func TestMachine_SetVars(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + vars map[string]core.Value + } + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + tt.wantErr(t, m.SetVars(tt.args.vars), fmt.Sprintf("SetVars(%v)", tt.args.vars)) + }) + } +} + +func TestMachine_SetVarsFromJSON(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + vars map[string]json.RawMessage + } + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + tt.wantErr(t, m.SetVarsFromJSON(tt.args.vars), fmt.Sprintf("SetVarsFromJSON(%v)", tt.args.vars)) + }) + } +} + +func TestMachine_credit(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + account core.AccountAddress + funding core.Funding + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + m.credit(tt.args.account, tt.args.funding) + }) + } +} + +func TestMachine_getResource(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + addr core.Address + } + tests := []struct { + name string + fields fields + args args + want *core.Value + want1 bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + got, got1 := m.getResource(tt.args.addr) + assert.Equalf(t, tt.want, got, "getResource(%v)", tt.args.addr) + assert.Equalf(t, tt.want1, got1, "getResource(%v)", tt.args.addr) + }) + } +} + +func TestMachine_popValue(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + tests := []struct { + name string + fields fields + want core.Value + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + assert.Equalf(t, tt.want, m.popValue(), "popValue()") + }) + } +} + +func TestMachine_pushValue(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + v core.Value + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + m.pushValue(tt.args.v) + }) + } +} + +func TestMachine_repay(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + funding core.Funding + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + m.repay(tt.args.funding) + }) + } +} + +func TestMachine_tick(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + tests := []struct { + name string + fields fields + want bool + want1 byte + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + got, got1 := m.tick() + assert.Equalf(t, tt.want, got, "tick()") + assert.Equalf(t, tt.want1, got1, "tick()") + }) + } +} + +func TestMachine_withdrawAll(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + account core.AccountAddress + asset core.Asset + overdraft *core.MonetaryInt + } + tests := []struct { + name string + fields fields + args args + want *core.Funding + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + got, err := m.withdrawAll(tt.args.account, tt.args.asset, tt.args.overdraft) + if !tt.wantErr(t, err, fmt.Sprintf("withdrawAll(%v, %v, %v)", tt.args.account, tt.args.asset, tt.args.overdraft)) { + return + } + assert.Equalf(t, tt.want, got, "withdrawAll(%v, %v, %v)", tt.args.account, tt.args.asset, tt.args.overdraft) + }) + } +} + +func TestMachine_withdrawAlways(t *testing.T) { + type fields struct { + P uint + Program program.Program + Vars map[string]core.Value + UnresolvedResources []program.Resource + Resources []core.Value + resolveCalled bool + Balances map[core.AccountAddress]map[core.Asset]*core.MonetaryInt + setBalanceCalled bool + Stack []core.Value + Postings []Posting + TxMeta map[string]core.Value + AccountsMeta map[core.AccountAddress]map[string]core.Value + Printer func(chan core.Value) + printChan chan core.Value + Debug bool + } + type args struct { + account core.AccountAddress + mon core.Monetary + } + tests := []struct { + name string + fields fields + args args + want *core.Funding + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Machine{ + P: tt.fields.P, + Program: tt.fields.Program, + Vars: tt.fields.Vars, + UnresolvedResources: tt.fields.UnresolvedResources, + Resources: tt.fields.Resources, + resolveCalled: tt.fields.resolveCalled, + Balances: tt.fields.Balances, + setBalanceCalled: tt.fields.setBalanceCalled, + Stack: tt.fields.Stack, + Postings: tt.fields.Postings, + TxMeta: tt.fields.TxMeta, + AccountsMeta: tt.fields.AccountsMeta, + Printer: tt.fields.Printer, + printChan: tt.fields.printChan, + Debug: tt.fields.Debug, + } + got, err := m.withdrawAlways(tt.args.account, tt.args.mon) + if !tt.wantErr(t, err, fmt.Sprintf("withdrawAlways(%v, %v)", tt.args.account, tt.args.mon)) { + return + } + assert.Equalf(t, tt.want, got, "withdrawAlways(%v, %v)", tt.args.account, tt.args.mon) + }) + } +} + +func TestNewMachine(t *testing.T) { + type args struct { + p program.Program + } + tests := []struct { + name string + args args + want *Machine + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, NewMachine(tt.args.p), "NewMachine(%v)", tt.args.p) + }) + } +} + +func TestNewScriptError(t *testing.T) { + type args struct { + code string + message string + } + tests := []struct { + name string + args args + want *ScriptError + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, NewScriptError(tt.args.code, tt.args.message), "NewScriptError(%v, %v)", tt.args.code, tt.args.message) + }) + } +} + +func TestScriptError_Error(t *testing.T) { + type fields struct { + Code string + Message string + } + tests := []struct { + name string + fields fields + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := ScriptError{ + Code: tt.fields.Code, + Message: tt.fields.Message, + } + assert.Equalf(t, tt.want, e.Error(), "Error()") + }) + } +} + +func TestScriptError_Is(t *testing.T) { + type fields struct { + Code string + Message string + } + type args struct { + err error + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := ScriptError{ + Code: tt.fields.Code, + Message: tt.fields.Message, + } + assert.Equalf(t, tt.want, e.Is(tt.args.err), "Is(%v)", tt.args.err) + }) + } +} + +func TestStdOutPrinter(t *testing.T) { + type args struct { + c chan core.Value + } + tests := []struct { + name string + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + StdOutPrinter(tt.args.c) + }) + } +} + +func TestStoreFn_GetAccountWithVolumes(t *testing.T) { + type args struct { + ctx context.Context + address string + } + tests := []struct { + name string + fn StoreFn + args args + want *core.AccountWithVolumes + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fn.GetAccountWithVolumes(tt.args.ctx, tt.args.address) + if !tt.wantErr(t, err, fmt.Sprintf("GetAccountWithVolumes(%v, %v)", tt.args.ctx, tt.args.address)) { + return + } + assert.Equalf(t, tt.want, got, "GetAccountWithVolumes(%v, %v)", tt.args.ctx, tt.args.address) + }) } - test(t, tc) } func TestSendWithArithmetic(t *testing.T) { diff --git a/pkg/machine/vm/program/program.go b/pkg/machine/vm/program/program.go index 9a8ad2ae3..9d7bc3707 100644 --- a/pkg/machine/vm/program/program.go +++ b/pkg/machine/vm/program/program.go @@ -102,7 +102,7 @@ func (p *Program) ParseVariablesJSON(vars map[string]json.RawMessage) (map[strin "invalid JSON value for variable $%s of type %v: %w", param.Name, param.Typ, err) } - variables[param.Name] = *val + variables[param.Name] = val delete(vars, param.Name) } } @@ -128,7 +128,7 @@ func (p *Program) GetInvolvedAccounts(vars map[string]json.RawMessage) ([]string if err != nil { return nil, err } - involvedAccountsMap[string((*value).(core.AccountAddress))] = struct{}{} + involvedAccountsMap[string((value).(core.AccountAddress))] = struct{}{} } } } @@ -157,7 +157,7 @@ func (p *Program) GetInvolvedSources(vars map[string]json.RawMessage) ([]string, if err != nil { return nil, err } - involvedSourcesMap[string((*value).(core.AccountAddress))] = struct{}{} + involvedSourcesMap[string((value).(core.AccountAddress))] = struct{}{} } } } diff --git a/pkg/machine/vm/store.go b/pkg/machine/vm/store.go new file mode 100644 index 000000000..4ff2eb281 --- /dev/null +++ b/pkg/machine/vm/store.go @@ -0,0 +1,33 @@ +package vm + +import ( + "context" + "database/sql" + + "github.com/formancehq/ledger/pkg/core" +) + +type Store interface { + GetAccountWithVolumes(ctx context.Context, address string) (*core.AccountWithVolumes, error) +} +type StoreFn func(ctx context.Context, address string) (*core.AccountWithVolumes, error) + +func (fn StoreFn) GetAccountWithVolumes(ctx context.Context, address string) (*core.AccountWithVolumes, error) { + return fn(ctx, address) +} + +var EmptyStore = StoreFn(func(ctx context.Context, address string) (*core.AccountWithVolumes, error) { + return nil, nil +}) + +type StaticStore map[string]*core.AccountWithVolumes + +func (s StaticStore) GetAccountWithVolumes(ctx context.Context, address string) (*core.AccountWithVolumes, error) { + v, ok := s[address] + if !ok { + return nil, sql.ErrNoRows + } + return v, nil +} + +var _ Store = (*StaticStore)(nil)