Skip to content

Commit

Permalink
Merge pull request #3511 from onflow/bastian/fix-storage-cap-migration
Browse files Browse the repository at this point in the history
  • Loading branch information
turbolent authored Aug 8, 2024
2 parents c945dc6 + 0ed2234 commit 9279d74
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 49 deletions.
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

0 comments on commit 9279d74

Please sign in to comment.