From 342bbd79a32afa33701a9ffaed8f861692168b6d Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 20 Jan 2024 02:43:20 +0600 Subject: [PATCH 1/3] Map process implementation Signed-off-by: Alexey Semenyuk --- internal/blockchain/tezos/ffi2michelson.go | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/internal/blockchain/tezos/ffi2michelson.go b/internal/blockchain/tezos/ffi2michelson.go index 1930ddb4e..57c5337a0 100644 --- a/internal/blockchain/tezos/ffi2michelson.go +++ b/internal/blockchain/tezos/ffi2michelson.go @@ -34,6 +34,7 @@ const ( _internalBoolean = "boolean" _internalList = "list" _internalStruct = "struct" + _internalMap = "map" _internalInteger = "integer" _internalNat = "nat" _internalString = "string" @@ -143,6 +144,45 @@ func processMichelson(entry interface{}, details map[string]interface{}) (resp m func processSchemaEntry(entry interface{}, schema map[string]interface{}) (resp micheline.Prim, err error) { entryType := schema["type"].(string) switch entryType { + case _internalMap: + schemaArgs := schema["args"].([]interface{}) + + mapResp := micheline.NewMap() + mapEntries := entry.(map[string]interface{})["mapEntries"] + if mapEntries == nil { + return resp, fmt.Errorf("mapEntries property in the map payload schema must be not nil") + } + for _, mapEntry := range mapEntries.([]interface{}) { + elem := mapEntry.(map[string]interface{}) + + var k micheline.Prim + var v micheline.Prim + for i := len(schemaArgs) - 1; i >= 0; i-- { + arg := schemaArgs[i].(map[string]interface{}) + + if arg["name"] == "key" { + k, err = processSchemaEntry(elem["key"], arg) + if err != nil { + return resp, err + } + } + + if arg["name"] == "value" { + v, err = processSchemaEntry(elem["value"], arg) + if err != nil { + return resp, err + } + } + } + + if k.IsNil() { + return resp, fmt.Errorf("key property in the map payload schema must be not nil") + } + mapElem := micheline.NewMapElem(k, v) + mapResp.Args = append(mapResp.Args, mapElem) + } + + resp = mapResp case _internalStruct: schemaArgs := schema["args"].([]interface{}) From 5868eba53ac52b78e66789f0148e7ed32bc5378c Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 20 Jan 2024 15:12:42 +0600 Subject: [PATCH 2/3] Test for map implementation Signed-off-by: Alexey Semenyuk --- internal/blockchain/tezos/ffi2michelson.go | 48 ++-- .../blockchain/tezos/ffi2michelson_test.go | 213 ++++++++++++++++++ 2 files changed, 243 insertions(+), 18 deletions(-) diff --git a/internal/blockchain/tezos/ffi2michelson.go b/internal/blockchain/tezos/ffi2michelson.go index 57c5337a0..2d5679bab 100644 --- a/internal/blockchain/tezos/ffi2michelson.go +++ b/internal/blockchain/tezos/ffi2michelson.go @@ -19,6 +19,7 @@ package tezos import ( "errors" "fmt" + "slices" "blockwatch.cc/tzgo/micheline" "blockwatch.cc/tzgo/tezos" @@ -44,6 +45,13 @@ const ( _internalBytes = "bytes" ) +// Tezos map +const ( + _key = "key" + _value = "value" + _mapEntries = "mapEntries" +) + func processArgs(payloadSchema map[string]interface{}, input map[string]interface{}, methodName string) (micheline.Parameters, error) { params := micheline.Parameters{ Entrypoint: methodName, @@ -148,36 +156,35 @@ func processSchemaEntry(entry interface{}, schema map[string]interface{}) (resp schemaArgs := schema["args"].([]interface{}) mapResp := micheline.NewMap() - mapEntries := entry.(map[string]interface{})["mapEntries"] + mapEntries := entry.(map[string]interface{})[_mapEntries] if mapEntries == nil { - return resp, fmt.Errorf("mapEntries property in the map payload schema must be not nil") + return resp, fmt.Errorf("mapEntries schema property must be present") } for _, mapEntry := range mapEntries.([]interface{}) { - elem := mapEntry.(map[string]interface{}) + for name := range mapEntry.(map[string]interface{}) { + if !slices.Contains([]string{_key, _value}, name) { + return resp, errors.New("Unknown schema field '" + name + "' in map entry") + } + } var k micheline.Prim var v micheline.Prim for i := len(schemaArgs) - 1; i >= 0; i-- { arg := schemaArgs[i].(map[string]interface{}) - if arg["name"] == "key" { - k, err = processSchemaEntry(elem["key"], arg) - if err != nil { + if arg["name"] == _key { + if k, err = extractValue(_key, arg, mapEntry); err != nil { return resp, err } } - if arg["name"] == "value" { - v, err = processSchemaEntry(elem["value"], arg) - if err != nil { + if arg["name"] == _value { + if v, err = extractValue(_value, arg, mapEntry); err != nil { return resp, err } } } - if k.IsNil() { - return resp, fmt.Errorf("key property in the map payload schema must be not nil") - } mapElem := micheline.NewMapElem(k, v) mapResp.Args = append(mapResp.Args, mapElem) } @@ -191,15 +198,11 @@ func processSchemaEntry(entry interface{}, schema map[string]interface{}) (resp arg := schemaArgs[i].(map[string]interface{}) argName := arg["name"].(string) - elem := entry.(map[string]interface{}) - if _, ok := elem[argName]; !ok { - return resp, errors.New("Schema field '" + argName + "' wasn't found") - } - - processedEntry, err := processSchemaEntry(elem[argName], arg) + processedEntry, err := extractValue(argName, arg, entry) if err != nil { return resp, err } + newPair := forgePair(processedEntry, rightPairElem) rightPairElem = &newPair @@ -247,6 +250,15 @@ func processSchemaEntry(entry interface{}, schema map[string]interface{}) (resp return resp, err } +func extractValue(argName string, arg map[string]interface{}, entry interface{}) (resp micheline.Prim, err error) { + elem := entry.(map[string]interface{}) + if _, ok := elem[argName]; !ok { + return resp, errors.New("Schema field '" + argName + "' wasn't found") + } + + return processSchemaEntry(elem[argName], arg) +} + // TODO: define an algorithm to support any number of variants. // at the moment, support for up to 4 variants covers most cases func wrapWithVariant(elem micheline.Prim, variantPos int, totalVariantsCount int) (resp micheline.Prim) { diff --git a/internal/blockchain/tezos/ffi2michelson_test.go b/internal/blockchain/tezos/ffi2michelson_test.go index 2a0568e5a..1eccd005e 100644 --- a/internal/blockchain/tezos/ffi2michelson_test.go +++ b/internal/blockchain/tezos/ffi2michelson_test.go @@ -503,6 +503,48 @@ func Test_processArgsOk(t *testing.T) { }, }, }, + { + name: "valid map input param", + processSchemaReq: map[string]interface{}{ + "type": "array", + "prefixItems": []interface{}{ + map[string]interface{}{ + "name": "varMap", + "type": "object", + "details": map[string]interface{}{ + "type": "schema", + "internalSchema": map[string]interface{}{ + "type": "map", + "args": []interface{}{ + map[string]interface{}{ + "name": "key", + "type": "integer", + }, + map[string]interface{}{ + "name": "value", + "type": "string", + }, + }, + }, + }, + }, + }, + }, + input: map[string]interface{}{ + "varMap": map[string]interface{}{ + "mapEntries": []interface{}{ + map[string]interface{}{ + "key": float64(1), + "value": "val1", + }, + map[string]interface{}{ + "key": float64(3), + "value": "val3", + }, + }, + }, + }, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -944,6 +986,177 @@ func Test_processArgsErr(t *testing.T) { }, expectedError: "invalid object passed", }, + { + name: "no mapEntries for map input param", + processSchemaReq: map[string]interface{}{ + "type": "array", + "prefixItems": []interface{}{ + map[string]interface{}{ + "name": "varMap", + "type": "object", + "details": map[string]interface{}{ + "type": "schema", + "internalSchema": map[string]interface{}{ + "type": "map", + "args": []interface{}{ + map[string]interface{}{ + "name": "key", + "type": "integer", + }, + map[string]interface{}{ + "name": "value", + "type": "string", + }, + }, + }, + }, + }, + }, + }, + input: map[string]interface{}{ + "varMap": map[string]interface{}{ + "invalid": []interface{}{ + map[string]interface{}{ + "key": float64(1), + "value": "val1", + }, + map[string]interface{}{ + "key": float64(3), + "value": "val3", + }, + }, + }, + }, + expectedError: "mapEntries schema property must be present", + }, + { + name: "value missing for map input param", + processSchemaReq: map[string]interface{}{ + "type": "array", + "prefixItems": []interface{}{ + map[string]interface{}{ + "name": "varMap", + "type": "object", + "details": map[string]interface{}{ + "type": "schema", + "internalSchema": map[string]interface{}{ + "type": "map", + "args": []interface{}{ + map[string]interface{}{ + "name": "key", + "type": "integer", + }, + map[string]interface{}{ + "name": "value", + "type": "string", + }, + }, + }, + }, + }, + }, + }, + input: map[string]interface{}{ + "varMap": map[string]interface{}{ + "mapEntries": []interface{}{ + map[string]interface{}{ + "key": float64(1), + }, + map[string]interface{}{ + "key": float64(3), + "value": "val3", + }, + }, + }, + }, + expectedError: "Schema field 'value' wasn't found", + }, + { + name: "key missing for map input param", + processSchemaReq: map[string]interface{}{ + "type": "array", + "prefixItems": []interface{}{ + map[string]interface{}{ + "name": "varMap", + "type": "object", + "details": map[string]interface{}{ + "type": "schema", + "internalSchema": map[string]interface{}{ + "type": "map", + "args": []interface{}{ + map[string]interface{}{ + "name": "key", + "type": "integer", + }, + map[string]interface{}{ + "name": "value", + "type": "string", + }, + }, + }, + }, + }, + }, + }, + input: map[string]interface{}{ + "varMap": map[string]interface{}{ + "mapEntries": []interface{}{ + map[string]interface{}{ + "value": "val1", + }, + map[string]interface{}{ + "key": float64(3), + "value": "val3", + }, + }, + }, + }, + expectedError: "Schema field 'key' wasn't found", + }, + { + name: "unknown field for map input param", + processSchemaReq: map[string]interface{}{ + "type": "array", + "prefixItems": []interface{}{ + map[string]interface{}{ + "name": "varMap", + "type": "object", + "details": map[string]interface{}{ + "type": "schema", + "internalSchema": map[string]interface{}{ + "type": "map", + "args": []interface{}{ + map[string]interface{}{ + "name": "key", + "type": "integer", + }, + map[string]interface{}{ + "name": "value", + "type": "string", + }, + }, + }, + }, + }, + }, + }, + input: map[string]interface{}{ + "varMap": map[string]interface{}{ + "mapEntries": []interface{}{ + map[string]interface{}{ + "key": float64(1), + "value": "val1", + "unknown": float64(1), + }, + map[string]interface{}{ + "key": float64(3), + "value": "val3", + }, + }, + }, + }, + expectedError: "Unknown schema field 'unknown' in map entry", + }, } for _, tc := range testCases { From 6a51572d12cd75bd956fb2c1790758e5bc2250f2 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 23 Jan 2024 23:15:41 +0600 Subject: [PATCH 3/3] Fix year for linter Signed-off-by: Alexey Semenyuk --- internal/blockchain/tezos/ffi2michelson.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/blockchain/tezos/ffi2michelson.go b/internal/blockchain/tezos/ffi2michelson.go index 2d5679bab..f6afb6871 100644 --- a/internal/blockchain/tezos/ffi2michelson.go +++ b/internal/blockchain/tezos/ffi2michelson.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 //