From cf39171485b70056331ecee3bca3a7e252fd8c73 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 26 Nov 2019 13:13:17 +0300 Subject: [PATCH] rpc: implement invokefunction, fix #347 Param getters were redone to return errors because otherwise bad FuncParam values could lead to panic. FuncParam itself might be not the most elegant solution, but it works good enough for now. --- docs/rpc.md | 12 ++- pkg/rpc/param.go | 91 +++++++++++++++++++-- pkg/rpc/param_test.go | 144 +++++++++++++++++++++++++++++++++- pkg/rpc/server.go | 68 +++++++++++++--- pkg/rpc/server_helper_test.go | 13 +++ pkg/rpc/server_test.go | 34 ++++++++ pkg/rpc/txBuilder.go | 121 ++++++++++++++++++++++++++++ pkg/rpc/tx_builder_test.go | 89 +++++++++++++++++++++ 8 files changed, 550 insertions(+), 22 deletions(-) create mode 100644 pkg/rpc/tx_builder_test.go diff --git a/docs/rpc.md b/docs/rpc.md index baf060fff..4c65245e8 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -55,12 +55,22 @@ which would yield the response: | `getunspents` | Yes | | `getversion` | Yes | | `invoke` | No (#346) | -| `invokefunction` | No (#347) | +| `invokefunction` | Yes | | `invokescript` | Yes | | `sendrawtransaction` | Yes | | `submitblock` | No (#344) | | `validateaddress` | Yes | +#### Implementation notices + +##### `invokefunction` + +neo-go's implementation of `invokefunction` does not return `tx` field in the +answer because that requires signing the transaction with some key in the +server which doesn't fit the model of our node-client interactions. Lacking +this signature the transaction is almost useless, so there is no point in +returning it. + ## Reference * [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification) diff --git a/pkg/rpc/param.go b/pkg/rpc/param.go index 08f50615c..1e6ee7972 100644 --- a/pkg/rpc/param.go +++ b/pkg/rpc/param.go @@ -1,10 +1,12 @@ package rpc import ( + "bytes" "encoding/hex" "encoding/json" "fmt" + "github.com/CityOfZion/neo-go/pkg/crypto" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -19,6 +21,12 @@ type ( } paramType int + // FuncParam represents a function argument parameter used in the + // invokefunction RPC method. + FuncParam struct { + Type StackParamType `json:"type"` + Value Param `json:"value"` + } ) const ( @@ -26,6 +34,7 @@ const ( stringT numberT arrayT + funcParamT ) func (p Param) String() string { @@ -33,27 +42,82 @@ func (p Param) String() string { } // GetString returns string value of the parameter. -func (p Param) GetString() string { return p.Value.(string) } +func (p Param) GetString() (string, error) { + str, ok := p.Value.(string) + if !ok { + return "", errors.New("not a string") + } + return str, nil +} // GetInt returns int value of te parameter. -func (p Param) GetInt() int { return p.Value.(int) } +func (p Param) GetInt() (int, error) { + i, ok := p.Value.(int) + if !ok { + return 0, errors.New("not an integer") + } + return i, nil +} + +// GetArray returns a slice of Params stored in the parameter. +func (p Param) GetArray() ([]Param, error) { + a, ok := p.Value.([]Param) + if !ok { + return nil, errors.New("not an array") + } + return a, nil +} // GetUint256 returns Uint256 value of the parameter. func (p Param) GetUint256() (util.Uint256, error) { - s, ok := p.Value.(string) - if !ok { - return util.Uint256{}, errors.New("must be a string") + s, err := p.GetString() + if err != nil { + return util.Uint256{}, err } return util.Uint256DecodeReverseString(s) } +// GetUint160FromHex returns Uint160 value of the parameter encoded in hex. +func (p Param) GetUint160FromHex() (util.Uint160, error) { + s, err := p.GetString() + if err != nil { + return util.Uint160{}, err + } + + scriptHashLE, err := util.Uint160DecodeString(s) + if err != nil { + return util.Uint160{}, err + } + return util.Uint160DecodeBytes(scriptHashLE.BytesReverse()) +} + +// GetUint160FromAddress returns Uint160 value of the parameter that was +// supplied as an address. +func (p Param) GetUint160FromAddress() (util.Uint160, error) { + s, err := p.GetString() + if err != nil { + return util.Uint160{}, err + } + + return crypto.Uint160DecodeAddress(s) +} + +// GetFuncParam returns current parameter as a function call parameter. +func (p Param) GetFuncParam() (FuncParam, error) { + fp, ok := p.Value.(FuncParam) + if !ok { + return FuncParam{}, errors.New("not a function parameter") + } + return fp, nil +} + // GetBytesHex returns []byte value of the parameter if // it is a hex-encoded string. func (p Param) GetBytesHex() ([]byte, error) { - s, ok := p.Value.(string) - if !ok { - return nil, errors.New("must be a string") + s, err := p.GetString() + if err != nil { + return nil, err } return hex.DecodeString(s) @@ -77,6 +141,17 @@ func (p *Param) UnmarshalJSON(data []byte) error { return nil } + r := bytes.NewReader(data) + jd := json.NewDecoder(r) + jd.DisallowUnknownFields() + var fp FuncParam + if err := jd.Decode(&fp); err == nil { + p.Type = funcParamT + p.Value = fp + + return nil + } + var ps []Param if err := json.Unmarshal(data, &ps); err == nil { p.Type = arrayT diff --git a/pkg/rpc/param_test.go b/pkg/rpc/param_test.go index fca26196b..cd4465bfb 100644 --- a/pkg/rpc/param_test.go +++ b/pkg/rpc/param_test.go @@ -1,14 +1,18 @@ package rpc import ( + "encoding/hex" "encoding/json" "testing" + "github.com/CityOfZion/neo-go/pkg/crypto" + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestParam_UnmarshalJSON(t *testing.T) { - msg := `["str1", 123, ["str2", 3]]` + msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]` expected := Params{ { Type: stringT, @@ -31,6 +35,21 @@ func TestParam_UnmarshalJSON(t *testing.T) { }, }, }, + { + Type: arrayT, + Value: []Param{ + { + Type: funcParamT, + Value: FuncParam{ + Type: String, + Value: Param{ + Type: stringT, + Value: "jajaja", + }, + }, + }, + }, + }, } var ps Params @@ -40,3 +59,126 @@ func TestParam_UnmarshalJSON(t *testing.T) { msg = `[{"2": 3}]` require.Error(t, json.Unmarshal([]byte(msg), &ps)) } + +func TestParamGetString(t *testing.T) { + p := Param{stringT, "jajaja"} + str, err := p.GetString() + assert.Equal(t, "jajaja", str) + require.Nil(t, err) + + p = Param{stringT, int(100500)} + _, err = p.GetString() + require.NotNil(t, err) +} + +func TestParamGetInt(t *testing.T) { + p := Param{numberT, int(100500)} + i, err := p.GetInt() + assert.Equal(t, 100500, i) + require.Nil(t, err) + + p = Param{numberT, "jajaja"} + _, err = p.GetInt() + require.NotNil(t, err) +} + +func TestParamGetArray(t *testing.T) { + p := Param{arrayT, []Param{{numberT, 42}}} + a, err := p.GetArray() + assert.Equal(t, []Param{{numberT, 42}}, a) + require.Nil(t, err) + + p = Param{arrayT, 42} + _, err = p.GetArray() + require.NotNil(t, err) +} + +func TestParamGetUint256(t *testing.T) { + gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" + u256, _ := util.Uint256DecodeReverseString(gas) + p := Param{stringT, gas} + u, err := p.GetUint256() + assert.Equal(t, u256, u) + require.Nil(t, err) + + p = Param{stringT, 42} + _, err = p.GetUint256() + require.NotNil(t, err) + + p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + _, err = p.GetUint256() + require.NotNil(t, err) +} + +func TestParamGetUint160FromHex(t *testing.T) { + in := "50befd26fdf6e4d957c11e078b24ebce6291456f" + u160, _ := util.Uint160DecodeString(in) + u160, _ = util.Uint160DecodeBytes(util.ArrayReverse(u160[:])) + p := Param{stringT, in} + u, err := p.GetUint160FromHex() + assert.Equal(t, u160, u) + require.Nil(t, err) + + p = Param{stringT, 42} + _, err = p.GetUint160FromHex() + require.NotNil(t, err) + + p = Param{stringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} + _, err = p.GetUint160FromHex() + require.NotNil(t, err) +} + +func TestParamGetUint160FromAddress(t *testing.T) { + in := "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y" + u160, _ := crypto.Uint160DecodeAddress(in) + p := Param{stringT, in} + u, err := p.GetUint160FromAddress() + assert.Equal(t, u160, u) + require.Nil(t, err) + + p = Param{stringT, 42} + _, err = p.GetUint160FromAddress() + require.NotNil(t, err) + + p = Param{stringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} + _, err = p.GetUint160FromAddress() + require.NotNil(t, err) +} + +func TestParamGetFuncParam(t *testing.T) { + fp := FuncParam{ + Type: String, + Value: Param{ + Type: stringT, + Value: "jajaja", + }, + } + p := Param{ + Type: funcParamT, + Value: fp, + } + newfp, err := p.GetFuncParam() + assert.Equal(t, fp, newfp) + require.Nil(t, err) + + p = Param{funcParamT, 42} + _, err = p.GetFuncParam() + require.NotNil(t, err) +} + +func TestParamGetBytesHex(t *testing.T) { + in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" + inb, _ := hex.DecodeString(in) + p := Param{stringT, in} + bh, err := p.GetBytesHex() + assert.Equal(t, inb, bh) + require.Nil(t, err) + + p = Param{stringT, 42} + _, err = p.GetBytesHex() + require.NotNil(t, err) + + p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + _, err = p.GetBytesHex() + require.NotNil(t, err) +} diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 507ae38f4..f0c26790f 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -10,7 +10,6 @@ import ( "github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/crypto" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/rpc/result" @@ -136,12 +135,12 @@ Methods: break Methods } case numberT: - if !s.validBlockHeight(param) { + num, err := s.blockHeightFromParam(param) + if err != nil { resultsErr = errInvalidParams break Methods } - - hash = s.chain.GetHeaderHash(param.GetInt()) + hash = s.chain.GetHeaderHash(num) case defaultT: resultsErr = errInvalidParams break Methods @@ -164,12 +163,14 @@ Methods: if !ok { resultsErr = errInvalidParams break Methods - } else if !s.validBlockHeight(param) { - resultsErr = invalidBlockHeightError(0, param.GetInt()) + } + num, err := s.blockHeightFromParam(param) + if err != nil { + resultsErr = errInvalidParams break Methods } - results = s.chain.GetHeaderHash(param.GetInt()) + results = s.chain.GetHeaderHash(num) case "getconnectioncount": getconnectioncountCalled.Inc() @@ -242,6 +243,9 @@ Methods: getunspentsCalled.Inc() results, resultsErr = s.getAccountState(reqParams, true) + case "invokefunction": + results, resultsErr = s.invokeFunction(reqParams) + case "invokescript": results, resultsErr = s.invokescript(reqParams) @@ -306,11 +310,15 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, param, ok := reqParams.ValueWithType(0, stringT) if !ok { return nil, errInvalidParams - } else if scriptHash, err := crypto.Uint160DecodeAddress(param.GetString()); err != nil { + } else if scriptHash, err := param.GetUint160FromAddress(); err != nil { return nil, errInvalidParams } else if as := s.chain.GetAccountState(scriptHash); as != nil { if unspents { - results = wrappers.NewUnspents(as, s.chain, param.GetString()) + str, err := param.GetString() + if err != nil { + return nil, errInvalidParams + } + results = wrappers.NewUnspents(as, s.chain, str) } else { results = wrappers.NewAccountState(as) } @@ -320,6 +328,32 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, return results, resultsErr } +// invokescript implements the `invokescript` RPC call. +func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { + scriptHashHex, ok := reqParams.ValueWithType(0, stringT) + if !ok { + return nil, errInvalidParams + } + scriptHash, err := scriptHashHex.GetUint160FromHex() + if err != nil { + return nil, err + } + script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:]) + if err != nil { + return nil, err + } + vm, _ := s.chain.GetTestVM() + vm.LoadScript(script) + _ = vm.Run() + result := &wrappers.InvokeResult{ + State: vm.State(), + GasConsumed: "0.1", + Script: hex.EncodeToString(script), + Stack: vm.Estack(), + } + return result, nil +} + // invokescript implements the `invokescript` RPC call. func (s *Server) invokescript(reqParams Params) (interface{}, error) { if len(reqParams) < 1 { @@ -334,10 +368,12 @@ func (s *Server) invokescript(reqParams Params) (interface{}, error) { vm, _ := s.chain.GetTestVM() vm.LoadScript(script) _ = vm.Run() + // It's already being GetBytesHex'ed, so it's a correct string. + echo, _ := reqParams[0].GetString() result := &wrappers.InvokeResult{ State: vm.State(), GasConsumed: "0.1", - Script: reqParams[0].GetString(), + Script: echo, Stack: vm.Estack(), } return result, nil @@ -384,6 +420,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { return results, resultsErr } -func (s Server) validBlockHeight(param *Param) bool { - return param.GetInt() >= 0 && param.GetInt() <= int(s.chain.BlockHeight()) +func (s Server) blockHeightFromParam(param *Param) (int, error) { + num, err := param.GetInt() + if err != nil { + return 0, nil + } + + if num < 0 || num > int(s.chain.BlockHeight()) { + return 0, invalidBlockHeightError(0, num) + } + return num, nil } diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go index c57584bf5..493abbce9 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server_helper_test.go @@ -32,6 +32,19 @@ type SendTXResponse struct { ID int `json:"id"` } +// InvokeFunctionResponse struct for testing. +type InvokeFunctionResponse struct { + Jsonrpc string `json:"jsonrpc"` + Result struct { + Script string `json:"script"` + State string `json:"state"` + GasConsumed string `json:"gas_consumed"` + Stack []FuncParam `json:"stack"` + TX string `json:"tx,omitempty"` + } `json:"result"` + ID int `json:"id"` +} + // ValidateAddrResponse struct for testing. type ValidateAddrResponse struct { Jsonrpc string `json:"jsonrpc"` diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server_test.go index 1e4e3ed6a..48568b805 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server_test.go @@ -246,6 +246,40 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, }, + "invokefunction": { + { + name: "positive", + params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`, + result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, + check: func(t *testing.T, e *executor, result interface{}) { + res, ok := result.(*InvokeFunctionResponse) + require.True(t, ok) + assert.NotEqual(t, "", res.Result.Script) + assert.NotEqual(t, "", res.Result.State) + assert.NotEqual(t, 0, res.Result.GasConsumed) + }, + }, + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "not a string", + params: `[42, "test", []]`, + fail: true, + }, + { + name: "not a scripthash", + params: `["qwerty", "test", []]`, + fail: true, + }, + { + name: "bad params", + params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`, + fail: true, + }, + }, "sendrawtransaction": { { name: "positive", diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/txBuilder.go index b13dfd972..5ad22d481 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/txBuilder.go @@ -2,6 +2,9 @@ package rpc import ( "bytes" + "errors" + "fmt" + "strconv" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto" @@ -162,3 +165,121 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro } return script.Bytes(), nil } + +// CreateFunctionInvocationScript creates a script to invoke given contract with +// given parameters. +func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) { + script := new(bytes.Buffer) + for i := len(params) - 1; i >= 0; i-- { + switch params[i].Type { + case stringT: + if err := vm.EmitString(script, params[i].String()); err != nil { + return nil, err + } + case numberT: + num, err := params[i].GetInt() + if err != nil { + return nil, err + } + if err := vm.EmitString(script, strconv.Itoa(num)); err != nil { + return nil, err + } + case arrayT: + slice, err := params[i].GetArray() + if err != nil { + return nil, err + } + for j := len(slice) - 1; j >= 0; j-- { + fp, err := slice[j].GetFuncParam() + if err != nil { + return nil, err + } + switch fp.Type { + case ByteArray, Signature: + str, err := fp.Value.GetBytesHex() + if err != nil { + return nil, err + } + if err := vm.EmitBytes(script, str); err != nil { + return nil, err + } + case String: + str, err := fp.Value.GetString() + if err != nil { + return nil, err + } + if err := vm.EmitString(script, str); err != nil { + return nil, err + } + case Hash160: + hash, err := fp.Value.GetUint160FromHex() + if err != nil { + return nil, err + } + if err := vm.EmitBytes(script, hash.Bytes()); err != nil { + return nil, err + } + case Hash256: + hash, err := fp.Value.GetUint256() + if err != nil { + return nil, err + } + if err := vm.EmitBytes(script, hash.Bytes()); err != nil { + return nil, err + } + case PublicKey: + str, err := fp.Value.GetString() + if err != nil { + return nil, err + } + key, err := keys.NewPublicKeyFromString(string(str)) + if err != nil { + return nil, err + } + if err := vm.EmitBytes(script, key.Bytes()); err != nil { + return nil, err + } + case Integer: + val, err := fp.Value.GetInt() + if err != nil { + return nil, err + } + if err := vm.EmitInt(script, int64(val)); err != nil { + return nil, err + } + case Boolean: + str, err := fp.Value.GetString() + if err != nil { + return nil, err + } + switch str { + case "true": + err = vm.EmitInt(script, 1) + case "false": + err = vm.EmitInt(script, 0) + default: + err = errors.New("wrong boolean value") + } + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("parameter type %v is not supported", fp.Type) + } + } + err = vm.EmitInt(script, int64(len(slice))) + if err != nil { + return nil, err + } + err = vm.EmitOpcode(script, vm.PACK) + if err != nil { + return nil, err + } + } + } + + if err := vm.EmitAppCall(script, contract, false); err != nil { + return nil, err + } + return script.Bytes(), nil +} diff --git a/pkg/rpc/tx_builder_test.go b/pkg/rpc/tx_builder_test.go new file mode 100644 index 000000000..0c3e1fa16 --- /dev/null +++ b/pkg/rpc/tx_builder_test.go @@ -0,0 +1,89 @@ +package rpc + +import ( + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInvocationScriptCreationGood(t *testing.T) { + p := Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"} + contract, err := p.GetUint160FromHex() + require.Nil(t, err) + + var paramScripts = []struct { + ps Params + script string + }{{ + script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "transfer"}}, + script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{numberT, 42}}, + script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{}}}, + script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, + script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{String, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, + script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, + script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Integer, Param{numberT, 42}}}}}}, + script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "true"}}}}}}, + script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "false"}}}}}}, + script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }} + for _, ps := range paramScripts { + script, err := CreateFunctionInvocationScript(contract, ps.ps) + assert.Nil(t, err) + assert.Equal(t, ps.script, hex.EncodeToString(script)) + } +} + +func TestInvocationScriptCreationBad(t *testing.T) { + contract := util.Uint160{} + + var testParams = []Params{ + Params{{numberT, "qwerty"}}, + Params{{arrayT, 42}}, + Params{{arrayT, []Param{{numberT, 42}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "qwerty"}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "qwerty"}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{String, Param{numberT, 42}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "qwerty"}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "qwerty"}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{numberT, 42}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "qwerty"}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{Integer, Param{stringT, "qwerty"}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{numberT, 42}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "qwerty"}}}}}}, + Params{{arrayT, []Param{{funcParamT, FuncParam{Unknown, Param{}}}}}}, + } + for _, ps := range testParams { + _, err := CreateFunctionInvocationScript(contract, ps) + assert.NotNil(t, err) + } +}