Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return user error when CCF encodes attachment field #3494

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading