From 1b96d4b15c5e2cf6b809d546a6b3be1926fe5c34 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:20:05 -0600 Subject: [PATCH] Support FunctionType.Purity in CCF codec For backward compability, decoded FunctionType.Purity defaults to FunctionPurityUnspecified if it isn't encoded explicitly. --- encoding/ccf/ccf_test.go | 76 ++++++++++++++++++++++++++++++++++------ encoding/ccf/decode.go | 23 ++++++++++-- encoding/ccf/encode.go | 15 +++++--- types.go | 10 ++++++ 4 files changed, 106 insertions(+), 18 deletions(-) diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 95e2287f06..960541fa6b 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -9475,14 +9475,15 @@ func TestEncodeType(t *testing.T) { {Label: "qux", Identifier: "baz", Type: cadence.StringType}, }, ReturnType: cadence.IntType, + Purity: cadence.FunctionPurityView, }, }, []byte{ // language=json, format=json-cdc - // {"value":{"staticType":{"kind":"Function","typeParameters":[{"name":"T","typeBound":{"kind":"AnyStruct"}}],"parameters":[{"type":{"kind":"String"},"label":"qux","id":"baz"}],"return":{"kind":"Int"}}},"type":"Type"} + // {"value":{"staticType":{"kind":"Function","typeParameters":[{"name":"T","typeBound":{"kind":"AnyStruct"}}],"parameters":[{"type":{"kind":"String"},"label":"qux","id":"baz"}],"return":{"kind":"Int"},"purity":"view"}},"type":"Type"} // // language=edn, format=ccf - // 130([137(41), 193([[["T", 185(39)]], [["qux", "baz", 185(1)]], 185(4)])]) + // 130([137(41), 193([[["T", 185(39)]], [["qux", "baz", 185(1)]], 185(4), 1])]) // // language=cbor, format=ccf // tag @@ -9495,8 +9496,8 @@ func TestEncodeType(t *testing.T) { 0x18, 0x29, // tag 0xd8, ccf.CBORTagFunctionTypeValue, - // array, 3 elements follow - 0x83, + // array, 4 elements follow + 0x84, // array, 1 elements follow 0x81, // array, 2 elements follow @@ -9529,6 +9530,8 @@ func TestEncodeType(t *testing.T) { 0xd8, ccf.CBORTagSimpleTypeValue, // Int type ID (4) 0x04, + // Purity 1 + 0x01, }, ) }) @@ -9554,7 +9557,7 @@ func TestEncodeType(t *testing.T) { // {"value":{"staticType":{"kind":"Function","typeParameters":[{"name":"T","typeBound":null}],"parameters":[{"type":{"kind":"String"},"label":"qux","id":"baz"}],"return":{"kind":"Int"}}},"type":"Type"} // // language=edn, format=ccf - // 130([137(41), 193([[["T", null]], [["qux", "baz", 185(1)]], 185(4)])]) + // 130([137(41), 193([[["T", null]], [["qux", "baz", 185(1)]], 185(4), 0])]) // // language=cbor, format=ccf // tag @@ -9567,8 +9570,8 @@ func TestEncodeType(t *testing.T) { 0x18, 0x29, // tag 0xd8, ccf.CBORTagFunctionTypeValue, - // array, 3 elements follow - 0x83, + // array, 4 elements follow + 0x84, // array, 1 elements follow 0x81, // array, 2 elements follow @@ -9599,6 +9602,8 @@ func TestEncodeType(t *testing.T) { 0xd8, ccf.CBORTagSimpleTypeValue, // Int type ID (4) 0x04, + // purity 0 + 0x00, }, ) }) @@ -12182,14 +12187,15 @@ func TestExportFunctionValue(t *testing.T) { FunctionType: &cadence.FunctionType{ Parameters: []cadence.Parameter{}, ReturnType: cadence.VoidType, + Purity: cadence.FunctionPurityView, }, }, []byte{ // language=json, format=json-cdc - // {"value":{"functionType":{"kind":"Function","typeParameters":[],"parameters":[],"return":{"kind":"Void"}}},"type":"Function"} + // {"value":{"functionType":{"kind":"Function","typeParameters":[],"parameters":[],"return":{"kind":"Void"},"purity":"view"}},"type":"Function"} // // language=edn, format=ccf - // 130([137(51), [[], [], 185(50)]]) + // 130([137(51), [[], [], 185(50), 1]]) // // language=cbor, format=ccf // tag @@ -12200,8 +12206,8 @@ func TestExportFunctionValue(t *testing.T) { 0xd8, ccf.CBORTagSimpleType, // Function type ID (51) 0x18, 0x33, - // array, 3 elements follow - 0x83, + // array, 4 elements follow + 0x84, // element 0: type parameters 0x80, // element 1: parameters @@ -12212,6 +12218,8 @@ func TestExportFunctionValue(t *testing.T) { 0xd8, ccf.CBORTagSimpleTypeValue, // Void type ID (50) 0x18, 0x32, + // element 3: purity + 0x01, }, ) } @@ -15661,3 +15669,49 @@ func TestHasMsgPrefix(t *testing.T) { }) } } + +// TestDecodeFunctionTypeBackwardCompatibility tests decoding of FunctionType +// without Purity encoded. +func TestDecodeFunctionTypeBackwardCompatibility(t *testing.T) { + + val := cadence.TypeValue{ + StaticType: &cadence.FunctionType{ + TypeParameters: []cadence.TypeParameter{}, + Parameters: []cadence.Parameter{}, + ReturnType: cadence.VoidType, + Purity: cadence.FunctionPurityUnspecified, + }, + } + + data := []byte{ + // language=edn, format=ccf + // 130([137(41), 193([[], [], 185(50)])]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 elements follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Meta type ID (41) + 0x18, 0x29, + // tag + 0xd8, ccf.CBORTagFunctionTypeValue, + // array, 3 elements follow + 0x83, + // element 0: type parameters + // array, 0 element + 0x80, + // element 1: parameters + // array, 0 element + 0x80, + // element 2: return type + // tag + 0xd8, ccf.CBORTagSimpleTypeValue, + // Void type ID (50) + 0x18, 0x32, + } + + testDecode(t, data, val) +} diff --git a/encoding/ccf/decode.go b/encoding/ccf/decode.go index 6a3df5b482..14702f1bce 100644 --- a/encoding/ccf/decode.go +++ b/encoding/ccf/decode.go @@ -2227,15 +2227,20 @@ func (d *Decoder) decodeParameterTypeValue(visited *cadenceTypeByCCFTypeID) (cad // ] // ] // return-type: type-value +// purity: int (optional) // // ] func (d *Decoder) decodeFunctionTypeValue(visited *cadenceTypeByCCFTypeID) (cadence.Type, error) { - // Decode array head of length 3 - err := decodeCBORArrayWithKnownSize(d.dec, 3) + // Decode array head for element count + c, err := d.dec.DecodeArrayHead() if err != nil { return nil, err } + if c != 3 && c != 4 { + return nil, fmt.Errorf("CBOR array of function-value has %d elements (expected 3 or 4 elements)", c) + } + // element 0: type parameters typeParameters, err := d.decodeTypeParameterTypeValues(visited) if err != nil { @@ -2258,9 +2263,21 @@ func (d *Decoder) decodeFunctionTypeValue(visited *cadenceTypeByCCFTypeID) (cade return nil, errors.New("unexpected nil function return type") } - // TODO: purity := cadence.FunctionPurityUnspecified + // optional element 3: purity + if c == 4 { + rawPurity, err := d.dec.DecodeInt64() + if err != nil { + return nil, err + } + + purity, err = cadence.NewFunctionaryPurity(int(rawPurity)) + if err != nil { + return nil, err + } + } + return cadence.NewMeteredFunctionType( d.gauge, purity, diff --git a/encoding/ccf/encode.go b/encoding/ccf/encode.go index 3feb255e5a..756fd97cdb 100644 --- a/encoding/ccf/encode.go +++ b/encoding/ccf/encode.go @@ -1184,13 +1184,14 @@ func (e *Encoder) encodeCapability(capability cadence.Capability) error { // ] // ] // return-type: type-value +// purity: int // // ] func (e *Encoder) encodeFunction(typ *cadence.FunctionType, visited ccfTypeIDByCadenceType) error { - // Encode array head of length 3. + // Encode array head of length 4. err := e.enc.EncodeRawBytes([]byte{ - // array, 3 items follow - 0x83, + // array, 4 items follow + 0x84, }) if err != nil { return err @@ -1209,7 +1210,13 @@ func (e *Encoder) encodeFunction(typ *cadence.FunctionType, visited ccfTypeIDByC } // element 2: return type as type-value. - return e.encodeTypeValue(typ.ReturnType, visited) + err = e.encodeTypeValue(typ.ReturnType, visited) + if err != nil { + return err + } + + // element 3: purity as int. + return e.enc.EncodeInt(int(typ.Purity)) } // encodeTypeValue encodes cadence.Type as diff --git a/types.go b/types.go index 399d247d2c..46b92fc275 100644 --- a/types.go +++ b/types.go @@ -1308,8 +1308,18 @@ type FunctionPurity int const ( FunctionPurityUnspecified FunctionPurity = iota FunctionPurityView + + // DO NOT add item after maxFunctionPurity + maxFunctionPurity ) +func NewFunctionaryPurity(rawPurity int) (FunctionPurity, error) { + if rawPurity < 0 || rawPurity >= int(maxFunctionPurity) { + return FunctionPurityUnspecified, fmt.Errorf("failed to convert %d to FunctionPurity", rawPurity) + } + return FunctionPurity(rawPurity), nil +} + type FunctionType struct { TypeParameters []TypeParameter Parameters []Parameter