diff --git a/pkg/core/native/management_test.go b/pkg/core/native/management_test.go index dd66e86d7..61fbd8b95 100644 --- a/pkg/core/native/management_test.go +++ b/pkg/core/native/management_test.go @@ -23,7 +23,6 @@ func TestDeployGetUpdateDestroyContract(t *testing.T) { ne, err := nef.NewFile(script) require.NoError(t, err) manif := manifest.NewManifest("Test") - require.NoError(t, err) h := state.CreateContractHash(sender, ne.Checksum, manif.Name) diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go index 11c2742fa..0408cac00 100644 --- a/pkg/core/native_management_test.go +++ b/pkg/core/native_management_test.go @@ -534,7 +534,7 @@ func compareContractStates(t *testing.T, expected *state.Contract, actual stacki act, ok := actual.Value().([]stackitem.Item) require.True(t, ok) - expectedManifest, err := json.Marshal(expected.Manifest) + expectedManifest, err := expected.Manifest.ToStackItem() require.NoError(t, err) expectedNef, err := expected.NEF.Bytes() require.NoError(t, err) @@ -544,7 +544,7 @@ func compareContractStates(t *testing.T, expected *state.Contract, actual stacki require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64())) require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte)) require.Equal(t, expectedNef, act[3].Value().([]byte)) - require.Equal(t, expectedManifest, act[4].Value().([]byte)) + require.Equal(t, expectedManifest, act[4]) } func TestMinimumDeploymentFee(t *testing.T) { diff --git a/pkg/core/state/contract.go b/pkg/core/state/contract.go index a3cf8cf03..b477676b0 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -1,7 +1,6 @@ package state import ( - "encoding/json" "errors" "math" "math/big" @@ -47,11 +46,11 @@ func (c *Contract) EncodeBinary(w *io.BinWriter) { // ToStackItem converts state.Contract to stackitem.Item func (c *Contract) ToStackItem() (stackitem.Item, error) { - manifest, err := json.Marshal(c.Manifest) + rawNef, err := c.NEF.Bytes() if err != nil { return nil, err } - rawNef, err := c.NEF.Bytes() + m, err := c.Manifest.ToStackItem() if err != nil { return nil, err } @@ -60,7 +59,7 @@ func (c *Contract) ToStackItem() (stackitem.Item, error) { stackitem.Make(c.UpdateCounter), stackitem.NewByteArray(c.Hash.BytesBE()), stackitem.NewByteArray(rawNef), - stackitem.NewByteArray(manifest), + m, }), nil } @@ -103,11 +102,13 @@ func (c *Contract) FromStackItem(item stackitem.Item) error { if err != nil { return err } - bytes, err = arr[4].TryBytes() + m := new(manifest.Manifest) + err = m.FromStackItem(arr[4]) if err != nil { return err } - return json.Unmarshal(bytes, &c.Manifest) + c.Manifest = *m + return nil } // CreateContractHash creates deployed contract hash from transaction sender diff --git a/pkg/core/state/contract_test.go b/pkg/core/state/contract_test.go index 6f102a9e5..c64fb9636 100644 --- a/pkg/core/state/contract_test.go +++ b/pkg/core/state/contract_test.go @@ -1,7 +1,6 @@ package state import ( - "encoding/json" "math" "testing" @@ -90,8 +89,7 @@ func TestContractFromStackItem(t *testing.T) { rawNef, _ = nefFile.Bytes() nefItem = stackitem.NewByteArray(rawNef) manifest = manifest.DefaultManifest("stack item") - manifestB, _ = json.Marshal(manifest) - manifItem = stackitem.Make(manifestB) + manifItem, _ = manifest.ToStackItem() badCases = []struct { name string @@ -105,8 +103,8 @@ func TestContractFromStackItem(t *testing.T) { {"hash is not a byte string", stackitem.Make([]stackitem.Item{id, counter, stackitem.NewArray(nil), nefItem, manifItem})}, {"hash is not a hash", stackitem.Make([]stackitem.Item{id, counter, stackitem.Make([]byte{1, 2, 3}), nefItem, manifItem})}, {"nef is not a byte string", stackitem.Make([]stackitem.Item{id, counter, chash, stackitem.NewArray(nil), manifItem})}, - {"manifest is not a byte string", stackitem.Make([]stackitem.Item{id, counter, chash, nefItem, stackitem.NewArray(nil)})}, - {"manifest is not correct", stackitem.Make([]stackitem.Item{id, counter, chash, nefItem, stackitem.Make(100500)})}, + {"manifest is not an array", stackitem.Make([]stackitem.Item{id, counter, chash, nefItem, stackitem.NewByteArray(nil)})}, + {"manifest is not correct", stackitem.Make([]stackitem.Item{id, counter, chash, nefItem, stackitem.NewArray([]stackitem.Item{stackitem.Make(100500)})})}, } ) for _, cs := range badCases { diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 7e5a713d8..a0d01a6a4 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -330,7 +330,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ } return c.GetContractStateByHash(hash) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"nef":{"magic":860243278,"compiler":"neo-go-3.0","script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","checksum":2512077441},"manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"nef":{"magic":860243278,"compiler":"neo-go-3.0","script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","checksum":2512077441},"manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":[],"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { @@ -351,7 +351,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetContractStateByAddressOrName("NWiu5oejTu925aeL9Hc1LX8SvaJhE23h15") }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"nef":{"magic":860243278,"compiler":"neo-go-3.0","script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","checksum":2512077441},"manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"nef":{"magic":860243278,"compiler":"neo-go-3.0","script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","checksum":2512077441},"manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":[],"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { @@ -372,7 +372,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetContractStateByID(0) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"nef":{"magic":860243278,"compiler":"neo-go-3.0","script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","checksum":2512077441},"manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"nef":{"magic":860243278,"compiler":"neo-go-3.0","script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","checksum":2512077441},"manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":[],"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index b622ab33d..a2c41c1fe 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -2,10 +2,12 @@ package manifest import ( "encoding/json" + "errors" "math" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) const ( @@ -65,6 +67,7 @@ func NewManifest(name string) *Manifest { Events: []Event{}, }, Groups: []Group{}, + Permissions: []Permission{}, SupportedStandards: []string{}, } m.Trusts.Restrict() @@ -138,3 +141,181 @@ func (m *Manifest) DecodeBinary(r *io.BinReader) { r.Err = err } } + +// ToStackItem converts Manifest to stackitem.Item. +func (m *Manifest) ToStackItem() (stackitem.Item, error) { + groups := make([]stackitem.Item, len(m.Groups)) + for i := range m.Groups { + groups[i] = m.Groups[i].ToStackItem() + } + supportedStandards := make([]stackitem.Item, len(m.SupportedStandards)) + for i := range m.SupportedStandards { + supportedStandards[i] = stackitem.Make(m.SupportedStandards[i]) + } + abi := m.ABI.ToStackItem() + permissions := make([]stackitem.Item, len(m.Permissions)) + for i := range m.Permissions { + permissions[i] = m.Permissions[i].ToStackItem() + } + trusts := stackitem.Item(stackitem.Null{}) + if !m.Trusts.IsWildcard() { + tItems := make([]stackitem.Item, len(m.Trusts.Value)) + for i := range m.Trusts.Value { + tItems[i] = stackitem.NewByteArray(m.Trusts.Value[i].BytesBE()) + } + trusts = stackitem.Make(tItems) + } + extra := stackitem.Make("null") + if m.Extra != nil { + e, err := json.Marshal(m.Extra) + if err != nil { + return nil, err + } + extra = stackitem.NewByteArray(e) + } + return stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(m.Name), + stackitem.Make(groups), + stackitem.Make(supportedStandards), + abi, + stackitem.Make(permissions), + trusts, + extra, + }), nil +} + +// FromStackItem converts stackitem.Item to Manifest. +func (m *Manifest) FromStackItem(item stackitem.Item) error { + var err error + if item.Type() != stackitem.StructT { + return errors.New("invalid Manifest stackitem type") + } + str := item.Value().([]stackitem.Item) + if len(str) != 7 { + return errors.New("invalid stackitem length") + } + m.Name, err = stackitem.ToString(str[0]) + if err != nil { + return err + } + if str[1].Type() != stackitem.ArrayT { + return errors.New("invalid Groups stackitem type") + } + groups := str[1].Value().([]stackitem.Item) + m.Groups = make([]Group, len(groups)) + for i := range groups { + group := new(Group) + err := group.FromStackItem(groups[i]) + if err != nil { + return err + } + m.Groups[i] = *group + } + if str[2].Type() != stackitem.ArrayT { + return errors.New("invalid SupportedStandards stackitem type") + } + supportedStandards := str[2].Value().([]stackitem.Item) + m.SupportedStandards = make([]string, len(supportedStandards)) + for i := range supportedStandards { + m.SupportedStandards[i], err = stackitem.ToString(supportedStandards[i]) + if err != nil { + return err + } + } + abi := new(ABI) + if err := abi.FromStackItem(str[3]); err != nil { + return err + } + m.ABI = *abi + if str[4].Type() != stackitem.ArrayT { + return errors.New("invalid Permissions stackitem type") + } + permissions := str[4].Value().([]stackitem.Item) + m.Permissions = make([]Permission, len(permissions)) + for i := range permissions { + p := new(Permission) + if err := p.FromStackItem(permissions[i]); err != nil { + return err + } + m.Permissions[i] = *p + } + if _, ok := str[5].(stackitem.Null); ok { + m.Trusts.Restrict() + } else { + if str[5].Type() != stackitem.ArrayT { + return errors.New("invalid Trusts stackitem type") + } + trusts := str[5].Value().([]stackitem.Item) + m.Trusts = WildUint160s{Value: make([]util.Uint160, len(trusts))} + for i := range trusts { + bytes, err := trusts[i].TryBytes() + if err != nil { + return err + } + m.Trusts.Value[i], err = util.Uint160DecodeBytesBE(bytes) + if err != nil { + return err + } + } + } + extra, err := str[6].TryBytes() + if err != nil { + return err + } + if string(extra) == "null" { + return nil + } + return json.Unmarshal(extra, &m.Extra) +} + +// ToStackItem converts ABI to stackitem.Item. +func (a *ABI) ToStackItem() stackitem.Item { + methods := make([]stackitem.Item, len(a.Methods)) + for i := range a.Methods { + methods[i] = a.Methods[i].ToStackItem() + } + events := make([]stackitem.Item, len(a.Events)) + for i := range a.Events { + events[i] = a.Events[i].ToStackItem() + } + return stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(methods), + stackitem.Make(events), + }) +} + +// FromStackItem converts stackitem.Item to ABI. +func (a *ABI) FromStackItem(item stackitem.Item) error { + if item.Type() != stackitem.StructT { + return errors.New("invalid ABI stackitem type") + } + str := item.Value().([]stackitem.Item) + if len(str) != 2 { + return errors.New("invalid ABI stackitem length") + } + if str[0].Type() != stackitem.ArrayT { + return errors.New("invalid Methods stackitem type") + } + methods := str[0].Value().([]stackitem.Item) + a.Methods = make([]Method, len(methods)) + for i := range methods { + m := new(Method) + if err := m.FromStackItem(methods[i]); err != nil { + return err + } + a.Methods[i] = *m + } + if str[1].Type() != stackitem.ArrayT { + return errors.New("invalid Events stackitem type") + } + events := str[1].Value().([]stackitem.Item) + a.Events = make([]Event, len(events)) + for i := range events { + e := new(Event) + if err := e.FromStackItem(events[i]); err != nil { + return err + } + a.Events[i] = *e + } + return nil +} diff --git a/pkg/smartcontract/manifest/manifest_test.go b/pkg/smartcontract/manifest/manifest_test.go index 667542fba..629bea1d9 100644 --- a/pkg/smartcontract/manifest/manifest_test.go +++ b/pkg/smartcontract/manifest/manifest_test.go @@ -2,10 +2,13 @@ package manifest import ( "encoding/json" + "math/big" "testing" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -146,3 +149,184 @@ func TestIsValid(t *testing.T) { }) }) } + +func TestManifestToStackItem(t *testing.T) { + check := func(t *testing.T, expected *Manifest) { + item, err := expected.ToStackItem() + require.NoError(t, err) + actual := new(Manifest) + require.NoError(t, actual.FromStackItem(item)) + require.Equal(t, expected, actual) + } + + t.Run("default", func(t *testing.T) { + expected := DefaultManifest("manifest") + check(t, expected) + }) + + t.Run("full", func(t *testing.T) { + pk, _ := keys.NewPrivateKey() + expected := &Manifest{ + Name: "manifest", + ABI: ABI{ + Methods: []Method{{ + Name: "method", + Offset: 15, + Parameters: []Parameter{{ + Name: "param", + Type: smartcontract.StringType, + }}, + ReturnType: smartcontract.BoolType, + Safe: true, + }}, + Events: []Event{{ + Name: "event", + Parameters: []Parameter{{ + Name: "param", + Type: smartcontract.BoolType, + }}, + }}, + }, + Groups: []Group{{ + PublicKey: pk.PublicKey(), + Signature: []byte{1, 2, 3}, + }}, + Permissions: []Permission{*NewPermission(PermissionWildcard)}, + SupportedStandards: []string{"NEP-17"}, + Trusts: WildUint160s{ + Value: []util.Uint160{{1, 2, 3}}, + }, + Extra: "some extra data", + } + check(t, expected) + }) +} + +func TestManifest_FromStackItemErrors(t *testing.T) { + errCases := map[string]stackitem.Item{ + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid name type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid groups type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid group": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{stackitem.Null{}}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid supported standards type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid supported standard": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{stackitem.Null{}}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid ABI": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid Permissions type": stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{})}), + stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid permission": stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{})}), + stackitem.NewArray([]stackitem.Item{stackitem.Null{}}), stackitem.Null{}, stackitem.Null{}}), + "invalid Trusts type": stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{})}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewInterop(nil), stackitem.Null{}}), + "invalid trust": stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{})}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{stackitem.Null{}}), stackitem.Null{}}), + "invalid Uint160 trust": stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{})}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{1, 2, 3})}), stackitem.Null{}}), + "invalid extra type": stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{})}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.Null{}}), + "invalid extra": stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{})}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewByteArray([]byte("not a json"))}), + } + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + p := new(Manifest) + require.Error(t, p.FromStackItem(errCase)) + }) + } +} + +func TestABI_ToStackItemFromStackItem(t *testing.T) { + a := &ABI{ + Methods: []Method{{ + Name: "mur", + Offset: 5, + Parameters: []Parameter{{Name: "p1", Type: smartcontract.BoolType}}, + ReturnType: smartcontract.StringType, + Safe: true, + }}, + Events: []Event{{ + Name: "mur", + Parameters: []Parameter{{Name: "p1", Type: smartcontract.BoolType}}, + }}, + } + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte("mur")), + stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte("p1")), + stackitem.NewBigInteger(big.NewInt(int64(smartcontract.BoolType))), + }), + }), + stackitem.NewBigInteger(big.NewInt(int64(smartcontract.StringType))), + stackitem.NewBigInteger(big.NewInt(int64(5))), + stackitem.NewBool(true), + }), + }), + stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte("mur")), + stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte("p1")), + stackitem.NewBigInteger(big.NewInt(int64(smartcontract.BoolType))), + }), + }), + }), + }), + }) + CheckToFromStackItem(t, a, expected) +} + +func TestABI_FromStackItemErrors(t *testing.T) { + errCases := map[string]stackitem.Item{ + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid methods type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}}), + "invalid method": stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{stackitem.Null{}}), stackitem.Null{}}), + "invalid events type": stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.Null{}}), + "invalid event": stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewArray([]stackitem.Item{stackitem.Null{}})}), + } + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + p := new(ABI) + require.Error(t, p.FromStackItem(errCase)) + }) + } +} diff --git a/pkg/smartcontract/manifest/method.go b/pkg/smartcontract/manifest/method.go index def5f14a3..b3ef30ab9 100644 --- a/pkg/smartcontract/manifest/method.go +++ b/pkg/smartcontract/manifest/method.go @@ -1,13 +1,16 @@ package manifest import ( + "crypto/elliptic" "encoding/hex" "encoding/json" + "errors" "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/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Parameter represents smartcontract's parameter's definition. @@ -84,3 +87,172 @@ func (g *Group) UnmarshalJSON(data []byte) error { g.Signature = aux.Signature return nil } + +// ToStackItem converts Group to stackitem.Item. +func (g *Group) ToStackItem() stackitem.Item { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(g.PublicKey.Bytes()), + stackitem.NewByteArray(g.Signature), + }) +} + +// FromStackItem converts stackitem.Item to Group. +func (g *Group) FromStackItem(item stackitem.Item) error { + if item.Type() != stackitem.StructT { + return errors.New("invalid Group stackitem type") + } + group := item.Value().([]stackitem.Item) + if len(group) != 2 { + return errors.New("invalid Group stackitem length") + } + pKey, err := group[0].TryBytes() + if err != nil { + return err + } + g.PublicKey, err = keys.NewPublicKeyFromBytes(pKey, elliptic.P256()) + if err != nil { + return err + } + sig, err := group[1].TryBytes() + if err != nil { + return err + } + g.Signature = sig + return nil +} + +// ToStackItem converts Method to stackitem.Item. +func (m *Method) ToStackItem() stackitem.Item { + params := make([]stackitem.Item, len(m.Parameters)) + for i := range m.Parameters { + params[i] = m.Parameters[i].ToStackItem() + } + return stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(m.Name), + stackitem.Make(params), + stackitem.Make(int(m.ReturnType)), + stackitem.Make(m.Offset), + stackitem.Make(m.Safe), + }) +} + +// FromStackItem converts stackitem.Item to Method. +func (m *Method) FromStackItem(item stackitem.Item) error { + var err error + if item.Type() != stackitem.StructT { + return errors.New("invalid Method stackitem type") + } + method := item.Value().([]stackitem.Item) + if len(method) != 5 { + return errors.New("invalid Method stackitem length") + } + m.Name, err = stackitem.ToString(method[0]) + if err != nil { + return err + } + if method[1].Type() != stackitem.ArrayT { + return errors.New("invalid Params stackitem type") + } + params := method[1].Value().([]stackitem.Item) + m.Parameters = make([]Parameter, len(params)) + for i := range params { + p := new(Parameter) + if err := p.FromStackItem(params[i]); err != nil { + return err + } + m.Parameters[i] = *p + } + rTyp, err := method[2].TryInteger() + if err != nil { + return err + } + m.ReturnType, err = smartcontract.ConvertToParamType(int(rTyp.Int64())) + if err != nil { + return err + } + offset, err := method[3].TryInteger() + if err != nil { + return err + } + m.Offset = int(offset.Int64()) + safe, err := method[4].TryBool() + if err != nil { + return err + } + m.Safe = safe + return nil +} + +// ToStackItem converts Parameter to stackitem.Item. +func (p *Parameter) ToStackItem() stackitem.Item { + return stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(p.Name), + stackitem.Make(int(p.Type)), + }) +} + +// FromStackItem converts stackitem.Item to Parameter. +func (p *Parameter) FromStackItem(item stackitem.Item) error { + var err error + if item.Type() != stackitem.StructT { + return errors.New("invalid Parameter stackitem type") + } + param := item.Value().([]stackitem.Item) + if len(param) != 2 { + return errors.New("invalid Parameter stackitem length") + } + p.Name, err = stackitem.ToString(param[0]) + if err != nil { + return err + } + typ, err := param[1].TryInteger() + if err != nil { + return err + } + p.Type, err = smartcontract.ConvertToParamType(int(typ.Int64())) + if err != nil { + return err + } + return nil +} + +// ToStackItem converts Event to stackitem.Item. +func (e *Event) ToStackItem() stackitem.Item { + params := make([]stackitem.Item, len(e.Parameters)) + for i := range e.Parameters { + params[i] = e.Parameters[i].ToStackItem() + } + return stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(e.Name), + stackitem.Make(params), + }) +} + +// FromStackItem converts stackitem.Item to Event. +func (e *Event) FromStackItem(item stackitem.Item) error { + var err error + if item.Type() != stackitem.StructT { + return errors.New("invalid Event stackitem type") + } + event := item.Value().([]stackitem.Item) + if len(event) != 2 { + return errors.New("invalid Event stackitem length") + } + e.Name, err = stackitem.ToString(event[0]) + if err != nil { + return err + } + if event[1].Type() != stackitem.ArrayT { + return errors.New("invalid Params stackitem type") + } + params := event[1].Value().([]stackitem.Item) + e.Parameters = make([]Parameter, len(params)) + for i := range params { + p := new(Parameter) + if err := p.FromStackItem(params[i]); err != nil { + return err + } + e.Parameters[i] = *p + } + return nil +} diff --git a/pkg/smartcontract/manifest/method_test.go b/pkg/smartcontract/manifest/method_test.go new file mode 100644 index 000000000..658b2894a --- /dev/null +++ b/pkg/smartcontract/manifest/method_test.go @@ -0,0 +1,144 @@ +package manifest + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestMethod_ToStackItemFromStackItem(t *testing.T) { + m := &Method{ + Name: "mur", + Offset: 5, + Parameters: []Parameter{{Name: "p1", Type: smartcontract.BoolType}}, + ReturnType: smartcontract.StringType, + Safe: true, + } + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte(m.Name)), + stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte(m.Parameters[0].Name)), + stackitem.NewBigInteger(big.NewInt(int64(m.Parameters[0].Type))), + }), + }), + stackitem.NewBigInteger(big.NewInt(int64(m.ReturnType))), + stackitem.NewBigInteger(big.NewInt(int64(m.Offset))), + stackitem.NewBool(m.Safe), + }) + CheckToFromStackItem(t, m, expected) +} + +func TestMethod_FromStackItemErrors(t *testing.T) { + errCases := map[string]stackitem.Item{ + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid name type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid parameters type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid parameter": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{stackitem.NewStruct([]stackitem.Item{})}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid return type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{}), stackitem.Null{}, stackitem.Null{}, stackitem.Null{}}), + "invalid offset": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{}), stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewInterop(nil), stackitem.Null{}}), + "invalid safe": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{}), stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewBigInteger(big.NewInt(5)), stackitem.NewInterop(nil)}), + } + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + p := new(Method) + require.Error(t, p.FromStackItem(errCase)) + }) + } +} + +func TestParameter_ToStackItemFromStackItem(t *testing.T) { + p := &Parameter{ + Name: "param", + Type: smartcontract.StringType, + } + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte(p.Name)), + stackitem.NewBigInteger(big.NewInt(int64(p.Type))), + }) + CheckToFromStackItem(t, p, expected) +} + +func TestParameter_FromStackItemErrors(t *testing.T) { + errCases := map[string]stackitem.Item{ + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid name type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}}), + "invalid type type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.Null{}}), + "invalid type value": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewBigInteger(big.NewInt(-100500))}), + } + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + p := new(Parameter) + require.Error(t, p.FromStackItem(errCase)) + }) + } +} + +func TestEvent_ToStackItemFromStackItem(t *testing.T) { + m := &Event{ + Name: "mur", + Parameters: []Parameter{{Name: "p1", Type: smartcontract.BoolType}}, + } + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte(m.Name)), + stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte(m.Parameters[0].Name)), + stackitem.NewBigInteger(big.NewInt(int64(m.Parameters[0].Type))), + }), + }), + }) + CheckToFromStackItem(t, m, expected) +} + +func TestEvent_FromStackItemErrors(t *testing.T) { + errCases := map[string]stackitem.Item{ + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid name type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}}), + "invalid parameters type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.Null{}}), + "invalid parameter": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewArray([]stackitem.Item{stackitem.NewStruct([]stackitem.Item{})})}), + } + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + p := new(Event) + require.Error(t, p.FromStackItem(errCase)) + }) + } +} + +func TestGroup_ToStackItemFromStackItem(t *testing.T) { + pk, _ := keys.NewPrivateKey() + g := &Group{ + PublicKey: pk.PublicKey(), + Signature: []byte{1, 2, 3}, + } + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(pk.PublicKey().Bytes()), + stackitem.NewByteArray([]byte{1, 2, 3}), + }) + CheckToFromStackItem(t, g, expected) +} + +func TestGroup_FromStackItemErrors(t *testing.T) { + pk, _ := keys.NewPrivateKey() + errCases := map[string]stackitem.Item{ + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid pub type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}}), + "invalid pub bytes": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{1}), stackitem.Null{}}), + "invalid sig type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray(pk.Bytes()), stackitem.NewInterop(nil)}), + } + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + p := new(Group) + require.Error(t, p.FromStackItem(errCase)) + }) + } +} diff --git a/pkg/smartcontract/manifest/permission.go b/pkg/smartcontract/manifest/permission.go index 00e433dfe..831229cf6 100644 --- a/pkg/smartcontract/manifest/permission.go +++ b/pkg/smartcontract/manifest/permission.go @@ -1,6 +1,7 @@ package manifest import ( + "crypto/elliptic" "encoding/hex" "encoding/json" "errors" @@ -8,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // PermissionType represents permission type. @@ -171,3 +173,91 @@ func (d *PermissionDesc) UnmarshalJSON(data []byte) error { } return errors.New("unknown permission") } + +// ToStackItem converts Permission to stackitem.Item. +func (p *Permission) ToStackItem() stackitem.Item { + var ( + contract stackitem.Item + methods stackitem.Item + ) + switch p.Contract.Type { + case PermissionWildcard: + contract = stackitem.Null{} + case PermissionHash: + contract = stackitem.NewByteArray(p.Contract.Hash().BytesBE()) + case PermissionGroup: + contract = stackitem.NewByteArray(p.Contract.Group().Bytes()) + } + if p.Methods.IsWildcard() { + methods = stackitem.Null{} + } else { + m := make([]stackitem.Item, len(p.Methods.Value)) + for i := range p.Methods.Value { + m[i] = stackitem.Make(p.Methods.Value[i]) + } + methods = stackitem.Make(m) + } + return stackitem.NewStruct([]stackitem.Item{ + contract, + methods, + }) +} + +// FromStackItem converts stackitem.Item to Permission. +func (p *Permission) FromStackItem(item stackitem.Item) error { + var err error + if item.Type() != stackitem.StructT { + return errors.New("invalid Permission stackitem type") + } + str := item.Value().([]stackitem.Item) + if len(str) != 2 { + return errors.New("invalid Permission stackitem length") + } + if _, ok := str[0].(stackitem.Null); ok { + p.Contract = PermissionDesc{ + Type: PermissionWildcard, + } + } else { + byteArr, err := str[0].TryBytes() + if err != nil { + return err + } + switch len(byteArr) { + case util.Uint160Size: + hash, _ := util.Uint160DecodeBytesBE(byteArr) + p.Contract = PermissionDesc{ + Type: PermissionHash, + Value: hash, + } + case 33: + pKey, err := keys.NewPublicKeyFromBytes(byteArr, elliptic.P256()) + if err != nil { + return err + } + p.Contract = PermissionDesc{ + Type: PermissionGroup, + Value: pKey, + } + default: + return errors.New("invalid Contract ByteArray length") + } + } + if _, ok := str[1].(stackitem.Null); ok { + p.Methods = WildStrings{Value: nil} + } else { + if str[1].Type() != stackitem.ArrayT { + return errors.New("invalid Methods stackitem type") + } + methods := str[1].Value().([]stackitem.Item) + p.Methods = WildStrings{ + Value: make([]string, len(methods)), + } + for i := range methods { + p.Methods.Value[i], err = stackitem.ToString(methods[i]) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/smartcontract/manifest/permission_test.go b/pkg/smartcontract/manifest/permission_test.go index 8a7b05a74..97eebb007 100644 --- a/pkg/smartcontract/manifest/permission_test.go +++ b/pkg/smartcontract/manifest/permission_test.go @@ -3,11 +3,13 @@ package manifest import ( "encoding/json" "fmt" + "reflect" "testing" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -92,3 +94,67 @@ func testMarshalUnmarshal(t *testing.T, expected, actual interface{}) { require.NoError(t, json.Unmarshal(data, actual)) require.Equal(t, expected, actual) } + +func TestPermission_ToStackItemFromStackItem(t *testing.T) { + t.Run("wildcard", func(t *testing.T) { + p := NewPermission(PermissionWildcard) + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.Null{}, + stackitem.Null{}, + }) + CheckToFromStackItem(t, p, expected) + }) + + t.Run("hash", func(t *testing.T) { + p := NewPermission(PermissionHash, util.Uint160{1, 2, 3}) + p.Methods = WildStrings{Value: []string{"a"}} + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(util.Uint160{1, 2, 3}.BytesBE()), + stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray([]byte("a")), + }), + }) + CheckToFromStackItem(t, p, expected) + }) + + t.Run("group", func(t *testing.T) { + pk, _ := keys.NewPrivateKey() + p := NewPermission(PermissionGroup, pk.PublicKey()) + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(pk.PublicKey().Bytes()), + stackitem.Null{}, + }) + CheckToFromStackItem(t, p, expected) + }) +} + +type Interoperable interface { + ToStackItem() stackitem.Item + FromStackItem(stackitem.Item) error +} + +func CheckToFromStackItem(t *testing.T, source Interoperable, expected stackitem.Item) { + actual := source.ToStackItem() + require.Equal(t, expected, actual) + actualSource := reflect.New(reflect.TypeOf(source).Elem()).Interface().(Interoperable) + require.NoError(t, actualSource.FromStackItem(actual)) + require.Equal(t, source, actualSource) +} + +func TestPermission_FromStackItemErrors(t *testing.T) { + errCases := map[string]stackitem.Item{ + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid contract type": stackitem.NewStruct([]stackitem.Item{stackitem.NewArray([]stackitem.Item{}), stackitem.NewBool(false)}), + "invalid contract length": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{1, 2, 3}), stackitem.NewBool(false)}), + "invalid contract pubkey": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray(make([]byte, 33)), stackitem.NewBool(false)}), + "invalid methods type": stackitem.NewStruct([]stackitem.Item{stackitem.Null{}, stackitem.NewBool(false)}), + "invalid method name": stackitem.NewStruct([]stackitem.Item{stackitem.Null{}, stackitem.NewArray([]stackitem.Item{stackitem.NewArray([]stackitem.Item{})})}), + } + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + p := new(Permission) + require.Error(t, p.FromStackItem(errCase)) + }) + } +} diff --git a/pkg/smartcontract/param_type.go b/pkg/smartcontract/param_type.go index 20b13df43..775ac49ee 100644 --- a/pkg/smartcontract/param_type.go +++ b/pkg/smartcontract/param_type.go @@ -35,6 +35,24 @@ const ( VoidType ParamType = 0xff ) +// validParamTypes contains a map of known ParamTypes +var validParamTypes = map[ParamType]bool{ + UnknownType: true, + AnyType: true, + BoolType: true, + IntegerType: true, + ByteArrayType: true, + StringType: true, + Hash160Type: true, + Hash256Type: true, + PublicKeyType: true, + SignatureType: true, + ArrayType: true, + MapType: true, + InteropInterfaceType: true, + VoidType: true, +} + // String implements the stringer interface. func (pt ParamType) String() string { switch pt { @@ -268,3 +286,11 @@ func inferParamType(val string) ParamType { // Anything can be a string. return StringType } + +// ConvertToParamType converts provided value to parameter type if it's a valid type. +func ConvertToParamType(val int) (ParamType, error) { + if validParamTypes[ParamType(val)] { + return ParamType(val), nil + } + return UnknownType, errors.New("unknown parameter type") +} diff --git a/pkg/smartcontract/param_type_test.go b/pkg/smartcontract/param_type_test.go index 1aa5a4a7a..c55e41f4d 100644 --- a/pkg/smartcontract/param_type_test.go +++ b/pkg/smartcontract/param_type_test.go @@ -6,6 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParseParamType(t *testing.T) { @@ -315,3 +316,29 @@ func mustHex(s string) []byte { } return b } + +func TestConvertToParamType(t *testing.T) { + for _, expected := range []ParamType{ + UnknownType, + AnyType, + BoolType, + IntegerType, + ByteArrayType, + StringType, + Hash160Type, + Hash256Type, + PublicKeyType, + SignatureType, + ArrayType, + MapType, + InteropInterfaceType, + VoidType, + } { + actual, err := ConvertToParamType(int(expected)) + require.NoError(t, err) + require.Equal(t, expected, actual) + } + + _, err := ConvertToParamType(0x01) + require.NotNil(t, err) +}