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 1/7] 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 2/7] 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 3/7] 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 4/7] 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 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 5/7] 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 6/7] 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 7/7] 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