From f3454b6805d28bd6fb4177b24375d2739ae41bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 4 Sep 2024 19:12:30 -0700 Subject: [PATCH 01/10] allow validation of Account.capabilities.get/borrow --- runtime/empty.go | 12 ++ runtime/environment.go | 51 ++++- runtime/interface.go | 9 + runtime/interpreter/config.go | 2 + runtime/interpreter/errors.go | 13 ++ runtime/interpreter/interpreter.go | 10 + runtime/runtime_test.go | 190 +++++++++++++++++++ runtime/stdlib/account.go | 24 ++- runtime/tests/runtime_utils/testinterface.go | 41 +++- 9 files changed, 333 insertions(+), 19 deletions(-) diff --git a/runtime/empty.go b/runtime/empty.go index a80db85745..f5eb631429 100644 --- a/runtime/empty.go +++ b/runtime/empty.go @@ -28,6 +28,7 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" ) // EmptyRuntimeInterface is an empty implementation of runtime.Interface. @@ -238,3 +239,14 @@ func (EmptyRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) func (EmptyRuntimeInterface) RecoverProgram(_ *ast.Program, _ common.Location) ([]byte, error) { panic("unexpected call to RecoverProgram") } + +func (EmptyRuntimeInterface) ValidateAccountCapabilitiesGet( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + _ interpreter.AddressValue, + _ interpreter.PathValue, + _ *sema.ReferenceType, + _ *sema.ReferenceType, +) (bool, error) { + panic("unexpected call to ValidateAccountCapabilitiesGet") +} diff --git a/runtime/environment.go b/runtime/environment.go index 2225f6fb69..324d4a6813 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -185,16 +185,17 @@ func (e *interpreterEnvironment) newInterpreterConfig() *interpreter.Config { // and disable storage validation after each value modification. // Instead, storage is validated after commits (if validation is enabled), // see interpreterEnvironment.CommitStorage - AtreeStorageValidationEnabled: false, - Debugger: e.config.Debugger, - OnStatement: e.newOnStatementHandler(), - OnMeterComputation: e.newOnMeterComputation(), - OnFunctionInvocation: e.newOnFunctionInvocationHandler(), - OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(), - CapabilityBorrowHandler: e.newCapabilityBorrowHandler(), - CapabilityCheckHandler: e.newCapabilityCheckHandler(), - LegacyContractUpgradeEnabled: e.config.LegacyContractUpgradeEnabled, - ContractUpdateTypeRemovalEnabled: e.config.ContractUpdateTypeRemovalEnabled, + AtreeStorageValidationEnabled: false, + Debugger: e.config.Debugger, + OnStatement: e.newOnStatementHandler(), + OnMeterComputation: e.newOnMeterComputation(), + OnFunctionInvocation: e.newOnFunctionInvocationHandler(), + OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(), + CapabilityBorrowHandler: e.newCapabilityBorrowHandler(), + CapabilityCheckHandler: e.newCapabilityCheckHandler(), + LegacyContractUpgradeEnabled: e.config.LegacyContractUpgradeEnabled, + ContractUpdateTypeRemovalEnabled: e.config.ContractUpdateTypeRemovalEnabled, + ValidateAccountCapabilitiesGetHandler: e.newValidateAccountCapabilitiesGetHandler(), } } @@ -1397,3 +1398,33 @@ func (e *interpreterEnvironment) newCapabilityCheckHandler() interpreter.Capabil ) } } + +func (e *interpreterEnvironment) newValidateAccountCapabilitiesGetHandler() interpreter.ValidateAccountCapabilitiesGetHandlerFunc { + return func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, + ) (bool, error) { + var ( + ok bool + err error + ) + errors.WrapPanic(func() { + ok, err = e.runtimeInterface.ValidateAccountCapabilitiesGet( + inter, + locationRange, + address, + path, + wantedBorrowType, + capabilityBorrowType, + ) + }) + if err != nil { + err = interpreter.WrappedExternalError(err) + } + return ok, err + } +} diff --git a/runtime/interface.go b/runtime/interface.go index 13c16789d3..678e7ffecd 100644 --- a/runtime/interface.go +++ b/runtime/interface.go @@ -28,6 +28,7 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" ) type Interface interface { @@ -145,6 +146,14 @@ type Interface interface { // GenerateAccountID generates a new, *non-zero*, unique ID for the given account. GenerateAccountID(address common.Address) (uint64, error) RecoverProgram(program *ast.Program, location common.Location) ([]byte, error) + ValidateAccountCapabilitiesGet( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, + ) (bool, error) } type MeterInterface interface { diff --git a/runtime/interpreter/config.go b/runtime/interpreter/config.go index f9d322bd7f..96a020f229 100644 --- a/runtime/interpreter/config.go +++ b/runtime/interpreter/config.go @@ -74,4 +74,6 @@ type Config struct { LegacyContractUpgradeEnabled bool // ContractUpdateTypeRemovalEnabled specifies if type removal is enabled in contract updates ContractUpdateTypeRemovalEnabled bool + // ValidateAccountCapabilitiesGetHandler is used to handle when a capability of an account is got. + ValidateAccountCapabilitiesGetHandler ValidateAccountCapabilitiesGetHandlerFunc } diff --git a/runtime/interpreter/errors.go b/runtime/interpreter/errors.go index 0e568b1f3f..6cb11f2156 100644 --- a/runtime/interpreter/errors.go +++ b/runtime/interpreter/errors.go @@ -1132,3 +1132,16 @@ func (ReferencedValueChangedError) IsUserError() {} func (e ReferencedValueChangedError) Error() string { return "referenced value has been changed after taking the reference" } + +// GetCapabilityError +type GetCapabilityError struct { + LocationRange +} + +var _ errors.UserError = GetCapabilityError{} + +func (GetCapabilityError) IsUserError() {} + +func (e GetCapabilityError) Error() string { + return "cannot get capability" +} diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index a99f854410..cb618e46f5 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -155,6 +155,16 @@ type AccountHandlerFunc func( address AddressValue, ) Value +// ValidateAccountCapabilitiesGetHandlerFunc is a function that is used to handle when a capability of an account is got. +type ValidateAccountCapabilitiesGetHandlerFunc func( + inter *Interpreter, + locationRange LocationRange, + address AddressValue, + path PathValue, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, +) (bool, error) + // UUIDHandlerFunc is a function that handles the generation of UUIDs. type UUIDHandlerFunc func() (uint64, error) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index e448b71a03..ed5b0b9040 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -10967,3 +10967,193 @@ func TestRuntimeAccountStorageBorrowEphemeralReferenceValue(t *testing.T) { var nestedReferenceErr interpreter.NestedReferenceError require.ErrorAs(t, err, &nestedReferenceErr) } + +func TestRuntimeForbidPublicEntitlementBorrow(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntime() + + script1 := []byte(` + transaction { + + prepare(signer: auth (Storage, Capabilities) &Account) { + signer.storage.save(42, to: /storage/number) + let cap = signer.capabilities.storage.issue(/storage/number) + signer.capabilities.publish(cap, at: /public/number) + } + } + `) + + script2 := []byte(` + access(all) + fun main() { + let number = getAccount(0x1).capabilities.borrow(/public/number) + assert(number == nil) + } + `) + + var loggedMessages []string + var events []cadence.Event + var validatedPaths []interpreter.PathValue + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{ + common.MustBytesToAddress([]byte{0x1}), + }, nil + }, + OnProgramLog: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnValidateAccountCapabilitiesGet: func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, + ) (bool, error) { + + validatedPaths = append(validatedPaths, path) + + _, wantedHasEntitlements := wantedBorrowType.Authorization.(sema.EntitlementSetAccess) + return !wantedHasEntitlements, nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nexScriptLocation := NewScriptLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: script1, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + _, err = runtime.ExecuteScript( + Script{ + Source: script2, + }, + Context{ + Interface: runtimeInterface, + Location: nexScriptLocation(), + }, + ) + require.NoError(t, err) + + assert.Equal(t, + []interpreter.PathValue{ + { + Domain: common.PathDomainPublic, + Identifier: "number", + }, + }, + validatedPaths, + ) +} + +func TestRuntimeForbidPublicEntitlementGet(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntime() + + script1 := []byte(` + transaction { + + prepare(signer: auth (Storage, Capabilities) &Account) { + signer.storage.save(42, to: /storage/number) + let cap = signer.capabilities.storage.issue(/storage/number) + signer.capabilities.publish(cap, at: /public/number) + } + } + `) + + script2 := []byte(` + access(all) + fun main() { + let cap = getAccount(0x1).capabilities.get(/public/number) + assert(cap.id == 0) + } + `) + + var loggedMessages []string + var events []cadence.Event + var validatedPaths []interpreter.PathValue + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{ + common.MustBytesToAddress([]byte{0x1}), + }, nil + }, + OnProgramLog: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnValidateAccountCapabilitiesGet: func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, + ) (bool, error) { + + validatedPaths = append(validatedPaths, path) + + _, wantedHasEntitlements := wantedBorrowType.Authorization.(sema.EntitlementSetAccess) + return !wantedHasEntitlements, nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nexScriptLocation := NewScriptLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: script1, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + _, err = runtime.ExecuteScript( + Script{ + Source: script2, + }, + Context{ + Interface: runtimeInterface, + Location: nexScriptLocation(), + }, + ) + require.NoError(t, err) + + assert.Equal(t, + []interpreter.PathValue{ + { + Domain: common.PathDomainPublic, + Identifier: "number", + }, + }, + validatedPaths, + ) +} diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index b90f53b94c..b9b03b976d 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -3865,7 +3865,7 @@ func CheckCapabilityController( func newAccountCapabilitiesGetFunction( inter *interpreter.Interpreter, addressValue interpreter.AddressValue, - handler CapabilityControllerHandler, + controllerHandler CapabilityControllerHandler, borrow bool, ) interpreter.BoundFunctionGenerator { return func(accountCapabilities interpreter.MemberAccessibleValue) interpreter.BoundFunctionValue { @@ -3979,6 +3979,24 @@ func newAccountCapabilitiesGetFunction( panic(errors.NewUnreachableError()) } + getHandler := inter.SharedState.Config.ValidateAccountCapabilitiesGetHandler + if getHandler != nil { + valid, err := getHandler( + inter, + locationRange, + addressValue, + pathValue, + wantedBorrowType, + capabilityBorrowType, + ) + if err != nil { + panic(err) + } + if !valid { + return failValue + } + } + var resultValue interpreter.Value if borrow { // When borrowing, @@ -3992,7 +4010,7 @@ func newAccountCapabilitiesGetFunction( capabilityID, wantedBorrowType, capabilityBorrowType, - handler, + controllerHandler, ) } else { // When not borrowing, @@ -4005,7 +4023,7 @@ func newAccountCapabilitiesGetFunction( capabilityID, wantedBorrowType, capabilityBorrowType, - handler, + controllerHandler, ) if controller != nil { resultBorrowStaticType := diff --git a/runtime/tests/runtime_utils/testinterface.go b/runtime/tests/runtime_utils/testinterface.go index a73f9199a7..60ae31018e 100644 --- a/runtime/tests/runtime_utils/testinterface.go +++ b/runtime/tests/runtime_utils/testinterface.go @@ -116,12 +116,20 @@ type TestRuntimeInterface struct { duration time.Duration, attrs []attribute.KeyValue, ) - OnMeterMemory func(usage common.MemoryUsage) error - OnComputationUsed func() (uint64, error) - OnMemoryUsed func() (uint64, error) - OnInteractionUsed func() (uint64, error) - OnGenerateAccountID func(address common.Address) (uint64, error) - OnRecoverProgram func(program *ast.Program, location common.Location) ([]byte, error) + OnMeterMemory func(usage common.MemoryUsage) error + OnComputationUsed func() (uint64, error) + OnMemoryUsed func() (uint64, error) + OnInteractionUsed func() (uint64, error) + OnGenerateAccountID func(address common.Address) (uint64, error) + OnRecoverProgram func(program *ast.Program, location common.Location) ([]byte, error) + OnValidateAccountCapabilitiesGet func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, + ) (bool, error) lastUUID uint64 accountIDs map[common.Address]uint64 @@ -614,3 +622,24 @@ func (i *TestRuntimeInterface) RecoverProgram(program *ast.Program, location com } return i.OnRecoverProgram(program, location) } + +func (i *TestRuntimeInterface) ValidateAccountCapabilitiesGet( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, +) (bool, error) { + if i.OnValidateAccountCapabilitiesGet == nil { + return true, nil + } + return i.OnValidateAccountCapabilitiesGet( + inter, + locationRange, + address, + path, + wantedBorrowType, + capabilityBorrowType, + ) +} From 4050d9e451f2def91541a04d0a33379d08d5e7a0 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 11 Sep 2024 15:59:08 -0700 Subject: [PATCH 02/10] Allow validation of capabilities during publish --- runtime/empty.go | 10 + runtime/environment.go | 51 +++-- runtime/interface.go | 7 + runtime/interpreter/config.go | 2 + runtime/interpreter/errors.go | 19 ++ runtime/interpreter/interpreter.go | 9 + runtime/runtime_test.go | 191 +++++++++++++++++-- runtime/stdlib/account.go | 37 ++++ runtime/tests/runtime_utils/testinterface.go | 26 +++ 9 files changed, 329 insertions(+), 23 deletions(-) diff --git a/runtime/empty.go b/runtime/empty.go index f5eb631429..86b5b0abce 100644 --- a/runtime/empty.go +++ b/runtime/empty.go @@ -250,3 +250,13 @@ func (EmptyRuntimeInterface) ValidateAccountCapabilitiesGet( ) (bool, error) { panic("unexpected call to ValidateAccountCapabilitiesGet") } + +func (EmptyRuntimeInterface) ValidateAccountCapabilitiesPublish( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + _ interpreter.AddressValue, + _ interpreter.PathValue, + _ *interpreter.ReferenceStaticType, +) (bool, error) { + panic("unexpected call to ValidateAccountCapabilitiesPublish") +} diff --git a/runtime/environment.go b/runtime/environment.go index 324d4a6813..72c5f7b52c 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -185,17 +185,18 @@ func (e *interpreterEnvironment) newInterpreterConfig() *interpreter.Config { // and disable storage validation after each value modification. // Instead, storage is validated after commits (if validation is enabled), // see interpreterEnvironment.CommitStorage - AtreeStorageValidationEnabled: false, - Debugger: e.config.Debugger, - OnStatement: e.newOnStatementHandler(), - OnMeterComputation: e.newOnMeterComputation(), - OnFunctionInvocation: e.newOnFunctionInvocationHandler(), - OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(), - CapabilityBorrowHandler: e.newCapabilityBorrowHandler(), - CapabilityCheckHandler: e.newCapabilityCheckHandler(), - LegacyContractUpgradeEnabled: e.config.LegacyContractUpgradeEnabled, - ContractUpdateTypeRemovalEnabled: e.config.ContractUpdateTypeRemovalEnabled, - ValidateAccountCapabilitiesGetHandler: e.newValidateAccountCapabilitiesGetHandler(), + AtreeStorageValidationEnabled: false, + Debugger: e.config.Debugger, + OnStatement: e.newOnStatementHandler(), + OnMeterComputation: e.newOnMeterComputation(), + OnFunctionInvocation: e.newOnFunctionInvocationHandler(), + OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(), + CapabilityBorrowHandler: e.newCapabilityBorrowHandler(), + CapabilityCheckHandler: e.newCapabilityCheckHandler(), + LegacyContractUpgradeEnabled: e.config.LegacyContractUpgradeEnabled, + ContractUpdateTypeRemovalEnabled: e.config.ContractUpdateTypeRemovalEnabled, + ValidateAccountCapabilitiesGetHandler: e.newValidateAccountCapabilitiesGetHandler(), + ValidateAccountCapabilitiesPublishHandler: e.newValidateAccountCapabilitiesPublishHandler(), } } @@ -1428,3 +1429,31 @@ func (e *interpreterEnvironment) newValidateAccountCapabilitiesGetHandler() inte return ok, err } } + +func (e *interpreterEnvironment) newValidateAccountCapabilitiesPublishHandler() interpreter.ValidateAccountCapabilitiesPublishHandlerFunc { + return func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + capabilityBorrowType *interpreter.ReferenceStaticType, + ) (bool, error) { + var ( + ok bool + err error + ) + errors.WrapPanic(func() { + ok, err = e.runtimeInterface.ValidateAccountCapabilitiesPublish( + inter, + locationRange, + address, + path, + capabilityBorrowType, + ) + }) + if err != nil { + err = interpreter.WrappedExternalError(err) + } + return ok, err + } +} diff --git a/runtime/interface.go b/runtime/interface.go index 678e7ffecd..9f20ba8f31 100644 --- a/runtime/interface.go +++ b/runtime/interface.go @@ -154,6 +154,13 @@ type Interface interface { wantedBorrowType *sema.ReferenceType, capabilityBorrowType *sema.ReferenceType, ) (bool, error) + ValidateAccountCapabilitiesPublish( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + capabilityBorrowType *interpreter.ReferenceStaticType, + ) (bool, error) } type MeterInterface interface { diff --git a/runtime/interpreter/config.go b/runtime/interpreter/config.go index 96a020f229..dc052342d1 100644 --- a/runtime/interpreter/config.go +++ b/runtime/interpreter/config.go @@ -76,4 +76,6 @@ type Config struct { ContractUpdateTypeRemovalEnabled bool // ValidateAccountCapabilitiesGetHandler is used to handle when a capability of an account is got. ValidateAccountCapabilitiesGetHandler ValidateAccountCapabilitiesGetHandlerFunc + // ValidateAccountCapabilitiesPublishHandler is used to handle when a capability of an account is got. + ValidateAccountCapabilitiesPublishHandler ValidateAccountCapabilitiesPublishHandlerFunc } diff --git a/runtime/interpreter/errors.go b/runtime/interpreter/errors.go index 6cb11f2156..fd4f0cf5ad 100644 --- a/runtime/interpreter/errors.go +++ b/runtime/interpreter/errors.go @@ -1027,6 +1027,25 @@ func (e CapabilityAddressPublishingError) Error() string { ) } +// PublicEntitledCapabilityPublishingError +type PublicEntitledCapabilityPublishingError struct { + LocationRange + BorrowType *ReferenceStaticType + Path PathValue +} + +var _ errors.UserError = PublicEntitledCapabilityPublishingError{} + +func (PublicEntitledCapabilityPublishingError) IsUserError() {} + +func (e PublicEntitledCapabilityPublishingError) Error() string { + return fmt.Sprintf( + "cannot publish capability of type `%s` to the path %s", + e.BorrowType.ID(), + e.Path.String(), + ) +} + // NestedReferenceError type NestedReferenceError struct { Value ReferenceValue diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index cb618e46f5..0362108d69 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -165,6 +165,15 @@ type ValidateAccountCapabilitiesGetHandlerFunc func( capabilityBorrowType *sema.ReferenceType, ) (bool, error) +// ValidateAccountCapabilitiesPublishHandlerFunc is a function that is used to handle when a capability of an account is got. +type ValidateAccountCapabilitiesPublishHandlerFunc func( + inter *Interpreter, + locationRange LocationRange, + address AddressValue, + path PathValue, + capabilityBorrowType *ReferenceStaticType, +) (bool, error) + // UUIDHandlerFunc is a function that handles the generation of UUIDs. type UUIDHandlerFunc func() (uint64, error) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index ed5b0b9040..6145d25528 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -10993,8 +10993,6 @@ func TestRuntimeForbidPublicEntitlementBorrow(t *testing.T) { } `) - var loggedMessages []string - var events []cadence.Event var validatedPaths []interpreter.PathValue runtimeInterface := &TestRuntimeInterface{ @@ -11004,11 +11002,7 @@ func TestRuntimeForbidPublicEntitlementBorrow(t *testing.T) { common.MustBytesToAddress([]byte{0x1}), }, nil }, - OnProgramLog: func(message string) { - loggedMessages = append(loggedMessages, message) - }, OnEmitEvent: func(event cadence.Event) error { - events = append(events, event) return nil }, OnValidateAccountCapabilitiesGet: func( @@ -11088,8 +11082,6 @@ func TestRuntimeForbidPublicEntitlementGet(t *testing.T) { } `) - var loggedMessages []string - var events []cadence.Event var validatedPaths []interpreter.PathValue runtimeInterface := &TestRuntimeInterface{ @@ -11099,11 +11091,7 @@ func TestRuntimeForbidPublicEntitlementGet(t *testing.T) { common.MustBytesToAddress([]byte{0x1}), }, nil }, - OnProgramLog: func(message string) { - loggedMessages = append(loggedMessages, message) - }, OnEmitEvent: func(event cadence.Event) error { - events = append(events, event) return nil }, OnValidateAccountCapabilitiesGet: func( @@ -11157,3 +11145,182 @@ func TestRuntimeForbidPublicEntitlementGet(t *testing.T) { validatedPaths, ) } + +func TestRuntimeForbidPublicEntitlementPublish(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntime() + + t.Run("entitled capability", func(t *testing.T) { + + t.Parallel() + + script1 := []byte(` + transaction { + + prepare(signer: auth (Storage, Capabilities) &Account) { + signer.storage.save(42, to: /storage/number) + let cap = signer.capabilities.storage.issue(/storage/number) + signer.capabilities.publish(cap, at: /public/number) + } + } + `) + + var validatedPaths []interpreter.PathValue + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{ + common.MustBytesToAddress([]byte{0x1}), + }, nil + }, + OnEmitEvent: func(event cadence.Event) error { + return nil + }, + OnValidateAccountCapabilitiesPublish: func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + capabilityBorrowType *interpreter.ReferenceStaticType, + ) (bool, error) { + + validatedPaths = append(validatedPaths, path) + + _, isEntitledCapability := capabilityBorrowType.Authorization.(interpreter.EntitlementSetAuthorization) + return !isEntitledCapability, nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: script1, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + + RequireError(t, err) + require.ErrorAs(t, err, &interpreter.PublicEntitledCapabilityPublishingError{}) + }) + + t.Run("non entitled capability", func(t *testing.T) { + t.Parallel() + + script1 := []byte(` + transaction { + + prepare(signer: auth (Storage, Capabilities) &Account) { + signer.storage.save(42, to: /storage/number) + let cap = signer.capabilities.storage.issue<&Int>(/storage/number) + signer.capabilities.publish(cap, at: /public/number) + } + } + `) + + var validatedPaths []interpreter.PathValue + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{ + common.MustBytesToAddress([]byte{0x1}), + }, nil + }, + OnEmitEvent: func(event cadence.Event) error { + return nil + }, + OnValidateAccountCapabilitiesPublish: func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + capabilityBorrowType *interpreter.ReferenceStaticType, + ) (bool, error) { + + validatedPaths = append(validatedPaths, path) + + _, isEntitledCapability := capabilityBorrowType.Authorization.(interpreter.EntitlementSetAuthorization) + return !isEntitledCapability, nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: script1, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + }) + + t.Run("untyped entitled capability", func(t *testing.T) { + + t.Parallel() + + script1 := []byte(` + transaction { + + prepare(signer: auth (Storage, Capabilities) &Account) { + signer.storage.save(42, to: /storage/number) + let cap = signer.capabilities.storage.issue(/storage/number) + let untypedCap: Capability = cap + signer.capabilities.publish(untypedCap, at: /public/number) + } + } + `) + + var validatedPaths []interpreter.PathValue + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{ + common.MustBytesToAddress([]byte{0x1}), + }, nil + }, + OnEmitEvent: func(event cadence.Event) error { + return nil + }, + OnValidateAccountCapabilitiesPublish: func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + capabilityBorrowType *interpreter.ReferenceStaticType, + ) (bool, error) { + + validatedPaths = append(validatedPaths, path) + + _, isEntitledCapability := capabilityBorrowType.Authorization.(interpreter.EntitlementSetAuthorization) + return !isEntitledCapability, nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: script1, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + + RequireError(t, err) + require.ErrorAs(t, err, &interpreter.PublicEntitledCapabilityPublishingError{}) + }) +} diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index b9b03b976d..d2dab71432 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -3542,6 +3542,43 @@ func newAccountCapabilitiesPublishFunction( domain := pathValue.Domain.Identifier() identifier := pathValue.Identifier + capabilityType, ok := capabilityValue.StaticType(inter).(*interpreter.CapabilityStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + borrowType := capabilityType.BorrowType + + // It is possible to have legacy capabilities without borrow type. + // So perform the validation only if the borrow type is present. + if borrowType != nil { + capabilityBorrowType, ok := borrowType.(*interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + getHandler := inter.SharedState.Config.ValidateAccountCapabilitiesPublishHandler + if getHandler != nil { + valid, err := getHandler( + inter, + locationRange, + capabilityAddressValue, + pathValue, + capabilityBorrowType, + ) + if err != nil { + panic(err) + } + if !valid { + panic(interpreter.PublicEntitledCapabilityPublishingError{ + LocationRange: locationRange, + BorrowType: capabilityBorrowType, + Path: pathValue, + }) + } + } + } + // Prevent an overwrite storageMapKey := interpreter.StringStorageMapKey(identifier) diff --git a/runtime/tests/runtime_utils/testinterface.go b/runtime/tests/runtime_utils/testinterface.go index 60ae31018e..e387075c54 100644 --- a/runtime/tests/runtime_utils/testinterface.go +++ b/runtime/tests/runtime_utils/testinterface.go @@ -130,6 +130,13 @@ type TestRuntimeInterface struct { wantedBorrowType *sema.ReferenceType, capabilityBorrowType *sema.ReferenceType, ) (bool, error) + OnValidateAccountCapabilitiesPublish func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + capabilityBorrowType *interpreter.ReferenceStaticType, + ) (bool, error) lastUUID uint64 accountIDs map[common.Address]uint64 @@ -643,3 +650,22 @@ func (i *TestRuntimeInterface) ValidateAccountCapabilitiesGet( capabilityBorrowType, ) } + +func (i *TestRuntimeInterface) ValidateAccountCapabilitiesPublish( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + path interpreter.PathValue, + capabilityBorrowType *interpreter.ReferenceStaticType, +) (bool, error) { + if i.OnValidateAccountCapabilitiesPublish == nil { + return true, nil + } + return i.OnValidateAccountCapabilitiesPublish( + inter, + locationRange, + address, + path, + capabilityBorrowType, + ) +} From a94949e6f0a7d9f903c13c94dec44a2a8049ae11 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 11 Sep 2024 16:14:32 -0700 Subject: [PATCH 03/10] Refactor code --- runtime/interpreter/errors.go | 10 +++++----- runtime/runtime_test.go | 4 ++-- runtime/stdlib/account.go | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/runtime/interpreter/errors.go b/runtime/interpreter/errors.go index fd4f0cf5ad..ac66aecda9 100644 --- a/runtime/interpreter/errors.go +++ b/runtime/interpreter/errors.go @@ -1027,18 +1027,18 @@ func (e CapabilityAddressPublishingError) Error() string { ) } -// PublicEntitledCapabilityPublishingError -type PublicEntitledCapabilityPublishingError struct { +// EntitledCapabilityPublishingError +type EntitledCapabilityPublishingError struct { LocationRange BorrowType *ReferenceStaticType Path PathValue } -var _ errors.UserError = PublicEntitledCapabilityPublishingError{} +var _ errors.UserError = EntitledCapabilityPublishingError{} -func (PublicEntitledCapabilityPublishingError) IsUserError() {} +func (EntitledCapabilityPublishingError) IsUserError() {} -func (e PublicEntitledCapabilityPublishingError) Error() string { +func (e EntitledCapabilityPublishingError) Error() string { return fmt.Sprintf( "cannot publish capability of type `%s` to the path %s", e.BorrowType.ID(), diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 6145d25528..7f30aed2e9 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -11207,7 +11207,7 @@ func TestRuntimeForbidPublicEntitlementPublish(t *testing.T) { ) RequireError(t, err) - require.ErrorAs(t, err, &interpreter.PublicEntitledCapabilityPublishingError{}) + require.ErrorAs(t, err, &interpreter.EntitledCapabilityPublishingError{}) }) t.Run("non entitled capability", func(t *testing.T) { @@ -11321,6 +11321,6 @@ func TestRuntimeForbidPublicEntitlementPublish(t *testing.T) { ) RequireError(t, err) - require.ErrorAs(t, err, &interpreter.PublicEntitledCapabilityPublishingError{}) + require.ErrorAs(t, err, &interpreter.EntitledCapabilityPublishingError{}) }) } diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index d2dab71432..64f3eb0f75 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -3557,9 +3557,9 @@ func newAccountCapabilitiesPublishFunction( panic(errors.NewUnreachableError()) } - getHandler := inter.SharedState.Config.ValidateAccountCapabilitiesPublishHandler - if getHandler != nil { - valid, err := getHandler( + publishHandler := inter.SharedState.Config.ValidateAccountCapabilitiesPublishHandler + if publishHandler != nil { + valid, err := publishHandler( inter, locationRange, capabilityAddressValue, @@ -3570,7 +3570,7 @@ func newAccountCapabilitiesPublishFunction( panic(err) } if !valid { - panic(interpreter.PublicEntitledCapabilityPublishingError{ + panic(interpreter.EntitledCapabilityPublishingError{ LocationRange: locationRange, BorrowType: capabilityBorrowType, Path: pathValue, From 7862536c053458cd8906530ac696059bdaec3061 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 16 Sep 2024 12:23:30 -0700 Subject: [PATCH 04/10] Fix runtime type of Account_Inbox_claim() function --- runtime/stdlib/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index b90f53b94c..493b2da4c5 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -1100,7 +1100,7 @@ func newAccountInboxClaimFunction( return interpreter.NewBoundHostFunctionValue( inter, accountInbox, - sema.Account_InboxTypePublishFunctionType, + sema.Account_InboxTypeClaimFunctionType, func(invocation interpreter.Invocation) interpreter.Value { nameValue, ok := invocation.Arguments[0].(*interpreter.StringValue) if !ok { From e6f2246cb8650c48240b09fe6192a40663490127 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 16 Sep 2024 16:44:34 -0700 Subject: [PATCH 05/10] Fix interpreting default functions --- runtime/interpreter/interpreter.go | 37 ++++-- runtime/tests/interpreter/interface_test.go | 125 ++++++++++++++++++++ 2 files changed, 151 insertions(+), 11 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index a99f854410..2550c94cec 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1248,16 +1248,7 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( functions.Set(resourceDefaultDestroyEventName(compositeType), destroyEventConstructor) } - wrapFunctions := func(ty *sema.InterfaceType, code WrapperCode) { - - // Wrap initializer - - initializerFunctionWrapper := - code.InitializerFunctionWrapper - - if initializerFunctionWrapper != nil { - initializerFunction = initializerFunctionWrapper(initializerFunction) - } + applyDefaultFunctions := func(ty *sema.InterfaceType, code WrapperCode) { // Apply default functions, if conforming type does not provide the function @@ -1276,6 +1267,18 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( functions.Set(name, function) }) } + } + + wrapFunctions := func(ty *sema.InterfaceType, code WrapperCode) { + + // Wrap initializer + + initializerFunctionWrapper := + code.InitializerFunctionWrapper + + if initializerFunctionWrapper != nil { + initializerFunction = initializerFunctionWrapper(initializerFunction) + } // Wrap functions @@ -1294,9 +1297,21 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( } conformances := compositeType.EffectiveInterfaceConformances() + interfaceCodes := declarationInterpreter.SharedState.typeCodes.InterfaceCodes + + // First apply the default functions, and then wrap with conditions. + // These needs to be done in separate phases. + // Otherwise, if the condition and the default implementation are coming from two different inherited interfaces, + // then the condition would wrap an empty implementation, because the default impl is not resolved by the time. + + for i := len(conformances) - 1; i >= 0; i-- { + conformance := conformances[i].InterfaceType + applyDefaultFunctions(conformance, interfaceCodes[conformance.ID()]) + } + for i := len(conformances) - 1; i >= 0; i-- { conformance := conformances[i].InterfaceType - wrapFunctions(conformance, declarationInterpreter.SharedState.typeCodes.InterfaceCodes[conformance.ID()]) + wrapFunctions(conformance, interfaceCodes[conformance.ID()]) } declarationInterpreter.SharedState.typeCodes.CompositeCodes[compositeType.ID()] = CompositeTypeCode{ diff --git a/runtime/tests/interpreter/interface_test.go b/runtime/tests/interpreter/interface_test.go index d17c5d305d..e53bbcae4d 100644 --- a/runtime/tests/interpreter/interface_test.go +++ b/runtime/tests/interpreter/interface_test.go @@ -871,6 +871,131 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { // A.Nested and B.Nested are two distinct separate functions assert.Equal(t, []string{"B"}, logs) }) + + t.Run("pre condition in parent, default impl in child", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) resource interface A { + access(all) fun get(): Int { + pre { + true + } + } + } + + access(all) resource interface B: A { + access(all) fun get(): Int { + return 4 + } + } + + access(all) resource R: B {} + + access(all) fun main(): Int { + let r <- create R() + let value = r.get() + destroy r + return value + } + `) + + value, err := inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, + interpreter.NewUnmeteredIntValueFromInt64(4), + value, + ) + }) + + t.Run("post condition in parent, default impl in child", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) resource interface A { + access(all) fun get(): Int { + post { + true + } + } + } + + access(all) resource interface B: A { + access(all) fun get(): Int { + return 4 + } + } + + access(all) resource R: B {} + + access(all) fun main(): Int { + let r <- create R() + let value = r.get() + destroy r + return value + } + `) + + value, err := inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, + interpreter.NewUnmeteredIntValueFromInt64(4), + value, + ) + }) + + t.Run("result variable in conditions", func(t *testing.T) { + + t.Parallel() + + inter, getLogs, err := parseCheckAndInterpretWithLogs(t, ` + access(all) resource interface I1 { + access(all) let s: String + + access(all) fun echo(_ s: String): String { + post { + result == self.s: "result must match stored input, got: ".concat(result) + } + } + } + + access(all) resource interface I2: I1 { + access(all) let s: String + + access(all) fun echo(_ s: String): String { + log(s) + return self.s + } + } + + access(all) resource R: I2 { + access(all) let s: String + + init() { + self.s = "hello" + } + } + + access(all) fun main() { + let r <- create R() + r.echo("hello") + destroy r + } + `) + require.NoError(t, err) + + _, err = inter.Invoke("main") + require.NoError(t, err) + + logs := getLogs() + require.Len(t, logs, 1) + assert.Equal(t, "\"hello\"", logs[0]) + }) + } func TestInterpretNestedInterfaceCast(t *testing.T) { From 20976c6dfa97844869ac744cd789444209789632 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 17 Sep 2024 12:14:03 -0700 Subject: [PATCH 06/10] Add validation for wrapper functions with no body --- runtime/interpreter/interpreter.go | 123 ++++++++++---------- runtime/tests/interpreter/interface_test.go | 72 ++++++++++++ 2 files changed, 136 insertions(+), 59 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 2550c94cec..fe8aca758e 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1287,7 +1287,11 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( // the order does not matter. for name, functionWrapper := range code.FunctionWrappers { //nolint:maprange - fn, _ := functions.Get(name) + fn, ok := functions.Get(name) + // If there is a wrapper, there MUST be a body. + if !ok { + panic(errors.NewUnreachableError()) + } functions.Set(name, functionWrapper(fn)) } @@ -2447,6 +2451,13 @@ func (interpreter *Interpreter) functionConditionsWrapper( } return func(inner FunctionValue) FunctionValue { + + // NOTE: The `inner` function cannot be nil. + // An executing function always have a body. + if inner == nil { + panic(errors.NewUnreachableError()) + } + // Condition wrapper is a static function. return NewStaticHostFunctionValue( interpreter, @@ -2472,72 +2483,66 @@ func (interpreter *Interpreter) functionConditionsWrapper( interpreter.declareVariable(sema.BaseIdentifier, invocation.Base) } - // NOTE: The `inner` function might be nil. - // This is the case if the conforming type did not declare a function. - - var body func() StatementResult - if inner != nil { - // NOTE: It is important to wrap the invocation in a function, - // so the inner function isn't invoked here - - body = func() StatementResult { - - // Pre- and post-condition wrappers "re-declare" the same - // parameters as are used in the actual body of the function, - // see the use of bindParameterArguments at the start of this function wrapper. - // - // When these parameters are given resource-kinded arguments, - // this can trick the resource analysis into believing that these - // resources exist in multiple variables at once - // (one for each condition wrapper + the function itself). - // - // This is not the case, however, as execution of the pre- and post-conditions - // occurs strictly before and after execution of the body respectively. - // - // To prevent the analysis from reporting a false positive here, - // when we enter the body of the wrapped function, - // we invalidate any resources that were assigned to parameters by the precondition block, - // and then restore them after execution of the wrapped function, - // for use by the post-condition block. - - type argumentVariable struct { - variable Variable - value ResourceKindedValue + // NOTE: It is important to wrap the invocation in a function, + // so the inner function isn't invoked here + + body := func() StatementResult { + + // Pre- and post-condition wrappers "re-declare" the same + // parameters as are used in the actual body of the function, + // see the use of bindParameterArguments at the start of this function wrapper. + // + // When these parameters are given resource-kinded arguments, + // this can trick the resource analysis into believing that these + // resources exist in multiple variables at once + // (one for each condition wrapper + the function itself). + // + // This is not the case, however, as execution of the pre- and post-conditions + // occurs strictly before and after execution of the body respectively. + // + // To prevent the analysis from reporting a false positive here, + // when we enter the body of the wrapped function, + // we invalidate any resources that were assigned to parameters by the precondition block, + // and then restore them after execution of the wrapped function, + // for use by the post-condition block. + + type argumentVariable struct { + variable Variable + value ResourceKindedValue + } + + var argumentVariables []argumentVariable + for _, argument := range invocation.Arguments { + resourceKindedValue := interpreter.resourceForValidation(argument) + if resourceKindedValue == nil { + continue } - var argumentVariables []argumentVariable - for _, argument := range invocation.Arguments { - resourceKindedValue := interpreter.resourceForValidation(argument) - if resourceKindedValue == nil { - continue - } - - argumentVariables = append( - argumentVariables, - argumentVariable{ - variable: interpreter.SharedState.resourceVariables[resourceKindedValue], - value: resourceKindedValue, - }, - ) + argumentVariables = append( + argumentVariables, + argumentVariable{ + variable: interpreter.SharedState.resourceVariables[resourceKindedValue], + value: resourceKindedValue, + }, + ) - interpreter.invalidateResource(resourceKindedValue) - } + interpreter.invalidateResource(resourceKindedValue) + } - // NOTE: It is important to actually return the value returned - // from the inner function, otherwise it is lost + // NOTE: It is important to actually return the value returned + // from the inner function, otherwise it is lost - returnValue := inner.invoke(invocation) + returnValue := inner.invoke(invocation) - // Restore the resources which were temporarily invalidated - // before execution of the inner function + // Restore the resources which were temporarily invalidated + // before execution of the inner function - for _, argumentVariable := range argumentVariables { - value := argumentVariable.value - interpreter.invalidateResource(value) - interpreter.SharedState.resourceVariables[value] = argumentVariable.variable - } - return ReturnResult{Value: returnValue} + for _, argumentVariable := range argumentVariables { + value := argumentVariable.value + interpreter.invalidateResource(value) + interpreter.SharedState.resourceVariables[value] = argumentVariable.variable } + return ReturnResult{Value: returnValue} } declarationLocationRange := LocationRange{ diff --git a/runtime/tests/interpreter/interface_test.go b/runtime/tests/interpreter/interface_test.go index e53bbcae4d..22d3a3369b 100644 --- a/runtime/tests/interpreter/interface_test.go +++ b/runtime/tests/interpreter/interface_test.go @@ -948,6 +948,78 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { ) }) + t.Run("siblings with condition in first and default impl in second", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) struct interface A { + access(all) fun get(): Int { + post { true } + } + } + + access(all) struct interface B { + access(all) fun get(): Int { + return 4 + } + } + + struct interface C: A, B {} + + access(all) struct S: C {} + + access(all) fun main(): Int { + let s = S() + return s.get() + } + `) + + value, err := inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, + interpreter.NewUnmeteredIntValueFromInt64(4), + value, + ) + }) + + t.Run("siblings with default impl in first and condition in second", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) struct interface A { + access(all) fun get(): Int { + return 4 + } + } + + access(all) struct interface B { + access(all) fun get(): Int { + post { true } + } + } + + struct interface C: A, B {} + + access(all) struct S: C {} + + access(all) fun main(): Int { + let s = S() + return s.get() + } + `) + + value, err := inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, + interpreter.NewUnmeteredIntValueFromInt64(4), + value, + ) + }) + t.Run("result variable in conditions", func(t *testing.T) { t.Parallel() From 7137ac7f804727759a5bddad1cccda73f6d15e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 4 Sep 2024 12:57:57 -0700 Subject: [PATCH 07/10] flow CLI is 1.0 now --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eeeaeb07bd..634088911f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: run: make ci - name: Cadence Testing Framework - run: cd runtime/stdlib/contracts && flow-c1 test --cover --covercode="contracts" crypto_test.cdc + run: cd runtime/stdlib/contracts && flow test --cover --covercode="contracts" crypto_test.cdc - name: Upload coverage report uses: codecov/codecov-action@v2 From fb1917d5dc55fdbc8f6b69cd9764459b68020249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 25 Sep 2024 16:04:12 -0700 Subject: [PATCH 08/10] try git rev-parse --- .github/workflows/compatibility-check-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compatibility-check-template.yml b/.github/workflows/compatibility-check-template.yml index 0bb57d5a58..0308ae3085 100644 --- a/.github/workflows/compatibility-check-template.yml +++ b/.github/workflows/compatibility-check-template.yml @@ -95,7 +95,7 @@ jobs: - name: Check contracts using ${{ inputs.base-branch }} working-directory: ./tools/compatibility-check run: | - GOPROXY=direct go mod edit -replace github.com/onflow/cadence=github.com/${{ inputs.repo }}@${{ inputs.base-branch }} + GOPROXY=direct go mod edit -replace github.com/onflow/cadence=github.com/${{ inputs.repo }}@`git rev-parse ${{ inputs.base-branch }}` go mod tidy go run ./cmd/check_contracts/main.go ../../tmp/contracts.csv ../../tmp/output-old.txt From 2b762be25eff7bbdaf2630fc8d9057c7f658c763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 25 Sep 2024 17:03:54 -0700 Subject: [PATCH 09/10] remove GOPROXY for base branch --- .github/workflows/compatibility-check-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compatibility-check-template.yml b/.github/workflows/compatibility-check-template.yml index 0308ae3085..d43c279945 100644 --- a/.github/workflows/compatibility-check-template.yml +++ b/.github/workflows/compatibility-check-template.yml @@ -95,7 +95,7 @@ jobs: - name: Check contracts using ${{ inputs.base-branch }} working-directory: ./tools/compatibility-check run: | - GOPROXY=direct go mod edit -replace github.com/onflow/cadence=github.com/${{ inputs.repo }}@`git rev-parse ${{ inputs.base-branch }}` + go mod edit -replace github.com/onflow/cadence=github.com/${{ inputs.repo }}@${{ inputs.base-branch }} go mod tidy go run ./cmd/check_contracts/main.go ../../tmp/contracts.csv ../../tmp/output-old.txt From 1b790e080c357cc30002d7b50582c52219a4b262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 25 Sep 2024 17:38:23 -0700 Subject: [PATCH 10/10] try git rev-parse with remote name --- .github/workflows/compatibility-check-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compatibility-check-template.yml b/.github/workflows/compatibility-check-template.yml index d43c279945..fc6845f3a5 100644 --- a/.github/workflows/compatibility-check-template.yml +++ b/.github/workflows/compatibility-check-template.yml @@ -95,7 +95,7 @@ jobs: - name: Check contracts using ${{ inputs.base-branch }} working-directory: ./tools/compatibility-check run: | - go mod edit -replace github.com/onflow/cadence=github.com/${{ inputs.repo }}@${{ inputs.base-branch }} + GOPROXY=direct go mod edit -replace github.com/onflow/cadence=github.com/${{ inputs.repo }}@`git rev-parse origin/${{ inputs.base-branch }}` go mod tidy go run ./cmd/check_contracts/main.go ../../tmp/contracts.csv ../../tmp/output-old.txt