Skip to content

Commit 5ea3f3b

Browse files
committed
add built-in CCF contract with encode function
1 parent 9b853da commit 5ea3f3b

File tree

8 files changed

+271
-0
lines changed

8 files changed

+271
-0
lines changed

cmd/cmd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"strings"
2727
"time"
2828

29+
"github.com/onflow/cadence"
2930
"github.com/onflow/cadence/activations"
3031
"github.com/onflow/cadence/ast"
3132
"github.com/onflow/cadence/common"
@@ -433,6 +434,17 @@ func (h *StandardLibraryHandler) IsContractBeingAdded(common.AddressLocation) bo
433434
return false
434435
}
435436

437+
func (h *StandardLibraryHandler) ExportValue(
438+
_ interpreter.Value,
439+
_ *interpreter.Interpreter,
440+
_ interpreter.LocationRange,
441+
) (
442+
cadence.Value,
443+
error,
444+
) {
445+
return nil, goerrors.New("exporting values is not supported in this environment")
446+
}
447+
436448
func formatLocationRange(locationRange interpreter.LocationRange) string {
437449
var builder strings.Builder
438450
if locationRange.Location != nil {

common/computationkind.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,6 @@ const (
142142
// RLP
143143
ComputationKindSTDLIBRLPDecodeString
144144
ComputationKindSTDLIBRLPDecodeList
145+
// CCF
146+
ComputationKindSTDLIBCCFEncode
145147
)

runtime/environment.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ var _ stdlib.BLSPoPVerifier = &interpreterEnvironment{}
146146
var _ stdlib.BLSPublicKeyAggregator = &interpreterEnvironment{}
147147
var _ stdlib.BLSSignatureAggregator = &interpreterEnvironment{}
148148
var _ stdlib.Hasher = &interpreterEnvironment{}
149+
var _ stdlib.Exporter = &interpreterEnvironment{}
149150
var _ ArgumentDecoder = &interpreterEnvironment{}
150151
var _ common.MemoryGauge = &interpreterEnvironment{}
151152

@@ -1426,3 +1427,18 @@ func (e *interpreterEnvironment) newValidateAccountCapabilitiesPublishHandler()
14261427
return ok, err
14271428
}
14281429
}
1430+
1431+
func (e *interpreterEnvironment) ExportValue(
1432+
value interpreter.Value,
1433+
interpreter *interpreter.Interpreter,
1434+
locationRange interpreter.LocationRange,
1435+
) (
1436+
cadence.Value,
1437+
error,
1438+
) {
1439+
return ExportValue(
1440+
value,
1441+
interpreter,
1442+
locationRange,
1443+
)
1444+
}

stdlib/builtin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type StandardLibraryHandler interface {
3636
BLSPublicKeyAggregator
3737
BLSSignatureAggregator
3838
Hasher
39+
Exporter
3940
}
4041

4142
func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibraryValue {
@@ -54,6 +55,7 @@ func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibr
5455
NewPublicKeyConstructor(handler),
5556
NewBLSContract(nil, handler),
5657
NewHashAlgorithmConstructor(handler),
58+
NewCCFContract(nil, handler),
5759
}
5860
}
5961

stdlib/ccf.cdc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
access(all)
2+
contract CCF {
3+
/// Encodes an encodable value to CCF.
4+
/// Returns nil if the value cannot be encoded.
5+
access(all)
6+
view fun encode(_ input: &Any): [UInt8]?
7+
}

stdlib/ccf.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Cadence - The resource-oriented smart contract programming language
3+
*
4+
* Copyright Flow Foundation
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package stdlib
20+
21+
//go:generate go run ../sema/gen -p stdlib ccf.cdc ccf.gen.go
22+
23+
import (
24+
"github.com/onflow/cadence"
25+
"github.com/onflow/cadence/common"
26+
"github.com/onflow/cadence/encoding/ccf"
27+
"github.com/onflow/cadence/errors"
28+
"github.com/onflow/cadence/interpreter"
29+
)
30+
31+
type Exporter interface {
32+
ExportValue(
33+
value interpreter.Value,
34+
interpreter *interpreter.Interpreter,
35+
locationRange interpreter.LocationRange,
36+
) (
37+
cadence.Value,
38+
error,
39+
)
40+
}
41+
42+
type CCFContractHandler interface {
43+
Exporter
44+
}
45+
46+
// newCCFEncodeFunction creates a new host function that encodes a value using the CCF encoding format.
47+
func newCCFEncodeFunction(
48+
gauge common.MemoryGauge,
49+
handler CCFContractHandler,
50+
) *interpreter.HostFunctionValue {
51+
return interpreter.NewStaticHostFunctionValue(
52+
gauge,
53+
CCFTypeEncodeFunctionType,
54+
func(invocation interpreter.Invocation) interpreter.Value {
55+
inter := invocation.Interpreter
56+
locationRange := invocation.LocationRange
57+
58+
referenceValue, ok := invocation.Arguments[0].(interpreter.ReferenceValue)
59+
if !ok {
60+
panic(errors.NewUnreachableError())
61+
}
62+
63+
referencedValue := referenceValue.ReferencedValue(inter, locationRange, true)
64+
if referencedValue == nil {
65+
return interpreter.Nil
66+
}
67+
68+
exportedValue, err := handler.ExportValue(*referencedValue, inter, locationRange)
69+
if err != nil {
70+
return interpreter.Nil
71+
}
72+
73+
encoded, err := ccf.Encode(exportedValue)
74+
if err != nil {
75+
return interpreter.Nil
76+
}
77+
78+
res := interpreter.ByteSliceToByteArrayValue(inter, encoded)
79+
80+
return interpreter.NewSomeValueNonCopying(inter, res)
81+
},
82+
)
83+
}
84+
85+
var CCFTypeStaticType = interpreter.ConvertSemaToStaticType(nil, CCFType)
86+
87+
func NewCCFContract(
88+
gauge common.MemoryGauge,
89+
handler CCFContractHandler,
90+
) StandardLibraryValue {
91+
92+
ccfContractFields := map[string]interpreter.Value{
93+
CCFTypeEncodeFunctionName: newCCFEncodeFunction(gauge, handler),
94+
}
95+
96+
var ccfContractValue = interpreter.NewSimpleCompositeValue(
97+
gauge,
98+
CCFType.ID(),
99+
CCFTypeStaticType,
100+
nil,
101+
ccfContractFields,
102+
nil,
103+
nil,
104+
nil,
105+
)
106+
107+
return StandardLibraryValue{
108+
Name: CCFTypeName,
109+
Type: CCFType,
110+
Value: ccfContractValue,
111+
Kind: common.DeclarationKindContract,
112+
}
113+
}

tests/ccf_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Cadence - The resource-oriented smart contract programming language
3+
*
4+
* Copyright Flow Foundation
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package tests
20+
21+
import (
22+
"fmt"
23+
"testing"
24+
25+
"github.com/stretchr/testify/assert"
26+
"github.com/stretchr/testify/require"
27+
28+
"github.com/onflow/cadence"
29+
"github.com/onflow/cadence/common"
30+
. "github.com/onflow/cadence/runtime"
31+
. "github.com/onflow/cadence/tests/runtime_utils"
32+
)
33+
34+
func TestRuntimeCCFEncodeStruct(t *testing.T) {
35+
36+
t.Parallel()
37+
38+
type testCase struct {
39+
name string
40+
value string
41+
output []byte
42+
}
43+
44+
tests := []testCase{
45+
{
46+
name: "String",
47+
value: `"test"`,
48+
output: []byte{0xd8, 0x82, 0x82, 0xd8, 0x89, 0x1, 0x64, 0x74, 0x65, 0x73, 0x74},
49+
},
50+
{
51+
name: "Bool",
52+
value: `true`,
53+
output: []byte{0xd8, 0x82, 0x82, 0xd8, 0x89, 0x0, 0xf5},
54+
},
55+
{
56+
name: "function",
57+
value: `fun (): Int { return 1 }`,
58+
output: []byte{0xd8, 0x82, 0x82, 0xd8, 0x89, 0x18, 0x33, 0x84, 0x80, 0x80, 0xd8, 0xb9, 0x4, 0x0},
59+
},
60+
}
61+
62+
test := func(test testCase) {
63+
t.Run(test.name, func(t *testing.T) {
64+
65+
t.Parallel()
66+
67+
runtime := NewTestInterpreterRuntime()
68+
69+
runtimeInterface := &TestRuntimeInterface{
70+
Storage: NewTestLedger(nil, nil),
71+
}
72+
73+
script := []byte(fmt.Sprintf(
74+
`
75+
access(all) fun main(): [UInt8]? {
76+
let value = %s
77+
return CCF.encode(&value as &AnyStruct)
78+
}
79+
`,
80+
test.value,
81+
))
82+
83+
result, err := runtime.ExecuteScript(
84+
Script{
85+
Source: script,
86+
},
87+
Context{
88+
Interface: runtimeInterface,
89+
Location: common.ScriptLocation{},
90+
},
91+
)
92+
require.NoError(t, err)
93+
94+
assert.Equal(t,
95+
cadence.NewOptional(
96+
cadence.ByteSliceToByteArray(test.output),
97+
),
98+
result,
99+
)
100+
})
101+
}
102+
103+
for _, testCase := range tests {
104+
test(testCase)
105+
}
106+
}

values.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,19 @@ func (v Array) String() string {
14361436
return format.Array(values)
14371437
}
14381438

1439+
var ByteArrayType = NewVariableSizedArrayType(PrimitiveType(interpreter.PrimitiveStaticTypeUInt8))
1440+
1441+
// ByteSliceToByteArray converts a byte slice to a Cadence byte array of type [UInt8].
1442+
func ByteSliceToByteArray(b []byte) Array {
1443+
values := make([]Value, len(b))
1444+
1445+
for i, v := range b {
1446+
values[i] = UInt8(v)
1447+
}
1448+
1449+
return NewArray(values).WithType(ByteArrayType)
1450+
}
1451+
14391452
// Dictionary
14401453

14411454
type Dictionary struct {

0 commit comments

Comments
 (0)