package invoker import ( "errors" "testing" "github.com/google/uuid" "git.marketally.com/tutus-one/tutus-chain/pkg/core/transaction" "git.marketally.com/tutus-one/tutus-chain/pkg/tutusrpc/result" "git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract" "git.marketally.com/tutus-one/tutus-chain/pkg/util" "git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) type rpcInv struct { resInv *result.Invoke resTrm bool resItm []stackitem.Item err error } func (r *rpcInv) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) { return r.resInv, r.err } func (r *rpcInv) TerminateSession(sessionID uuid.UUID) (bool, error) { return r.resTrm, r.err } func (r *rpcInv) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) { if r.err != nil { return nil, r.err } maxItemsCount = min(maxItemsCount, len(r.resItm)) items := r.resItm[:maxItemsCount] r.resItm = r.resItm[maxItemsCount:] return items, nil } func TestInvoker(t *testing.T) { resExp := &result.Invoke{State: "HALT"} ri := &rpcInv{resExp, true, nil, nil} testInv := func(t *testing.T, inv *Invoker) { res, err := inv.Call(util.Uint160{}, "method") require.NoError(t, err) require.Equal(t, resExp, res) res, err = inv.Verify(util.Uint160{}, nil) require.NoError(t, err) require.Equal(t, resExp, res) res, err = inv.Run([]byte{1}) require.NoError(t, err) require.Equal(t, resExp, res) res, err = inv.Call(util.Uint160{}, "method") require.NoError(t, err) require.Equal(t, resExp, res) res, err = inv.Verify(util.Uint160{}, nil, "param") require.NoError(t, err) require.Equal(t, resExp, res) res, err = inv.Call(util.Uint160{}, "method", 42) require.NoError(t, err) require.Equal(t, resExp, res) _, err = inv.Verify(util.Uint160{}, nil, make(chan struct{})) require.Error(t, err) _, err = inv.Call(util.Uint160{}, "method", make(chan struct{})) require.Error(t, err) res, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, 42) require.NoError(t, err) require.Equal(t, resExp, res) _, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, make(chan struct{})) require.Error(t, err) } t.Run("standard", func(t *testing.T) { testInv(t, New(ri, nil)) }) t.Run("historic, height", func(t *testing.T) { testInv(t, NewHistoricAtHeight(100500, ri, nil)) }) t.Run("historic, state", func(t *testing.T) { testInv(t, NewHistoricWithState(util.Uint256{}, ri, nil)) }) t.Run("broken historic", func(t *testing.T) { inv := New(&historicConverter{client: ri}, nil) // It's not possible to do this from outside. require.Panics(t, func() { _, _ = inv.Call(util.Uint160{}, "method") }) require.Panics(t, func() { _, _ = inv.Verify(util.Uint160{}, nil, "param") }) require.Panics(t, func() { _, _ = inv.Run([]byte{1}) }) }) t.Run("terminate session", func(t *testing.T) { for _, inv := range []*Invoker{New(ri, nil), NewHistoricWithState(util.Uint256{}, ri, nil)} { ri.err = errors.New("") require.Error(t, inv.TerminateSession(uuid.UUID{})) ri.err = nil ri.resTrm = false require.Error(t, inv.TerminateSession(uuid.UUID{})) ri.resTrm = true require.NoError(t, inv.TerminateSession(uuid.UUID{})) } }) t.Run("traverse iterator", func(t *testing.T) { for _, inv := range []*Invoker{New(ri, nil), NewHistoricWithState(util.Uint256{}, ri, nil)} { res, err := inv.TraverseIterator(uuid.UUID{}, &result.Iterator{ Values: []stackitem.Item{stackitem.Make(42)}, }, 0) require.NoError(t, err) require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res) res, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{ Values: []stackitem.Item{stackitem.Make(42)}, }, 1) require.NoError(t, err) require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res) res, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{ Values: []stackitem.Item{stackitem.Make(42)}, }, 2) require.NoError(t, err) require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res) ri.err = errors.New("") _, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{ ID: &uuid.UUID{}, }, 2) require.Error(t, err) ri.err = nil ri.resItm = []stackitem.Item{stackitem.Make(42)} res, err = inv.TraverseIterator(uuid.UUID{}, &result.Iterator{ ID: &uuid.UUID{}, }, 2) require.NoError(t, err) require.Equal(t, []stackitem.Item{stackitem.Make(42)}, res) } }) t.Run("traverse iterator with session expansion", func(t *testing.T) { mockClient := &rpcInv{ resItm: []stackitem.Item{ stackitem.Make(1), stackitem.Make(2), stackitem.Make(3), }, err: nil, } inv := New(mockClient, nil) sessionID := uuid.New() iteratorID := uuid.New() iter := &result.Iterator{ ID: &iteratorID, Values: []stackitem.Item{stackitem.Make(10), stackitem.Make(20)}, } res, err := inv.TraverseIterator(sessionID, iter, 2) require.NoError(t, err) require.Equal(t, []stackitem.Item{stackitem.Make(10), stackitem.Make(20)}, res) res, err = inv.TraverseIterator(sessionID, iter, 2) require.NoError(t, err) require.Equal(t, []stackitem.Item{stackitem.Make(1), stackitem.Make(2)}, res) res, err = inv.TraverseIterator(sessionID, iter, 2) require.NoError(t, err) require.Equal(t, []stackitem.Item{stackitem.Make(3)}, res) mockClient.resItm = nil res, err = inv.TraverseIterator(sessionID, iter, 2) require.NoError(t, err) require.Empty(t, res) }) } func TestInvokerSigners(t *testing.T) { resExp := &result.Invoke{State: "HALT"} ri := &rpcInv{resExp, true, nil, nil} inv := New(ri, nil) require.Nil(t, inv.Signers()) s := []transaction.Signer{} inv = New(ri, s) require.Equal(t, s, inv.Signers()) s = append(s, transaction.Signer{Account: util.Uint160{1, 2, 3}, Scopes: transaction.CalledByEntry}) inv = New(ri, s) require.Equal(t, s, inv.Signers()) }