diff --git a/internal/blockchain/tezos/ffi2michelson.go b/internal/blockchain/tezos/ffi2michelson.go index 1930ddb4e..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 // @@ -19,6 +19,7 @@ package tezos import ( "errors" "fmt" + "slices" "blockwatch.cc/tzgo/micheline" "blockwatch.cc/tzgo/tezos" @@ -34,6 +35,7 @@ const ( _internalBoolean = "boolean" _internalList = "list" _internalStruct = "struct" + _internalMap = "map" _internalInteger = "integer" _internalNat = "nat" _internalString = "string" @@ -43,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, @@ -143,6 +152,44 @@ 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 schema property must be present") + } + for _, mapEntry := range mapEntries.([]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 { + if k, err = extractValue(_key, arg, mapEntry); err != nil { + return resp, err + } + } + + if arg["name"] == _value { + if v, err = extractValue(_value, arg, mapEntry); err != nil { + return resp, err + } + } + } + + mapElem := micheline.NewMapElem(k, v) + mapResp.Args = append(mapResp.Args, mapElem) + } + + resp = mapResp case _internalStruct: schemaArgs := schema["args"].([]interface{}) @@ -151,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 @@ -207,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 {