neo-go/pkg/services/rpcsrv/params/param_test.go
Anna Shaleva 132531fabe rpcsrv: support Map parameter in invokefunction RPC handler
We have smartcontract.ParameterPair structure that can be properly
marshalled and passed to RPC server as an element of smartcontract.Map
structure. However, RPC server can't unmarshal map values properly
without this change.

This change is compatible with C# node.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2024-02-28 15:54:33 +03:00

576 lines
17 KiB
Go

package params
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"math/big"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neorpc"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParam_UnmarshalJSON(t *testing.T) {
type testCase struct {
check func(t *testing.T, p *Param)
expectedRawMessage []byte
}
msg := `["123", 123, null, ["str2", 3], [{"type": "String", "value": "jajaja"}],
{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"},
{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"},
[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]]`
expected := []testCase{
{
check: func(t *testing.T, p *Param) {
expectedS := "123"
actualS, err := p.GetStringStrict()
require.NoError(t, err)
require.Equal(t, expectedS, actualS)
actualS, err = p.GetString()
require.NoError(t, err)
require.Equal(t, expectedS, actualS)
expectedI := 123
_, err = p.GetIntStrict()
require.Error(t, err)
actualI, err := p.GetInt()
require.NoError(t, err)
require.Equal(t, expectedI, actualI)
expectedB := true
_, err = p.GetBooleanStrict()
require.Error(t, err)
actualB, err := p.GetBoolean()
require.NoError(t, err)
require.Equal(t, expectedB, actualB)
_, err = p.GetArray()
require.Error(t, err)
},
expectedRawMessage: []byte(`"123"`),
},
{
check: func(t *testing.T, p *Param) {
expectedS := "123"
_, err := p.GetStringStrict()
require.Error(t, err)
actualS, err := p.GetString()
require.NoError(t, err)
require.Equal(t, expectedS, actualS)
expectedI := 123
actualI, err := p.GetIntStrict()
require.NoError(t, err)
require.Equal(t, expectedI, actualI)
actualI, err = p.GetInt()
require.NoError(t, err)
require.Equal(t, expectedI, actualI)
expectedB := true
_, err = p.GetBooleanStrict()
require.Error(t, err)
actualB, err := p.GetBoolean()
require.NoError(t, err)
require.Equal(t, expectedB, actualB)
_, err = p.GetArray()
require.Error(t, err)
},
expectedRawMessage: []byte(`123`),
},
{
check: func(t *testing.T, p *Param) {
_, err := p.GetStringStrict()
require.Error(t, err)
_, err = p.GetString()
require.Error(t, err)
_, err = p.GetIntStrict()
require.Error(t, err)
_, err = p.GetInt()
require.Error(t, err)
_, err = p.GetBooleanStrict()
require.Error(t, err)
_, err = p.GetBoolean()
require.Error(t, err)
_, err = p.GetArray()
require.Error(t, err)
},
expectedRawMessage: []byte(`null`),
},
{
check: func(t *testing.T, p *Param) {
_, err := p.GetStringStrict()
require.Error(t, err)
_, err = p.GetString()
require.Error(t, err)
_, err = p.GetIntStrict()
require.Error(t, err)
_, err = p.GetInt()
require.Error(t, err)
_, err = p.GetBooleanStrict()
require.Error(t, err)
_, err = p.GetBoolean()
require.Error(t, err)
a, err := p.GetArray()
require.NoError(t, err)
require.Equal(t, []Param{
{RawMessage: json.RawMessage(`"str2"`)},
{RawMessage: json.RawMessage(`3`)},
}, a)
},
expectedRawMessage: []byte(`["str2", 3]`),
},
{
check: func(t *testing.T, p *Param) {
a, err := p.GetArray()
require.NoError(t, err)
require.Equal(t, 1, len(a))
fp, err := a[0].GetFuncParam()
require.NoError(t, err)
require.Equal(t, smartcontract.StringType, fp.Type)
strVal, err := fp.Value.GetStringStrict()
require.NoError(t, err)
require.Equal(t, "jajaja", strVal)
},
expectedRawMessage: []byte(`[{"type": "String", "value": "jajaja"}]`),
},
{
check: func(t *testing.T, p *Param) {
actual, err := p.GetSignerWithWitness()
require.NoError(t, err)
expectedAcc, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
require.NoError(t, err)
require.Equal(t, neorpc.SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc}}, actual)
},
expectedRawMessage: []byte(`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`),
},
{
check: func(t *testing.T, p *Param) {
actual, err := p.GetSignerWithWitness()
require.NoError(t, err)
expectedAcc, err := address.StringToUint160("NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag")
require.NoError(t, err)
require.Equal(t, neorpc.SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc, Scopes: transaction.Global}}, actual)
},
expectedRawMessage: []byte(`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`),
},
{
check: func(t *testing.T, p *Param) {
actualSigs, actualWtns, err := p.GetSignersWithWitnesses()
require.NoError(t, err)
require.Equal(t, 1, len(actualSigs))
require.Equal(t, 1, len(actualWtns))
expectedAcc, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
require.NoError(t, err)
require.Equal(t, transaction.Signer{Account: expectedAcc, Scopes: transaction.Global}, actualSigs[0])
require.Equal(t, transaction.Witness{}, actualWtns[0])
},
expectedRawMessage: []byte(`[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]`),
},
}
var ps Params
require.NoError(t, json.Unmarshal([]byte(msg), &ps))
require.Equal(t, len(expected), len(ps))
for i, tc := range expected {
require.NotNil(t, ps[i])
require.Equal(t, json.RawMessage(tc.expectedRawMessage), ps[i].RawMessage, i)
tc.check(t, &ps[i])
}
}
func TestGetBigInt(t *testing.T) {
maxUint64 := new(big.Int).SetUint64(math.MaxUint64)
minInt64 := big.NewInt(math.MinInt64)
testCases := []struct {
raw string
expected *big.Int
}{
{"true", big.NewInt(1)},
{"false", new(big.Int)},
{"42", big.NewInt(42)},
{`"` + minInt64.String() + `"`, minInt64},
{`"` + maxUint64.String() + `"`, maxUint64},
{`"` + minInt64.String() + `000"`, new(big.Int).Mul(minInt64, big.NewInt(1000))},
{`"` + maxUint64.String() + `000"`, new(big.Int).Mul(maxUint64, big.NewInt(1000))},
{`"abc"`, nil},
{`[]`, nil},
{`null`, nil},
}
for _, tc := range testCases {
var p Param
require.NoError(t, json.Unmarshal([]byte(tc.raw), &p))
actual, err := p.GetBigInt()
if tc.expected == nil {
require.Error(t, err)
continue
}
require.NoError(t, err)
require.Equal(t, tc.expected, actual)
expected := tc.expected.Int64()
actualInt, err := p.GetInt()
if !actual.IsInt64() || int64(int(expected)) != expected {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, int(expected), actualInt)
}
}
}
func TestGetWitness(t *testing.T) {
accountHash, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
require.NoError(t, err)
addrHash, err := address.StringToUint160("NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag")
require.NoError(t, err)
testCases := []struct {
raw string
expected neorpc.SignerWithWitness
shouldFail bool
}{
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.None,
},
},
},
{
raw: `{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: addrHash,
Scopes: transaction.Global,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.Global,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 128}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.Global,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.None,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 1}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.CalledByEntry,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 17}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.CalledByEntry | transaction.CustomContracts,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 178}`,
shouldFail: true,
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 2}`,
shouldFail: true,
},
}
for _, tc := range testCases {
p := Param{RawMessage: json.RawMessage(tc.raw)}
actual, err := p.GetSignerWithWitness()
if tc.shouldFail {
require.Error(t, err, tc.raw)
} else {
require.NoError(t, err, tc.raw)
require.Equal(t, tc.expected, actual)
actual, err = p.GetSignerWithWitness() // valid second invocation.
require.NoError(t, err, tc.raw)
require.Equal(t, tc.expected, actual)
}
}
}
func TestParamGetUint256(t *testing.T) {
gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
u256, _ := util.Uint256DecodeStringLE(gas)
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, gas))}
u, err := p.GetUint256()
assert.Equal(t, u256, u)
require.Nil(t, err)
p = Param{RawMessage: []byte(fmt.Sprintf(`"0x%s"`, gas))}
u, err = p.GetUint256()
require.NoError(t, err)
assert.Equal(t, u256, u)
p = Param{RawMessage: []byte(`42`)}
_, err = p.GetUint256()
require.NotNil(t, err)
p = Param{RawMessage: []byte(`"qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"`)}
_, err = p.GetUint256()
require.NotNil(t, err)
}
func TestParamGetUint160FromHex(t *testing.T) {
in := "50befd26fdf6e4d957c11e078b24ebce6291456f"
u160, _ := util.Uint160DecodeStringLE(in)
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
u, err := p.GetUint160FromHex()
assert.Equal(t, u160, u)
require.Nil(t, err)
p = Param{RawMessage: []byte(`42`)}
_, err = p.GetUint160FromHex()
require.NotNil(t, err)
p = Param{RawMessage: []byte(`"wwbefd26fdf6e4d957c11e078b24ebce6291456f"`)}
_, err = p.GetUint160FromHex()
require.NotNil(t, err)
}
func TestParamGetUint160FromAddress(t *testing.T) {
in := "NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8"
u160, _ := address.StringToUint160(in)
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
u, err := p.GetUint160FromAddress()
assert.Equal(t, u160, u)
require.Nil(t, err)
p = Param{RawMessage: []byte(`42`)}
_, err = p.GetUint160FromAddress()
require.NotNil(t, err)
p = Param{RawMessage: []byte(`"QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"`)}
_, err = p.GetUint160FromAddress()
require.NotNil(t, err)
}
func TestParam_GetUint160FromAddressOrHex(t *testing.T) {
in := "NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8"
inHex, _ := address.StringToUint160(in)
t.Run("Address", func(t *testing.T) {
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
u, err := p.GetUint160FromAddressOrHex()
require.NoError(t, err)
require.Equal(t, inHex, u)
})
t.Run("Hex", func(t *testing.T) {
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, inHex.StringLE()))}
u, err := p.GetUint160FromAddressOrHex()
require.NoError(t, err)
require.Equal(t, inHex, u)
})
}
func TestParamGetFuncParam(t *testing.T) {
fp := FuncParam{
Type: smartcontract.StringType,
Value: Param{RawMessage: []byte(`"jajaja"`)},
}
p := Param{RawMessage: []byte(`{"type": "String", "value": "jajaja"}`)}
newfp, err := p.GetFuncParam()
assert.Equal(t, fp, newfp)
require.Nil(t, err)
p = Param{RawMessage: []byte(`42`)}
_, err = p.GetFuncParam()
require.NotNil(t, err)
}
func TestParamGetFuncParamPair(t *testing.T) {
fp := FuncParam{
Type: smartcontract.MapType,
Value: Param{RawMessage: []byte(`[{"key": {"type": "String", "value": "key1"}, "value": {"type": "Integer", "value": 123}}, {"key": {"type": "String", "value": "key2"}, "value": {"type": "Integer", "value": 456}}]`)},
}
p := Param{RawMessage: []byte(`{"type": "Map", "value": [{"key": {"type": "String", "value": "key1"}, "value": {"type": "Integer", "value": 123}}, {"key": {"type": "String", "value": "key2"}, "value": {"type": "Integer", "value": 456}}]}`)}
newfp, err := p.GetFuncParam()
assert.Equal(t, fp, newfp)
require.NoError(t, err)
kvs, err := newfp.Value.GetArray()
require.NoError(t, err)
p1, err := kvs[0].GetFuncParamPair()
require.NoError(t, err)
fp1Key := FuncParam{
Type: smartcontract.StringType,
Value: Param{RawMessage: []byte(`"key1"`)},
}
fp1Value := FuncParam{
Type: smartcontract.IntegerType,
Value: Param{RawMessage: []byte(`123`)},
}
assert.Equal(t, fp1Key, p1.Key)
assert.Equal(t, fp1Value, p1.Value)
p2, err := kvs[1].GetFuncParamPair()
require.NoError(t, err)
fp2Key := FuncParam{
Type: smartcontract.StringType,
Value: Param{RawMessage: []byte(`"key2"`)},
}
fp2Value := FuncParam{
Type: smartcontract.IntegerType,
Value: Param{RawMessage: []byte(`456`)},
}
assert.Equal(t, fp2Key, p2.Key)
assert.Equal(t, fp2Value, p2.Value)
}
func TestParamGetBytesHex(t *testing.T) {
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
inb, _ := hex.DecodeString(in)
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
bh, err := p.GetBytesHex()
assert.Equal(t, inb, bh)
require.Nil(t, err)
p = Param{RawMessage: []byte(`42`)}
h, err := p.GetBytesHex()
assert.Equal(t, []byte{0x42}, h) // that's the way how C# works: 42 -> "42" -> []byte{0x42}
require.Nil(t, err)
p = Param{RawMessage: []byte(`"qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"`)}
_, err = p.GetBytesHex()
require.NotNil(t, err)
}
func TestParamGetBytesBase64(t *testing.T) {
in := "Aj4A8DoW6HB84EXrQu6A05JFFUHuUQ3BjhyL77rFTXQm"
inb, err := base64.StdEncoding.DecodeString(in)
require.NoError(t, err)
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, in))}
bh, err := p.GetBytesBase64()
assert.Equal(t, inb, bh)
require.Nil(t, err)
p = Param{RawMessage: []byte(`42`)}
_, err = p.GetBytesBase64()
require.NotNil(t, err)
p = Param{RawMessage: []byte("@j4A8DoW6HB84EXrQu6A05JFFUHuUQ3BjhyL77rFTXQm")}
_, err = p.GetBytesBase64()
require.NotNil(t, err)
}
func TestParamGetSigner(t *testing.T) {
c := neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: util.Uint160{1, 2, 3, 4},
Scopes: transaction.Global,
},
Witness: transaction.Witness{
InvocationScript: []byte{1, 2, 3},
VerificationScript: []byte{1, 2, 3},
},
}
p := Param{RawMessage: []byte(`{"account": "0x0000000000000000000000000000000004030201", "scopes": "Global", "invocation": "AQID", "verification": "AQID"}`)}
actual, err := p.GetSignerWithWitness()
require.NoError(t, err)
require.Equal(t, c, actual)
p = Param{RawMessage: []byte(`"not a signer"`)}
_, err = p.GetSignerWithWitness()
require.Error(t, err)
}
func TestParamGetSigners(t *testing.T) {
u1 := util.Uint160{1, 2, 3, 4}
u2 := util.Uint160{5, 6, 7, 8}
t.Run("from hashes", func(t *testing.T) {
p := Param{RawMessage: []byte(fmt.Sprintf(`["%s", "%s"]`, u1.StringLE(), u2.StringLE()))}
actual, _, err := p.GetSignersWithWitnesses()
require.NoError(t, err)
require.Equal(t, 2, len(actual))
require.True(t, u1.Equals(actual[0].Account))
require.True(t, u2.Equals(actual[1].Account))
})
t.Run("overflow", func(t *testing.T) {
var hashes = make([]util.Uint256, transaction.MaxAttributes+1)
msg, err := json.Marshal(hashes)
require.NoError(t, err)
p := Param{RawMessage: msg}
_, _, err = p.GetSignersWithWitnesses()
require.Error(t, err)
})
t.Run("bad format", func(t *testing.T) {
p := Param{RawMessage: []byte(`"not a signer"`)}
_, _, err := p.GetSignersWithWitnesses()
require.Error(t, err)
})
}
func TestParamGetUUID(t *testing.T) {
t.Run("from null", func(t *testing.T) {
p := Param{RawMessage: []byte("null")}
_, err := p.GetUUID()
require.ErrorIs(t, err, errNotAString)
})
t.Run("invalid uuid", func(t *testing.T) {
p := Param{RawMessage: []byte(`"not-a-uuid"`)}
_, err := p.GetUUID()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "not a valid UUID"), err.Error())
})
t.Run("compat", func(t *testing.T) {
expected := "2107da59-4f9c-462c-9c51-7666842519a9"
p := Param{RawMessage: []byte(fmt.Sprintf(`"%s"`, expected))}
id, err := p.GetUUID()
require.NoError(t, err)
require.Equal(t, id.String(), expected)
})
}