diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index a8db59e9..07ab7d6f 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -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'") ) diff --git a/pkg/ffi2abi/ffi.go b/pkg/ffi2abi/ffi.go index 8bc267b6..036820d7 100644 --- a/pkg/ffi2abi/ffi.go +++ b/pkg/ffi2abi/ffi.go @@ -19,7 +19,6 @@ package ffi2abi import ( "context" "encoding/json" - "fmt" "strings" "github.com/hyperledger/firefly-common/pkg/fftypes" @@ -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"` @@ -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) @@ -309,8 +308,8 @@ 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 @@ -318,40 +317,48 @@ func convertFFIParamsToABIParameters(ctx context.Context, params fftypes.FFIPara 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) { @@ -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 { diff --git a/pkg/ffi2abi/ffi_test.go b/pkg/ffi2abi/ffi_test.go index 633f5b61..5578ee3f 100644 --- a/pkg/ffi2abi/ffi_test.go +++ b/pkg/ffi2abi/ffi_test.go @@ -888,11 +888,11 @@ func TestConvertFFIEventDefinitionToABIInvalidSchema(t *testing.T) { func TestProcessFieldInvalidSchema(t *testing.T) { s := &Schema{ - Type: fftypes.JSONAnyPtr(`"object"`), + Type: "object", Details: ¶mDetails{}, Properties: map[string]*Schema{ "badProperty": { - Type: fftypes.JSONAnyPtr("foo"), + Type: "foo", }, }, } @@ -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)) }