Skip to content

Commit

Permalink
Merge pull request #3494 from onflow/fxamacker/return-error-from-ccf-…
Browse files Browse the repository at this point in the history
…encoder-when-field-types-and-field-values-mismatch

Return user error when CCF encodes attachment field
  • Loading branch information
fxamacker authored Jul 25, 2024
2 parents 0276b47 + c985da4 commit 9ac4307
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 1 deletion.
66 changes: 66 additions & 0 deletions encoding/ccf/ccf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
}
52 changes: 51 additions & 1 deletion encoding/ccf/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit 9ac4307

Please sign in to comment.