diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 258cc85c81..f4eeacce09 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -35,9 +35,11 @@ import ( "github.com/onflow/cadence/encoding/ccf" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/tests/checker" + "github.com/onflow/cadence/runtime/tests/runtime_utils" "github.com/onflow/cadence/runtime/tests/utils" ) @@ -17108,3 +17110,67 @@ func TestDecodeFunctionTypeBackwardCompatibility(t *testing.T) { testDecode(t, data, val) } + +func TestEncodeEventWithAttachement(t *testing.T) { + script := ` + access(all) struct S { + access(all) let x: Int + init(x: Int) { + self.x = x + } + } + + access(all) attachment A for S { + access(all) let y: Int + init(y: Int) { + self.y = y + } + } + + access(all) event Foo(s: S) + + access(all) + fun main() { + let s = attach A(y: 3) to S(x: 4) + emit Foo(s: s) + } + ` + + v := exportEventFromScript(t, script) + + _, err := ccf.Encode(v) + + var attachmentFieldError ccf.AttachmentFieldNotSupportedEncodingError + require.ErrorAs(t, err, &attachmentFieldError) + require.Implements(t, (*errors.UserError)(nil), attachmentFieldError) +} + +func exportEventFromScript(t *testing.T, script string) cadence.Event { + rt := runtime_utils.NewTestInterpreterRuntime() + + var events []cadence.Event + + inter := &runtime_utils.TestRuntimeInterface{ + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + } + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: []byte(script), + }, + runtime.Context{ + Interface: inter, + Location: common.ScriptLocation{}, + }, + ) + + require.NoError(t, err) + require.Len(t, events, 1) + + event := events[0] + + return event +} diff --git a/encoding/ccf/encode.go b/encoding/ccf/encode.go index 9317773150..f7fa8a8ca5 100644 --- a/encoding/ccf/encode.go +++ b/encoding/ccf/encode.go @@ -176,6 +176,40 @@ func (em *encMode) NewEncoder(w io.Writer) *Encoder { var defaultEncMode = &encMode{} +// AttachmentFieldNotSupportedEncodingError is a user error that is returned +// when encoding composite value (such as Event) that has cadence.Attachment field. +type AttachmentFieldNotSupportedEncodingError struct { + compositeType string + fieldCount int + fieldTypeCount int +} + +func newAttachmentFieldNotSupportedError( + compositeType string, + fieldCount int, + fieldTypeCount int, +) error { + return AttachmentFieldNotSupportedEncodingError{ + compositeType: compositeType, + fieldCount: fieldCount, + fieldTypeCount: fieldTypeCount, + } +} + +func (e AttachmentFieldNotSupportedEncodingError) Error() string { + return fmt.Sprintf( + "encoding attachment field in composite value isn't supported: %s field count %d doesn't match declared field type count %d", + e.compositeType, + e.fieldCount, + e.fieldTypeCount, + ) +} + +func (e AttachmentFieldNotSupportedEncodingError) IsUserError() { +} + +var _ cadenceErrors.UserError = AttachmentFieldNotSupportedEncodingError{} + // Encode returns the CCF-encoded representation of the given value // by using default CCF encoding options. This function returns an // error if the Cadence value cannot be represented in CCF. @@ -219,7 +253,7 @@ func (e *Encoder) Encode(value cadence.Value) (err error) { // Add context to error if there is any. if err != nil { err = fmt.Errorf( - "ccf: failed to encode value (type %T, %q): %s", + "ccf: failed to encode value (type %T, %q): %w", value, value.Type().ID(), err, @@ -1102,6 +1136,22 @@ func (e *Encoder) encodeComposite( staticFieldTypes := getCompositeTypeFields(typ) if len(staticFieldTypes) != len(fields) { + + // The CCF encoder requires field values and types to match. However, + // composite values that have attachment field don't satisfy this requirement because: + // - composite field values INCLUDE attachment field values + // - composite field types EXCLUDE attachment field types + // Given this, CCF encoder will return a user error if composite value has attachment field. + for _, f := range fields { + if _, ok := f.(cadence.Attachment); ok { + panic(newAttachmentFieldNotSupportedError( + typ.ID(), + len(fields), + len(staticFieldTypes), + )) + } + } + panic(cadenceErrors.NewUnexpectedError( "%s field count %d doesn't match declared field type count %d", typ.ID(),