Skip to content

Commit

Permalink
add built-in CCF contract with encode function
Browse files Browse the repository at this point in the history
  • Loading branch information
turbolent committed Oct 23, 2024
1 parent 9b853da commit 1d2d5b3
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 0 deletions.
12 changes: 12 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"strings"
"time"

"github.com/onflow/cadence"
"github.com/onflow/cadence/activations"
"github.com/onflow/cadence/ast"
"github.com/onflow/cadence/common"
Expand Down Expand Up @@ -433,6 +434,17 @@ func (h *StandardLibraryHandler) IsContractBeingAdded(common.AddressLocation) bo
return false
}

func (h *StandardLibraryHandler) ExportValue(
_ interpreter.Value,
_ *interpreter.Interpreter,
_ interpreter.LocationRange,
) (
cadence.Value,
error,
) {
return nil, goerrors.New("exporting values is not supported in this environment")
}

func formatLocationRange(locationRange interpreter.LocationRange) string {
var builder strings.Builder
if locationRange.Location != nil {
Expand Down
2 changes: 2 additions & 0 deletions common/computationkind.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,6 @@ const (
// RLP
ComputationKindSTDLIBRLPDecodeString
ComputationKindSTDLIBRLPDecodeList
// CCF
ComputationKindSTDLIBCCFEncode
)
16 changes: 16 additions & 0 deletions runtime/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ var _ stdlib.BLSPoPVerifier = &interpreterEnvironment{}
var _ stdlib.BLSPublicKeyAggregator = &interpreterEnvironment{}
var _ stdlib.BLSSignatureAggregator = &interpreterEnvironment{}
var _ stdlib.Hasher = &interpreterEnvironment{}
var _ stdlib.Exporter = &interpreterEnvironment{}
var _ ArgumentDecoder = &interpreterEnvironment{}
var _ common.MemoryGauge = &interpreterEnvironment{}

Expand Down Expand Up @@ -1426,3 +1427,18 @@ func (e *interpreterEnvironment) newValidateAccountCapabilitiesPublishHandler()
return ok, err
}
}

func (e *interpreterEnvironment) ExportValue(
value interpreter.Value,
interpreter *interpreter.Interpreter,
locationRange interpreter.LocationRange,
) (
cadence.Value,
error,
) {
return ExportValue(
value,
interpreter,
locationRange,
)
}
2 changes: 2 additions & 0 deletions stdlib/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type StandardLibraryHandler interface {
BLSPublicKeyAggregator
BLSSignatureAggregator
Hasher
Exporter
}

func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibraryValue {
Expand All @@ -54,6 +55,7 @@ func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibr
NewPublicKeyConstructor(handler),
NewBLSContract(nil, handler),
NewHashAlgorithmConstructor(handler),
NewCCFContract(nil, handler),
}
}

Expand Down
7 changes: 7 additions & 0 deletions stdlib/ccf.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
access(all)
contract CCF {
/// Encodes an encodable value to CCF.
/// Returns nil if the value cannot be encoded.
access(all)
view fun encode(_ input: &Any): [UInt8]?
}
82 changes: 82 additions & 0 deletions stdlib/ccf.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

113 changes: 113 additions & 0 deletions stdlib/ccf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Cadence - The resource-oriented smart contract programming language
*
* Copyright Flow Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package stdlib

//go:generate go run ../sema/gen -p stdlib ccf.cdc ccf.gen.go

import (
"github.com/onflow/cadence"
"github.com/onflow/cadence/common"
"github.com/onflow/cadence/encoding/ccf"
"github.com/onflow/cadence/errors"
"github.com/onflow/cadence/interpreter"
)

type Exporter interface {
ExportValue(
value interpreter.Value,
interpreter *interpreter.Interpreter,
locationRange interpreter.LocationRange,
) (
cadence.Value,
error,
)
}

type CCFContractHandler interface {
Exporter
}

// newCCFEncodeFunction creates a new host function that encodes a value using the CCF encoding format.
func newCCFEncodeFunction(
gauge common.MemoryGauge,
handler CCFContractHandler,
) *interpreter.HostFunctionValue {
return interpreter.NewStaticHostFunctionValue(
gauge,
CCFTypeEncodeFunctionType,
func(invocation interpreter.Invocation) interpreter.Value {
inter := invocation.Interpreter
locationRange := invocation.LocationRange

referenceValue, ok := invocation.Arguments[0].(interpreter.ReferenceValue)
if !ok {
panic(errors.NewUnreachableError())
}

referencedValue := referenceValue.ReferencedValue(inter, locationRange, true)
if referencedValue == nil {
return interpreter.Nil
}

exportedValue, err := handler.ExportValue(*referencedValue, inter, locationRange)
if err != nil {
return interpreter.Nil
}

encoded, err := ccf.Encode(exportedValue)
if err != nil {
return interpreter.Nil
}

res := interpreter.ByteSliceToByteArrayValue(inter, encoded)

return interpreter.NewSomeValueNonCopying(inter, res)
},
)
}

var CCFTypeStaticType = interpreter.ConvertSemaToStaticType(nil, CCFType)

func NewCCFContract(
gauge common.MemoryGauge,
handler CCFContractHandler,
) StandardLibraryValue {

ccfContractFields := map[string]interpreter.Value{
CCFTypeEncodeFunctionName: newCCFEncodeFunction(gauge, handler),
}

var ccfContractValue = interpreter.NewSimpleCompositeValue(
gauge,
CCFType.ID(),
CCFTypeStaticType,
nil,
ccfContractFields,
nil,
nil,
nil,
)

return StandardLibraryValue{
Name: CCFTypeName,
Type: CCFType,
Value: ccfContractValue,
Kind: common.DeclarationKindContract,
}
}
106 changes: 106 additions & 0 deletions tests/ccf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Cadence - The resource-oriented smart contract programming language
*
* Copyright Flow Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package tests

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/onflow/cadence"
"github.com/onflow/cadence/common"
. "github.com/onflow/cadence/runtime"
. "github.com/onflow/cadence/tests/runtime_utils"
)

func TestRuntimeCCFEncodeStruct(t *testing.T) {

t.Parallel()

type testCase struct {
name string
value string
output []byte
}

tests := []testCase{
{
name: "String",
value: `"test"`,
output: []byte{0xd8, 0x82, 0x82, 0xd8, 0x89, 0x1, 0x64, 0x74, 0x65, 0x73, 0x74},
},
{
name: "Bool",
value: `true`,
output: []byte{0xd8, 0x82, 0x82, 0xd8, 0x89, 0x0, 0xf5},
},
{
name: "function",
value: `fun (): Int { return 1 }`,
output: []byte{0xd8, 0x82, 0x82, 0xd8, 0x89, 0x18, 0x33, 0x84, 0x80, 0x80, 0xd8, 0xb9, 0x4, 0x0},
},
}

test := func(test testCase) {
t.Run(test.name, func(t *testing.T) {

t.Parallel()

runtime := NewTestInterpreterRuntime()

runtimeInterface := &TestRuntimeInterface{
Storage: NewTestLedger(nil, nil),
}

script := []byte(fmt.Sprintf(
`
access(all) fun main(): [UInt8]? {
let value = %s
return CCF.encode(&value as &AnyStruct)
}
`,
test.value,
))

result, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: common.ScriptLocation{},
},
)
require.NoError(t, err)

assert.Equal(t,
cadence.NewOptional(
cadence.ByteSliceToByteArray(test.output),
),
result,
)
})
}

for _, testCase := range tests {
test(testCase)
}
}
Loading

0 comments on commit 1d2d5b3

Please sign in to comment.