diff --git a/runtime/interpreter/value_pathcapability.go b/runtime/interpreter/value_pathcapability.go index 867532c904..ae1be727d2 100644 --- a/runtime/interpreter/value_pathcapability.go +++ b/runtime/interpreter/value_pathcapability.go @@ -25,6 +25,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/sema" ) // TODO: remove once migrated @@ -50,8 +51,9 @@ func (v *PathCapabilityValue) Accept(_ *Interpreter, _ Visitor, _ LocationRange) panic(errors.NewUnreachableError()) } -func (v *PathCapabilityValue) Walk(_ *Interpreter, _ func(Value), _ LocationRange) { - panic(errors.NewUnreachableError()) +func (v *PathCapabilityValue) Walk(_ *Interpreter, walkChild func(Value), _ LocationRange) { + walkChild(v.Address) + walkChild(v.Path) } func (v *PathCapabilityValue) StaticType(inter *Interpreter) StaticType { @@ -62,7 +64,7 @@ func (v *PathCapabilityValue) StaticType(inter *Interpreter) StaticType { } func (v *PathCapabilityValue) IsImportable(_ *Interpreter, _ LocationRange) bool { - panic(errors.NewUnreachableError()) + return false } func (v *PathCapabilityValue) String() string { return v.RecursiveString(SeenReferences{}) @@ -110,8 +112,62 @@ func (v *PathCapabilityValue) MeteredString( } } -func (v *PathCapabilityValue) GetMember(_ *Interpreter, _ LocationRange, _ string) Value { - panic(errors.NewUnreachableError()) +func (v *PathCapabilityValue) newBorrowFunction( + interpreter *Interpreter, + borrowType *sema.ReferenceType, +) BoundFunctionValue { + return NewBoundHostFunctionValue( + interpreter, + v, + sema.CapabilityTypeBorrowFunctionType(borrowType), + func(_ Invocation) Value { + // Borrowing is never allowed + return Nil + }, + ) +} + +func (v *PathCapabilityValue) newCheckFunction( + interpreter *Interpreter, + borrowType *sema.ReferenceType, +) BoundFunctionValue { + return NewBoundHostFunctionValue( + interpreter, + v, + sema.CapabilityTypeCheckFunctionType(borrowType), + func(_ Invocation) Value { + // Borrowing is never allowed + return FalseValue + }, + ) +} + +func (v *PathCapabilityValue) GetMember(interpreter *Interpreter, _ LocationRange, name string) Value { + switch name { + case sema.CapabilityTypeBorrowFunctionName: + var borrowType *sema.ReferenceType + if v.BorrowType != nil { + // this function will panic already if this conversion fails + borrowType, _ = interpreter.MustConvertStaticToSemaType(v.BorrowType).(*sema.ReferenceType) + } + return v.newBorrowFunction(interpreter, borrowType) + + case sema.CapabilityTypeCheckFunctionName: + var borrowType *sema.ReferenceType + if v.BorrowType != nil { + // this function will panic already if this conversion fails + borrowType, _ = interpreter.MustConvertStaticToSemaType(v.BorrowType).(*sema.ReferenceType) + } + return v.newCheckFunction(interpreter, borrowType) + + case sema.CapabilityTypeAddressFieldName: + return v.Address + + case sema.CapabilityTypeIDFieldName: + return InvalidCapabilityID + } + + return nil } func (*PathCapabilityValue) RemoveMember(_ *Interpreter, _ LocationRange, _ string) Value { @@ -127,7 +183,7 @@ func (v *PathCapabilityValue) ConformsToStaticType( _ LocationRange, _ TypeConformanceResults, ) bool { - panic(errors.NewUnreachableError()) + return true } func (v *PathCapabilityValue) Equal(interpreter *Interpreter, locationRange LocationRange, other Value) bool { @@ -151,7 +207,7 @@ func (v *PathCapabilityValue) Equal(interpreter *Interpreter, locationRange Loca } func (*PathCapabilityValue) IsStorable() bool { - panic(errors.NewUnreachableError()) + return true } func (v *PathCapabilityValue) Storable( diff --git a/runtime/tests/interpreter/pathcapability_test.go b/runtime/tests/interpreter/pathcapability_test.go new file mode 100644 index 0000000000..ba59739481 --- /dev/null +++ b/runtime/tests/interpreter/pathcapability_test.go @@ -0,0 +1,147 @@ +/* + * 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 interpreter_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/activations" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" +) + +func TestInterpretPathCapability(t *testing.T) { + + t.Parallel() + + test := func(t *testing.T, code string) (*interpreter.Interpreter, error) { + borrowType := &sema.ReferenceType{ + Type: sema.StringType, + Authorization: sema.UnauthorizedAccess, + } + + borrowStaticType := interpreter.ConvertSemaToStaticType(nil, borrowType) + + value := stdlib.StandardLibraryValue{ + Type: &sema.CapabilityType{ + BorrowType: borrowType, + }, + Value: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: borrowStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: "foo", + }, + Address: interpreter.AddressValue{0x42}, + }, + Name: "cap", + Kind: common.DeclarationKindConstant, + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(value) + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, value) + + return parseCheckAndInterpretWithOptions( + t, + code, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + BaseActivationHandler: func(_ common.Location) *interpreter.VariableActivation { + return baseActivation + }, + }, + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + HandleCheckerError: nil, + }, + ) + } + + t.Run("transfer", func(t *testing.T) { + + t.Parallel() + + _, err := test(t, ` + fun f(_ cap: Capability<&String>): Capability<&String>? { + return cap + } + + let capOpt: Capability<&String>? = f(cap) + `) + require.NoError(t, err) + }) + + t.Run("borrow", func(t *testing.T) { + + t.Parallel() + + inter, err := test(t, ` + fun test(): &String? { + return cap.borrow() + } + `) + require.NoError(t, err) + + res, err := inter.Invoke("test") + require.NoError(t, err) + require.Equal(t, interpreter.Nil, res) + }) + + t.Run("check", func(t *testing.T) { + + t.Parallel() + + inter, err := test(t, ` + fun test(): Bool { + return cap.check() + } + `) + require.NoError(t, err) + + res, err := inter.Invoke("test") + require.NoError(t, err) + require.Equal(t, interpreter.FalseValue, res) + }) + + t.Run("id", func(t *testing.T) { + + t.Parallel() + + inter, err := test(t, ` + fun test(): UInt64 { + return cap.id + } + `) + require.NoError(t, err) + + res, err := inter.Invoke("test") + require.NoError(t, err) + require.Equal(t, interpreter.UInt64Value(0), res) + }) +}