Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix migration of storage path capabilities #3511

Merged
merged 7 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions migrations/capcons/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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 capcons

import (
"sync"

"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/interpreter"
)

type AccountCapability struct {
Path interpreter.PathValue
BorrowType interpreter.StaticType
}

type AccountCapabilities struct {
Capabilities []AccountCapability
}

func (c *AccountCapabilities) Record(path interpreter.PathValue, borrowType interpreter.StaticType) {
c.Capabilities = append(
c.Capabilities,
AccountCapability{
Path: path,
BorrowType: borrowType,
},
)
}

type AccountsCapabilities struct {
// accountCapabilities maps common.Address to *AccountCapabilities
accountCapabilities sync.Map
}

func (m *AccountsCapabilities) Record(
addressPath interpreter.AddressPath,
borrowType interpreter.StaticType,
) {
var accountCapabilities *AccountCapabilities
rawAccountCapabilities, ok := m.accountCapabilities.Load(addressPath.Address)
if ok {
accountCapabilities = rawAccountCapabilities.(*AccountCapabilities)
} else {
accountCapabilities = &AccountCapabilities{}
m.accountCapabilities.Store(addressPath.Address, accountCapabilities)
}
accountCapabilities.Record(addressPath.Path, borrowType)
}

func (m *AccountsCapabilities) ForEach(
address common.Address,
f func(AccountCapability) bool,
) {
rawAccountCapabilities, ok := m.accountCapabilities.Load(address)
if !ok {
return
}

accountCapabilities := rawAccountCapabilities.(*AccountCapabilities)
for _, accountCapability := range accountCapabilities.Capabilities {
if !f(accountCapability) {
return
}
}
}

func (m *AccountsCapabilities) Get(address common.Address) *AccountCapabilities {
rawAccountCapabilities, ok := m.accountCapabilities.Load(address)
if !ok {
return nil
}
return rawAccountCapabilities.(*AccountCapabilities)
}
57 changes: 11 additions & 46 deletions migrations/capcons/capabilitymigration.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"github.com/onflow/cadence/runtime/errors"
"github.com/onflow/cadence/runtime/interpreter"
"github.com/onflow/cadence/runtime/sema"
"github.com/onflow/cadence/runtime/stdlib"
)

type CapabilityMigrationReporter interface {
Expand All @@ -44,7 +43,6 @@ type CapabilityMigrationReporter interface {
// using the path to ID capability controller mapping generated by LinkValueMigration.
type CapabilityValueMigration struct {
CapabilityMapping *CapabilityMapping
IssueHandler stdlib.CapabilityControllerIssueHandler
Reporter CapabilityMigrationReporter
}

Expand All @@ -70,7 +68,7 @@ func (m *CapabilityValueMigration) Migrate(
storageKey interpreter.StorageKey,
_ interpreter.StorageMapKey,
value interpreter.Value,
inter *interpreter.Interpreter,
_ *interpreter.Interpreter,
_ migrations.ValueMigrationPosition,
) (
interpreter.Value,
Expand All @@ -79,7 +77,7 @@ func (m *CapabilityValueMigration) Migrate(

// Migrate path capabilities to ID capabilities
if pathCapabilityValue, ok := value.(*interpreter.PathCapabilityValue); ok { //nolint:staticcheck
return m.migratePathCapabilityValue(pathCapabilityValue, storageKey, inter)
return m.migratePathCapabilityValue(pathCapabilityValue, storageKey)
}

return nil, nil
Expand All @@ -88,7 +86,6 @@ func (m *CapabilityValueMigration) Migrate(
func (m *CapabilityValueMigration) migratePathCapabilityValue(
oldCapability *interpreter.PathCapabilityValue, //nolint:staticcheck
storageKey interpreter.StorageKey,
inter *interpreter.Interpreter,
) (interpreter.Value, error) {

reporter := m.Reporter
Expand All @@ -98,48 +95,16 @@ func (m *CapabilityValueMigration) migratePathCapabilityValue(
var capabilityID interpreter.UInt64Value
var controllerBorrowType sema.Type

targetPath := capabilityAddressPath.Path

switch targetPath.Domain {
case common.PathDomainPublic,
common.PathDomainPrivate:

var ok bool
capabilityID, controllerBorrowType, ok = m.CapabilityMapping.Get(capabilityAddressPath)
if !ok {
if reporter != nil {
reporter.MissingCapabilityID(
storageKey.Address,
capabilityAddressPath,
)
}
return nil, nil
var ok bool
capabilityID, controllerBorrowType, ok = m.CapabilityMapping.Get(capabilityAddressPath)
if !ok {
if reporter != nil {
reporter.MissingCapabilityID(
storageKey.Address,
capabilityAddressPath,
)
}

case common.PathDomainStorage:
// The capability may incorrectly target a storage path,
// due to an incorrect implementation of the link function in an early version of Cadence.
// This bug was fixed in https://github.com/onflow/cadence/pull/865,
// and subsequent capabilities created by the link function have correct targets,
// but networks may still have capabilities with incorrect targets.
//
// In this case, there is no corresponding capability controller in the mapping,
// so issue a new capability controller.

borrowType := inter.MustConvertStaticToSemaType(oldCapability.BorrowType).(*sema.ReferenceType)
controllerBorrowType = borrowType

capabilityID, _ = stdlib.IssueStorageCapabilityController(
inter,
interpreter.EmptyLocationRange,
m.IssueHandler,
common.Address(oldCapability.Address),
borrowType,
targetPath,
)

default:
panic(errors.NewUnexpectedError("unexpected capability target path: %s", targetPath))
return nil, nil
}

oldBorrowType := oldCapability.BorrowType
Expand Down
177 changes: 174 additions & 3 deletions migrations/capcons/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,28 @@ func testPathCapabilityValueMigration(

handler := &testCapConHandler{}

storageDomainCapabilities := &AccountsCapabilities{}

migration.Migrate(
migration.NewValueMigrationsPathMigrator(
reporter,
&StorageCapMigration{
StorageDomainCapabilities: storageDomainCapabilities,
},
),
)

storageCapabilities := storageDomainCapabilities.Get(testAddress)
if storageCapabilities != nil {
IssueAccountCapabilities(
inter,
testAddress,
storageCapabilities,
handler,
capabilityMapping,
)
}

migration.Migrate(
migration.NewValueMigrationsPathMigrator(
reporter,
Expand All @@ -511,7 +533,6 @@ func testPathCapabilityValueMigration(
reporter,
&CapabilityValueMigration{
CapabilityMapping: capabilityMapping,
IssueHandler: handler,
Reporter: reporter,
},
),
Expand Down Expand Up @@ -2369,7 +2390,6 @@ func TestPublishedPathCapabilityValueMigration(t *testing.T) {
reporter,
&CapabilityValueMigration{
CapabilityMapping: capabilityMapping,
IssueHandler: handler,
Reporter: reporter,
},
),
Expand Down Expand Up @@ -2622,7 +2642,6 @@ func TestUntypedPathCapabilityValueMigration(t *testing.T) {
reporter,
&CapabilityValueMigration{
CapabilityMapping: capabilityMapping,
IssueHandler: handler,
Reporter: reporter,
},
),
Expand Down Expand Up @@ -2793,3 +2812,155 @@ func TestCanSkipCapabilityValueMigration(t *testing.T) {
test(ty, expected)
}
}

func TestStorageCapMigration(t *testing.T) {
t.Parallel()

testBorrowType := interpreter.NewReferenceStaticType(
nil,
interpreter.UnauthorizedAccess,
interpreter.PrimitiveStaticTypeString,
)

// Equivalent to: getCapability<&String>(/storage/test)
capabilityValue := &interpreter.PathCapabilityValue{ //nolint:staticcheck
BorrowType: testBorrowType,
Path: interpreter.PathValue{
Domain: common.PathDomainStorage,
Identifier: testPathIdentifier,
},
Address: interpreter.AddressValue(testAddress),
}

rt := NewTestInterpreterRuntime()

var events []cadence.Event

runtimeInterface := &TestRuntimeInterface{
Storage: NewTestLedger(nil, nil),
OnGetSigningAccounts: func() ([]runtime.Address, error) {
return []runtime.Address{testAddress}, nil
},
OnEmitEvent: func(event cadence.Event) error {
events = append(events, event)
return nil
},
}

nextTransactionLocation := NewTransactionLocationGenerator()

// Setup

setupTransactionLocation := nextTransactionLocation()

environment := runtime.NewScriptInterpreterEnvironment(runtime.Config{})

// Inject the path capability value.
//
// We don't have a way to create a path capability value in a Cadence program anymore,
// so we have to inject it manually.

environment.DeclareValue(
stdlib.StandardLibraryValue{
Name: "cap",
Type: &sema.CapabilityType{},
Kind: common.DeclarationKindConstant,
Value: capabilityValue,
},
setupTransactionLocation,
)

// Save capability value into account

// language=cadence
setupTx := `
transaction {
prepare(signer: auth(SaveValue) &Account) {
signer.storage.save(cap, to: /storage/cap)
}
}
`

err := rt.ExecuteTransaction(
runtime.Script{
Source: []byte(setupTx),
},
runtime.Context{
Interface: runtimeInterface,
Environment: environment,
Location: setupTransactionLocation,
},
)
require.NoError(t, err)

// Migrate

storage, inter, err := rt.Storage(runtime.Context{
Interface: runtimeInterface,
})
require.NoError(t, err)

migration, err := migrations.NewStorageMigration(inter, storage, "test", testAddress)
require.NoError(t, err)

reporter := &testMigrationReporter{}

storageDomainCapabilities := &AccountsCapabilities{}

migration.Migrate(
migration.NewValueMigrationsPathMigrator(
reporter,
&StorageCapMigration{
StorageDomainCapabilities: storageDomainCapabilities,
},
),
)

err = migration.Commit()
require.NoError(t, err)

// Assert

require.Empty(t, reporter.migrations)
require.Empty(t, reporter.errors)

err = storage.CheckHealth()
require.NoError(t, err)

type actual struct {
address common.Address
capability AccountCapability
}

var actuals []actual

storageDomainCapabilities.ForEach(
testAddress,
func(accountCapability AccountCapability) bool {
actuals = append(
actuals,
actual{
address: testAddress,
capability: accountCapability,
},
)
return true
},
)

assert.Equal(t,
[]actual{
{
address: testAddress,
capability: AccountCapability{
Path: interpreter.PathValue{
Domain: common.PathDomainStorage,
Identifier: testPathIdentifier,
},
BorrowType: testBorrowType,
},
},
},
actuals,
)
}
Loading
Loading