diff --git a/cli/multisig_test.go b/cli/multisig_test.go index 66d093067..c1829ec9f 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -11,7 +11,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/stretchr/testify/require" ) @@ -91,7 +90,7 @@ func TestSignMultisigTx(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet1Path, "--address", multisigAddr, "--out", txPath, - client.NeoContractHash.StringLE(), "transfer", + e.Chain.GoverningTokenHash().StringLE(), "transfer", "bytes:"+multisigHash.StringBE(), "bytes:"+priv.GetScriptHash().StringBE(), "int:1", diff --git a/cli/nep5_test.go b/cli/nep5_test.go index cdd7e8b42..cf6b2f4a1 100644 --- a/cli/nep5_test.go +++ b/cli/nep5_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/encoding/address" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" @@ -138,6 +137,8 @@ func TestNEP5MultiTransfer(t *testing.T) { e := newExecutor(t, true) defer e.Close(t) + neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + require.NoError(t, err) args := []string{ "neo-go", "wallet", "nep5", "multitransfer", "--rpc-endpoint", "http://" + e.RPC.Addr, @@ -145,7 +146,7 @@ func TestNEP5MultiTransfer(t *testing.T) { "--from", validatorAddr, "neo:" + privs[0].Address() + ":42", "GAS:" + privs[1].Address() + ":7", - client.NeoContractHash.StringLE() + ":" + privs[2].Address() + ":13", + neoContractHash.StringLE() + ":" + privs[2].Address() + ":13", } e.In.WriteString("one\r") @@ -168,27 +169,31 @@ func TestNEP5ImportToken(t *testing.T) { walletPath := path.Join(tmpDir, "walletForImport.json") defer os.Remove(walletPath) + neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + require.NoError(t, err) + gasContractHash, err := e.Chain.GetNativeContractScriptHash("gas") + require.NoError(t, err) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "nep5", "import", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, - "--token", client.GasContractHash.StringLE()) + "--token", gasContractHash.StringLE()) e.Run(t, "neo-go", "wallet", "nep5", "import", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, - "--token", client.NeoContractHash.StringLE()) + "--token", neoContractHash.StringLE()) t.Run("Info", func(t *testing.T) { checkGASInfo := func(t *testing.T) { e.checkNextLine(t, "^Name:\\s*GAS") e.checkNextLine(t, "^Symbol:\\s*gas") - e.checkNextLine(t, "^Hash:\\s*"+client.GasContractHash.StringLE()) + e.checkNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*8") - e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.GasContractHash)) + e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(gasContractHash)) } t.Run("WithToken", func(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep5", "info", - "--wallet", walletPath, "--token", client.GasContractHash.StringLE()) + "--wallet", walletPath, "--token", gasContractHash.StringLE()) checkGASInfo(t) }) t.Run("NoToken", func(t *testing.T) { @@ -199,14 +204,14 @@ func TestNEP5ImportToken(t *testing.T) { require.NoError(t, err) e.checkNextLine(t, "^Name:\\s*NEO") e.checkNextLine(t, "^Symbol:\\s*neo") - e.checkNextLine(t, "^Hash:\\s*"+client.NeoContractHash.StringLE()) + e.checkNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*0") - e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.NeoContractHash)) + e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(neoContractHash)) }) t.Run("Remove", func(t *testing.T) { e.In.WriteString("y\r") e.Run(t, "neo-go", "wallet", "nep5", "remove", - "--wallet", walletPath, "--token", client.NeoContractHash.StringLE()) + "--wallet", walletPath, "--token", neoContractHash.StringLE()) e.Run(t, "neo-go", "wallet", "nep5", "info", "--wallet", walletPath) checkGASInfo(t) diff --git a/cli/options/options_test.go b/cli/options/options_test.go index ba0c48ecf..57ea27683 100644 --- a/cli/options/options_test.go +++ b/cli/options/options_test.go @@ -2,8 +2,6 @@ package options import ( "flag" - "net/http" - "net/http/httptest" "testing" "time" @@ -57,34 +55,3 @@ func TestGetTimeoutContext(t *testing.T) { require.True(t, start.Before(dl) && dl.Before(end.Add(time.Nanosecond*20))) }) } - -func TestGetRPCClient(t *testing.T) { - // need test server for proper client.Init() handling - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - response := `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` - - _, err := w.Write([]byte(response)) - if err != nil { - t.Fatalf("Error writing response: %s", err.Error()) - } - })) - defer srv.Close() - t.Run("no endpoint", func(t *testing.T) { - set := flag.NewFlagSet("flagSet", flag.ExitOnError) - ctx := cli.NewContext(cli.NewApp(), set, nil) - gctx, _ := GetTimeoutContext(ctx) - _, ec := GetRPCClient(gctx, ctx) - require.Equal(t, 1, ec.ExitCode()) - }) - - t.Run("success", func(t *testing.T) { - set := flag.NewFlagSet("flagSet", flag.ExitOnError) - set.String(RPCEndpointFlag, srv.URL, "") - ctx := cli.NewContext(cli.NewApp(), set, nil) - gctx, _ := GetTimeoutContext(ctx) - _, ec := GetRPCClient(gctx, ctx) - require.Nil(t, ec) - }) - -} diff --git a/cli/options_test.go b/cli/options_test.go new file mode 100644 index 000000000..912501f89 --- /dev/null +++ b/cli/options_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "flag" + "testing" + + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +func TestGetRPCClient(t *testing.T) { + e := newExecutor(t, true) + defer e.Close(t) + + t.Run("no endpoint", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + ctx := cli.NewContext(cli.NewApp(), set, nil) + gctx, _ := options.GetTimeoutContext(ctx) + _, ec := options.GetRPCClient(gctx, ctx) + require.Equal(t, 1, ec.ExitCode()) + }) + + t.Run("success", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String(options.RPCEndpointFlag, "http://"+e.RPC.Addr, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + gctx, _ := options.GetTimeoutContext(ctx) + _, ec := options.GetRPCClient(gctx, ctx) + require.Nil(t, ec) + }) +} diff --git a/cli/wallet/nep5.go b/cli/wallet/nep5.go index 961286c8e..e95ceac8a 100644 --- a/cli/wallet/nep5.go +++ b/cli/wallet/nep5.go @@ -15,11 +15,6 @@ import ( "github.com/urfave/cli" ) -var ( - neoToken = wallet.NewToken(client.NeoContractHash, "NEO", "neo", 0) - gasToken = wallet.NewToken(client.GasContractHash, "GAS", "gas", 8) -) - var ( tokenFlag = cli.StringFlag{ Name: "token", @@ -163,6 +158,10 @@ func getNEP5Balance(ctx *cli.Context) error { if err != nil { return err } + err = c.Init() + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1) + } name := ctx.String("token") @@ -210,12 +209,6 @@ func getNEP5Balance(ctx *cli.Context) error { } func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string) (*wallet.Token, error) { - switch strings.ToLower(name) { - case "neo", client.NeoContractHash.StringLE(): - return neoToken, nil - case "gas", client.GasContractHash.StringLE(): - return gasToken, nil - } return getMatchingTokenAux(ctx, func(i int) *wallet.Token { return w.Extra.Tokens[i] }, len(w.Extra.Tokens), name) @@ -373,6 +366,10 @@ func multiTransferNEP5(ctx *cli.Context) error { if err != nil { return err } + err = c.Init() + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1) + } if ctx.NArg() == 0 { return cli.NewExitError("empty recipients list", 1) @@ -436,6 +433,10 @@ func transferNEP5(ctx *cli.Context) error { if err != nil { return err } + err = c.Init() + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1) + } toFlag := ctx.Generic("to").(*flags.Address) to := toFlag.Uint160() diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 16d33fdb4..705b7667f 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -10,7 +10,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -100,8 +99,12 @@ func handleCandidate(ctx *cli.Context, method string) error { } gas := flags.Fixed8FromContext(ctx, "gas") + neoContractHash, err := c.GetNativeContractHash("neo") + if err != nil { + return err + } w := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, method, acc.PrivateKey().PublicKey().Bytes()) + emit.AppCallWithOperationAndArgs(w.BinWriter, neoContractHash, method, acc.PrivateKey().PublicKey().Bytes()) emit.Opcodes(w.BinWriter, opcode.ASSERT) tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{ Account: acc.Contract.ScriptHash(), @@ -158,8 +161,12 @@ func handleVote(ctx *cli.Context) error { } gas := flags.Fixed8FromContext(ctx, "gas") + neoContractHash, err := c.GetNativeContractHash("neo") + if err != nil { + return cli.NewExitError(err, 1) + } w := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg) + emit.AppCallWithOperationAndArgs(w.BinWriter, neoContractHash, "vote", addr.BytesBE(), pubArg) emit.Opcodes(w.BinWriter, opcode.ASSERT) tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{ diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 2b3c98532..66318f727 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -12,7 +12,6 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -234,7 +233,11 @@ func claimGas(ctx *cli.Context) error { return err } - hash, err := c.TransferNEP5(acc, scriptHash, client.NeoContractHash, 0, 0) + neoContractHash, err := c.GetNativeContractHash("neo") + if err != nil { + return cli.NewExitError(err, 1) + } + hash, err := c.TransferNEP5(acc, scriptHash, neoContractHash, 0, 0) if err != nil { return cli.NewExitError(err, 1) } @@ -426,7 +429,7 @@ func importDeployed(ctx *cli.Context) error { return err } - cs, err := c.GetContractState(h) + cs, err := c.GetContractStateByHash(h) if err != nil { return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1) } diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 44fe25f29..e72becf7f 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response" + "github.com/nspcc-dev/neo-go/pkg/util" ) const ( @@ -53,6 +54,7 @@ type Options struct { // cache stores cache values for the RPC client methods type cache struct { calculateValidUntilBlock calculateValidUntilBlockCache + nativeHashes map[string]util.Uint160 } // calculateValidUntilBlockCache stores cached number of validators and @@ -95,21 +97,34 @@ func New(ctx context.Context, endpoint string, opts Options) (*Client, error) { ctx: ctx, cli: httpClient, endpoint: url, + cache: cache{ + nativeHashes: make(map[string]util.Uint160), + }, } cl.opts = opts cl.requestF = cl.makeHTTPRequest return cl, nil } -// Init sets magic of the network client connected to. This method should be called -// before any transaction-, header- or block-related requests in order to deserialize -// responses properly. +// Init sets magic of the network client connected to and native NEO and GAS +// contracts scripthashes. This method should be called before any transaction-, +// header- or block-related requests in order to deserialize responses properly. func (c *Client) Init() error { version, err := c.GetVersion() if err != nil { return fmt.Errorf("failed to get network magic: %w", err) } c.network = version.Magic + neoContractHash, err := c.GetContractStateByAddressOrName("neo") + if err != nil { + return fmt.Errorf("failed to get NEO contract scripthash: %w", err) + } + c.cache.nativeHashes["neo"] = neoContractHash.ScriptHash() + gasContractHash, err := c.GetContractStateByAddressOrName("gas") + if err != nil { + return fmt.Errorf("failed to get GAS contract scripthash: %w", err) + } + c.cache.nativeHashes["gas"] = gasContractHash.ScriptHash() c.initDone = true return nil } diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index 3de9fbae7..ea8db995a 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -23,13 +23,6 @@ type TransferTarget struct { Amount int64 } -var ( - // NeoContractHash is a hash of the NEO native contract. - NeoContractHash, _ = util.Uint160DecodeStringBE("25059ecb4878d3a875f91c51ceded330d4575fde") - // GasContractHash is a hash of the GAS native contract. - GasContractHash, _ = util.Uint160DecodeStringBE("bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e66") -) - // NEP5Decimals invokes `decimals` NEP5 method on a specified contract. func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) { result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 33a596c01..7ac474acb 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "errors" "fmt" + "strings" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core" @@ -208,10 +209,25 @@ func (c *Client) GetCommittee() (keys.PublicKeys, error) { return *resp, nil } -// GetContractState queries contract information, according to the contract script hash. -func (c *Client) GetContractState(hash util.Uint160) (*state.Contract, error) { +// GetContractStateByHash queries contract information, according to the contract script hash. +func (c *Client) GetContractStateByHash(hash util.Uint160) (*state.Contract, error) { + return c.getContractState(hash.StringLE()) +} + +// GetContractStateByAddressOrName queries contract information, according to the contract address or name. +func (c *Client) GetContractStateByAddressOrName(addressOrName string) (*state.Contract, error) { + return c.getContractState(addressOrName) +} + +// GetContractStateByID queries contract information, according to the contract ID. +func (c *Client) GetContractStateByID(id int32) (*state.Contract, error) { + return c.getContractState(id) +} + +// getContractState is an internal representation of GetContractStateBy* methods. +func (c *Client) getContractState(param interface{}) (*state.Contract, error) { var ( - params = request.NewRawParams(hash.StringLE()) + params = request.NewRawParams(param) resp = &state.Contract{} ) if err := c.performRequest("getcontractstate", params, resp); err != nil { @@ -597,3 +613,18 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs func (c *Client) GetNetwork() netmode.Magic { return c.network } + +// GetNativeContractHash returns native contract hash by its name. It is not case-sensitive. +func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) { + lowercasedName := strings.ToLower(name) + hash, ok := c.cache.nativeHashes[lowercasedName] + if ok { + return hash, nil + } + cs, err := c.GetContractStateByAddressOrName(name) + if err != nil { + return util.Uint160{}, err + } + c.cache.nativeHashes[lowercasedName] = cs.ScriptHash() + return cs.ScriptHash(), nil +} diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index d1913a12f..66844cd74 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -315,13 +315,57 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, "getcontractstate": { { - name: "positive", + name: "positive, by hash", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176") if err != nil { panic(err) } - return c.GetContractState(hash) + return c.GetContractStateByHash(hash) + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + result: func(c *Client) interface{} { + script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") + if err != nil { + panic(err) + } + m := manifest.NewManifest(hash.Hash160(script)) + m.Features = smartcontract.HasStorage + cs := &state.Contract{ + ID: 0, + Script: script, + Manifest: *m, + } + _ = cs.ScriptHash() + return cs + }, + }, + { + name: "positive, by address", + invoke: func(c *Client) (interface{}, error) { + return c.GetContractStateByAddressOrName("NWiu5oejTu925aeL9Hc1LX8SvaJhE23h15") + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + result: func(c *Client) interface{} { + script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") + if err != nil { + panic(err) + } + m := manifest.NewManifest(hash.Hash160(script)) + m.Features = smartcontract.HasStorage + cs := &state.Contract{ + ID: 0, + Script: script, + Manifest: *m, + } + _ = cs.ScriptHash() + return cs + }, + }, + { + name: "positive, by id", + invoke: func(c *Client) (interface{}, error) { + return c.GetContractStateByID(0) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { @@ -645,7 +689,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, "invokefunction": { { - name: "positive", + name: "positive, by scripthash", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("91b83e96f2a7c4fdf0c1688441ec61986c7cae26") if err != nil { @@ -991,7 +1035,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ { name: "getcontractstate_invalid_params_error", invoke: func(c *Client) (interface{}, error) { - return c.GetContractState(util.Uint160{}) + return c.GetContractStateByHash(util.Uint160{}) }, }, { @@ -1178,7 +1222,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ { name: "getcontractstate_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { - return c.GetContractState(util.Uint160{}) + return c.GetContractStateByHash(util.Uint160{}) }, }, { @@ -1372,13 +1416,7 @@ func initTestServer(t *testing.T, resp string) *httptest.Server { if err != nil { t.Fatalf("Cannot decode request body: %s", req.Body) } - var response string - switch r.Method { - case "getversion": - response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` - default: - response = resp - } + response := wrapInitResponse(r, resp) ws.SetWriteDeadline(time.Now().Add(2 * time.Second)) err = ws.WriteMessage(1, []byte(response)) if err != nil { @@ -1393,27 +1431,49 @@ func initTestServer(t *testing.T, resp string) *httptest.Server { if err != nil { t.Fatalf("Cannot decode request body: %s", req.Body) } - requestHandler(t, r.Method, w, resp) + requestHandler(t, r, w, resp) })) return srv } -func requestHandler(t *testing.T, method string, w http.ResponseWriter, resp string) { +func requestHandler(t *testing.T, r *request.In, w http.ResponseWriter, resp string) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - var response string - switch method { - case "getversion": - response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` - default: - response = resp - } + response := wrapInitResponse(r, resp) _, err := w.Write([]byte(response)) if err != nil { t.Fatalf("Error writing response: %s", err.Error()) } } +func wrapInitResponse(r *request.In, resp string) string { + var response string + switch r.Method { + case "getversion": + response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` + case "getcontractstate": + p, err := r.Params() + if err != nil { + response = resp + } + name, err := p.ValueWithType(0, request.StringT).GetString() + if err != nil { + response = resp + } + switch name { + case "neo": + response = `{"id":1,"jsonrpc":"2.0","result":{"id":-1,"script":"DANORU9Ba2d4Cw==","manifest":{"abi":{"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"unclaimedGas","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer"},{"name":"registerCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"unregisterCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"vote","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"getCandidates","offset":0,"parameters":null,"returntype":"Array"},{"name":"getŠ”ommittee","offset":0,"parameters":null,"returntype":"Array"},{"name":"getNextBlockValidators","offset":0,"parameters":null,"returntype":"Array"},{"name":"getGasPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setGasPerBlock","offset":0,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Boolean"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"features":{"payable":false,"storage":false},"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf","unclaimedGas","getCandidates","getŠ”ommittee","getNextBlockValidators"],"extra":null},"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525"}}` + case "gas": + response = `{"id":1,"jsonrpc":"2.0","result":{"id":-2,"script":"DANHQVNBa2d4Cw==","manifest":{"abi":{"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"features":{"payable":false,"storage":false},"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf"],"extra":null},"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc"}}` + default: + response = resp + } + default: + response = resp + } + return response +} + func TestCalculateValidUntilBlock(t *testing.T) { var ( getBlockCountCalled int @@ -1434,7 +1494,7 @@ func TestCalculateValidUntilBlock(t *testing.T) { getValidatorsCalled++ response = `{"id":1,"jsonrpc":"2.0","result":[{"publickey":"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2","votes":"0","active":true},{"publickey":"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e","votes":"0","active":true},{"publickey":"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699","votes":"0","active":true},{"publickey":"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62","votes":"0","active":true}]}` } - requestHandler(t, r.Method, w, response) + requestHandler(t, r, w, response) })) defer srv.Close() @@ -1462,8 +1522,13 @@ func TestCalculateValidUntilBlock(t *testing.T) { func TestGetNetwork(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + r := request.NewIn() + err := r.DecodeData(req.Body) + if err != nil { + t.Fatalf("Cannot decode request body: %s", req.Body) + } // request handler already have `getversion` response wrapper - requestHandler(t, "getversion", w, "") + requestHandler(t, r, w, "") })) defer srv.Close() endpoint := srv.URL diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index f5e8adc96..4fe350bc1 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -277,7 +277,10 @@ func TestCreateNEP5TransferTx(t *testing.T) { acc, err := wallet.NewAccountFromWIF(priv.WIF()) require.NoError(t, err) - tx, err := c.CreateNEP5TransferTx(acc, util.Uint160{}, client.GasContractHash, 1000, 0) + gasContractHash, err := c.GetNativeContractHash("gas") + require.NoError(t, err) + + tx, err := c.CreateNEP5TransferTx(acc, util.Uint160{}, gasContractHash, 1000, 0) require.NoError(t, err) require.NoError(t, acc.SignTx(tx)) require.NoError(t, chain.VerifyTx(tx))