Skip to content

Commit

Permalink
Merge pull request #13 from kaleido-io/ffi-schema-fixes
Browse files Browse the repository at this point in the history
Fix oneOf FFI param schema
  • Loading branch information
peterbroadhurst committed Jul 6, 2022
2 parents c2155da + e335f5e commit 4390cf5
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 54 deletions.
1 change: 1 addition & 0 deletions internal/signermsgs/en_error_messges.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ var (
MsgInvalidFFIDetailsSchema = ffe("FF22052", "Invalid FFI details schema for '%s'")
MsgEventsInsufficientTopics = ffe("FF22053", "Ran out of topics for indexed fields at field %d of %s")
MsgEventSignatureMismatch = ffe("FF22054", "Event signature mismatch for '%s': expected='%s' found='%s'")
MsgFFITypeMismatch = ffe("FF22055", "Input type '%s' is not valid for ABI type '%s'")
)
84 changes: 44 additions & 40 deletions pkg/ffi2abi/ffi.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package ffi2abi
import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/hyperledger/firefly-common/pkg/fftypes"
Expand Down Expand Up @@ -63,8 +62,8 @@ type paramDetails struct {
}

type Schema struct {
Type string `json:"type,omitempty"`
OneOf []SchemaType `json:"oneOf,omitempty"`
Type *fftypes.JSONAny `json:"type,omitempty"`
Details *paramDetails `json:"details,omitempty"`
Properties map[string]*Schema `json:"properties,omitempty"`
Items *Schema `json:"items,omitempty"`
Expand Down Expand Up @@ -255,19 +254,19 @@ func getSchemaForABIInput(ctx context.Context, typeComponent abi.TypeComponent)
}
schema.Description = i18n.Expand(ctx, signermsgs.APIBoolDescription)
case abi.JSONEncodingTypeBytes:
schema.Type = fftypes.JSONAnyPtr(fmt.Sprintf(`"%s"`, jsonStringType))
schema.Type = jsonStringType
schema.Description = i18n.Expand(ctx, signermsgs.APIHexDescription)
default:
schema.Type = fftypes.JSONAnyPtr(fmt.Sprintf(`"%s"`, jsonStringType))
schema.Type = jsonStringType
}
case abi.FixedArrayComponent, abi.DynamicArrayComponent:
schema.Type = fftypes.JSONAnyPtr(fmt.Sprintf(`"%s"`, jsonArrayType))
schema.Type = jsonArrayType
childSchema := getSchemaForABIInput(ctx, typeComponent.ArrayChild())
schema.Items = childSchema
schema.Details = childSchema.Details
childSchema.Details = nil
case abi.TupleComponent:
schema.Type = fftypes.JSONAnyPtr(fmt.Sprintf(`"%s"`, jsonObjectType))
schema.Type = jsonObjectType
schema.Properties = make(map[string]*Schema, len(typeComponent.TupleChildren()))
for i, tupleChild := range typeComponent.TupleChildren() {
childSchema := getSchemaForABIInput(ctx, tupleChild)
Expand Down Expand Up @@ -309,49 +308,57 @@ func convertFFIParamsToABIParameters(ctx context.Context, params fftypes.FFIPara
if err != nil {
return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidFFIDetailsSchema, param.Name)
}
if !inputTypeValidForTypeComponent(ctx, s.Type, tc) {
return nil, i18n.NewError(ctx, signermsgs.MsgInvalidFFIDetailsSchema, param.Name)
if err := inputTypeValidForTypeComponent(ctx, s, tc); err != nil {
return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidFFIDetailsSchema, param.Name)
}

abiParamList[i] = abiParameter
}
return abiParamList, nil
}

func inputTypeValidForTypeComponent(ctx context.Context, inputType *fftypes.JSONAny, tc abi.TypeComponent) bool {
func inputTypeValidForTypeComponent(ctx context.Context, inputSchema *Schema, tc abi.TypeComponent) error {
var inputTypeString string
if err := inputType.Unmarshal(ctx, &inputTypeString); err != nil {
if o, ok := inputType.JSONObjectOk(); ok {
if a, ok := o.GetObjectArrayOk("oneOf"); ok {
for _, t := range a {
// Look for the entry in the oneOf that isn't "string"
jsonType := t.GetString("type")
if jsonType != "" && jsonType != inputTypeString {
inputTypeString = jsonType
}
}
if inputSchema.OneOf != nil {
for _, t := range inputSchema.OneOf {
if t.Type != jsonStringType {
inputTypeString = t.Type
}
}
} else {
inputTypeString = inputSchema.Type
}
switch inputTypeString {
case jsonBooleanType:
// Booleans are only valid for boolean types
return tc.ComponentType() == abi.ElementaryComponent && tc.ElementaryType().JSONEncodingType() == abi.JSONEncodingTypeBool
if tc.ComponentType() == abi.ElementaryComponent && tc.ElementaryType().JSONEncodingType() == abi.JSONEncodingTypeBool {
return nil
}
case jsonIntegerType:
// Integers are only valid for integer types
return tc.ComponentType() == abi.ElementaryComponent && tc.ElementaryType().JSONEncodingType() == abi.JSONEncodingTypeInteger
if tc.ComponentType() == abi.ElementaryComponent && tc.ElementaryType().JSONEncodingType() == abi.JSONEncodingTypeInteger {
return nil
}
case jsonNumberType:
// Integers are only valid for float types
return tc.ComponentType() == abi.ElementaryComponent && tc.ElementaryType().JSONEncodingType() == abi.JSONEncodingTypeFloat
if tc.ComponentType() == abi.ElementaryComponent && tc.ElementaryType().JSONEncodingType() == abi.JSONEncodingTypeFloat {
return nil
}
case jsonStringType:
// Strings are valid for all elementary components
return tc.ComponentType() == abi.ElementaryComponent
if tc.ComponentType() == abi.ElementaryComponent {
return nil
}
case jsonArrayType:
return tc.ComponentType() == abi.DynamicArrayComponent || tc.ComponentType() == abi.FixedArrayComponent
if tc.ComponentType() == abi.DynamicArrayComponent || tc.ComponentType() == abi.FixedArrayComponent {
return nil
}
case jsonObjectType:
return tc.ComponentType() == abi.TupleComponent
if tc.ComponentType() == abi.TupleComponent {
return nil
}
}
return false
return i18n.NewError(ctx, signermsgs.MsgFFITypeMismatch, inputTypeString, tc.ElementaryType().String())
}

func buildABIParameterArrayForObject(ctx context.Context, properties map[string]*Schema) (abi.ParameterArray, error) {
Expand All @@ -366,29 +373,26 @@ func buildABIParameterArrayForObject(ctx context.Context, properties map[string]
return parameters, nil
}

func processField(ctx context.Context, name string, schema *Schema) (*abi.Parameter, error) {
func processField(ctx context.Context, name string, schema *Schema) (parameter *abi.Parameter, err error) {
if schema.Details == nil {
return nil, i18n.NewError(ctx, signermsgs.MsgInvalidFFIDetailsSchema, name)
}
parameter := &abi.Parameter{
parameter = &abi.Parameter{
Name: name,
Type: schema.Details.Type,
InternalType: schema.Details.InternalType,
Indexed: schema.Details.Indexed,
}
var schemaTypeString string
if err := json.Unmarshal(schema.Type.Bytes(), &schemaTypeString); err == nil {
switch schemaTypeString {
case jsonObjectType:
parameter.Components, err = buildABIParameterArrayForObject(ctx, schema.Properties)
case jsonArrayType:
parameter.Components, err = buildABIParameterArrayForObject(ctx, schema.Items.Properties)
}
if err != nil {
return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidFFIDetailsSchema, name)
}
switch schema.Type {
case jsonObjectType:
parameter.Components, err = buildABIParameterArrayForObject(ctx, schema.Properties)
case jsonArrayType:
parameter.Components, err = buildABIParameterArrayForObject(ctx, schema.Items.Properties)
}
if err != nil {
return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidFFIDetailsSchema, name)
}
return parameter, nil
return
}

func ABIArgumentToTypeString(typeName string, components abi.ParameterArray) string {
Expand Down
32 changes: 18 additions & 14 deletions pkg/ffi2abi/ffi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -888,11 +888,11 @@ func TestConvertFFIEventDefinitionToABIInvalidSchema(t *testing.T) {

func TestProcessFieldInvalidSchema(t *testing.T) {
s := &Schema{
Type: fftypes.JSONAnyPtr(`"object"`),
Type: "object",
Details: &paramDetails{},
Properties: map[string]*Schema{
"badProperty": {
Type: fftypes.JSONAnyPtr("foo"),
Type: "foo",
},
},
}
Expand Down Expand Up @@ -978,37 +978,41 @@ func TestConvertFFIParamsToABIParametersTypeMismatch(t *testing.T) {
}

func TestInputTypeValidForTypeComponent(t *testing.T) {
inputType := fftypes.JSONAnyPtr(`"boolean"`)
inputSchema := &Schema{
Type: "boolean",
}
param := abi.Parameter{
Type: "bool",
}
tc, _ := param.TypeComponentTree()
assert.True(t, inputTypeValidForTypeComponent(context.Background(), inputType, tc))
assert.NoError(t, inputTypeValidForTypeComponent(context.Background(), inputSchema, tc))
}

func TestInputTypeValidForTypeComponentOneOf(t *testing.T) {
inputType := fftypes.JSONAnyPtr(`{
"oneOf": [
inputSchema := &Schema{
OneOf: []SchemaType{
{
"type": "integer"
Type: "integer",
},
{
"type": "string"
}
]
}`)
Type: "string",
},
},
}
param := abi.Parameter{
Type: "uint256",
}
tc, _ := param.TypeComponentTree()
assert.True(t, inputTypeValidForTypeComponent(context.Background(), inputType, tc))
assert.NoError(t, inputTypeValidForTypeComponent(context.Background(), inputSchema, tc))
}

func TestInputTypeValidForTypeComponentInvalid(t *testing.T) {
inputType := fftypes.JSONAnyPtr(`"foobar"`)
inputSchema := &Schema{
Type: "foobar",
}
param := abi.Parameter{
Type: "bool",
}
tc, _ := param.TypeComponentTree()
assert.False(t, inputTypeValidForTypeComponent(context.Background(), inputType, tc))
assert.Regexp(t, "FF22055", inputTypeValidForTypeComponent(context.Background(), inputSchema, tc))
}

0 comments on commit 4390cf5

Please sign in to comment.