From 159ed5850bb8fcc6bdb42b1ae523246568598cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 17 May 2023 15:51:54 -0700 Subject: [PATCH 01/51] export link to capability controller migration --- runtime/stdlib/account.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index 4a60a3b369..f01853d789 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -2504,7 +2504,7 @@ func newAccountAccountCapabilitiesIssueFunction( } capabilityIDValue, borrowStaticType := - issueAccountCapabilityController( + IssueAccountCapabilityController( inter, locationRange, idGenerator, @@ -2522,7 +2522,7 @@ func newAccountAccountCapabilitiesIssueFunction( ) } -func issueAccountCapabilityController( +func IssueAccountCapabilityController( inter *interpreter.Interpreter, locationRange interpreter.LocationRange, idGenerator AccountIDGenerator, From 82b5389d0d71cb9d04e720a8daeb4110d1c017f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 18 May 2023 14:44:06 -0700 Subject: [PATCH 02/51] start work on cap cons migration: migrate links of accounts --- runtime/capcons.go | 233 ++++++++++++++++++++++++++++++++++++++++ runtime/capcons_test.go | 195 +++++++++++++++++++++++++++++++++ 2 files changed, 428 insertions(+) create mode 100644 runtime/capcons.go create mode 100644 runtime/capcons_test.go diff --git a/runtime/capcons.go b/runtime/capcons.go new file mode 100644 index 0000000000..4bab244ed1 --- /dev/null +++ b/runtime/capcons.go @@ -0,0 +1,233 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 runtime + +import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/stdlib" +) + +type AddressIterator interface { + NextAddress() common.Address +} + +type AddressIteratorFunc func() common.Address + +func (a AddressIteratorFunc) NextAddress() common.Address { + return a() +} + +var _ AddressIterator = AddressIteratorFunc(nil) + +func NewAddressSliceIterator(addresses []common.Address) AddressIterator { + var index int + return AddressIteratorFunc( + func() common.Address { + if index >= len(addresses) { + return common.ZeroAddress + } + address := addresses[index] + index++ + return address + }, + ) +} + +type CapConsMigrationReporter interface { + CapConsLinkMigrationReporter +} + +type CapConsLinkMigrationReporter interface { + MigratedLink( + addressPath interpreter.AddressPath, + capabilityID interpreter.UInt64Value, + ) +} + +type CapConsMigration struct { + storage *Storage + interpreter *interpreter.Interpreter + capabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value +} + +func NewCapConsMigration(runtime Runtime, context Context) (*CapConsMigration, error) { + storage, inter, err := runtime.Storage(context) + if err != nil { + return nil, err + } + + return &CapConsMigration{ + storage: storage, + interpreter: inter, + }, nil +} + +// Migrate migrates the links to capability controllers, +// and all path capabilities and account capabilities to ID capabilities, +// in all accounts of the given iterator. +func (m *CapConsMigration) Migrate( + addressIterator AddressIterator, + accountIDGenerator stdlib.AccountIDGenerator, + reporter CapConsMigrationReporter, +) { + m.capabilityIDs = make(map[interpreter.AddressPath]interpreter.UInt64Value) + defer func() { + m.capabilityIDs = nil + }() + m.migrateLinks( + addressIterator, + accountIDGenerator, + reporter, + ) + // TODO: m.migratePathCapabilities() +} + +// migrateLinks migrates the links to capability controllers +// in all accounts of the given iterator. +// It constructs a source path to capability ID mapping, +// which is later needed to path capabilities to ID capabilities. +func (m *CapConsMigration) migrateLinks( + addressIterator AddressIterator, + accountIDGenerator stdlib.AccountIDGenerator, + reporter CapConsLinkMigrationReporter, +) { + for { + address := addressIterator.NextAddress() + if address == common.ZeroAddress { + break + } + + m.migrateLinksInAccount( + address, + accountIDGenerator, + reporter, + ) + } +} + +// migrateLinksInAccount migrates the links in the given account to capability controllers +// It records an entry in the source path to capability ID mapping, +// which is later needed to migrate path capabilities to ID capabilities. +func (m *CapConsMigration) migrateLinksInAccount( + address common.Address, + accountIDGenerator stdlib.AccountIDGenerator, + reporter CapConsLinkMigrationReporter, +) { + + migrateDomain := func(domain common.PathDomain) { + m.migrateAccountLinksInAccountDomain( + address, + accountIDGenerator, + domain, + reporter, + ) + } + + migrateDomain(common.PathDomainPublic) + migrateDomain(common.PathDomainPrivate) +} + +// migrateAccountLinksInAccountDomain migrates the links in the given account's storage domain +// to capability controllers. +// It records an entry in the source path to capability ID mapping, +// which is later needed to migrate path capabilities to ID capabilities. +func (m *CapConsMigration) migrateAccountLinksInAccountDomain( + address common.Address, + accountIDGenerator stdlib.AccountIDGenerator, + domain common.PathDomain, + reporter CapConsLinkMigrationReporter, +) { + addressValue := interpreter.AddressValue(address) + + storageMap := m.storage.GetStorageMap(address, domain.Identifier(), false) + if storageMap == nil { + return + } + + iterator := storageMap.Iterator(m.interpreter) + + count := storageMap.Count() + if count > 0 { + for key := iterator.NextKey(); key != nil; key = iterator.NextKey() { + // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey + identifier := string(key.(interpreter.StringAtreeValue)) + + pathValue := interpreter.NewUnmeteredPathValue(domain, identifier) + + m.migrateLink( + addressValue, + pathValue, + accountIDGenerator, + reporter, + ) + } + } +} + +// migrateAccountLinksInAccountDomain migrates the links in the given account's storage domain +// to capability controllers. +// It constructs a source path to ID mapping, +// which is later needed to migrate path capabilities to ID capabilities. +func (m *CapConsMigration) migrateLink( + address interpreter.AddressValue, + path interpreter.PathValue, + accountIDGenerator stdlib.AccountIDGenerator, + reporter CapConsLinkMigrationReporter, +) { + capabilityID := stdlib.MigrateLinkToCapabilityController( + m.interpreter, + interpreter.EmptyLocationRange, + address, + path, + accountIDGenerator, + ) + if capabilityID == 0 { + return + } + + // Record new capability ID in source path mapping. + // The mapping is used later for migrating path capabilities to ID capabilities. + + addressPath := interpreter.AddressPath{ + Address: address.ToAddress(), + Path: path, + } + m.capabilityIDs[addressPath] = capabilityID + + if reporter != nil { + reporter.MigratedLink(addressPath, capabilityID) + } +} + +// migratePathCapabilities migrates the path capabilities to ID capabilities +// in all accounts of the given iterator. +// It uses the source path to capability ID mapping which was constructed in migrateLinks. +func (m *CapConsMigration) migratePathCapabilities( + addressIterator AddressIterator, +) { + for { + address := addressIterator.NextAddress() + if address == common.ZeroAddress { + break + } + + // TODO: m.migratePathCapabilitiesInAccount(address) + } +} diff --git a/runtime/capcons_test.go b/runtime/capcons_test.go new file mode 100644 index 0000000000..2dc2088afa --- /dev/null +++ b/runtime/capcons_test.go @@ -0,0 +1,195 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 runtime + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + . "github.com/onflow/cadence/runtime/tests/utils" +) + +type testAccountIDGenerator struct { + ids map[common.Address]uint64 +} + +func (g *testAccountIDGenerator) GenerateAccountID(address common.Address) (uint64, error) { + if g.ids == nil { + g.ids = make(map[common.Address]uint64) + } + g.ids[address]++ + return g.ids[address], nil +} + +type testCapConsLinkMigration struct { + addressPath interpreter.AddressPath + capabilityID interpreter.UInt64Value +} + +type testCapConsMigrationReporter struct { + linkMigrations []testCapConsLinkMigration +} + +func (t *testCapConsMigrationReporter) MigratedLink( + addressPath interpreter.AddressPath, + capabilityID interpreter.UInt64Value, +) { + t.linkMigrations = append( + t.linkMigrations, + testCapConsLinkMigration{ + addressPath: addressPath, + capabilityID: capabilityID, + }, + ) +} + +var _ CapConsMigrationReporter = &testCapConsMigrationReporter{} + +func TestCapConsMigration(t *testing.T) { + + t.Parallel() + + rt := newTestInterpreterRuntime() + + // language=cadence + contract := ` + pub contract Test { + pub resource R {} + } + ` + + address := common.MustBytesToAddress([]byte{0x1}) + + accountCodes := map[Location][]byte{} + var events []cadence.Event + var loggedMessages []string + + runtimeInterface := &testRuntimeInterface{ + getCode: func(location Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{address}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + updateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + emitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + log: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + } + + nextTransactionLocation := newTransactionLocationGenerator() + + // Deploy contract + + deployTransaction := DeploymentTransaction("Test", []byte(contract)) + err := rt.ExecuteTransaction( + Script{ + Source: deployTransaction, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Setup + + // language=cadence + linkTransaction := ` + import Test from 0x1 + + transaction { + prepare(signer: AuthAccount) { + signer.link<&Test.R>(/public/r, target: /private/r) + signer.link<&Test.R>(/private/r, target: /storage/r) + } + } + ` + err = rt.ExecuteTransaction( + Script{ + Source: []byte(linkTransaction), + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Migrate + + migrator, err := NewCapConsMigration( + rt, + Context{ + Interface: runtimeInterface, + }, + ) + require.NoError(t, err) + + reporter := &testCapConsMigrationReporter{} + + migrator.Migrate( + NewAddressSliceIterator([]common.Address{address}), + &testAccountIDGenerator{}, + reporter, + ) + + require.Equal(t, + []testCapConsLinkMigration{ + { + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + "r", + ), + }, + capabilityID: 1, + }, + { + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + "r", + ), + }, + capabilityID: 2, + }, + }, + reporter.linkMigrations, + ) +} From d087fd19d5390dc1f6380301654d7b95a8060ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 19 May 2023 17:10:09 -0700 Subject: [PATCH 03/51] migrate path capabilities which are stored in a storage map --- runtime/capcons.go | 123 +++++++++++++++++++++----- runtime/capcons_test.go | 187 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 286 insertions(+), 24 deletions(-) diff --git a/runtime/capcons.go b/runtime/capcons.go index 4bab244ed1..c7bc0a4ce0 100644 --- a/runtime/capcons.go +++ b/runtime/capcons.go @@ -26,32 +26,33 @@ import ( type AddressIterator interface { NextAddress() common.Address + Reset() } -type AddressIteratorFunc func() common.Address - -func (a AddressIteratorFunc) NextAddress() common.Address { - return a() +type AddressSliceIterator struct { + Addresses []common.Address + index int } -var _ AddressIterator = AddressIteratorFunc(nil) +var _ AddressIterator = &AddressSliceIterator{} -func NewAddressSliceIterator(addresses []common.Address) AddressIterator { - var index int - return AddressIteratorFunc( - func() common.Address { - if index >= len(addresses) { - return common.ZeroAddress - } - address := addresses[index] - index++ - return address - }, - ) +func (a *AddressSliceIterator) NextAddress() common.Address { + index := a.index + if index >= len(a.Addresses) { + return common.ZeroAddress + } + address := a.Addresses[index] + a.index++ + return address +} + +func (a *AddressSliceIterator) Reset() { + a.index = 0 } type CapConsMigrationReporter interface { CapConsLinkMigrationReporter + CapConsPathCapabilityMigrationReporter } type CapConsLinkMigrationReporter interface { @@ -61,6 +62,17 @@ type CapConsLinkMigrationReporter interface { ) } +type CapConsPathCapabilityMigrationReporter interface { + MigratedPathCapability( + address common.Address, + addressPath interpreter.AddressPath, + ) + MissingCapabilityID( + address common.Address, + addressPath interpreter.AddressPath, + ) +} + type CapConsMigration struct { storage *Storage interpreter *interpreter.Interpreter @@ -86,7 +98,7 @@ func (m *CapConsMigration) Migrate( addressIterator AddressIterator, accountIDGenerator stdlib.AccountIDGenerator, reporter CapConsMigrationReporter, -) { +) error { m.capabilityIDs = make(map[interpreter.AddressPath]interpreter.UInt64Value) defer func() { m.capabilityIDs = nil @@ -96,7 +108,14 @@ func (m *CapConsMigration) Migrate( accountIDGenerator, reporter, ) - // TODO: m.migratePathCapabilities() + + addressIterator.Reset() + m.migratePathCapabilities( + addressIterator, + reporter, + ) + + return m.storage.Commit(m.interpreter, false) } // migrateLinks migrates the links to capability controllers @@ -221,6 +240,7 @@ func (m *CapConsMigration) migrateLink( // It uses the source path to capability ID mapping which was constructed in migrateLinks. func (m *CapConsMigration) migratePathCapabilities( addressIterator AddressIterator, + reporter CapConsPathCapabilityMigrationReporter, ) { for { address := addressIterator.NextAddress() @@ -228,6 +248,69 @@ func (m *CapConsMigration) migratePathCapabilities( break } - // TODO: m.migratePathCapabilitiesInAccount(address) + m.migratePathCapabilitiesInAccount(address, reporter) + } +} + +var pathDomainStorage = common.PathDomainStorage.Identifier() + +func (m *CapConsMigration) migratePathCapabilitiesInAccount(address common.Address, reporter CapConsPathCapabilityMigrationReporter) { + + storageMap := m.storage.GetStorageMap(address, pathDomainStorage, false) + if storageMap == nil { + return + } + + iterator := storageMap.Iterator(m.interpreter) + + count := storageMap.Count() + if count > 0 { + for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { + + m.migratePathCapability( + address, + value, + func(newValue interpreter.Value) { + // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey + identifier := string(key.(interpreter.StringAtreeValue)) + storageMap.SetValue( + m.interpreter, + interpreter.StringStorageMapKey(identifier), + newValue, + ) + }, + reporter, + ) + } + } +} + +func (m *CapConsMigration) migratePathCapability( + address common.Address, + value interpreter.Value, + update func(newValue interpreter.Value), + reporter CapConsPathCapabilityMigrationReporter, +) { + switch value := value.(type) { + case *interpreter.PathCapabilityValue: + oldCapability := value + addressPath := oldCapability.AddressPath() + capabilityID, ok := m.capabilityIDs[addressPath] + if !ok { + if reporter != nil { + reporter.MissingCapabilityID(address, addressPath) + } + break + } + newCapability := interpreter.NewUnmeteredIDCapabilityValue( + capabilityID, + oldCapability.Address, + oldCapability.BorrowType, + ) + update(newCapability) + if reporter != nil { + reporter.MigratedPathCapability(address, addressPath) + } } + // TODO: traverse composites, optionals, arrays, dictionaries, etc. } diff --git a/runtime/capcons_test.go b/runtime/capcons_test.go index 2dc2088afa..d5975f0f7e 100644 --- a/runtime/capcons_test.go +++ b/runtime/capcons_test.go @@ -21,6 +21,7 @@ package runtime import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence" @@ -46,10 +47,24 @@ type testCapConsLinkMigration struct { capabilityID interpreter.UInt64Value } +type testCapConsPathCapabilityMigration struct { + address common.Address + addressPath interpreter.AddressPath +} + +type testCapConsMissingCapabilityID struct { + address common.Address + addressPath interpreter.AddressPath +} + type testCapConsMigrationReporter struct { - linkMigrations []testCapConsLinkMigration + linkMigrations []testCapConsLinkMigration + pathCapabilityMigrations []testCapConsPathCapabilityMigration + missingCapabilityIDs []testCapConsMissingCapabilityID } +var _ CapConsMigrationReporter = &testCapConsMigrationReporter{} + func (t *testCapConsMigrationReporter) MigratedLink( addressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, @@ -63,7 +78,31 @@ func (t *testCapConsMigrationReporter) MigratedLink( ) } -var _ CapConsMigrationReporter = &testCapConsMigrationReporter{} +func (t *testCapConsMigrationReporter) MigratedPathCapability( + address common.Address, + addressPath interpreter.AddressPath, +) { + t.pathCapabilityMigrations = append( + t.pathCapabilityMigrations, + testCapConsPathCapabilityMigration{ + address: address, + addressPath: addressPath, + }, + ) +} + +func (t *testCapConsMigrationReporter) MissingCapabilityID( + address common.Address, + addressPath interpreter.AddressPath, +) { + t.missingCapabilityIDs = append( + t.missingCapabilityIDs, + testCapConsMissingCapabilityID{ + address: address, + addressPath: addressPath, + }, + ) +} func TestCapConsMigration(t *testing.T) { @@ -74,7 +113,44 @@ func TestCapConsMigration(t *testing.T) { // language=cadence contract := ` pub contract Test { + pub resource R {} + + pub struct CapabilityWrapper { + + pub let capability: Capability + + init(_ capability: Capability) { + self.capability = capability + } + } + + pub struct CapabilityOptionalWrapper { + + pub let capability: Capability? + + init(_ capability: Capability?) { + self.capability = capability + } + } + + pub struct CapabilityArrayWrapper { + + pub let capabilities: [Capability] + + init(_ capabilities: [Capability]) { + self.capabilities = capabilities + } + } + + pub struct CapabilityDictionaryWrapper { + + pub let capabilities: {Int: Capability} + + init(_ capabilities: {Int: Capability}) { + self.capabilities = capabilities + } + } } ` @@ -131,10 +207,50 @@ func TestCapConsMigration(t *testing.T) { linkTransaction := ` import Test from 0x1 + pub fun save(kind: String, capability: Capability, account: AuthAccount) { + account.save( + capability, + to: StoragePath(identifier: kind.concat("_capability"))! + ) + // TODO: + // account.save( + // Test.CapabilityWrapper(capability), + // to: StoragePath(identifier: kind.concat("_capabilityWrapper"))! + // ) + // account.save( + // Test.CapabilityOptionalWrapper(capability), + // to: StoragePath(identifier: kind.concat("_capabilityOptionalWrapper"))! + // ) + // account.save( + // Test.CapabilityArrayWrapper([capability]), + // to: StoragePath(identifier: kind.concat("_capabilityArrayWrapper"))! + // ) + // account.save( + // Test.CapabilityDictionaryWrapper({0: capability}), + // to: StoragePath(identifier: kind.concat("_capabilityDictionaryWrapper"))! + // ) + // TODO: add more cases + // TODO: test non existing + } + transaction { prepare(signer: AuthAccount) { signer.link<&Test.R>(/public/r, target: /private/r) signer.link<&Test.R>(/private/r, target: /storage/r) + + let publicCap = signer.getCapability<&Test.R>(/public/r) + let privateCap = signer.getCapability<&Test.R>(/private/r) + + save( + kind: "public", + capability: publicCap, + account: signer + ) + save( + kind: "private", + capability: privateCap, + account: signer + ) } } ` @@ -161,11 +277,14 @@ func TestCapConsMigration(t *testing.T) { reporter := &testCapConsMigrationReporter{} - migrator.Migrate( - NewAddressSliceIterator([]common.Address{address}), + err = migrator.Migrate( + &AddressSliceIterator{Addresses: []common.Address{address}}, &testAccountIDGenerator{}, reporter, ) + require.NoError(t, err) + + // Check migrated links require.Equal(t, []testCapConsLinkMigration{ @@ -192,4 +311,64 @@ func TestCapConsMigration(t *testing.T) { }, reporter.linkMigrations, ) + + // Check migrated capabilities + + require.Equal(t, + []testCapConsPathCapabilityMigration{ + { + address: address, + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + "r", + ), + }, + }, + { + address: address, + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + "r", + ), + }, + }, + }, + reporter.pathCapabilityMigrations, + ) + + storage, _, err := rt.Storage(Context{ + Interface: runtimeInterface, + }) + require.NoError(t, err) + + storageMap := storage.GetStorageMap(address, pathDomainStorage, false) + + expectedReferenceStaticType := interpreter.ReferenceStaticType{ + BorrowedType: interpreter.CompositeStaticType{ + Location: common.AddressLocation{ + Name: "Test", + Address: address, + }, + QualifiedIdentifier: "Test.R", + TypeID: "A.0000000000000001.Test.R", + }, + } + + expectedCapabilityID := interpreter.UInt64Value(1) + for _, kind := range []string{"public", "private"} { + + publicCapability := storageMap.ReadValue(nil, interpreter.StringStorageMapKey(kind+"_capability")) + require.IsType(t, &interpreter.IDCapabilityValue{}, publicCapability) + if publicCapability, ok := publicCapability.(*interpreter.IDCapabilityValue); ok { + assert.Equal(t, expectedCapabilityID, publicCapability.ID) + expectedCapabilityID++ + + assert.Equal(t, address, common.Address(publicCapability.Address)) + assert.True(t, publicCapability.BorrowType.Equal(expectedReferenceStaticType)) + } + } } From 49c7e3eaa37d7a9d4a7f6fb607740e8bba04200f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 19 Jun 2023 18:55:25 -0700 Subject: [PATCH 04/51] migrate composites, optionals, arrays, and dictionaries --- runtime/capcons.go | 118 +++++++-- runtime/capcons_test.go | 538 +++++++++++++++++++++++----------------- 2 files changed, 417 insertions(+), 239 deletions(-) diff --git a/runtime/capcons.go b/runtime/capcons.go index c7bc0a4ce0..ca30ea5bf1 100644 --- a/runtime/capcons.go +++ b/runtime/capcons.go @@ -20,6 +20,7 @@ package runtime import ( "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/stdlib" ) @@ -267,20 +268,21 @@ func (m *CapConsMigration) migratePathCapabilitiesInAccount(address common.Addre if count > 0 { for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { - m.migratePathCapability( + newValue := m.migratePathCapability( address, value, - func(newValue interpreter.Value) { - // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey - identifier := string(key.(interpreter.StringAtreeValue)) - storageMap.SetValue( - m.interpreter, - interpreter.StringStorageMapKey(identifier), - newValue, - ) - }, reporter, ) + + if newValue != nil { + // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey + identifier := string(key.(interpreter.StringAtreeValue)) + storageMap.SetValue( + m.interpreter, + interpreter.StringStorageMapKey(identifier), + newValue, + ) + } } } } @@ -288,12 +290,14 @@ func (m *CapConsMigration) migratePathCapabilitiesInAccount(address common.Addre func (m *CapConsMigration) migratePathCapability( address common.Address, value interpreter.Value, - update func(newValue interpreter.Value), reporter CapConsPathCapabilityMigrationReporter, -) { +) interpreter.Value { + locationRange := interpreter.EmptyLocationRange + switch value := value.(type) { case *interpreter.PathCapabilityValue: oldCapability := value + addressPath := oldCapability.AddressPath() capabilityID, ok := m.capabilityIDs[addressPath] if !ok { @@ -302,15 +306,101 @@ func (m *CapConsMigration) migratePathCapability( } break } + newCapability := interpreter.NewUnmeteredIDCapabilityValue( capabilityID, oldCapability.Address, oldCapability.BorrowType, ) - update(newCapability) + if reporter != nil { reporter.MigratedPathCapability(address, addressPath) } + + return newCapability + + case *interpreter.CompositeValue: + composite := value + + composite.ForEachField(nil, func(fieldName string, fieldValue interpreter.Value) { + newFieldValue := m.migratePathCapability(address, fieldValue, reporter) + if newFieldValue != nil { + composite.SetMember( + m.interpreter, + locationRange, + fieldName, + newFieldValue, + ) + } + }) + + return nil + + case *interpreter.SomeValue: + innerValue := value.InnerValue(m.interpreter, locationRange) + newInnerValue := m.migratePathCapability(address, innerValue, reporter) + if newInnerValue != nil { + return interpreter.NewSomeValueNonCopying(m.interpreter, newInnerValue) + } + + return nil + + case *interpreter.ArrayValue: + array := value + var index int + + array.Iterate(m.interpreter, func(element interpreter.Value) (resume bool) { + newElement := m.migratePathCapability(address, element, reporter) + if newElement != nil { + array.Set( + m.interpreter, + locationRange, + index, + newElement, + ) + } + + index++ + + return true + }) + + return nil + + case *interpreter.DictionaryValue: + dictionary := value + + dictionary.Iterate(m.interpreter, func(key, value interpreter.Value) (resume bool) { + if _, ok := key.(interpreter.CapabilityValue); ok { + panic(errors.NewUnreachableError()) + } + + newValue := m.migratePathCapability(address, value, reporter) + + if newValue != nil { + dictionary.Insert( + m.interpreter, + locationRange, + key, + newValue, + ) + } + + return true + }) + + return nil + + case interpreter.NumberValue, + *interpreter.StringValue, + interpreter.CharacterValue, + interpreter.BoolValue, + interpreter.TypeValue, + interpreter.PathValue, + interpreter.NilValue: + + return nil } - // TODO: traverse composites, optionals, arrays, dictionaries, etc. + + panic(errors.NewUnexpectedError("unsupported value type: %T", value)) } diff --git a/runtime/capcons_test.go b/runtime/capcons_test.go index d5975f0f7e..39d91b35e6 100644 --- a/runtime/capcons_test.go +++ b/runtime/capcons_test.go @@ -19,9 +19,9 @@ package runtime import ( + "fmt" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence" @@ -108,267 +108,355 @@ func TestCapConsMigration(t *testing.T) { t.Parallel() - rt := newTestInterpreterRuntime() + address := common.MustBytesToAddress([]byte{0x1}) - // language=cadence - contract := ` - pub contract Test { + test := func(setupFunction, checkFunction string) { - pub resource R {} + rt := newTestInterpreterRuntime() - pub struct CapabilityWrapper { + // language=cadence + contract := ` + pub contract Test { - pub let capability: Capability + pub resource R {} - init(_ capability: Capability) { - self.capability = capability - } - } + pub struct CapabilityWrapper { - pub struct CapabilityOptionalWrapper { + pub let capability: Capability - pub let capability: Capability? + init(_ capability: Capability) { + self.capability = capability + } + } - init(_ capability: Capability?) { - self.capability = capability - } - } + pub struct CapabilityOptionalWrapper { - pub struct CapabilityArrayWrapper { + pub let capability: Capability? - pub let capabilities: [Capability] + init(_ capability: Capability?) { + self.capability = capability + } + } - init(_ capabilities: [Capability]) { - self.capabilities = capabilities - } - } + pub struct CapabilityArrayWrapper { - pub struct CapabilityDictionaryWrapper { + pub let capabilities: [Capability] - pub let capabilities: {Int: Capability} + init(_ capabilities: [Capability]) { + self.capabilities = capabilities + } + } - init(_ capabilities: {Int: Capability}) { - self.capabilities = capabilities - } - } - } - ` + pub struct CapabilityDictionaryWrapper { - address := common.MustBytesToAddress([]byte{0x1}) + pub let capabilities: {Int: Capability} - accountCodes := map[Location][]byte{} - var events []cadence.Event - var loggedMessages []string + init(_ capabilities: {Int: Capability}) { + self.capabilities = capabilities + } + } - runtimeInterface := &testRuntimeInterface{ - getCode: func(location Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { - return []Address{address}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - updateAccountContractCode: func(location common.AddressLocation, code []byte) error { - accountCodes[location] = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - log: func(message string) { - loggedMessages = append(loggedMessages, message) - }, - } + pub fun saveExisting(_ wrapper: ((Capability): AnyStruct)) { + self.account.link<&Test.R>(/public/r, target: /private/r) + self.account.link<&Test.R>(/private/r, target: /storage/r) - nextTransactionLocation := newTransactionLocationGenerator() + let publicCap = self.account.getCapability<&Test.R>(/public/r) + let privateCap = self.account.getCapability<&Test.R>(/private/r) - // Deploy contract + self.account.save(wrapper(publicCap), to: /storage/publicCapValue) + self.account.save(wrapper(privateCap), to: /storage/privateCapValue) + } - deployTransaction := DeploymentTransaction("Test", []byte(contract)) - err := rt.ExecuteTransaction( - Script{ - Source: deployTransaction, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Setup - - // language=cadence - linkTransaction := ` - import Test from 0x1 - - pub fun save(kind: String, capability: Capability, account: AuthAccount) { - account.save( - capability, - to: StoragePath(identifier: kind.concat("_capability"))! - ) - // TODO: - // account.save( - // Test.CapabilityWrapper(capability), - // to: StoragePath(identifier: kind.concat("_capabilityWrapper"))! - // ) - // account.save( - // Test.CapabilityOptionalWrapper(capability), - // to: StoragePath(identifier: kind.concat("_capabilityOptionalWrapper"))! - // ) - // account.save( - // Test.CapabilityArrayWrapper([capability]), - // to: StoragePath(identifier: kind.concat("_capabilityArrayWrapper"))! - // ) - // account.save( - // Test.CapabilityDictionaryWrapper({0: capability}), - // to: StoragePath(identifier: kind.concat("_capabilityDictionaryWrapper"))! - // ) - // TODO: add more cases - // TODO: test non existing - } - - transaction { - prepare(signer: AuthAccount) { - signer.link<&Test.R>(/public/r, target: /private/r) - signer.link<&Test.R>(/private/r, target: /storage/r) - - let publicCap = signer.getCapability<&Test.R>(/public/r) - let privateCap = signer.getCapability<&Test.R>(/private/r) - - save( - kind: "public", - capability: publicCap, - account: signer - ) - save( - kind: "private", - capability: privateCap, - account: signer - ) + pub fun checkMigratedValues(getter: ((AnyStruct): Capability)) { + self.account.save(<-create R(), to: /storage/r) + self.checkMigratedValue(capValuePath: /storage/publicCapValue, getter: getter) + self.checkMigratedValue(capValuePath: /storage/privateCapValue, getter: getter) + } + + priv fun checkMigratedValue(capValuePath: StoragePath, getter: ((AnyStruct): Capability)) { + let capValue = self.account.copy(from: capValuePath)! + let cap = getter(capValue) + assert(cap.id != 0) + let ref = cap.borrow<&R>()! + } } - } - ` - err = rt.ExecuteTransaction( - Script{ - Source: []byte(linkTransaction), - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) + ` - // Migrate + accountCodes := map[Location][]byte{} + var events []cadence.Event + var loggedMessages []string - migrator, err := NewCapConsMigration( - rt, - Context{ - Interface: runtimeInterface, - }, - ) - require.NoError(t, err) + runtimeInterface := &testRuntimeInterface{ + getCode: func(location Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{address}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + updateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + emitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + log: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + } - reporter := &testCapConsMigrationReporter{} + nextTransactionLocation := newTransactionLocationGenerator() - err = migrator.Migrate( - &AddressSliceIterator{Addresses: []common.Address{address}}, - &testAccountIDGenerator{}, - reporter, - ) - require.NoError(t, err) - - // Check migrated links - - require.Equal(t, - []testCapConsLinkMigration{ - { - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPublic, - "r", - ), - }, - capabilityID: 1, + // Deploy contract + + deployTransaction := DeploymentTransaction("Test", []byte(contract)) + err := rt.ExecuteTransaction( + Script{ + Source: deployTransaction, }, - { - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPrivate, - "r", - ), - }, - capabilityID: 2, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), }, - }, - reporter.linkMigrations, - ) + ) + require.NoError(t, err) + + // Setup + + setupTx := fmt.Sprintf( + // language=cadence + ` + import Test from 0x1 + + transaction { + prepare(signer: AuthAccount) { + Test.saveExisting(%s) + } + } + `, + setupFunction, + ) + err = rt.ExecuteTransaction( + Script{ + Source: []byte(setupTx), + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Migrate + + migrator, err := NewCapConsMigration( + rt, + Context{ + Interface: runtimeInterface, + }, + ) + require.NoError(t, err) + + reporter := &testCapConsMigrationReporter{} - // Check migrated capabilities - - require.Equal(t, - []testCapConsPathCapabilityMigration{ - { - address: address, - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPrivate, - "r", - ), + err = migrator.Migrate( + &AddressSliceIterator{ + Addresses: []common.Address{ + address, }, }, - { - address: address, - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPublic, - "r", - ), + &testAccountIDGenerator{}, + reporter, + ) + require.NoError(t, err) + + // Check migrated links + + require.Equal(t, + []testCapConsLinkMigration{ + { + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + "r", + ), + }, + capabilityID: 1, + }, + { + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + "r", + ), + }, + capabilityID: 2, }, }, - }, - reporter.pathCapabilityMigrations, - ) + reporter.linkMigrations, + ) + + // Check migrated capabilities + + require.Equal(t, + []testCapConsPathCapabilityMigration{ + { + address: address, + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + "r", + ), + }, + }, + { + address: address, + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + "r", + ), + }, + }, + }, + reporter.pathCapabilityMigrations, + ) + + // Check + + checkScript := fmt.Sprintf( + // language=cadence + ` + import Test from 0x1 + + pub fun main() { + Test.checkMigratedValues(getter: %s) + } + `, + checkFunction, + ) + _, err = rt.ExecuteScript( + Script{ + Source: []byte(checkScript), + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + require.NoError(t, err) + } - storage, _, err := rt.Storage(Context{ - Interface: runtimeInterface, + t.Run("directly", func(t *testing.T) { + t.Parallel() + + test( + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return cap + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + return value as! Capability + } + `, + ) }) - require.NoError(t, err) - storageMap := storage.GetStorageMap(address, pathDomainStorage, false) + t.Run("composite", func(t *testing.T) { + t.Parallel() + + test( + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityWrapper(cap) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityWrapper + return wrapper.capability + } + `, + ) + }) - expectedReferenceStaticType := interpreter.ReferenceStaticType{ - BorrowedType: interpreter.CompositeStaticType{ - Location: common.AddressLocation{ - Name: "Test", - Address: address, - }, - QualifiedIdentifier: "Test.R", - TypeID: "A.0000000000000001.Test.R", - }, - } + t.Run("optional", func(t *testing.T) { + t.Parallel() + + test( + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityOptionalWrapper(cap) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityOptionalWrapper + return wrapper.capability! + } + `, + ) + }) - expectedCapabilityID := interpreter.UInt64Value(1) - for _, kind := range []string{"public", "private"} { + t.Run("array", func(t *testing.T) { + t.Parallel() + + test( + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityArrayWrapper([cap]) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityArrayWrapper + return wrapper.capabilities[0] + } + `, + ) + }) - publicCapability := storageMap.ReadValue(nil, interpreter.StringStorageMapKey(kind+"_capability")) - require.IsType(t, &interpreter.IDCapabilityValue{}, publicCapability) - if publicCapability, ok := publicCapability.(*interpreter.IDCapabilityValue); ok { - assert.Equal(t, expectedCapabilityID, publicCapability.ID) - expectedCapabilityID++ + t.Run("dictionary, value", func(t *testing.T) { + t.Parallel() + + test( + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityDictionaryWrapper({2: cap}) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityDictionaryWrapper + return wrapper.capabilities[2]! + } + `, + ) + }) + + // TODO: add more cases + // TODO: test non existing - assert.Equal(t, address, common.Address(publicCapability.Address)) - assert.True(t, publicCapability.BorrowType.Equal(expectedReferenceStaticType)) - } - } } From 4a6155ae913044818c75b0692c898915553522ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 20 Jun 2023 09:32:19 -0700 Subject: [PATCH 05/51] Revert "use existing DeploymentTransaction helper" This reverts commit eae5bd3945170da5f3fd4ad0fa022548de8e4446. --- runtime/runtime_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index f79b8ad7c0..078e20467a 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -3784,10 +3784,10 @@ func TestRuntimeInvokeStoredInterfaceFunction(t *testing.T) { return []byte(fmt.Sprintf( ` transaction { - prepare(signer: auth(Storage) &Account) { - let acct = Account(payer: signer) - acct.contracts.add(name: "%s", code: "%s".decodeHex()) - } + prepare(signer: auth(Storage) &Account) { + let acct = Account(payer: signer) + acct.contracts.add(name: "%s", code: "%s".decodeHex()) + } } `, name, From ac2c791d5874824c2e543cb605d96893da3bfc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 20 Jun 2023 09:33:09 -0700 Subject: [PATCH 06/51] clarify naming --- runtime/runtime_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 078e20467a..a70d97f496 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -3780,7 +3780,7 @@ func TestRuntimeInvokeStoredInterfaceFunction(t *testing.T) { runtime := NewTestInterpreterRuntime() - makeDeployTransaction := func(name, code string) []byte { + makeDeployToNewAccountTransaction := func(name, code string) []byte { return []byte(fmt.Sprintf( ` transaction { @@ -3896,10 +3896,10 @@ func TestRuntimeInvokeStoredInterfaceFunction(t *testing.T) { nextTransactionLocation := NewTransactionLocationGenerator() - deployTransaction := makeDeployTransaction("TestContractInterface", contractInterfaceCode) + deployToNewAccountTransaction := makeDeployToNewAccountTransaction("TestContractInterface", contractInterfaceCode) err := runtime.ExecuteTransaction( Script{ - Source: deployTransaction, + Source: deployToNewAccountTransaction, }, Context{ Interface: runtimeInterface, @@ -3908,10 +3908,10 @@ func TestRuntimeInvokeStoredInterfaceFunction(t *testing.T) { ) require.NoError(t, err) - deployTransaction = makeDeployTransaction("TestContract", contractCode) + deployToNewAccountTransaction = makeDeployToNewAccountTransaction("TestContract", contractCode) err = runtime.ExecuteTransaction( Script{ - Source: deployTransaction, + Source: deployToNewAccountTransaction, }, Context{ Interface: runtimeInterface, From 393798b67fb6c787226b7544904adb5041c868b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 20 Jun 2023 09:49:13 -0700 Subject: [PATCH 07/51] add comments --- runtime/capcons.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/runtime/capcons.go b/runtime/capcons.go index ca30ea5bf1..c45856a639 100644 --- a/runtime/capcons.go +++ b/runtime/capcons.go @@ -287,6 +287,9 @@ func (m *CapConsMigration) migratePathCapabilitiesInAccount(address common.Addre } } +// migratePathCapability migrates a path capability to an ID capability in the given value. +// If a value is returned, the value must be updated with the replacement in the parent. +// If nil is returned, the value was not updated and no operation has to be performed. func (m *CapConsMigration) migratePathCapability( address common.Address, value interpreter.Value, @@ -296,6 +299,9 @@ func (m *CapConsMigration) migratePathCapability( switch value := value.(type) { case *interpreter.PathCapabilityValue: + + // Migrate the path capability to an ID capability + oldCapability := value addressPath := oldCapability.AddressPath() @@ -322,6 +328,8 @@ func (m *CapConsMigration) migratePathCapability( case *interpreter.CompositeValue: composite := value + // Migrate composite's fields + composite.ForEachField(nil, func(fieldName string, fieldValue interpreter.Value) { newFieldValue := m.migratePathCapability(address, fieldValue, reporter) if newFieldValue != nil { @@ -334,6 +342,8 @@ func (m *CapConsMigration) migratePathCapability( } }) + // The composite itself does not have to be replaced + return nil case *interpreter.SomeValue: @@ -349,6 +359,8 @@ func (m *CapConsMigration) migratePathCapability( array := value var index int + // Migrate array's elements + array.Iterate(m.interpreter, func(element interpreter.Value) (resume bool) { newElement := m.migratePathCapability(address, element, reporter) if newElement != nil { @@ -365,16 +377,26 @@ func (m *CapConsMigration) migratePathCapability( return true }) + // The array itself does not have to be replaced + return nil case *interpreter.DictionaryValue: dictionary := value + // Migrate dictionary's values + dictionary.Iterate(m.interpreter, func(key, value interpreter.Value) (resume bool) { + + // Keys cannot be capabilities at the moment, + // so this should never occur in stored data + if _, ok := key.(interpreter.CapabilityValue); ok { panic(errors.NewUnreachableError()) } + // Migrate the value of the key-value pair + newValue := m.migratePathCapability(address, value, reporter) if newValue != nil { @@ -389,6 +411,8 @@ func (m *CapConsMigration) migratePathCapability( return true }) + // The dictionary itself does not have to be replaced + return nil case interpreter.NumberValue, @@ -399,6 +423,9 @@ func (m *CapConsMigration) migratePathCapability( interpreter.PathValue, interpreter.NilValue: + // Primitive values do not have to be updated, + // as they do not contain path capabilities. + return nil } From d880408e9eee14b4296dbb9770218ad56df4c72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 20 Sep 2023 18:28:38 -0700 Subject: [PATCH 08/51] bring back dependencies of cap cons migration --- {runtime => migration/capcons}/capcons.go | 379 +++++++++++++--- migration/capcons/capcons_test.go | 455 +++++++++++++++++++ migration/capcons/error.go | 55 +++ migration/capcons/target.go | 40 ++ runtime/capcons_test.go | 462 -------------------- runtime/interpreter/statictype.go | 2 +- runtime/interpreter/value_link.go | 239 ++++++++++ runtime/interpreter/value_pathcapability.go | 161 +++++++ runtime/stdlib/account.go | 4 +- 9 files changed, 1271 insertions(+), 526 deletions(-) rename {runtime => migration/capcons}/capcons.go (53%) create mode 100644 migration/capcons/capcons_test.go create mode 100644 migration/capcons/error.go create mode 100644 migration/capcons/target.go delete mode 100644 runtime/capcons_test.go create mode 100644 runtime/interpreter/value_link.go create mode 100644 runtime/interpreter/value_pathcapability.go diff --git a/runtime/capcons.go b/migration/capcons/capcons.go similarity index 53% rename from runtime/capcons.go rename to migration/capcons/capcons.go index c45856a639..493e089cd3 100644 --- a/runtime/capcons.go +++ b/migration/capcons/capcons.go @@ -16,12 +16,16 @@ * limitations under the License. */ -package runtime +package capcons import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" ) @@ -51,19 +55,19 @@ func (a *AddressSliceIterator) Reset() { a.index = 0 } -type CapConsMigrationReporter interface { - CapConsLinkMigrationReporter - CapConsPathCapabilityMigrationReporter +type MigrationReporter interface { + LinkMigrationReporter + PathCapabilityMigrationReporter } -type CapConsLinkMigrationReporter interface { +type LinkMigrationReporter interface { MigratedLink( addressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, ) } -type CapConsPathCapabilityMigrationReporter interface { +type PathCapabilityMigrationReporter interface { MigratedPathCapability( address common.Address, addressPath interpreter.AddressPath, @@ -74,47 +78,47 @@ type CapConsPathCapabilityMigrationReporter interface { ) } -type CapConsMigration struct { - storage *Storage - interpreter *interpreter.Interpreter - capabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value +type Migration struct { + storage *runtime.Storage + interpreter *interpreter.Interpreter + capabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value + addressIterator AddressIterator + accountIDGenerator stdlib.AccountIDGenerator } -func NewCapConsMigration(runtime Runtime, context Context) (*CapConsMigration, error) { +func NewMigration( + runtime runtime.Runtime, + context runtime.Context, + addressIterator AddressIterator, + accountIDGenerator stdlib.AccountIDGenerator, +) (*Migration, error) { storage, inter, err := runtime.Storage(context) if err != nil { return nil, err } - return &CapConsMigration{ - storage: storage, - interpreter: inter, + return &Migration{ + storage: storage, + interpreter: inter, + addressIterator: addressIterator, + accountIDGenerator: accountIDGenerator, }, nil } // Migrate migrates the links to capability controllers, // and all path capabilities and account capabilities to ID capabilities, // in all accounts of the given iterator. -func (m *CapConsMigration) Migrate( - addressIterator AddressIterator, - accountIDGenerator stdlib.AccountIDGenerator, - reporter CapConsMigrationReporter, +func (m *Migration) Migrate( + reporter MigrationReporter, ) error { m.capabilityIDs = make(map[interpreter.AddressPath]interpreter.UInt64Value) defer func() { m.capabilityIDs = nil }() - m.migrateLinks( - addressIterator, - accountIDGenerator, - reporter, - ) + m.migrateLinks(reporter) - addressIterator.Reset() - m.migratePathCapabilities( - addressIterator, - reporter, - ) + m.addressIterator.Reset() + m.migratePathCapabilities(reporter) return m.storage.Commit(m.interpreter, false) } @@ -123,20 +127,17 @@ func (m *CapConsMigration) Migrate( // in all accounts of the given iterator. // It constructs a source path to capability ID mapping, // which is later needed to path capabilities to ID capabilities. -func (m *CapConsMigration) migrateLinks( - addressIterator AddressIterator, - accountIDGenerator stdlib.AccountIDGenerator, - reporter CapConsLinkMigrationReporter, +func (m *Migration) migrateLinks( + reporter LinkMigrationReporter, ) { for { - address := addressIterator.NextAddress() + address := m.addressIterator.NextAddress() if address == common.ZeroAddress { break } m.migrateLinksInAccount( address, - accountIDGenerator, reporter, ) } @@ -145,16 +146,14 @@ func (m *CapConsMigration) migrateLinks( // migrateLinksInAccount migrates the links in the given account to capability controllers // It records an entry in the source path to capability ID mapping, // which is later needed to migrate path capabilities to ID capabilities. -func (m *CapConsMigration) migrateLinksInAccount( +func (m *Migration) migrateLinksInAccount( address common.Address, - accountIDGenerator stdlib.AccountIDGenerator, - reporter CapConsLinkMigrationReporter, + reporter LinkMigrationReporter, ) { migrateDomain := func(domain common.PathDomain) { m.migrateAccountLinksInAccountDomain( address, - accountIDGenerator, domain, reporter, ) @@ -168,11 +167,10 @@ func (m *CapConsMigration) migrateLinksInAccount( // to capability controllers. // It records an entry in the source path to capability ID mapping, // which is later needed to migrate path capabilities to ID capabilities. -func (m *CapConsMigration) migrateAccountLinksInAccountDomain( +func (m *Migration) migrateAccountLinksInAccountDomain( address common.Address, - accountIDGenerator stdlib.AccountIDGenerator, domain common.PathDomain, - reporter CapConsLinkMigrationReporter, + reporter LinkMigrationReporter, ) { addressValue := interpreter.AddressValue(address) @@ -194,7 +192,6 @@ func (m *CapConsMigration) migrateAccountLinksInAccountDomain( m.migrateLink( addressValue, pathValue, - accountIDGenerator, reporter, ) } @@ -205,19 +202,12 @@ func (m *CapConsMigration) migrateAccountLinksInAccountDomain( // to capability controllers. // It constructs a source path to ID mapping, // which is later needed to migrate path capabilities to ID capabilities. -func (m *CapConsMigration) migrateLink( +func (m *Migration) migrateLink( address interpreter.AddressValue, path interpreter.PathValue, - accountIDGenerator stdlib.AccountIDGenerator, - reporter CapConsLinkMigrationReporter, + reporter LinkMigrationReporter, ) { - capabilityID := stdlib.MigrateLinkToCapabilityController( - m.interpreter, - interpreter.EmptyLocationRange, - address, - path, - accountIDGenerator, - ) + capabilityID := m.migrateLinkToCapabilityController(address, path) if capabilityID == 0 { return } @@ -239,12 +229,11 @@ func (m *CapConsMigration) migrateLink( // migratePathCapabilities migrates the path capabilities to ID capabilities // in all accounts of the given iterator. // It uses the source path to capability ID mapping which was constructed in migrateLinks. -func (m *CapConsMigration) migratePathCapabilities( - addressIterator AddressIterator, - reporter CapConsPathCapabilityMigrationReporter, +func (m *Migration) migratePathCapabilities( + reporter PathCapabilityMigrationReporter, ) { for { - address := addressIterator.NextAddress() + address := m.addressIterator.NextAddress() if address == common.ZeroAddress { break } @@ -255,7 +244,7 @@ func (m *CapConsMigration) migratePathCapabilities( var pathDomainStorage = common.PathDomainStorage.Identifier() -func (m *CapConsMigration) migratePathCapabilitiesInAccount(address common.Address, reporter CapConsPathCapabilityMigrationReporter) { +func (m *Migration) migratePathCapabilitiesInAccount(address common.Address, reporter PathCapabilityMigrationReporter) { storageMap := m.storage.GetStorageMap(address, pathDomainStorage, false) if storageMap == nil { @@ -290,10 +279,10 @@ func (m *CapConsMigration) migratePathCapabilitiesInAccount(address common.Addre // migratePathCapability migrates a path capability to an ID capability in the given value. // If a value is returned, the value must be updated with the replacement in the parent. // If nil is returned, the value was not updated and no operation has to be performed. -func (m *CapConsMigration) migratePathCapability( +func (m *Migration) migratePathCapability( address common.Address, value interpreter.Value, - reporter CapConsPathCapabilityMigrationReporter, + reporter PathCapabilityMigrationReporter, ) interpreter.Value { locationRange := interpreter.EmptyLocationRange @@ -313,7 +302,7 @@ func (m *CapConsMigration) migratePathCapability( break } - newCapability := interpreter.NewUnmeteredIDCapabilityValue( + newCapability := interpreter.NewUnmeteredCapabilityValue( capabilityID, oldCapability.Address, oldCapability.BorrowType, @@ -391,7 +380,10 @@ func (m *CapConsMigration) migratePathCapability( // Keys cannot be capabilities at the moment, // so this should never occur in stored data - if _, ok := key.(interpreter.CapabilityValue); ok { + switch key.(type) { + case *interpreter.CapabilityValue, + *interpreter.PathCapabilityValue: + panic(errors.NewUnreachableError()) } @@ -427,7 +419,272 @@ func (m *CapConsMigration) migratePathCapability( // as they do not contain path capabilities. return nil + + case *interpreter.CapabilityValue: + // Already migrated + return nil } panic(errors.NewUnexpectedError("unsupported value type: %T", value)) } + +func (m *Migration) migrateLinkToCapabilityController( + addressValue interpreter.AddressValue, + pathValue interpreter.PathValue, +) interpreter.UInt64Value { + + locationRange := interpreter.EmptyLocationRange + + address := addressValue.ToAddress() + + domain := pathValue.Domain.Identifier() + identifier := pathValue.Identifier + + storageMapKey := interpreter.StringStorageMapKey(identifier) + + readValue := m.interpreter.ReadStored(address, domain, storageMapKey) + if readValue == nil { + return 0 + } + + var borrowStaticType *interpreter.ReferenceStaticType + + switch readValue := readValue.(type) { + case *interpreter.CapabilityValue: + // Already migrated + return 0 + + case interpreter.PathLinkValue: + var ok bool + borrowStaticType, ok = readValue.Type.(*interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + case interpreter.AccountLinkValue: + borrowStaticType = interpreter.NewReferenceStaticType( + nil, + interpreter.FullyEntitledAccountAccess, + interpreter.PrimitiveStaticTypeAccount, + ) + + default: + panic(errors.NewUnreachableError()) + } + + borrowType, ok := m.interpreter.MustConvertStaticToSemaType(borrowStaticType).(*sema.ReferenceType) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Get target + + target, _, err := m.getPathCapabilityFinalTarget( + address, + pathValue, + // TODO: + // Use top-most type to follow link all the way to final target + &sema.ReferenceType{ + Type: sema.AnyType, + }, + ) + // TODO: skip cyclic links + if err != nil { + panic(err) + } + + // Issue appropriate capability controller + + var capabilityID interpreter.UInt64Value + + switch target := target.(type) { + case nil: + // TODO: report/warn + return 0 + + case pathCapabilityTarget: + + targetPath := interpreter.PathValue(target) + + capabilityID, _ = stdlib.IssueStorageCapabilityController( + m.interpreter, + locationRange, + m.accountIDGenerator, + address, + borrowType, + targetPath, + ) + + case accountCapabilityTarget: + + capabilityID, _ = stdlib.IssueAccountCapabilityController( + m.interpreter, + locationRange, + m.accountIDGenerator, + address, + borrowType, + ) + + default: + panic(errors.NewUnreachableError()) + } + + // Publish: overwrite link value with capability + + capabilityValue := interpreter.NewCapabilityValue( + m.interpreter, + capabilityID, + addressValue, + borrowStaticType, + ) + + capabilityValue, ok = capabilityValue.Transfer( + m.interpreter, + locationRange, + atree.Address(address), + true, + nil, + nil, + ).(*interpreter.CapabilityValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + m.interpreter.WriteStored( + address, + domain, + storageMapKey, + capabilityValue, + ) + + return capabilityID +} + +var authAccountReferenceStaticType = interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.PrimitiveStaticTypeAuthAccount, +) + +func (m *Migration) getPathCapabilityFinalTarget( + address common.Address, + path interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, +) ( + target capabilityTarget, + authorization interpreter.Authorization, + err error, +) { + + seenPaths := map[interpreter.PathValue]struct{}{} + paths := []interpreter.PathValue{path} + + for { + // Detect cyclic links + + if _, ok := seenPaths[path]; ok { + return nil, + interpreter.UnauthorizedAccess, + CyclicLinkError{ + Address: address, + Paths: paths, + } + } else { + seenPaths[path] = struct{}{} + } + + domain := path.Domain.Identifier() + identifier := path.Identifier + + storageMapKey := interpreter.StringStorageMapKey(identifier) + + switch path.Domain { + case common.PathDomainStorage: + + return pathCapabilityTarget(path), + interpreter.ConvertSemaAccessToStaticAuthorization( + m.interpreter, + wantedBorrowType.Authorization, + ), + nil + + case common.PathDomainPublic, + common.PathDomainPrivate: + + value := m.interpreter.ReadStored(address, domain, storageMapKey) + if value == nil { + return nil, interpreter.UnauthorizedAccess, nil + } + + switch value := value.(type) { + case interpreter.PathLinkValue: + allowedType := m.interpreter.MustConvertStaticToSemaType(value.Type) + + if !sema.IsSubType(allowedType, wantedBorrowType) { + return nil, interpreter.UnauthorizedAccess, nil + } + + targetPath := value.TargetPath + paths = append(paths, targetPath) + path = targetPath + + case interpreter.AccountLinkValue: + if !m.interpreter.IsSubTypeOfSemaType( + authAccountReferenceStaticType, + wantedBorrowType, + ) { + return nil, interpreter.UnauthorizedAccess, nil + } + + return accountCapabilityTarget(address), + interpreter.UnauthorizedAccess, + nil + + case *interpreter.CapabilityValue: + + // For backwards-compatibility, follow ID capability values + // which are published in the public or private domain + + capabilityBorrowType, ok := + m.interpreter.MustConvertStaticToSemaType(value.BorrowType).(*sema.ReferenceType) + if !ok { + panic(errors.NewUnreachableError()) + } + + reference := m.interpreter.SharedState.Config.CapabilityBorrowHandler( + m.interpreter, + interpreter.EmptyLocationRange, + value.Address, + value.ID, + wantedBorrowType, + capabilityBorrowType, + ) + if reference == nil { + return nil, interpreter.UnauthorizedAccess, nil + } + + switch reference := reference.(type) { + case *interpreter.StorageReferenceValue: + address = reference.TargetStorageAddress + targetPath := reference.TargetPath + paths = append(paths, targetPath) + path = targetPath + + case *interpreter.EphemeralReferenceValue: + accountValue := reference.Value.(*interpreter.SimpleCompositeValue) + address := accountValue.Fields[sema.AccountTypeAddressFieldName].(interpreter.AddressValue) + + return accountCapabilityTarget(address), + interpreter.UnauthorizedAccess, + nil + + default: + return nil, interpreter.UnauthorizedAccess, nil + } + + default: + panic(errors.NewUnreachableError()) + } + } + } +} diff --git a/migration/capcons/capcons_test.go b/migration/capcons/capcons_test.go new file mode 100644 index 0000000000..4041681164 --- /dev/null +++ b/migration/capcons/capcons_test.go @@ -0,0 +1,455 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" +) + +type testAccountIDGenerator struct { + ids map[common.Address]uint64 +} + +func (g *testAccountIDGenerator) GenerateAccountID(address common.Address) (uint64, error) { + if g.ids == nil { + g.ids = make(map[common.Address]uint64) + } + g.ids[address]++ + return g.ids[address], nil +} + +type testCapConsLinkMigration struct { + addressPath interpreter.AddressPath + capabilityID interpreter.UInt64Value +} + +type testCapConsPathCapabilityMigration struct { + address common.Address + addressPath interpreter.AddressPath +} + +type testCapConsMissingCapabilityID struct { + address common.Address + addressPath interpreter.AddressPath +} + +type testCapConsMigrationReporter struct { + linkMigrations []testCapConsLinkMigration + pathCapabilityMigrations []testCapConsPathCapabilityMigration + missingCapabilityIDs []testCapConsMissingCapabilityID +} + +var _ MigrationReporter = &testCapConsMigrationReporter{} + +func (t *testCapConsMigrationReporter) MigratedLink( + addressPath interpreter.AddressPath, + capabilityID interpreter.UInt64Value, +) { + t.linkMigrations = append( + t.linkMigrations, + testCapConsLinkMigration{ + addressPath: addressPath, + capabilityID: capabilityID, + }, + ) +} + +func (t *testCapConsMigrationReporter) MigratedPathCapability( + address common.Address, + addressPath interpreter.AddressPath, +) { + t.pathCapabilityMigrations = append( + t.pathCapabilityMigrations, + testCapConsPathCapabilityMigration{ + address: address, + addressPath: addressPath, + }, + ) +} + +func (t *testCapConsMigrationReporter) MissingCapabilityID( + address common.Address, + addressPath interpreter.AddressPath, +) { + t.missingCapabilityIDs = append( + t.missingCapabilityIDs, + testCapConsMissingCapabilityID{ + address: address, + addressPath: addressPath, + }, + ) +} + +//func TestCapConsMigration(t *testing.T) { +// +// t.Parallel() +// +// address := common.MustBytesToAddress([]byte{0x1}) +// +// test := func(setupFunction, checkFunction string) { +// +// rt := runtime.newTestInterpreterRuntime() +// +// // language=cadence +// contract := ` +// pub contract Test { +// +// pub resource R {} +// +// pub struct CapabilityWrapper { +// +// pub let capability: Capability +// +// init(_ capability: Capability) { +// self.capability = capability +// } +// } +// +// pub struct CapabilityOptionalWrapper { +// +// pub let capability: Capability? +// +// init(_ capability: Capability?) { +// self.capability = capability +// } +// } +// +// pub struct CapabilityArrayWrapper { +// +// pub let capabilities: [Capability] +// +// init(_ capabilities: [Capability]) { +// self.capabilities = capabilities +// } +// } +// +// pub struct CapabilityDictionaryWrapper { +// +// pub let capabilities: {Int: Capability} +// +// init(_ capabilities: {Int: Capability}) { +// self.capabilities = capabilities +// } +// } +// +// pub fun saveExisting(_ wrapper: ((Capability): AnyStruct)) { +// self.account.link<&Test.R>(/public/r, target: /private/r) +// self.account.link<&Test.R>(/private/r, target: /storage/r) +// +// let publicCap = self.account.getCapability<&Test.R>(/public/r) +// let privateCap = self.account.getCapability<&Test.R>(/private/r) +// +// self.account.save(wrapper(publicCap), to: /storage/publicCapValue) +// self.account.save(wrapper(privateCap), to: /storage/privateCapValue) +// } +// +// pub fun checkMigratedValues(getter: ((AnyStruct): Capability)) { +// self.account.save(<-create R(), to: /storage/r) +// self.checkMigratedValue(capValuePath: /storage/publicCapValue, getter: getter) +// self.checkMigratedValue(capValuePath: /storage/privateCapValue, getter: getter) +// } +// +// priv fun checkMigratedValue(capValuePath: StoragePath, getter: ((AnyStruct): Capability)) { +// let capValue = self.account.copy(from: capValuePath)! +// let cap = getter(capValue) +// assert(cap.id != 0) +// let ref = cap.borrow<&R>()! +// } +// } +// ` +// +// accountCodes := map[runtime.Location][]byte{} +// var events []cadence.Event +// var loggedMessages []string +// +// runtimeInterface := &runtime.testRuntimeInterface{ +// getCode: func(location runtime.Location) (bytes []byte, err error) { +// return accountCodes[location], nil +// }, +// storage: runtime.newTestLedger(nil, nil), +// getSigningAccounts: func() ([]runtime.Address, error) { +// return []runtime.Address{address}, nil +// }, +// resolveLocation: runtime.singleIdentifierLocationResolver(t), +// getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { +// return accountCodes[location], nil +// }, +// updateAccountContractCode: func(location common.AddressLocation, code []byte) error { +// accountCodes[location] = code +// return nil +// }, +// emitEvent: func(event cadence.Event) error { +// events = append(events, event) +// return nil +// }, +// log: func(message string) { +// loggedMessages = append(loggedMessages, message) +// }, +// } +// +// nextTransactionLocation := runtime.newTransactionLocationGenerator() +// +// // Deploy contract +// +// deployTransaction := DeploymentTransaction("Test", []byte(contract)) +// err := rt.ExecuteTransaction( +// runtime.Script{ +// Source: deployTransaction, +// }, +// runtime.Context{ +// Interface: runtimeInterface, +// Location: nextTransactionLocation(), +// }, +// ) +// require.NoError(t, err) +// +// // Setup +// +// setupTx := fmt.Sprintf( +// // language=cadence +// ` +// import Test from 0x1 +// +// transaction { +// prepare(signer: AuthAccount) { +// Test.saveExisting(%s) +// } +// } +// `, +// setupFunction, +// ) +// err = rt.ExecuteTransaction( +// runtime.Script{ +// Source: []byte(setupTx), +// }, +// runtime.Context{ +// Interface: runtimeInterface, +// Location: nextTransactionLocation(), +// }, +// ) +// require.NoError(t, err) +// +// // Migrate +// +// migrator, err := NewCapConsMigration( +// rt, +// runtime.Context{ +// Interface: runtimeInterface, +// }, +// ) +// require.NoError(t, err) +// +// reporter := &testCapConsMigrationReporter{} +// +// err = migrator.Migrate( +// &AddressSliceIterator{ +// Addresses: []common.Address{ +// address, +// }, +// }, +// &testAccountIDGenerator{}, +// reporter, +// ) +// require.NoError(t, err) +// +// // Check migrated links +// +// require.Equal(t, +// []testCapConsLinkMigration{ +// { +// addressPath: interpreter.AddressPath{ +// Address: address, +// Path: interpreter.NewUnmeteredPathValue( +// common.PathDomainPublic, +// "r", +// ), +// }, +// capabilityID: 1, +// }, +// { +// addressPath: interpreter.AddressPath{ +// Address: address, +// Path: interpreter.NewUnmeteredPathValue( +// common.PathDomainPrivate, +// "r", +// ), +// }, +// capabilityID: 2, +// }, +// }, +// reporter.linkMigrations, +// ) +// +// // Check migrated capabilities +// +// require.Equal(t, +// []testCapConsPathCapabilityMigration{ +// { +// address: address, +// addressPath: interpreter.AddressPath{ +// Address: address, +// Path: interpreter.NewUnmeteredPathValue( +// common.PathDomainPrivate, +// "r", +// ), +// }, +// }, +// { +// address: address, +// addressPath: interpreter.AddressPath{ +// Address: address, +// Path: interpreter.NewUnmeteredPathValue( +// common.PathDomainPublic, +// "r", +// ), +// }, +// }, +// }, +// reporter.pathCapabilityMigrations, +// ) +// +// // Check +// +// checkScript := fmt.Sprintf( +// // language=cadence +// ` +// import Test from 0x1 +// +// pub fun main() { +// Test.checkMigratedValues(getter: %s) +// } +// `, +// checkFunction, +// ) +// _, err = rt.ExecuteScript( +// runtime.Script{ +// Source: []byte(checkScript), +// }, +// runtime.Context{ +// Interface: runtimeInterface, +// Location: common.ScriptLocation{}, +// }, +// ) +// require.NoError(t, err) +// } +// +// t.Run("directly", func(t *testing.T) { +// t.Parallel() +// +// test( +// // language=cadence +// ` +// fun (cap: Capability): AnyStruct { +// return cap +// } +// `, +// // language=cadence +// ` +// fun (value: AnyStruct): Capability { +// return value as! Capability +// } +// `, +// ) +// }) +// +// t.Run("composite", func(t *testing.T) { +// t.Parallel() +// +// test( +// // language=cadence +// ` +// fun (cap: Capability): AnyStruct { +// return Test.CapabilityWrapper(cap) +// } +// `, +// // language=cadence +// ` +// fun (value: AnyStruct): Capability { +// let wrapper = value as! Test.CapabilityWrapper +// return wrapper.capability +// } +// `, +// ) +// }) +// +// t.Run("optional", func(t *testing.T) { +// t.Parallel() +// +// test( +// // language=cadence +// ` +// fun (cap: Capability): AnyStruct { +// return Test.CapabilityOptionalWrapper(cap) +// } +// `, +// // language=cadence +// ` +// fun (value: AnyStruct): Capability { +// let wrapper = value as! Test.CapabilityOptionalWrapper +// return wrapper.capability! +// } +// `, +// ) +// }) +// +// t.Run("array", func(t *testing.T) { +// t.Parallel() +// +// test( +// // language=cadence +// ` +// fun (cap: Capability): AnyStruct { +// return Test.CapabilityArrayWrapper([cap]) +// } +// `, +// // language=cadence +// ` +// fun (value: AnyStruct): Capability { +// let wrapper = value as! Test.CapabilityArrayWrapper +// return wrapper.capabilities[0] +// } +// `, +// ) +// }) +// +// t.Run("dictionary, value", func(t *testing.T) { +// t.Parallel() +// +// test( +// // language=cadence +// ` +// fun (cap: Capability): AnyStruct { +// return Test.CapabilityDictionaryWrapper({2: cap}) +// } +// `, +// // language=cadence +// ` +// fun (value: AnyStruct): Capability { +// let wrapper = value as! Test.CapabilityDictionaryWrapper +// return wrapper.capabilities[2]! +// } +// `, +// ) +// }) +// +// // TODO: add more cases +// // TODO: test non existing +// +//} diff --git a/migration/capcons/error.go b/migration/capcons/error.go new file mode 100644 index 0000000000..4589ebb2ed --- /dev/null +++ b/migration/capcons/error.go @@ -0,0 +1,55 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + "fmt" + "strings" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" +) + +// CyclicLinkError +type CyclicLinkError struct { + Paths []interpreter.PathValue + Address common.Address +} + +var _ errors.UserError = CyclicLinkError{} + +func (CyclicLinkError) IsUserError() {} + +func (e CyclicLinkError) Error() string { + var builder strings.Builder + for i, path := range e.Paths { + if i > 0 { + builder.WriteString(" -> ") + } + builder.WriteString(path.String()) + } + paths := builder.String() + + return fmt.Sprintf( + "cyclic link in account %s: %s", + e.Address.ShortHexWithPrefix(), + paths, + ) +} diff --git a/migration/capcons/target.go b/migration/capcons/target.go new file mode 100644 index 0000000000..fa2f57de38 --- /dev/null +++ b/migration/capcons/target.go @@ -0,0 +1,40 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" +) + +type capabilityTarget interface { + isCapabilityTarget() +} + +type pathCapabilityTarget interpreter.PathValue + +func (pathCapabilityTarget) isCapabilityTarget() {} + +var _ capabilityTarget = pathCapabilityTarget{} + +type accountCapabilityTarget common.Address + +var _ capabilityTarget = accountCapabilityTarget{} + +func (accountCapabilityTarget) isCapabilityTarget() {} diff --git a/runtime/capcons_test.go b/runtime/capcons_test.go deleted file mode 100644 index 39d91b35e6..0000000000 --- a/runtime/capcons_test.go +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Cadence - The resource-oriented smart contract programming language - * - * Copyright Dapper Labs, Inc. - * - * 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 runtime - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/onflow/cadence" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - . "github.com/onflow/cadence/runtime/tests/utils" -) - -type testAccountIDGenerator struct { - ids map[common.Address]uint64 -} - -func (g *testAccountIDGenerator) GenerateAccountID(address common.Address) (uint64, error) { - if g.ids == nil { - g.ids = make(map[common.Address]uint64) - } - g.ids[address]++ - return g.ids[address], nil -} - -type testCapConsLinkMigration struct { - addressPath interpreter.AddressPath - capabilityID interpreter.UInt64Value -} - -type testCapConsPathCapabilityMigration struct { - address common.Address - addressPath interpreter.AddressPath -} - -type testCapConsMissingCapabilityID struct { - address common.Address - addressPath interpreter.AddressPath -} - -type testCapConsMigrationReporter struct { - linkMigrations []testCapConsLinkMigration - pathCapabilityMigrations []testCapConsPathCapabilityMigration - missingCapabilityIDs []testCapConsMissingCapabilityID -} - -var _ CapConsMigrationReporter = &testCapConsMigrationReporter{} - -func (t *testCapConsMigrationReporter) MigratedLink( - addressPath interpreter.AddressPath, - capabilityID interpreter.UInt64Value, -) { - t.linkMigrations = append( - t.linkMigrations, - testCapConsLinkMigration{ - addressPath: addressPath, - capabilityID: capabilityID, - }, - ) -} - -func (t *testCapConsMigrationReporter) MigratedPathCapability( - address common.Address, - addressPath interpreter.AddressPath, -) { - t.pathCapabilityMigrations = append( - t.pathCapabilityMigrations, - testCapConsPathCapabilityMigration{ - address: address, - addressPath: addressPath, - }, - ) -} - -func (t *testCapConsMigrationReporter) MissingCapabilityID( - address common.Address, - addressPath interpreter.AddressPath, -) { - t.missingCapabilityIDs = append( - t.missingCapabilityIDs, - testCapConsMissingCapabilityID{ - address: address, - addressPath: addressPath, - }, - ) -} - -func TestCapConsMigration(t *testing.T) { - - t.Parallel() - - address := common.MustBytesToAddress([]byte{0x1}) - - test := func(setupFunction, checkFunction string) { - - rt := newTestInterpreterRuntime() - - // language=cadence - contract := ` - pub contract Test { - - pub resource R {} - - pub struct CapabilityWrapper { - - pub let capability: Capability - - init(_ capability: Capability) { - self.capability = capability - } - } - - pub struct CapabilityOptionalWrapper { - - pub let capability: Capability? - - init(_ capability: Capability?) { - self.capability = capability - } - } - - pub struct CapabilityArrayWrapper { - - pub let capabilities: [Capability] - - init(_ capabilities: [Capability]) { - self.capabilities = capabilities - } - } - - pub struct CapabilityDictionaryWrapper { - - pub let capabilities: {Int: Capability} - - init(_ capabilities: {Int: Capability}) { - self.capabilities = capabilities - } - } - - pub fun saveExisting(_ wrapper: ((Capability): AnyStruct)) { - self.account.link<&Test.R>(/public/r, target: /private/r) - self.account.link<&Test.R>(/private/r, target: /storage/r) - - let publicCap = self.account.getCapability<&Test.R>(/public/r) - let privateCap = self.account.getCapability<&Test.R>(/private/r) - - self.account.save(wrapper(publicCap), to: /storage/publicCapValue) - self.account.save(wrapper(privateCap), to: /storage/privateCapValue) - } - - pub fun checkMigratedValues(getter: ((AnyStruct): Capability)) { - self.account.save(<-create R(), to: /storage/r) - self.checkMigratedValue(capValuePath: /storage/publicCapValue, getter: getter) - self.checkMigratedValue(capValuePath: /storage/privateCapValue, getter: getter) - } - - priv fun checkMigratedValue(capValuePath: StoragePath, getter: ((AnyStruct): Capability)) { - let capValue = self.account.copy(from: capValuePath)! - let cap = getter(capValue) - assert(cap.id != 0) - let ref = cap.borrow<&R>()! - } - } - ` - - accountCodes := map[Location][]byte{} - var events []cadence.Event - var loggedMessages []string - - runtimeInterface := &testRuntimeInterface{ - getCode: func(location Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { - return []Address{address}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - updateAccountContractCode: func(location common.AddressLocation, code []byte) error { - accountCodes[location] = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - log: func(message string) { - loggedMessages = append(loggedMessages, message) - }, - } - - nextTransactionLocation := newTransactionLocationGenerator() - - // Deploy contract - - deployTransaction := DeploymentTransaction("Test", []byte(contract)) - err := rt.ExecuteTransaction( - Script{ - Source: deployTransaction, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Setup - - setupTx := fmt.Sprintf( - // language=cadence - ` - import Test from 0x1 - - transaction { - prepare(signer: AuthAccount) { - Test.saveExisting(%s) - } - } - `, - setupFunction, - ) - err = rt.ExecuteTransaction( - Script{ - Source: []byte(setupTx), - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Migrate - - migrator, err := NewCapConsMigration( - rt, - Context{ - Interface: runtimeInterface, - }, - ) - require.NoError(t, err) - - reporter := &testCapConsMigrationReporter{} - - err = migrator.Migrate( - &AddressSliceIterator{ - Addresses: []common.Address{ - address, - }, - }, - &testAccountIDGenerator{}, - reporter, - ) - require.NoError(t, err) - - // Check migrated links - - require.Equal(t, - []testCapConsLinkMigration{ - { - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPublic, - "r", - ), - }, - capabilityID: 1, - }, - { - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPrivate, - "r", - ), - }, - capabilityID: 2, - }, - }, - reporter.linkMigrations, - ) - - // Check migrated capabilities - - require.Equal(t, - []testCapConsPathCapabilityMigration{ - { - address: address, - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPrivate, - "r", - ), - }, - }, - { - address: address, - addressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPublic, - "r", - ), - }, - }, - }, - reporter.pathCapabilityMigrations, - ) - - // Check - - checkScript := fmt.Sprintf( - // language=cadence - ` - import Test from 0x1 - - pub fun main() { - Test.checkMigratedValues(getter: %s) - } - `, - checkFunction, - ) - _, err = rt.ExecuteScript( - Script{ - Source: []byte(checkScript), - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - }, - ) - require.NoError(t, err) - } - - t.Run("directly", func(t *testing.T) { - t.Parallel() - - test( - // language=cadence - ` - fun (cap: Capability): AnyStruct { - return cap - } - `, - // language=cadence - ` - fun (value: AnyStruct): Capability { - return value as! Capability - } - `, - ) - }) - - t.Run("composite", func(t *testing.T) { - t.Parallel() - - test( - // language=cadence - ` - fun (cap: Capability): AnyStruct { - return Test.CapabilityWrapper(cap) - } - `, - // language=cadence - ` - fun (value: AnyStruct): Capability { - let wrapper = value as! Test.CapabilityWrapper - return wrapper.capability - } - `, - ) - }) - - t.Run("optional", func(t *testing.T) { - t.Parallel() - - test( - // language=cadence - ` - fun (cap: Capability): AnyStruct { - return Test.CapabilityOptionalWrapper(cap) - } - `, - // language=cadence - ` - fun (value: AnyStruct): Capability { - let wrapper = value as! Test.CapabilityOptionalWrapper - return wrapper.capability! - } - `, - ) - }) - - t.Run("array", func(t *testing.T) { - t.Parallel() - - test( - // language=cadence - ` - fun (cap: Capability): AnyStruct { - return Test.CapabilityArrayWrapper([cap]) - } - `, - // language=cadence - ` - fun (value: AnyStruct): Capability { - let wrapper = value as! Test.CapabilityArrayWrapper - return wrapper.capabilities[0] - } - `, - ) - }) - - t.Run("dictionary, value", func(t *testing.T) { - t.Parallel() - - test( - // language=cadence - ` - fun (cap: Capability): AnyStruct { - return Test.CapabilityDictionaryWrapper({2: cap}) - } - `, - // language=cadence - ` - fun (value: AnyStruct): Capability { - let wrapper = value as! Test.CapabilityDictionaryWrapper - return wrapper.capabilities[2]! - } - `, - ) - }) - - // TODO: add more cases - // TODO: test non existing - -} diff --git a/runtime/interpreter/statictype.go b/runtime/interpreter/statictype.go index 975ecbd29d..b4b4bc7ce4 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -706,9 +706,9 @@ func (a EntitlementMapAuthorization) Equal(other Authorization) bool { // ReferenceStaticType type ReferenceStaticType struct { + Authorization Authorization // ReferencedType is type of the referenced value (the type of the target) ReferencedType StaticType - Authorization Authorization } var _ StaticType = &ReferenceStaticType{} diff --git a/runtime/interpreter/value_link.go b/runtime/interpreter/value_link.go new file mode 100644 index 0000000000..2e90fa0002 --- /dev/null +++ b/runtime/interpreter/value_link.go @@ -0,0 +1,239 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" +) + +// Deprecated: LinkValue +type LinkValue interface { + Value + isLinkValue() +} + +// Deprecated: PathLinkValue +type PathLinkValue struct { + Type StaticType + TargetPath PathValue +} + +var _ Value = PathLinkValue{} +var _ atree.Value = PathLinkValue{} +var _ EquatableValue = PathLinkValue{} +var _ LinkValue = PathLinkValue{} + +func (PathLinkValue) isValue() {} + +func (PathLinkValue) isLinkValue() {} + +func (v PathLinkValue) Accept(_ *Interpreter, _ Visitor) { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) Walk(_ *Interpreter, _ func(Value)) { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) StaticType(_ *Interpreter) StaticType { + panic(errors.NewUnreachableError()) +} + +func (PathLinkValue) IsImportable(_ *Interpreter) bool { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) String() string { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) RecursiveString(_ SeenReferences) string { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) MeteredString(_ common.MemoryGauge, _ SeenReferences) string { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) ConformsToStaticType( + _ *Interpreter, + _ LocationRange, + _ TypeConformanceResults, +) bool { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) Equal(_ *Interpreter, _ LocationRange, _ Value) bool { + panic(errors.NewUnreachableError()) +} + +func (PathLinkValue) IsStorable() bool { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) Storable(_ atree.SlabStorage, _ atree.Address, _ uint64) (atree.Storable, error) { + panic(errors.NewUnreachableError()) +} + +func (PathLinkValue) NeedsStoreTo(_ atree.Address) bool { + panic(errors.NewUnreachableError()) +} + +func (PathLinkValue) IsResourceKinded(_ *Interpreter) bool { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) Transfer( + interpreter *Interpreter, + _ LocationRange, + _ atree.Address, + remove bool, + storable atree.Storable, + _ map[atree.StorageID]struct{}, +) Value { + if remove { + interpreter.RemoveReferencedSlab(storable) + } + return v +} + +func (v PathLinkValue) Clone(_ *Interpreter) Value { + panic(errors.NewUnreachableError()) +} + +func (PathLinkValue) DeepRemove(_ *Interpreter) { + // NO-OP +} + +func (v PathLinkValue) ByteSize() uint32 { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { + panic(errors.NewUnreachableError()) +} + +func (v PathLinkValue) ChildStorables() []atree.Storable { + panic(errors.NewUnreachableError()) +} + +// Deprecated: AccountLinkValue +type AccountLinkValue struct{} + +var _ Value = AccountLinkValue{} +var _ atree.Value = AccountLinkValue{} +var _ EquatableValue = AccountLinkValue{} +var _ LinkValue = AccountLinkValue{} + +func (AccountLinkValue) isValue() {} + +func (AccountLinkValue) isLinkValue() {} + +func (v AccountLinkValue) Accept(_ *Interpreter, _ Visitor) { + panic(errors.NewUnreachableError()) +} + +func (AccountLinkValue) Walk(_ *Interpreter, _ func(Value)) { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) StaticType(_ *Interpreter) StaticType { + panic(errors.NewUnreachableError()) +} + +func (AccountLinkValue) IsImportable(_ *Interpreter) bool { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) String() string { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) RecursiveString(_ SeenReferences) string { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) MeteredString(_ common.MemoryGauge, _ SeenReferences) string { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) ConformsToStaticType( + _ *Interpreter, + _ LocationRange, + _ TypeConformanceResults, +) bool { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) Equal(_ *Interpreter, _ LocationRange, _ Value) bool { + panic(errors.NewUnreachableError()) +} + +func (AccountLinkValue) IsStorable() bool { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) Storable(_ atree.SlabStorage, _ atree.Address, _ uint64) (atree.Storable, error) { + panic(errors.NewUnreachableError()) +} + +func (AccountLinkValue) NeedsStoreTo(_ atree.Address) bool { + panic(errors.NewUnreachableError()) +} + +func (AccountLinkValue) IsResourceKinded(_ *Interpreter) bool { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) Transfer( + interpreter *Interpreter, + _ LocationRange, + _ atree.Address, + remove bool, + storable atree.Storable, + _ map[atree.StorageID]struct{}, +) Value { + if remove { + interpreter.RemoveReferencedSlab(storable) + } + return v +} + +func (AccountLinkValue) Clone(_ *Interpreter) Value { + return AccountLinkValue{} +} + +func (AccountLinkValue) DeepRemove(_ *Interpreter) { + // NO-OP +} + +func (v AccountLinkValue) ByteSize() uint32 { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { + panic(errors.NewUnreachableError()) +} + +func (v AccountLinkValue) ChildStorables() []atree.Storable { + panic(errors.NewUnreachableError()) +} diff --git a/runtime/interpreter/value_pathcapability.go b/runtime/interpreter/value_pathcapability.go new file mode 100644 index 0000000000..2ad12feefa --- /dev/null +++ b/runtime/interpreter/value_pathcapability.go @@ -0,0 +1,161 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" +) + +// Deprecated: PathCapabilityValue +type PathCapabilityValue struct { + BorrowType StaticType + Path PathValue + Address AddressValue +} + +var _ Value = &PathCapabilityValue{} +var _ atree.Storable = &PathCapabilityValue{} +var _ EquatableValue = &PathCapabilityValue{} +var _ MemberAccessibleValue = &PathCapabilityValue{} + +func (*PathCapabilityValue) isValue() {} + +func (*PathCapabilityValue) isCapabilityValue() {} + +func (v *PathCapabilityValue) Accept(_ *Interpreter, _ Visitor) { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) Walk(_ *Interpreter, _ func(Value)) { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) StaticType(_ *Interpreter) StaticType { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) IsImportable(_ *Interpreter) bool { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) String() string { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) RecursiveString(_ SeenReferences) string { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) MeteredString(_ common.MemoryGauge, _ SeenReferences) string { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) GetMember(_ *Interpreter, _ LocationRange, _ string) Value { + panic(errors.NewUnreachableError()) +} + +func (*PathCapabilityValue) RemoveMember(_ *Interpreter, _ LocationRange, _ string) Value { + panic(errors.NewUnreachableError()) +} + +func (*PathCapabilityValue) SetMember(_ *Interpreter, _ LocationRange, _ string, _ Value) bool { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) ConformsToStaticType( + _ *Interpreter, + _ LocationRange, + _ TypeConformanceResults, +) bool { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) Equal(interpreter *Interpreter, locationRange LocationRange, other Value) bool { + panic(errors.NewUnreachableError()) +} + +func (*PathCapabilityValue) IsStorable() bool { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) Storable( + _ atree.SlabStorage, + _ atree.Address, + _ uint64, +) (atree.Storable, error) { + panic(errors.NewUnreachableError()) +} + +func (*PathCapabilityValue) NeedsStoreTo(_ atree.Address) bool { + panic(errors.NewUnreachableError()) +} + +func (*PathCapabilityValue) IsResourceKinded(_ *Interpreter) bool { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) Transfer( + interpreter *Interpreter, + _ LocationRange, + _ atree.Address, + remove bool, + storable atree.Storable, + _ map[atree.StorageID]struct{}, +) Value { + if remove { + v.DeepRemove(interpreter) + interpreter.RemoveReferencedSlab(storable) + } + return v +} + +func (v *PathCapabilityValue) Clone(_ *Interpreter) Value { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) DeepRemove(interpreter *Interpreter) { + v.Address.DeepRemove(interpreter) + v.Path.DeepRemove(interpreter) +} + +func (v *PathCapabilityValue) ByteSize() uint32 { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) ChildStorables() []atree.Storable { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) Encode(_ *atree.Encoder) error { + panic(errors.NewUnreachableError()) +} + +func (v *PathCapabilityValue) AddressPath() AddressPath { + return AddressPath{ + Address: common.Address(v.Address), + Path: v.Path, + } +} diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index f01853d789..4bdff82d54 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -2420,7 +2420,7 @@ func newAccountStorageCapabilitiesIssueFunction( panic(errors.NewUnreachableError()) } - capabilityIDValue, borrowStaticType := issueStorageCapabilityController( + capabilityIDValue, borrowStaticType := IssueStorageCapabilityController( inter, locationRange, idGenerator, @@ -2439,7 +2439,7 @@ func newAccountStorageCapabilitiesIssueFunction( ) } -func issueStorageCapabilityController( +func IssueStorageCapabilityController( inter *interpreter.Interpreter, locationRange interpreter.LocationRange, idGenerator AccountIDGenerator, From 0d43b6f0426773b4447ba62c97968befc5a6782c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 30 Oct 2023 14:59:06 -0700 Subject: [PATCH 09/51] add new return value --- migration/capcons/capcons.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/migration/capcons/capcons.go b/migration/capcons/capcons.go index 493e089cd3..21a01caf66 100644 --- a/migration/capcons/capcons.go +++ b/migration/capcons/capcons.go @@ -319,7 +319,7 @@ func (m *Migration) migratePathCapability( // Migrate composite's fields - composite.ForEachField(nil, func(fieldName string, fieldValue interpreter.Value) { + composite.ForEachField(nil, func(fieldName string, fieldValue interpreter.Value) (resume bool) { newFieldValue := m.migratePathCapability(address, fieldValue, reporter) if newFieldValue != nil { composite.SetMember( @@ -329,6 +329,9 @@ func (m *Migration) migratePathCapability( newFieldValue, ) } + + // continue iteration + return true }) // The composite itself does not have to be replaced From 73351836f6bf6aca1ca7a14982c040737fc0f354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 30 Oct 2023 15:17:58 -0700 Subject: [PATCH 10/51] update tests to Cadence 1.0 --- migration/capcons/capcons.go | 3 +- migration/capcons/capcons_test.go | 738 ++++++++++++++++-------------- 2 files changed, 383 insertions(+), 358 deletions(-) diff --git a/migration/capcons/capcons.go b/migration/capcons/capcons.go index 21a01caf66..3b84c0d72c 100644 --- a/migration/capcons/capcons.go +++ b/migration/capcons/capcons.go @@ -491,7 +491,7 @@ func (m *Migration) migrateLinkToCapabilityController( Type: sema.AnyType, }, ) - // TODO: skip cyclic links + // TODO: skip cyclic links instead of panic-ing if err != nil { panic(err) } @@ -519,7 +519,6 @@ func (m *Migration) migrateLinkToCapabilityController( ) case accountCapabilityTarget: - capabilityID, _ = stdlib.IssueAccountCapabilityController( m.interpreter, locationRange, diff --git a/migration/capcons/capcons_test.go b/migration/capcons/capcons_test.go index 4041681164..e126f60975 100644 --- a/migration/capcons/capcons_test.go +++ b/migration/capcons/capcons_test.go @@ -19,8 +19,17 @@ package capcons import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + . "github.com/onflow/cadence/runtime/tests/runtime_utils" + "github.com/onflow/cadence/runtime/tests/utils" ) type testAccountIDGenerator struct { @@ -97,359 +106,376 @@ func (t *testCapConsMigrationReporter) MissingCapabilityID( ) } -//func TestCapConsMigration(t *testing.T) { -// -// t.Parallel() -// -// address := common.MustBytesToAddress([]byte{0x1}) -// -// test := func(setupFunction, checkFunction string) { -// -// rt := runtime.newTestInterpreterRuntime() -// -// // language=cadence -// contract := ` -// pub contract Test { -// -// pub resource R {} -// -// pub struct CapabilityWrapper { -// -// pub let capability: Capability -// -// init(_ capability: Capability) { -// self.capability = capability -// } -// } -// -// pub struct CapabilityOptionalWrapper { -// -// pub let capability: Capability? -// -// init(_ capability: Capability?) { -// self.capability = capability -// } -// } -// -// pub struct CapabilityArrayWrapper { -// -// pub let capabilities: [Capability] -// -// init(_ capabilities: [Capability]) { -// self.capabilities = capabilities -// } -// } -// -// pub struct CapabilityDictionaryWrapper { -// -// pub let capabilities: {Int: Capability} -// -// init(_ capabilities: {Int: Capability}) { -// self.capabilities = capabilities -// } -// } -// -// pub fun saveExisting(_ wrapper: ((Capability): AnyStruct)) { -// self.account.link<&Test.R>(/public/r, target: /private/r) -// self.account.link<&Test.R>(/private/r, target: /storage/r) -// -// let publicCap = self.account.getCapability<&Test.R>(/public/r) -// let privateCap = self.account.getCapability<&Test.R>(/private/r) -// -// self.account.save(wrapper(publicCap), to: /storage/publicCapValue) -// self.account.save(wrapper(privateCap), to: /storage/privateCapValue) -// } -// -// pub fun checkMigratedValues(getter: ((AnyStruct): Capability)) { -// self.account.save(<-create R(), to: /storage/r) -// self.checkMigratedValue(capValuePath: /storage/publicCapValue, getter: getter) -// self.checkMigratedValue(capValuePath: /storage/privateCapValue, getter: getter) -// } -// -// priv fun checkMigratedValue(capValuePath: StoragePath, getter: ((AnyStruct): Capability)) { -// let capValue = self.account.copy(from: capValuePath)! -// let cap = getter(capValue) -// assert(cap.id != 0) -// let ref = cap.borrow<&R>()! -// } -// } -// ` -// -// accountCodes := map[runtime.Location][]byte{} -// var events []cadence.Event -// var loggedMessages []string -// -// runtimeInterface := &runtime.testRuntimeInterface{ -// getCode: func(location runtime.Location) (bytes []byte, err error) { -// return accountCodes[location], nil -// }, -// storage: runtime.newTestLedger(nil, nil), -// getSigningAccounts: func() ([]runtime.Address, error) { -// return []runtime.Address{address}, nil -// }, -// resolveLocation: runtime.singleIdentifierLocationResolver(t), -// getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { -// return accountCodes[location], nil -// }, -// updateAccountContractCode: func(location common.AddressLocation, code []byte) error { -// accountCodes[location] = code -// return nil -// }, -// emitEvent: func(event cadence.Event) error { -// events = append(events, event) -// return nil -// }, -// log: func(message string) { -// loggedMessages = append(loggedMessages, message) -// }, -// } -// -// nextTransactionLocation := runtime.newTransactionLocationGenerator() -// -// // Deploy contract -// -// deployTransaction := DeploymentTransaction("Test", []byte(contract)) -// err := rt.ExecuteTransaction( -// runtime.Script{ -// Source: deployTransaction, -// }, -// runtime.Context{ -// Interface: runtimeInterface, -// Location: nextTransactionLocation(), -// }, -// ) -// require.NoError(t, err) -// -// // Setup -// -// setupTx := fmt.Sprintf( -// // language=cadence -// ` -// import Test from 0x1 -// -// transaction { -// prepare(signer: AuthAccount) { -// Test.saveExisting(%s) -// } -// } -// `, -// setupFunction, -// ) -// err = rt.ExecuteTransaction( -// runtime.Script{ -// Source: []byte(setupTx), -// }, -// runtime.Context{ -// Interface: runtimeInterface, -// Location: nextTransactionLocation(), -// }, -// ) -// require.NoError(t, err) -// -// // Migrate -// -// migrator, err := NewCapConsMigration( -// rt, -// runtime.Context{ -// Interface: runtimeInterface, -// }, -// ) -// require.NoError(t, err) -// -// reporter := &testCapConsMigrationReporter{} -// -// err = migrator.Migrate( -// &AddressSliceIterator{ -// Addresses: []common.Address{ -// address, -// }, -// }, -// &testAccountIDGenerator{}, -// reporter, -// ) -// require.NoError(t, err) -// -// // Check migrated links -// -// require.Equal(t, -// []testCapConsLinkMigration{ -// { -// addressPath: interpreter.AddressPath{ -// Address: address, -// Path: interpreter.NewUnmeteredPathValue( -// common.PathDomainPublic, -// "r", -// ), -// }, -// capabilityID: 1, -// }, -// { -// addressPath: interpreter.AddressPath{ -// Address: address, -// Path: interpreter.NewUnmeteredPathValue( -// common.PathDomainPrivate, -// "r", -// ), -// }, -// capabilityID: 2, -// }, -// }, -// reporter.linkMigrations, -// ) -// -// // Check migrated capabilities -// -// require.Equal(t, -// []testCapConsPathCapabilityMigration{ -// { -// address: address, -// addressPath: interpreter.AddressPath{ -// Address: address, -// Path: interpreter.NewUnmeteredPathValue( -// common.PathDomainPrivate, -// "r", -// ), -// }, -// }, -// { -// address: address, -// addressPath: interpreter.AddressPath{ -// Address: address, -// Path: interpreter.NewUnmeteredPathValue( -// common.PathDomainPublic, -// "r", -// ), -// }, -// }, -// }, -// reporter.pathCapabilityMigrations, -// ) -// -// // Check -// -// checkScript := fmt.Sprintf( -// // language=cadence -// ` -// import Test from 0x1 -// -// pub fun main() { -// Test.checkMigratedValues(getter: %s) -// } -// `, -// checkFunction, -// ) -// _, err = rt.ExecuteScript( -// runtime.Script{ -// Source: []byte(checkScript), -// }, -// runtime.Context{ -// Interface: runtimeInterface, -// Location: common.ScriptLocation{}, -// }, -// ) -// require.NoError(t, err) -// } -// -// t.Run("directly", func(t *testing.T) { -// t.Parallel() -// -// test( -// // language=cadence -// ` -// fun (cap: Capability): AnyStruct { -// return cap -// } -// `, -// // language=cadence -// ` -// fun (value: AnyStruct): Capability { -// return value as! Capability -// } -// `, -// ) -// }) -// -// t.Run("composite", func(t *testing.T) { -// t.Parallel() -// -// test( -// // language=cadence -// ` -// fun (cap: Capability): AnyStruct { -// return Test.CapabilityWrapper(cap) -// } -// `, -// // language=cadence -// ` -// fun (value: AnyStruct): Capability { -// let wrapper = value as! Test.CapabilityWrapper -// return wrapper.capability -// } -// `, -// ) -// }) -// -// t.Run("optional", func(t *testing.T) { -// t.Parallel() -// -// test( -// // language=cadence -// ` -// fun (cap: Capability): AnyStruct { -// return Test.CapabilityOptionalWrapper(cap) -// } -// `, -// // language=cadence -// ` -// fun (value: AnyStruct): Capability { -// let wrapper = value as! Test.CapabilityOptionalWrapper -// return wrapper.capability! -// } -// `, -// ) -// }) -// -// t.Run("array", func(t *testing.T) { -// t.Parallel() -// -// test( -// // language=cadence -// ` -// fun (cap: Capability): AnyStruct { -// return Test.CapabilityArrayWrapper([cap]) -// } -// `, -// // language=cadence -// ` -// fun (value: AnyStruct): Capability { -// let wrapper = value as! Test.CapabilityArrayWrapper -// return wrapper.capabilities[0] -// } -// `, -// ) -// }) -// -// t.Run("dictionary, value", func(t *testing.T) { -// t.Parallel() -// -// test( -// // language=cadence -// ` -// fun (cap: Capability): AnyStruct { -// return Test.CapabilityDictionaryWrapper({2: cap}) -// } -// `, -// // language=cadence -// ` -// fun (value: AnyStruct): Capability { -// let wrapper = value as! Test.CapabilityDictionaryWrapper -// return wrapper.capabilities[2]! -// } -// `, -// ) -// }) -// -// // TODO: add more cases -// // TODO: test non existing -// -//} +func TestCapConsMigration(t *testing.T) { + + t.Parallel() + + address := common.MustBytesToAddress([]byte{0x1}) + + test := func(t *testing.T, setupFunction, checkFunction string) { + + rt := NewTestInterpreterRuntime() + + // language=cadence + contract := ` + access(all) + contract Test { + + access(all) + resource R {} + + access(all) + struct CapabilityWrapper { + + access(all) + let capability: Capability + + init(_ capability: Capability) { + self.capability = capability + } + } + + access(all) + struct CapabilityOptionalWrapper { + + access(all) + let capability: Capability? + + init(_ capability: Capability?) { + self.capability = capability + } + } + + access(all) + struct CapabilityArrayWrapper { + + access(all) + let capabilities: [Capability] + + init(_ capabilities: [Capability]) { + self.capabilities = capabilities + } + } + + access(all) + struct CapabilityDictionaryWrapper { + + access(all) + let capabilities: {Int: Capability} + + init(_ capabilities: {Int: Capability}) { + self.capabilities = capabilities + } + } + + access(all) + fun saveExisting(_ wrapper: fun(Capability): AnyStruct) { + self.account.link<&Test.R>(/public/r, target: /private/r) + self.account.link<&Test.R>(/private/r, target: /storage/r) + + let publicCap = self.account.getCapability<&Test.R>(/public/r) + let privateCap = self.account.getCapability<&Test.R>(/private/r) + + self.account.save(wrapper(publicCap), to: /storage/publicCapValue) + self.account.save(wrapper(privateCap), to: /storage/privateCapValue) + } + + access(all) + fun checkMigratedValues(getter: fun(AnyStruct): Capability) { + self.account.save(<-create R(), to: /storage/r) + self.checkMigratedValue(capValuePath: /storage/publicCapValue, getter: getter) + self.checkMigratedValue(capValuePath: /storage/privateCapValue, getter: getter) + } + + access(self) + fun checkMigratedValue(capValuePath: StoragePath, getter: fun(AnyStruct): Capability) { + let capValue = self.account.copy(from: capValuePath)! + let cap = getter(capValue) + assert(cap.id != 0) + let ref = cap.borrow<&R>()! + } + } + ` + + accountCodes := map[runtime.Location][]byte{} + var events []cadence.Event + var loggedMessages []string + + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: func(location runtime.Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{address}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnProgramLog: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + // Deploy contract + + deployTransaction := utils.DeploymentTransaction("Test", []byte(contract)) + err := rt.ExecuteTransaction( + runtime.Script{ + Source: deployTransaction, + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Setup + + setupTx := fmt.Sprintf( + // language=cadence + ` + import Test from 0x1 + + transaction { + prepare(signer: AuthAccount) { + Test.saveExisting(%s) + } + } + `, + setupFunction, + ) + err = rt.ExecuteTransaction( + runtime.Script{ + Source: []byte(setupTx), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Migrate + + migrator, err := NewMigration( + rt, + runtime.Context{ + Interface: runtimeInterface, + }, + &AddressSliceIterator{ + Addresses: []common.Address{ + address, + }, + }, + &testAccountIDGenerator{}, + ) + require.NoError(t, err) + + reporter := &testCapConsMigrationReporter{} + + err = migrator.Migrate(reporter) + require.NoError(t, err) + + // Check migrated links + + require.Equal(t, + []testCapConsLinkMigration{ + { + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + "r", + ), + }, + capabilityID: 1, + }, + { + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + "r", + ), + }, + capabilityID: 2, + }, + }, + reporter.linkMigrations, + ) + + // Check migrated capabilities + + require.Equal(t, + []testCapConsPathCapabilityMigration{ + { + address: address, + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + "r", + ), + }, + }, + { + address: address, + addressPath: interpreter.AddressPath{ + Address: address, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + "r", + ), + }, + }, + }, + reporter.pathCapabilityMigrations, + ) + + // Check + + checkScript := fmt.Sprintf( + // language=cadence + ` + import Test from 0x1 + + access(all) + fun main() { + Test.checkMigratedValues(getter: %s) + } + `, + checkFunction, + ) + _, err = rt.ExecuteScript( + runtime.Script{ + Source: []byte(checkScript), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + require.NoError(t, err) + } + + t.Run("directly", func(t *testing.T) { + t.Parallel() + t.Skip() + + test(t, + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return cap + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + return value as! Capability + } + `, + ) + }) + + t.Run("composite", func(t *testing.T) { + t.Parallel() + t.Skip() + + test(t, + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityWrapper(cap) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityWrapper + return wrapper.capability + } + `, + ) + }) + + t.Run("optional", func(t *testing.T) { + t.Parallel() + t.Skip() + + test(t, + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityOptionalWrapper(cap) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityOptionalWrapper + return wrapper.capability! + } + `, + ) + }) + + t.Run("array", func(t *testing.T) { + t.Parallel() + t.Skip() + + test(t, + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityArrayWrapper([cap]) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityArrayWrapper + return wrapper.capabilities[0] + } + `, + ) + }) + + t.Run("dictionary, value", func(t *testing.T) { + t.Parallel() + t.Skip() + + test(t, + // language=cadence + ` + fun (cap: Capability): AnyStruct { + return Test.CapabilityDictionaryWrapper({2: cap}) + } + `, + // language=cadence + ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityDictionaryWrapper + return wrapper.capabilities[2]! + } + `, + ) + }) + + // TODO: add more cases + // TODO: test non existing + +} From de4771963d4887048f71d8269a81123dfe2cfd03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 30 Oct 2023 18:11:42 -0700 Subject: [PATCH 11/51] bring back path capability value, path link value, and account link value storage functionality --- runtime/interpreter/decode.go | 159 ++++++++++++++++++++ runtime/interpreter/encode.go | 9 +- runtime/interpreter/value_link.go | 85 ++++++++++- runtime/interpreter/value_pathcapability.go | 113 ++++++++++++-- 4 files changed, 340 insertions(+), 26 deletions(-) diff --git a/runtime/interpreter/decode.go b/runtime/interpreter/decode.go index acb204ea96..3b86852520 100644 --- a/runtime/interpreter/decode.go +++ b/runtime/interpreter/decode.go @@ -324,6 +324,15 @@ func (d StorableDecoder) decodeStorable() (atree.Storable, error) { case CBORTagAccountCapabilityControllerValue: storable, err = d.decodeAccountCapabilityController() + case CBORTagPathCapabilityValue: + storable, err = d.decodePathCapability() + + case CBORTagPathLinkValue: + storable, err = d.decodePathLink() + + case CBORTagAccountLinkValue: + storable, err = d.decodeAccountLink() + default: return nil, UnsupportedTagDecodingError{ Tag: num, @@ -1220,6 +1229,156 @@ func (d StorableDecoder) decodeType() (TypeValue, error) { return NewTypeValue(d.memoryGauge, staticType), nil } +// Deprecated: decodePathCapability +func (d StorableDecoder) decodePathCapability() (*PathCapabilityValue, error) { + + const expectedLength = encodedPathCapabilityValueLength + + size, err := d.decoder.DecodeArrayHead() + if err != nil { + if e, ok := err.(*cbor.WrongTypeError); ok { + return nil, errors.NewUnexpectedError( + "invalid capability encoding: expected [%d]any, got %s", + expectedLength, + e.ActualType.String(), + ) + } + return nil, err + } + + if size != expectedLength { + return nil, errors.NewUnexpectedError( + "invalid capability encoding: expected [%d]any, got [%d]any", + expectedLength, + size, + ) + } + + // address + + // Decode address at array index encodedPathCapabilityValueAddressFieldKey + var num uint64 + num, err = d.decoder.DecodeTagNumber() + if err != nil { + return nil, errors.NewUnexpectedError( + "invalid capability address: %w", + err, + ) + } + if num != CBORTagAddressValue { + return nil, errors.NewUnexpectedError( + "invalid capability address: wrong tag %d", + num, + ) + } + address, err := d.decodeAddress() + if err != nil { + return nil, errors.NewUnexpectedError( + "invalid capability address: %w", + err, + ) + } + + // path + + // Decode path at array index encodedPathCapabilityValuePathFieldKey + pathStorable, err := d.decodeStorable() + if err != nil { + return nil, errors.NewUnexpectedError("invalid capability path: %w", err) + } + pathValue, ok := pathStorable.(PathValue) + if !ok { + return nil, errors.NewUnexpectedError("invalid capability path: invalid type %T", pathValue) + } + + // Decode borrow type at array index encodedPathCapabilityValueBorrowTypeFieldKey + + // borrow type (optional, for backwards compatibility) + // Capabilities used to be untyped, i.e. they didn't have a borrow type. + // Later an optional type parameter, the borrow type, was added to it, + // which specifies as what type the capability should be borrowed. + // + // The decoding must be backwards-compatible and support both capability values + // with a borrow type and ones without + + var borrowType StaticType + + // Optional borrow type can be CBOR nil. + err = d.decoder.DecodeNil() + if _, ok := err.(*cbor.WrongTypeError); ok { + borrowType, err = d.DecodeStaticType() + } + if err != nil { + return nil, errors.NewUnexpectedError("invalid capability borrow type encoding: %w", err) + } + + return &PathCapabilityValue{ + Address: address, + Path: pathValue, + BorrowType: borrowType, + }, nil +} + +// Deprecated: decodePathLink +func (d StorableDecoder) decodePathLink() (PathLinkValue, error) { + + const expectedLength = encodedPathLinkValueLength + + size, err := d.decoder.DecodeArrayHead() + if err != nil { + if e, ok := err.(*cbor.WrongTypeError); ok { + return EmptyPathLinkValue, errors.NewUnexpectedError( + "invalid link encoding: expected [%d]any, got %s", + expectedLength, + e.ActualType.String(), + ) + } + return EmptyPathLinkValue, err + } + + if size != expectedLength { + return EmptyPathLinkValue, errors.NewUnexpectedError( + "invalid link encoding: expected [%d]any, got [%d]any", + expectedLength, + size, + ) + } + + // Decode path at array index encodedPathLinkValueTargetPathFieldKey + num, err := d.decoder.DecodeTagNumber() + if err != nil { + return EmptyPathLinkValue, errors.NewUnexpectedError("invalid link target path encoding: %w", err) + } + if num != CBORTagPathValue { + return EmptyPathLinkValue, errors.NewUnexpectedError("invalid link target path encoding: expected CBOR tag %d, got %d", CBORTagPathValue, num) + } + pathValue, err := d.decodePath() + if err != nil { + return EmptyPathLinkValue, errors.NewUnexpectedError("invalid link target path encoding: %w", err) + } + + // Decode type at array index encodedPathLinkValueTypeFieldKey + staticType, err := d.DecodeStaticType() + if err != nil { + return EmptyPathLinkValue, errors.NewUnexpectedError("invalid link type encoding: %w", err) + } + + return PathLinkValue{ + Type: staticType, + TargetPath: pathValue, + }, nil +} + +// Deprecated: decodeAccountLink +func (d StorableDecoder) decodeAccountLink() (AccountLinkValue, error) { + err := d.decoder.Skip() + if err != nil { + return AccountLinkValue{}, err + } + + return AccountLinkValue{}, nil +} + type TypeDecoder struct { decoder *cbor.StreamDecoder memoryGauge common.MemoryGauge diff --git a/runtime/interpreter/encode.go b/runtime/interpreter/encode.go index 997f6d5bf7..35c79df6cc 100644 --- a/runtime/interpreter/encode.go +++ b/runtime/interpreter/encode.go @@ -195,11 +195,14 @@ const ( // Storage CBORTagPathValue - _ // DO NOT REPLACE! used to be used for path capabilities + // Deprecated: CBORTagPathCapabilityValue + CBORTagPathCapabilityValue _ // DO NOT REPLACE! used to be used for storage references - _ // DO NOT REPLACE! used to be used for path links + // Deprecated: CBORTagPathLinkValue + CBORTagPathLinkValue CBORTagPublishedValue - _ // DO NOT REPLACE! used to be used for account links + // Deprecated: CBORTagAccountLinkValue + CBORTagAccountLinkValue CBORTagStorageCapabilityControllerValue CBORTagAccountCapabilityControllerValue CBORTagCapabilityValue diff --git a/runtime/interpreter/value_link.go b/runtime/interpreter/value_link.go index 2e90fa0002..f4b4ec8390 100644 --- a/runtime/interpreter/value_link.go +++ b/runtime/interpreter/value_link.go @@ -25,6 +25,8 @@ import ( "github.com/onflow/cadence/runtime/errors" ) +// TODO: remove once migrated + // Deprecated: LinkValue type LinkValue interface { Value @@ -37,6 +39,8 @@ type PathLinkValue struct { TargetPath PathValue } +var EmptyPathLinkValue = PathLinkValue{} + var _ Value = PathLinkValue{} var _ atree.Value = PathLinkValue{} var _ EquatableValue = PathLinkValue{} @@ -82,16 +86,22 @@ func (v PathLinkValue) ConformsToStaticType( panic(errors.NewUnreachableError()) } -func (v PathLinkValue) Equal(_ *Interpreter, _ LocationRange, _ Value) bool { - panic(errors.NewUnreachableError()) +func (v PathLinkValue) Equal(interpreter *Interpreter, locationRange LocationRange, other Value) bool { + otherLink, ok := other.(PathLinkValue) + if !ok { + return false + } + + return otherLink.TargetPath.Equal(interpreter, locationRange, v.TargetPath) && + otherLink.Type.Equal(v.Type) } func (PathLinkValue) IsStorable() bool { panic(errors.NewUnreachableError()) } -func (v PathLinkValue) Storable(_ atree.SlabStorage, _ atree.Address, _ uint64) (atree.Storable, error) { - panic(errors.NewUnreachableError()) +func (v PathLinkValue) Storable(storage atree.SlabStorage, address atree.Address, maxInlineSize uint64) (atree.Storable, error) { + return maybeLargeImmutableStorable(v, storage, address, maxInlineSize) } func (PathLinkValue) NeedsStoreTo(_ atree.Address) bool { @@ -125,15 +135,17 @@ func (PathLinkValue) DeepRemove(_ *Interpreter) { } func (v PathLinkValue) ByteSize() uint32 { - panic(errors.NewUnreachableError()) + return mustStorableSize(v) } func (v PathLinkValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { - panic(errors.NewUnreachableError()) + return v, nil } func (v PathLinkValue) ChildStorables() []atree.Storable { - panic(errors.NewUnreachableError()) + return []atree.Storable{ + v.TargetPath, + } } // Deprecated: AccountLinkValue @@ -237,3 +249,62 @@ func (v AccountLinkValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) func (v AccountLinkValue) ChildStorables() []atree.Storable { panic(errors.NewUnreachableError()) } + +// NOTE: NEVER change, only add/increment; ensure uint64 +const ( + // encodedPathLinkValueTargetPathFieldKey uint64 = 0 + // encodedPathLinkValueTypeFieldKey uint64 = 1 + + // !!! *WARNING* !!! + // + // encodedPathLinkValueLength MUST be updated when new element is added. + // It is used to verify encoded link length during decoding. + encodedPathLinkValueLength = 2 +) + +// Encode encodes PathLinkValue as +// +// cbor.Tag{ +// Number: CBORTagPathLinkValue, +// Content: []any{ +// encodedPathLinkValueTargetPathFieldKey: PathValue(v.TargetPath), +// encodedPathLinkValueTypeFieldKey: StaticType(v.Type), +// }, +// } +func (v PathLinkValue) Encode(e *atree.Encoder) error { + // Encode tag number and array head + err := e.CBOR.EncodeRawBytes([]byte{ + // tag number + 0xd8, CBORTagPathLinkValue, + // array, 2 items follow + 0x82, + }) + if err != nil { + return err + } + // Encode path at array index encodedPathLinkValueTargetPathFieldKey + err = v.TargetPath.Encode(e) + if err != nil { + return err + } + // Encode type at array index encodedPathLinkValueTypeFieldKey + return v.Type.Encode(e.CBOR) +} + +// cborAccountLinkValue represents the CBOR value: +// +// cbor.Tag{ +// Number: CBORTagAccountLinkValue, +// Content: nil +// } +var cborAccountLinkValue = []byte{ + // tag + 0xd8, CBORTagAccountLinkValue, + // null + 0xf6, +} + +// Encode writes a value of type AccountValue to the encoder +func (AccountLinkValue) Encode(e *atree.Encoder) error { + return e.CBOR.EncodeRawBytes(cborAccountLinkValue) +} diff --git a/runtime/interpreter/value_pathcapability.go b/runtime/interpreter/value_pathcapability.go index 2ad12feefa..a51a7518f0 100644 --- a/runtime/interpreter/value_pathcapability.go +++ b/runtime/interpreter/value_pathcapability.go @@ -25,6 +25,8 @@ import ( "github.com/onflow/cadence/runtime/errors" ) +// TODO: remove once migrated + // Deprecated: PathCapabilityValue type PathCapabilityValue struct { BorrowType StaticType @@ -49,8 +51,11 @@ func (v *PathCapabilityValue) Walk(_ *Interpreter, _ func(Value)) { panic(errors.NewUnreachableError()) } -func (v *PathCapabilityValue) StaticType(_ *Interpreter) StaticType { - panic(errors.NewUnreachableError()) +func (v *PathCapabilityValue) StaticType(inter *Interpreter) StaticType { + return NewCapabilityStaticType( + inter, + v.BorrowType, + ) } func (v *PathCapabilityValue) IsImportable(_ *Interpreter) bool { @@ -90,7 +95,23 @@ func (v *PathCapabilityValue) ConformsToStaticType( } func (v *PathCapabilityValue) Equal(interpreter *Interpreter, locationRange LocationRange, other Value) bool { - panic(errors.NewUnreachableError()) + otherCapability, ok := other.(*PathCapabilityValue) + if !ok { + return false + } + + // BorrowType is optional + + if v.BorrowType == nil { + if otherCapability.BorrowType != nil { + return false + } + } else if !v.BorrowType.Equal(otherCapability.BorrowType) { + return false + } + + return otherCapability.Address.Equal(interpreter, locationRange, v.Address) && + otherCapability.Path.Equal(interpreter, locationRange, v.Path) } func (*PathCapabilityValue) IsStorable() bool { @@ -98,19 +119,24 @@ func (*PathCapabilityValue) IsStorable() bool { } func (v *PathCapabilityValue) Storable( - _ atree.SlabStorage, - _ atree.Address, - _ uint64, + storage atree.SlabStorage, + address atree.Address, + maxInlineSize uint64, ) (atree.Storable, error) { - panic(errors.NewUnreachableError()) + return maybeLargeImmutableStorable( + v, + storage, + address, + maxInlineSize, + ) } func (*PathCapabilityValue) NeedsStoreTo(_ atree.Address) bool { - panic(errors.NewUnreachableError()) + return false } func (*PathCapabilityValue) IsResourceKinded(_ *Interpreter) bool { - panic(errors.NewUnreachableError()) + return false } func (v *PathCapabilityValue) Transfer( @@ -138,19 +164,18 @@ func (v *PathCapabilityValue) DeepRemove(interpreter *Interpreter) { } func (v *PathCapabilityValue) ByteSize() uint32 { - panic(errors.NewUnreachableError()) + return mustStorableSize(v) } func (v *PathCapabilityValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { - panic(errors.NewUnreachableError()) + return v, nil } func (v *PathCapabilityValue) ChildStorables() []atree.Storable { - panic(errors.NewUnreachableError()) -} - -func (v *PathCapabilityValue) Encode(_ *atree.Encoder) error { - panic(errors.NewUnreachableError()) + return []atree.Storable{ + v.Address, + v.Path, + } } func (v *PathCapabilityValue) AddressPath() AddressPath { @@ -159,3 +184,59 @@ func (v *PathCapabilityValue) AddressPath() AddressPath { Path: v.Path, } } + +// NOTE: NEVER change, only add/increment; ensure uint64 +const ( + // encodedPathCapabilityValueAddressFieldKey uint64 = 0 + // encodedPathCapabilityValuePathFieldKey uint64 = 1 + // encodedPathCapabilityValueBorrowTypeFieldKey uint64 = 2 + + // !!! *WARNING* !!! + // + // encodedPathCapabilityValueLength MUST be updated when new element is added. + // It is used to verify encoded capability length during decoding. + encodedPathCapabilityValueLength = 3 +) + +// Encode encodes PathCapabilityValue as +// +// cbor.Tag{ +// Number: CBORTagPathCapabilityValue, +// Content: []any{ +// encodedPathCapabilityValueAddressFieldKey: AddressValue(v.Address), +// encodedPathCapabilityValuePathFieldKey: PathValue(v.Path), +// encodedPathCapabilityValueBorrowTypeFieldKey: StaticType(v.BorrowType), +// }, +// } +func (v *PathCapabilityValue) Encode(e *atree.Encoder) error { + // Encode tag number and array head + err := e.CBOR.EncodeRawBytes([]byte{ + // tag number + 0xd8, CBORTagPathCapabilityValue, + // array, 3 items follow + 0x83, + }) + if err != nil { + return err + } + + // Encode address at array index encodedPathCapabilityValueAddressFieldKey + err = v.Address.Encode(e) + if err != nil { + return err + } + + // Encode path at array index encodedPathCapabilityValuePathFieldKey + err = v.Path.Encode(e) + if err != nil { + return err + } + + // Encode borrow type at array index encodedPathCapabilityValueBorrowTypeFieldKey + + if v.BorrowType == nil { + return e.CBOR.EncodeNil() + } else { + return v.BorrowType.Encode(e.CBOR) + } +} From 72d7bfc69d7d93132f905a438f39f6b03133cb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 30 Oct 2023 18:12:08 -0700 Subject: [PATCH 12/51] fix migration --- migration/capcons/capcons.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/migration/capcons/capcons.go b/migration/capcons/capcons.go index 3b84c0d72c..961a1094c2 100644 --- a/migration/capcons/capcons.go +++ b/migration/capcons/capcons.go @@ -426,9 +426,12 @@ func (m *Migration) migratePathCapability( case *interpreter.CapabilityValue: // Already migrated return nil + + default: + panic(errors.NewUnexpectedError("unsupported value type: %T", value)) } - panic(errors.NewUnexpectedError("unsupported value type: %T", value)) + return nil } func (m *Migration) migrateLinkToCapabilityController( @@ -488,7 +491,8 @@ func (m *Migration) migrateLinkToCapabilityController( // TODO: // Use top-most type to follow link all the way to final target &sema.ReferenceType{ - Type: sema.AnyType, + Authorization: sema.UnauthorizedAccess, + Type: sema.AnyType, }, ) // TODO: skip cyclic links instead of panic-ing From f9bd17806457b0851775c602c818be6aad82cef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 30 Oct 2023 18:12:48 -0700 Subject: [PATCH 13/51] bring back tests: manually create path capability values and link values to be migrated --- migration/capcons/capcons_test.go | 131 ++++++++++++++++++++++++------ 1 file changed, 107 insertions(+), 24 deletions(-) diff --git a/migration/capcons/capcons_test.go b/migration/capcons/capcons_test.go index e126f60975..9a856333b3 100644 --- a/migration/capcons/capcons_test.go +++ b/migration/capcons/capcons_test.go @@ -22,12 +22,15 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" . "github.com/onflow/cadence/runtime/tests/runtime_utils" "github.com/onflow/cadence/runtime/tests/utils" ) @@ -169,27 +172,26 @@ func TestCapConsMigration(t *testing.T) { } access(all) - fun saveExisting(_ wrapper: fun(Capability): AnyStruct) { - self.account.link<&Test.R>(/public/r, target: /private/r) - self.account.link<&Test.R>(/private/r, target: /storage/r) - - let publicCap = self.account.getCapability<&Test.R>(/public/r) - let privateCap = self.account.getCapability<&Test.R>(/private/r) - - self.account.save(wrapper(publicCap), to: /storage/publicCapValue) - self.account.save(wrapper(privateCap), to: /storage/privateCapValue) + fun saveExisting( + publicCap: Capability, + privateCap: Capability, + wrapper: fun(Capability): AnyStruct + ) { + self.account.storage.save(wrapper(publicCap), to: /storage/publicCapValue) + self.account.storage.save(wrapper(privateCap), to: /storage/privateCapValue) } access(all) fun checkMigratedValues(getter: fun(AnyStruct): Capability) { - self.account.save(<-create R(), to: /storage/r) - self.checkMigratedValue(capValuePath: /storage/publicCapValue, getter: getter) + self.account.storage.save(<-create R(), to: /storage/r) + self.checkMigratedValue( + capValuePath: /storage/publicCapValue, getter: getter) self.checkMigratedValue(capValuePath: /storage/privateCapValue, getter: getter) } access(self) fun checkMigratedValue(capValuePath: StoragePath, getter: fun(AnyStruct): Capability) { - let capValue = self.account.copy(from: capValuePath)! + let capValue = self.account.storage.copy(from: capValuePath)! let cap = getter(capValue) assert(cap.id != 0) let ref = cap.borrow<&R>()! @@ -227,6 +229,7 @@ func TestCapConsMigration(t *testing.T) { } nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() // Deploy contract @@ -244,26 +247,109 @@ func TestCapConsMigration(t *testing.T) { // Setup + setupTransactionLocation := nextTransactionLocation() + contractLocation := common.NewAddressLocation(nil, address, "Test") + + environment := runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + + // Inject old PathCapabilityValues. + // We don't have a way to create them in Cadence anymore. + // + // Equivalent to: + // let publicCap = account.getCapability<&Test.R>(/public/r) + // let privateCap = account.getCapability<&Test.R>(/private/r) + + // TODO: what about the migration of reference type authorized flag? + + rCompositeStaticType := interpreter.NewCompositeStaticTypeComputeTypeID(nil, contractLocation, "Test.R") + rReferenceStaticType := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + rCompositeStaticType, + ) + + for name, domain := range map[string]common.PathDomain{ + "publicCap": common.PathDomainPublic, + "privateCap": common.PathDomainPrivate, + } { + environment.DeclareValue( + stdlib.StandardLibraryValue{ + Name: name, + Type: &sema.CapabilityType{}, + Kind: common.DeclarationKindConstant, + Value: &interpreter.PathCapabilityValue{ + BorrowType: rReferenceStaticType, + Path: interpreter.PathValue{ + Domain: domain, + Identifier: "r", + }, + Address: interpreter.AddressValue(address), + }, + }, + setupTransactionLocation, + ) + } + + // Create and store links. + // + // Equivalent to: + // account.link<&Test.R>(/public/r, target: /private/r) + // account.link<&Test.R>(/private/r, target: /storage/r) + + storage, inter, err := rt.Storage(runtime.Context{ + Interface: runtimeInterface, + }) + require.NoError(t, err) + + for sourceDomain, targetDomain := range map[common.PathDomain]common.PathDomain{ + common.PathDomainPublic: common.PathDomainPrivate, + common.PathDomainPrivate: common.PathDomainStorage, + } { + const pathIdentifier = "r" + + storage.GetStorageMap(address, sourceDomain.Identifier(), true). + SetValue( + inter, + interpreter.StringStorageMapKey(pathIdentifier), + interpreter.PathLinkValue{ + Type: rReferenceStaticType, + TargetPath: interpreter.PathValue{ + Domain: targetDomain, + Identifier: pathIdentifier, + }, + }, + ) + } + + err = storage.Commit(inter, false) + require.NoError(t, err) + setupTx := fmt.Sprintf( // language=cadence ` import Test from 0x1 transaction { - prepare(signer: AuthAccount) { - Test.saveExisting(%s) + prepare(signer: &Account) { + Test.saveExisting( + publicCap: publicCap, + privateCap: privateCap, + wrapper: %s + ) } } `, setupFunction, ) + err = rt.ExecuteTransaction( runtime.Script{ Source: []byte(setupTx), }, runtime.Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), + Interface: runtimeInterface, + Environment: environment, + Location: setupTransactionLocation, }, ) require.NoError(t, err) @@ -291,7 +377,7 @@ func TestCapConsMigration(t *testing.T) { // Check migrated links - require.Equal(t, + assert.Equal(t, []testCapConsLinkMigration{ { addressPath: interpreter.AddressPath{ @@ -319,7 +405,7 @@ func TestCapConsMigration(t *testing.T) { // Check migrated capabilities - require.Equal(t, + assert.Equal(t, []testCapConsPathCapabilityMigration{ { address: address, @@ -344,6 +430,7 @@ func TestCapConsMigration(t *testing.T) { }, reporter.pathCapabilityMigrations, ) + require.Empty(t, reporter.missingCapabilityIDs) // Check @@ -365,7 +452,7 @@ func TestCapConsMigration(t *testing.T) { }, runtime.Context{ Interface: runtimeInterface, - Location: common.ScriptLocation{}, + Location: nextScriptLocation(), }, ) require.NoError(t, err) @@ -373,7 +460,6 @@ func TestCapConsMigration(t *testing.T) { t.Run("directly", func(t *testing.T) { t.Parallel() - t.Skip() test(t, // language=cadence @@ -393,7 +479,6 @@ func TestCapConsMigration(t *testing.T) { t.Run("composite", func(t *testing.T) { t.Parallel() - t.Skip() test(t, // language=cadence @@ -414,7 +499,6 @@ func TestCapConsMigration(t *testing.T) { t.Run("optional", func(t *testing.T) { t.Parallel() - t.Skip() test(t, // language=cadence @@ -435,7 +519,6 @@ func TestCapConsMigration(t *testing.T) { t.Run("array", func(t *testing.T) { t.Parallel() - t.Skip() test(t, // language=cadence @@ -456,7 +539,6 @@ func TestCapConsMigration(t *testing.T) { t.Run("dictionary, value", func(t *testing.T) { t.Parallel() - t.Skip() test(t, // language=cadence @@ -477,5 +559,6 @@ func TestCapConsMigration(t *testing.T) { // TODO: add more cases // TODO: test non existing + // TODO: account link } From b1a76133e993565357ebae7a6769e920f519d167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 31 Oct 2023 11:09:31 -0700 Subject: [PATCH 14/51] ignore deprecation warnings, remove unused code --- migration/capcons/capcons.go | 14 +++++++------- migration/capcons/capcons_test.go | 4 ++-- runtime/interpreter/value_pathcapability.go | 2 -- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/migration/capcons/capcons.go b/migration/capcons/capcons.go index 961a1094c2..2e830d4fc7 100644 --- a/migration/capcons/capcons.go +++ b/migration/capcons/capcons.go @@ -287,7 +287,7 @@ func (m *Migration) migratePathCapability( locationRange := interpreter.EmptyLocationRange switch value := value.(type) { - case *interpreter.PathCapabilityValue: + case *interpreter.PathCapabilityValue: //nolint:staticcheck // Migrate the path capability to an ID capability @@ -385,7 +385,7 @@ func (m *Migration) migratePathCapability( switch key.(type) { case *interpreter.CapabilityValue, - *interpreter.PathCapabilityValue: + *interpreter.PathCapabilityValue: //nolint:staticcheck panic(errors.NewUnreachableError()) } @@ -460,14 +460,14 @@ func (m *Migration) migrateLinkToCapabilityController( // Already migrated return 0 - case interpreter.PathLinkValue: + case interpreter.PathLinkValue: //nolint:staticcheck var ok bool borrowStaticType, ok = readValue.Type.(*interpreter.ReferenceStaticType) if !ok { panic(errors.NewUnreachableError()) } - case interpreter.AccountLinkValue: + case interpreter.AccountLinkValue: //nolint:staticcheck borrowStaticType = interpreter.NewReferenceStaticType( nil, interpreter.FullyEntitledAccountAccess, @@ -569,7 +569,7 @@ func (m *Migration) migrateLinkToCapabilityController( var authAccountReferenceStaticType = interpreter.NewReferenceStaticType( nil, interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeAuthAccount, + interpreter.PrimitiveStaticTypeAuthAccount, //nolint:staticcheck ) func (m *Migration) getPathCapabilityFinalTarget( @@ -623,7 +623,7 @@ func (m *Migration) getPathCapabilityFinalTarget( } switch value := value.(type) { - case interpreter.PathLinkValue: + case interpreter.PathLinkValue: //nolint:staticcheck allowedType := m.interpreter.MustConvertStaticToSemaType(value.Type) if !sema.IsSubType(allowedType, wantedBorrowType) { @@ -634,7 +634,7 @@ func (m *Migration) getPathCapabilityFinalTarget( paths = append(paths, targetPath) path = targetPath - case interpreter.AccountLinkValue: + case interpreter.AccountLinkValue: //nolint:staticcheck if !m.interpreter.IsSubTypeOfSemaType( authAccountReferenceStaticType, wantedBorrowType, diff --git a/migration/capcons/capcons_test.go b/migration/capcons/capcons_test.go index 9a856333b3..51f0acd477 100644 --- a/migration/capcons/capcons_test.go +++ b/migration/capcons/capcons_test.go @@ -277,7 +277,7 @@ func TestCapConsMigration(t *testing.T) { Name: name, Type: &sema.CapabilityType{}, Kind: common.DeclarationKindConstant, - Value: &interpreter.PathCapabilityValue{ + Value: &interpreter.PathCapabilityValue{ //nolint:staticcheck BorrowType: rReferenceStaticType, Path: interpreter.PathValue{ Domain: domain, @@ -311,7 +311,7 @@ func TestCapConsMigration(t *testing.T) { SetValue( inter, interpreter.StringStorageMapKey(pathIdentifier), - interpreter.PathLinkValue{ + interpreter.PathLinkValue{ //nolint:staticcheck Type: rReferenceStaticType, TargetPath: interpreter.PathValue{ Domain: targetDomain, diff --git a/runtime/interpreter/value_pathcapability.go b/runtime/interpreter/value_pathcapability.go index a51a7518f0..eabc1dbd6a 100644 --- a/runtime/interpreter/value_pathcapability.go +++ b/runtime/interpreter/value_pathcapability.go @@ -41,8 +41,6 @@ var _ MemberAccessibleValue = &PathCapabilityValue{} func (*PathCapabilityValue) isValue() {} -func (*PathCapabilityValue) isCapabilityValue() {} - func (v *PathCapabilityValue) Accept(_ *Interpreter, _ Visitor) { panic(errors.NewUnreachableError()) } From 67140e3f0ee79f61372ab53d8cd896c7b7dd105d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 31 Oct 2023 11:10:55 -0700 Subject: [PATCH 15/51] rename --- migration/capcons/{capcons.go => migration.go} | 0 .../capcons/{capcons_test.go => migration_test.go} | 14 +++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) rename migration/capcons/{capcons.go => migration.go} (100%) rename migration/capcons/{capcons_test.go => migration_test.go} (97%) diff --git a/migration/capcons/capcons.go b/migration/capcons/migration.go similarity index 100% rename from migration/capcons/capcons.go rename to migration/capcons/migration.go diff --git a/migration/capcons/capcons_test.go b/migration/capcons/migration_test.go similarity index 97% rename from migration/capcons/capcons_test.go rename to migration/capcons/migration_test.go index 51f0acd477..7e5fc3d7de 100644 --- a/migration/capcons/capcons_test.go +++ b/migration/capcons/migration_test.go @@ -62,15 +62,15 @@ type testCapConsMissingCapabilityID struct { addressPath interpreter.AddressPath } -type testCapConsMigrationReporter struct { +type testMigrationReporter struct { linkMigrations []testCapConsLinkMigration pathCapabilityMigrations []testCapConsPathCapabilityMigration missingCapabilityIDs []testCapConsMissingCapabilityID } -var _ MigrationReporter = &testCapConsMigrationReporter{} +var _ MigrationReporter = &testMigrationReporter{} -func (t *testCapConsMigrationReporter) MigratedLink( +func (t *testMigrationReporter) MigratedLink( addressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, ) { @@ -83,7 +83,7 @@ func (t *testCapConsMigrationReporter) MigratedLink( ) } -func (t *testCapConsMigrationReporter) MigratedPathCapability( +func (t *testMigrationReporter) MigratedPathCapability( address common.Address, addressPath interpreter.AddressPath, ) { @@ -96,7 +96,7 @@ func (t *testCapConsMigrationReporter) MigratedPathCapability( ) } -func (t *testCapConsMigrationReporter) MissingCapabilityID( +func (t *testMigrationReporter) MissingCapabilityID( address common.Address, addressPath interpreter.AddressPath, ) { @@ -109,7 +109,7 @@ func (t *testCapConsMigrationReporter) MissingCapabilityID( ) } -func TestCapConsMigration(t *testing.T) { +func TestMigration(t *testing.T) { t.Parallel() @@ -370,7 +370,7 @@ func TestCapConsMigration(t *testing.T) { ) require.NoError(t, err) - reporter := &testCapConsMigrationReporter{} + reporter := &testMigrationReporter{} err = migrator.Migrate(reporter) require.NoError(t, err) From e81209b1e5c7724a10104732f7010b59591c3ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 31 Oct 2023 11:14:30 -0700 Subject: [PATCH 16/51] generalize link creation --- migration/capcons/migration_test.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 7e5fc3d7de..95dd2dbf23 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -301,21 +301,32 @@ func TestMigration(t *testing.T) { }) require.NoError(t, err) - for sourceDomain, targetDomain := range map[common.PathDomain]common.PathDomain{ - common.PathDomainPublic: common.PathDomainPrivate, - common.PathDomainPrivate: common.PathDomainStorage, + for sourcePath, targetPath := range map[interpreter.PathValue]interpreter.PathValue{ + { + Domain: common.PathDomainPublic, + Identifier: "r", + }: { + Domain: common.PathDomainPrivate, + Identifier: "r", + }, + { + Domain: common.PathDomainPrivate, + Identifier: "r", + }: { + Domain: common.PathDomainStorage, + Identifier: "r", + }, } { - const pathIdentifier = "r" - storage.GetStorageMap(address, sourceDomain.Identifier(), true). + storage.GetStorageMap(address, sourcePath.Domain.Identifier(), true). SetValue( inter, - interpreter.StringStorageMapKey(pathIdentifier), + interpreter.StringStorageMapKey(sourcePath.Identifier), interpreter.PathLinkValue{ //nolint:staticcheck Type: rReferenceStaticType, TargetPath: interpreter.PathValue{ - Domain: targetDomain, - Identifier: pathIdentifier, + Domain: targetPath.Domain, + Identifier: targetPath.Identifier, }, }, ) From f96502b1b56878df5930d9cbb593168449b0ab25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 31 Oct 2023 11:28:36 -0700 Subject: [PATCH 17/51] report cyclic errors instead of panic-ing --- migration/capcons/migration.go | 12 +++++-- migration/capcons/migration_test.go | 56 +++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index 2e830d4fc7..60fe53531f 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -19,6 +19,8 @@ package capcons import ( + goerrors "errors" + "github.com/onflow/atree" "github.com/onflow/cadence/runtime" @@ -65,6 +67,7 @@ type LinkMigrationReporter interface { addressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, ) + CyclicLink(err CyclicLinkError) } type PathCapabilityMigrationReporter interface { @@ -207,7 +210,7 @@ func (m *Migration) migrateLink( path interpreter.PathValue, reporter LinkMigrationReporter, ) { - capabilityID := m.migrateLinkToCapabilityController(address, path) + capabilityID := m.migrateLinkToCapabilityController(address, path, reporter) if capabilityID == 0 { return } @@ -437,6 +440,7 @@ func (m *Migration) migratePathCapability( func (m *Migration) migrateLinkToCapabilityController( addressValue interpreter.AddressValue, pathValue interpreter.PathValue, + reporter LinkMigrationReporter, ) interpreter.UInt64Value { locationRange := interpreter.EmptyLocationRange @@ -495,8 +499,12 @@ func (m *Migration) migrateLinkToCapabilityController( Type: sema.AnyType, }, ) - // TODO: skip cyclic links instead of panic-ing if err != nil { + var cyclicLinkErr CyclicLinkError + if goerrors.As(err, &cyclicLinkErr) { + reporter.CyclicLink(cyclicLinkErr) + return 0 + } panic(err) } diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 95dd2dbf23..118043571b 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -63,9 +63,17 @@ type testCapConsMissingCapabilityID struct { } type testMigrationReporter struct { - linkMigrations []testCapConsLinkMigration - pathCapabilityMigrations []testCapConsPathCapabilityMigration - missingCapabilityIDs []testCapConsMissingCapabilityID + linkMigrations []testCapConsLinkMigration + pathCapabilityMigrations []testCapConsPathCapabilityMigration + missingCapabilityIDs []testCapConsMissingCapabilityID + cyclicLinkCyclicLinkErrors []CyclicLinkError +} + +func (t *testMigrationReporter) CyclicLink(cyclicLinkError CyclicLinkError) { + t.cyclicLinkCyclicLinkErrors = append( + t.cyclicLinkCyclicLinkErrors, + cyclicLinkError, + ) } var _ MigrationReporter = &testMigrationReporter{} @@ -293,8 +301,12 @@ func TestMigration(t *testing.T) { // Create and store links. // // Equivalent to: + // // Working chain // account.link<&Test.R>(/public/r, target: /private/r) // account.link<&Test.R>(/private/r, target: /storage/r) + // // Cyclic chain + // account.link<&Test.R>(/public/r2, target: /private/r2) + // account.link<&Test.R>(/private/r2, target: /public/r2) storage, inter, err := rt.Storage(runtime.Context{ Interface: runtimeInterface, @@ -302,6 +314,7 @@ func TestMigration(t *testing.T) { require.NoError(t, err) for sourcePath, targetPath := range map[interpreter.PathValue]interpreter.PathValue{ + // Working chain { Domain: common.PathDomainPublic, Identifier: "r", @@ -316,6 +329,21 @@ func TestMigration(t *testing.T) { Domain: common.PathDomainStorage, Identifier: "r", }, + // Cyclic chain + { + Domain: common.PathDomainPublic, + Identifier: "r2", + }: { + Domain: common.PathDomainPrivate, + Identifier: "r2", + }, + { + Domain: common.PathDomainPrivate, + Identifier: "r2", + }: { + Domain: common.PathDomainPublic, + Identifier: "r2", + }, } { storage.GetStorageMap(address, sourcePath.Domain.Identifier(), true). @@ -413,6 +441,28 @@ func TestMigration(t *testing.T) { }, reporter.linkMigrations, ) + assert.Equal( + t, + []CyclicLinkError{ + { + Paths: []interpreter.PathValue{ + {Domain: common.PathDomainPublic, Identifier: "r2"}, + {Domain: common.PathDomainPrivate, Identifier: "r2"}, + {Domain: common.PathDomainPublic, Identifier: "r2"}, + }, + Address: address, + }, + { + Paths: []interpreter.PathValue{ + {Domain: common.PathDomainPrivate, Identifier: "r2"}, + {Domain: common.PathDomainPublic, Identifier: "r2"}, + {Domain: common.PathDomainPrivate, Identifier: "r2"}, + }, + Address: address, + }, + }, + reporter.cyclicLinkCyclicLinkErrors, + ) // Check migrated capabilities From 44b8c4dd99f24b9bd21bd87a8f0dceea4ebfd5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 31 Oct 2023 11:38:32 -0700 Subject: [PATCH 18/51] report missing link target --- migration/capcons/migration.go | 6 +- migration/capcons/migration_test.go | 107 ++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index 60fe53531f..0c18ce4d2b 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -68,6 +68,10 @@ type LinkMigrationReporter interface { capabilityID interpreter.UInt64Value, ) CyclicLink(err CyclicLinkError) + MissingTarget( + address interpreter.AddressValue, + path interpreter.PathValue, + ) } type PathCapabilityMigrationReporter interface { @@ -514,7 +518,7 @@ func (m *Migration) migrateLinkToCapabilityController( switch target := target.(type) { case nil: - // TODO: report/warn + reporter.MissingTarget(addressValue, pathValue) return 0 case pathCapabilityTarget: diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 118043571b..fdf69faf09 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -62,18 +62,17 @@ type testCapConsMissingCapabilityID struct { addressPath interpreter.AddressPath } +type testMissingTarget struct { + address interpreter.AddressValue + path interpreter.PathValue +} + type testMigrationReporter struct { linkMigrations []testCapConsLinkMigration pathCapabilityMigrations []testCapConsPathCapabilityMigration missingCapabilityIDs []testCapConsMissingCapabilityID cyclicLinkCyclicLinkErrors []CyclicLinkError -} - -func (t *testMigrationReporter) CyclicLink(cyclicLinkError CyclicLinkError) { - t.cyclicLinkCyclicLinkErrors = append( - t.cyclicLinkCyclicLinkErrors, - cyclicLinkError, - ) + missingTargets []testMissingTarget } var _ MigrationReporter = &testMigrationReporter{} @@ -117,6 +116,26 @@ func (t *testMigrationReporter) MissingCapabilityID( ) } +func (t *testMigrationReporter) CyclicLink(cyclicLinkError CyclicLinkError) { + t.cyclicLinkCyclicLinkErrors = append( + t.cyclicLinkCyclicLinkErrors, + cyclicLinkError, + ) +} + +func (t *testMigrationReporter) MissingTarget( + address interpreter.AddressValue, + path interpreter.PathValue, +) { + t.missingTargets = append( + t.missingTargets, + testMissingTarget{ + address: address, + path: path, + }, + ) +} + func TestMigration(t *testing.T) { t.Parallel() @@ -191,10 +210,15 @@ func TestMigration(t *testing.T) { access(all) fun checkMigratedValues(getter: fun(AnyStruct): Capability) { - self.account.storage.save(<-create R(), to: /storage/r) + self.account.storage.save(<-create R(), to: /storage/working) + self.checkMigratedValue( + capValuePath: /storage/publicCapValue, + getter: getter + ) self.checkMigratedValue( - capValuePath: /storage/publicCapValue, getter: getter) - self.checkMigratedValue(capValuePath: /storage/privateCapValue, getter: getter) + capValuePath: /storage/privateCapValue, + getter: getter + ) } access(self) @@ -276,6 +300,10 @@ func TestMigration(t *testing.T) { rCompositeStaticType, ) + const pathIdentifierWorking = "working" + const pathIdentifierCyclic = "cyclic" + const pathIdentifierDeadEnd = "deadEnd" + for name, domain := range map[string]common.PathDomain{ "publicCap": common.PathDomainPublic, "privateCap": common.PathDomainPrivate, @@ -289,7 +317,7 @@ func TestMigration(t *testing.T) { BorrowType: rReferenceStaticType, Path: interpreter.PathValue{ Domain: domain, - Identifier: "r", + Identifier: pathIdentifierWorking, }, Address: interpreter.AddressValue(address), }, @@ -317,35 +345,42 @@ func TestMigration(t *testing.T) { // Working chain { Domain: common.PathDomainPublic, - Identifier: "r", + Identifier: pathIdentifierWorking, }: { Domain: common.PathDomainPrivate, - Identifier: "r", + Identifier: pathIdentifierWorking, }, { Domain: common.PathDomainPrivate, - Identifier: "r", + Identifier: pathIdentifierWorking, }: { Domain: common.PathDomainStorage, - Identifier: "r", + Identifier: pathIdentifierWorking, }, // Cyclic chain { Domain: common.PathDomainPublic, - Identifier: "r2", + Identifier: pathIdentifierCyclic, }: { Domain: common.PathDomainPrivate, - Identifier: "r2", + Identifier: pathIdentifierCyclic, }, { Domain: common.PathDomainPrivate, - Identifier: "r2", + Identifier: pathIdentifierCyclic, }: { Domain: common.PathDomainPublic, - Identifier: "r2", + Identifier: pathIdentifierCyclic, + }, + // Dead end + { + Domain: common.PathDomainPublic, + Identifier: pathIdentifierDeadEnd, + }: { + Domain: common.PathDomainPrivate, + Identifier: pathIdentifierDeadEnd, }, } { - storage.GetStorageMap(address, sourcePath.Domain.Identifier(), true). SetValue( inter, @@ -423,7 +458,7 @@ func TestMigration(t *testing.T) { Address: address, Path: interpreter.NewUnmeteredPathValue( common.PathDomainPublic, - "r", + pathIdentifierWorking, ), }, capabilityID: 1, @@ -433,7 +468,7 @@ func TestMigration(t *testing.T) { Address: address, Path: interpreter.NewUnmeteredPathValue( common.PathDomainPrivate, - "r", + pathIdentifierWorking, ), }, capabilityID: 2, @@ -446,23 +481,35 @@ func TestMigration(t *testing.T) { []CyclicLinkError{ { Paths: []interpreter.PathValue{ - {Domain: common.PathDomainPublic, Identifier: "r2"}, - {Domain: common.PathDomainPrivate, Identifier: "r2"}, - {Domain: common.PathDomainPublic, Identifier: "r2"}, + {Domain: common.PathDomainPublic, Identifier: pathIdentifierCyclic}, + {Domain: common.PathDomainPrivate, Identifier: pathIdentifierCyclic}, + {Domain: common.PathDomainPublic, Identifier: pathIdentifierCyclic}, }, Address: address, }, { Paths: []interpreter.PathValue{ - {Domain: common.PathDomainPrivate, Identifier: "r2"}, - {Domain: common.PathDomainPublic, Identifier: "r2"}, - {Domain: common.PathDomainPrivate, Identifier: "r2"}, + {Domain: common.PathDomainPrivate, Identifier: pathIdentifierCyclic}, + {Domain: common.PathDomainPublic, Identifier: pathIdentifierCyclic}, + {Domain: common.PathDomainPrivate, Identifier: pathIdentifierCyclic}, }, Address: address, }, }, reporter.cyclicLinkCyclicLinkErrors, ) + assert.Equal(t, + []testMissingTarget{ + { + address: interpreter.AddressValue(address), + path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + pathIdentifierDeadEnd, + ), + }, + }, + reporter.missingTargets, + ) // Check migrated capabilities @@ -474,7 +521,7 @@ func TestMigration(t *testing.T) { Address: address, Path: interpreter.NewUnmeteredPathValue( common.PathDomainPrivate, - "r", + pathIdentifierWorking, ), }, }, @@ -484,7 +531,7 @@ func TestMigration(t *testing.T) { Address: address, Path: interpreter.NewUnmeteredPathValue( common.PathDomainPublic, - "r", + pathIdentifierWorking, ), }, }, From 76b3ab062bae72aaa4164831d3b2a73ac6ebba05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Nov 2023 10:25:25 -0800 Subject: [PATCH 19/51] use existing storage, do not create another one --- migration/capcons/migration.go | 10 +++------- migration/capcons/migration_test.go | 6 ++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index 0c18ce4d2b..07a4c64909 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -94,19 +94,15 @@ type Migration struct { } func NewMigration( - runtime runtime.Runtime, - context runtime.Context, + storage *runtime.Storage, + interpreter *interpreter.Interpreter, addressIterator AddressIterator, accountIDGenerator stdlib.AccountIDGenerator, ) (*Migration, error) { - storage, inter, err := runtime.Storage(context) - if err != nil { - return nil, err - } return &Migration{ storage: storage, - interpreter: inter, + interpreter: interpreter, addressIterator: addressIterator, accountIDGenerator: accountIDGenerator, }, nil diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index fdf69faf09..9bcd954e64 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -431,10 +431,8 @@ func TestMigration(t *testing.T) { // Migrate migrator, err := NewMigration( - rt, - runtime.Context{ - Interface: runtimeInterface, - }, + storage, + inter, &AddressSliceIterator{ Addresses: []common.Address{ address, From c10c7b3b6bc84b88f2dafce3f9979e8c8bae991d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Nov 2023 13:05:02 -0800 Subject: [PATCH 20/51] refactor tests --- migration/capcons/migration.go | 99 ++-- migration/capcons/migration_test.go | 882 +++++++++++++++------------- 2 files changed, 525 insertions(+), 456 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index 07a4c64909..dccde1845d 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -64,23 +64,23 @@ type MigrationReporter interface { type LinkMigrationReporter interface { MigratedLink( - addressPath interpreter.AddressPath, + accountAddressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, ) CyclicLink(err CyclicLinkError) MissingTarget( - address interpreter.AddressValue, + accountAddress interpreter.AddressValue, path interpreter.PathValue, ) } type PathCapabilityMigrationReporter interface { MigratedPathCapability( - address common.Address, + accountAddress common.Address, addressPath interpreter.AddressPath, ) MissingCapabilityID( - address common.Address, + accountAddress common.Address, addressPath interpreter.AddressPath, ) } @@ -150,13 +150,13 @@ func (m *Migration) migrateLinks( // It records an entry in the source path to capability ID mapping, // which is later needed to migrate path capabilities to ID capabilities. func (m *Migration) migrateLinksInAccount( - address common.Address, + accountAddress common.Address, reporter LinkMigrationReporter, ) { migrateDomain := func(domain common.PathDomain) { m.migrateAccountLinksInAccountDomain( - address, + accountAddress, domain, reporter, ) @@ -171,13 +171,13 @@ func (m *Migration) migrateLinksInAccount( // It records an entry in the source path to capability ID mapping, // which is later needed to migrate path capabilities to ID capabilities. func (m *Migration) migrateAccountLinksInAccountDomain( - address common.Address, + accountAddress common.Address, domain common.PathDomain, reporter LinkMigrationReporter, ) { - addressValue := interpreter.AddressValue(address) + accountAddressValue := interpreter.AddressValue(accountAddress) - storageMap := m.storage.GetStorageMap(address, domain.Identifier(), false) + storageMap := m.storage.GetStorageMap(accountAddress, domain.Identifier(), false) if storageMap == nil { return } @@ -193,7 +193,7 @@ func (m *Migration) migrateAccountLinksInAccountDomain( pathValue := interpreter.NewUnmeteredPathValue(domain, identifier) m.migrateLink( - addressValue, + accountAddressValue, pathValue, reporter, ) @@ -206,11 +206,11 @@ func (m *Migration) migrateAccountLinksInAccountDomain( // It constructs a source path to ID mapping, // which is later needed to migrate path capabilities to ID capabilities. func (m *Migration) migrateLink( - address interpreter.AddressValue, - path interpreter.PathValue, + accountAddressValue interpreter.AddressValue, + pathValue interpreter.PathValue, reporter LinkMigrationReporter, ) { - capabilityID := m.migrateLinkToCapabilityController(address, path, reporter) + capabilityID := m.migrateLinkToCapabilityController(accountAddressValue, pathValue, reporter) if capabilityID == 0 { return } @@ -218,14 +218,16 @@ func (m *Migration) migrateLink( // Record new capability ID in source path mapping. // The mapping is used later for migrating path capabilities to ID capabilities. - addressPath := interpreter.AddressPath{ - Address: address.ToAddress(), - Path: path, + accountAddress := accountAddressValue.ToAddress() + accountAddressPath := interpreter.AddressPath{ + Address: accountAddress, + Path: pathValue, } - m.capabilityIDs[addressPath] = capabilityID + + m.capabilityIDs[accountAddressPath] = capabilityID if reporter != nil { - reporter.MigratedLink(addressPath, capabilityID) + reporter.MigratedLink(accountAddressPath, capabilityID) } } @@ -247,9 +249,12 @@ func (m *Migration) migratePathCapabilities( var pathDomainStorage = common.PathDomainStorage.Identifier() -func (m *Migration) migratePathCapabilitiesInAccount(address common.Address, reporter PathCapabilityMigrationReporter) { +func (m *Migration) migratePathCapabilitiesInAccount( + accountAddress common.Address, + reporter PathCapabilityMigrationReporter, +) { - storageMap := m.storage.GetStorageMap(address, pathDomainStorage, false) + storageMap := m.storage.GetStorageMap(accountAddress, pathDomainStorage, false) if storageMap == nil { return } @@ -261,7 +266,7 @@ func (m *Migration) migratePathCapabilitiesInAccount(address common.Address, rep for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { newValue := m.migratePathCapability( - address, + accountAddress, value, reporter, ) @@ -283,7 +288,7 @@ func (m *Migration) migratePathCapabilitiesInAccount(address common.Address, rep // If a value is returned, the value must be updated with the replacement in the parent. // If nil is returned, the value was not updated and no operation has to be performed. func (m *Migration) migratePathCapability( - address common.Address, + accountAddress common.Address, value interpreter.Value, reporter PathCapabilityMigrationReporter, ) interpreter.Value { @@ -300,7 +305,7 @@ func (m *Migration) migratePathCapability( capabilityID, ok := m.capabilityIDs[addressPath] if !ok { if reporter != nil { - reporter.MissingCapabilityID(address, addressPath) + reporter.MissingCapabilityID(accountAddress, addressPath) } break } @@ -312,7 +317,7 @@ func (m *Migration) migratePathCapability( ) if reporter != nil { - reporter.MigratedPathCapability(address, addressPath) + reporter.MigratedPathCapability(accountAddress, addressPath) } return newCapability @@ -323,7 +328,7 @@ func (m *Migration) migratePathCapability( // Migrate composite's fields composite.ForEachField(nil, func(fieldName string, fieldValue interpreter.Value) (resume bool) { - newFieldValue := m.migratePathCapability(address, fieldValue, reporter) + newFieldValue := m.migratePathCapability(accountAddress, fieldValue, reporter) if newFieldValue != nil { composite.SetMember( m.interpreter, @@ -343,7 +348,7 @@ func (m *Migration) migratePathCapability( case *interpreter.SomeValue: innerValue := value.InnerValue(m.interpreter, locationRange) - newInnerValue := m.migratePathCapability(address, innerValue, reporter) + newInnerValue := m.migratePathCapability(accountAddress, innerValue, reporter) if newInnerValue != nil { return interpreter.NewSomeValueNonCopying(m.interpreter, newInnerValue) } @@ -357,7 +362,7 @@ func (m *Migration) migratePathCapability( // Migrate array's elements array.Iterate(m.interpreter, func(element interpreter.Value) (resume bool) { - newElement := m.migratePathCapability(address, element, reporter) + newElement := m.migratePathCapability(accountAddress, element, reporter) if newElement != nil { array.Set( m.interpreter, @@ -395,7 +400,7 @@ func (m *Migration) migratePathCapability( // Migrate the value of the key-value pair - newValue := m.migratePathCapability(address, value, reporter) + newValue := m.migratePathCapability(accountAddress, value, reporter) if newValue != nil { dictionary.Insert( @@ -438,14 +443,14 @@ func (m *Migration) migratePathCapability( } func (m *Migration) migrateLinkToCapabilityController( - addressValue interpreter.AddressValue, + accountAddressValue interpreter.AddressValue, pathValue interpreter.PathValue, reporter LinkMigrationReporter, ) interpreter.UInt64Value { locationRange := interpreter.EmptyLocationRange - address := addressValue.ToAddress() + address := accountAddressValue.ToAddress() domain := pathValue.Domain.Identifier() identifier := pathValue.Identifier @@ -514,7 +519,7 @@ func (m *Migration) migrateLinkToCapabilityController( switch target := target.(type) { case nil: - reporter.MissingTarget(addressValue, pathValue) + reporter.MissingTarget(accountAddressValue, pathValue) return 0 case pathCapabilityTarget: @@ -548,7 +553,7 @@ func (m *Migration) migrateLinkToCapabilityController( capabilityValue := interpreter.NewCapabilityValue( m.interpreter, capabilityID, - addressValue, + accountAddressValue, borrowStaticType, ) @@ -581,8 +586,8 @@ var authAccountReferenceStaticType = interpreter.NewReferenceStaticType( ) func (m *Migration) getPathCapabilityFinalTarget( - address common.Address, - path interpreter.PathValue, + accountAddress common.Address, + pathValue interpreter.PathValue, wantedBorrowType *sema.ReferenceType, ) ( target capabilityTarget, @@ -591,31 +596,31 @@ func (m *Migration) getPathCapabilityFinalTarget( ) { seenPaths := map[interpreter.PathValue]struct{}{} - paths := []interpreter.PathValue{path} + paths := []interpreter.PathValue{pathValue} for { // Detect cyclic links - if _, ok := seenPaths[path]; ok { + if _, ok := seenPaths[pathValue]; ok { return nil, interpreter.UnauthorizedAccess, CyclicLinkError{ - Address: address, + Address: accountAddress, Paths: paths, } } else { - seenPaths[path] = struct{}{} + seenPaths[pathValue] = struct{}{} } - domain := path.Domain.Identifier() - identifier := path.Identifier + domain := pathValue.Domain.Identifier() + identifier := pathValue.Identifier storageMapKey := interpreter.StringStorageMapKey(identifier) - switch path.Domain { + switch pathValue.Domain { case common.PathDomainStorage: - return pathCapabilityTarget(path), + return pathCapabilityTarget(pathValue), interpreter.ConvertSemaAccessToStaticAuthorization( m.interpreter, wantedBorrowType.Authorization, @@ -625,7 +630,7 @@ func (m *Migration) getPathCapabilityFinalTarget( case common.PathDomainPublic, common.PathDomainPrivate: - value := m.interpreter.ReadStored(address, domain, storageMapKey) + value := m.interpreter.ReadStored(accountAddress, domain, storageMapKey) if value == nil { return nil, interpreter.UnauthorizedAccess, nil } @@ -640,7 +645,7 @@ func (m *Migration) getPathCapabilityFinalTarget( targetPath := value.TargetPath paths = append(paths, targetPath) - path = targetPath + pathValue = targetPath case interpreter.AccountLinkValue: //nolint:staticcheck if !m.interpreter.IsSubTypeOfSemaType( @@ -650,7 +655,7 @@ func (m *Migration) getPathCapabilityFinalTarget( return nil, interpreter.UnauthorizedAccess, nil } - return accountCapabilityTarget(address), + return accountCapabilityTarget(accountAddress), interpreter.UnauthorizedAccess, nil @@ -679,10 +684,10 @@ func (m *Migration) getPathCapabilityFinalTarget( switch reference := reference.(type) { case *interpreter.StorageReferenceValue: - address = reference.TargetStorageAddress + accountAddress = reference.TargetStorageAddress targetPath := reference.TargetPath paths = append(paths, targetPath) - path = targetPath + pathValue = targetPath case *interpreter.EphemeralReferenceValue: accountValue := reference.Value.(*interpreter.SimpleCompositeValue) diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 9bcd954e64..dedfa61557 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -48,23 +48,23 @@ func (g *testAccountIDGenerator) GenerateAccountID(address common.Address) (uint } type testCapConsLinkMigration struct { - addressPath interpreter.AddressPath - capabilityID interpreter.UInt64Value + accountAddressPath interpreter.AddressPath + capabilityID interpreter.UInt64Value } type testCapConsPathCapabilityMigration struct { - address common.Address - addressPath interpreter.AddressPath + accountAddress common.Address + addressPath interpreter.AddressPath } type testCapConsMissingCapabilityID struct { - address common.Address - addressPath interpreter.AddressPath + accountAddress common.Address + addressPath interpreter.AddressPath } type testMissingTarget struct { - address interpreter.AddressValue - path interpreter.PathValue + accountAddressValue interpreter.AddressValue + path interpreter.PathValue } type testMigrationReporter struct { @@ -78,40 +78,40 @@ type testMigrationReporter struct { var _ MigrationReporter = &testMigrationReporter{} func (t *testMigrationReporter) MigratedLink( - addressPath interpreter.AddressPath, + accountAddressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, ) { t.linkMigrations = append( t.linkMigrations, testCapConsLinkMigration{ - addressPath: addressPath, - capabilityID: capabilityID, + accountAddressPath: accountAddressPath, + capabilityID: capabilityID, }, ) } func (t *testMigrationReporter) MigratedPathCapability( - address common.Address, + accountAddress common.Address, addressPath interpreter.AddressPath, ) { t.pathCapabilityMigrations = append( t.pathCapabilityMigrations, testCapConsPathCapabilityMigration{ - address: address, - addressPath: addressPath, + accountAddress: accountAddress, + addressPath: addressPath, }, ) } func (t *testMigrationReporter) MissingCapabilityID( - address common.Address, + accountAddress common.Address, addressPath interpreter.AddressPath, ) { t.missingCapabilityIDs = append( t.missingCapabilityIDs, testCapConsMissingCapabilityID{ - address: address, - addressPath: addressPath, + accountAddress: accountAddress, + addressPath: addressPath, }, ) } @@ -124,547 +124,611 @@ func (t *testMigrationReporter) CyclicLink(cyclicLinkError CyclicLinkError) { } func (t *testMigrationReporter) MissingTarget( - address interpreter.AddressValue, + accountAddressValue interpreter.AddressValue, path interpreter.PathValue, ) { t.missingTargets = append( t.missingTargets, testMissingTarget{ - address: address, - path: path, + accountAddressValue: accountAddressValue, + path: path, }, ) } -func TestMigration(t *testing.T) { - - t.Parallel() +const testPathIdentifier = "test" - address := common.MustBytesToAddress([]byte{0x1}) +var testAddress = common.MustBytesToAddress([]byte{0x1}) - test := func(t *testing.T, setupFunction, checkFunction string) { +var testRCompositeStaticType = interpreter.NewCompositeStaticTypeComputeTypeID( + nil, + common.NewAddressLocation(nil, testAddress, "Test"), + "Test.R", +) - rt := NewTestInterpreterRuntime() +var testRReferenceStaticType = interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + testRCompositeStaticType, +) - // language=cadence - contract := ` - access(all) - contract Test { +func testPathCapabilityValueMigration( + t *testing.T, + capabilityValue *interpreter.PathCapabilityValue, + pathLinks map[interpreter.PathValue]interpreter.PathValue, + accountLinks []interpreter.PathValue, + expectedMigrations []testCapConsPathCapabilityMigration, + expectedMissingCapabilityIDs []testCapConsMissingCapabilityID, + setupFunction, checkFunction string, +) { + require.True(t, + len(expectedMigrations) == 0 || + len(expectedMissingCapabilityIDs) == 0, + ) - access(all) - resource R {} + rt := NewTestInterpreterRuntime() - access(all) - struct CapabilityWrapper { + // language=cadence + contract := ` + access(all) + contract Test { - access(all) - let capability: Capability + access(all) + resource R {} - init(_ capability: Capability) { - self.capability = capability - } - } + access(all) + struct CapabilityWrapper { access(all) - struct CapabilityOptionalWrapper { + let capability: Capability - access(all) - let capability: Capability? - - init(_ capability: Capability?) { - self.capability = capability - } + init(_ capability: Capability) { + self.capability = capability } + } - access(all) - struct CapabilityArrayWrapper { + access(all) + struct CapabilityOptionalWrapper { - access(all) - let capabilities: [Capability] + access(all) + let capability: Capability? - init(_ capabilities: [Capability]) { - self.capabilities = capabilities - } + init(_ capability: Capability?) { + self.capability = capability } + } - access(all) - struct CapabilityDictionaryWrapper { + access(all) + struct CapabilityArrayWrapper { - access(all) - let capabilities: {Int: Capability} + access(all) + let capabilities: [Capability] - init(_ capabilities: {Int: Capability}) { - self.capabilities = capabilities - } + init(_ capabilities: [Capability]) { + self.capabilities = capabilities } + } - access(all) - fun saveExisting( - publicCap: Capability, - privateCap: Capability, - wrapper: fun(Capability): AnyStruct - ) { - self.account.storage.save(wrapper(publicCap), to: /storage/publicCapValue) - self.account.storage.save(wrapper(privateCap), to: /storage/privateCapValue) - } + access(all) + struct CapabilityDictionaryWrapper { access(all) - fun checkMigratedValues(getter: fun(AnyStruct): Capability) { - self.account.storage.save(<-create R(), to: /storage/working) - self.checkMigratedValue( - capValuePath: /storage/publicCapValue, - getter: getter - ) - self.checkMigratedValue( - capValuePath: /storage/privateCapValue, - getter: getter - ) - } + let capabilities: {Int: Capability} - access(self) - fun checkMigratedValue(capValuePath: StoragePath, getter: fun(AnyStruct): Capability) { - let capValue = self.account.storage.copy(from: capValuePath)! - let cap = getter(capValue) - assert(cap.id != 0) - let ref = cap.borrow<&R>()! + init(_ capabilities: {Int: Capability}) { + self.capabilities = capabilities } } - ` - accountCodes := map[runtime.Location][]byte{} - var events []cadence.Event - var loggedMessages []string + access(all) + fun saveExisting( + capability: Capability, + wrapper: fun(Capability): AnyStruct + ) { + self.account.storage.save( + wrapper(capability), + to: /storage/wrappedCapability + ) + } - runtimeInterface := &TestRuntimeInterface{ - OnGetCode: func(location runtime.Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - Storage: NewTestLedger(nil, nil), - OnGetSigningAccounts: func() ([]runtime.Address, error) { - return []runtime.Address{address}, nil - }, - OnResolveLocation: NewSingleIdentifierLocationResolver(t), - OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { - accountCodes[location] = code - return nil - }, - OnEmitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - OnProgramLog: func(message string) { - loggedMessages = append(loggedMessages, message) - }, - } + access(all) + fun checkMigratedValue(getter: fun(AnyStruct): Capability) { + self.account.storage.save(<-create R(), to: /storage/test) + let capValue = self.account.storage.copy(from: /storage/wrappedCapability)! + let cap = getter(capValue) + assert(cap.id != 0) + let ref = cap.borrow<&R>()! + } + } + ` - nextTransactionLocation := NewTransactionLocationGenerator() - nextScriptLocation := NewScriptLocationGenerator() + accountCodes := map[runtime.Location][]byte{} + var events []cadence.Event + var loggedMessages []string - // Deploy contract + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: func(location runtime.Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{testAddress}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnProgramLog: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + } - deployTransaction := utils.DeploymentTransaction("Test", []byte(contract)) - err := rt.ExecuteTransaction( - runtime.Script{ - Source: deployTransaction, - }, - runtime.Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() - // Setup + // Deploy contract - setupTransactionLocation := nextTransactionLocation() - contractLocation := common.NewAddressLocation(nil, address, "Test") + deployTransaction := utils.DeploymentTransaction("Test", []byte(contract)) + err := rt.ExecuteTransaction( + runtime.Script{ + Source: deployTransaction, + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) - environment := runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + // Setup - // Inject old PathCapabilityValues. - // We don't have a way to create them in Cadence anymore. - // - // Equivalent to: - // let publicCap = account.getCapability<&Test.R>(/public/r) - // let privateCap = account.getCapability<&Test.R>(/private/r) + setupTransactionLocation := nextTransactionLocation() - // TODO: what about the migration of reference type authorized flag? + environment := runtime.NewBaseInterpreterEnvironment(runtime.Config{}) - rCompositeStaticType := interpreter.NewCompositeStaticTypeComputeTypeID(nil, contractLocation, "Test.R") - rReferenceStaticType := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - rCompositeStaticType, - ) + // 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, + ) - const pathIdentifierWorking = "working" - const pathIdentifierCyclic = "cyclic" - const pathIdentifierDeadEnd = "deadEnd" - - for name, domain := range map[string]common.PathDomain{ - "publicCap": common.PathDomainPublic, - "privateCap": common.PathDomainPrivate, - } { - environment.DeclareValue( - stdlib.StandardLibraryValue{ - Name: name, - Type: &sema.CapabilityType{}, - Kind: common.DeclarationKindConstant, - Value: &interpreter.PathCapabilityValue{ //nolint:staticcheck - BorrowType: rReferenceStaticType, - Path: interpreter.PathValue{ - Domain: domain, - Identifier: pathIdentifierWorking, - }, - Address: interpreter.AddressValue(address), + // Create and store path and account links + + storage, inter, err := rt.Storage(runtime.Context{ + Interface: runtimeInterface, + }) + require.NoError(t, err) + + for sourcePath, targetPath := range pathLinks { + storage.GetStorageMap(testAddress, sourcePath.Domain.Identifier(), true). + SetValue( + inter, + interpreter.StringStorageMapKey(sourcePath.Identifier), + interpreter.PathLinkValue{ //nolint:staticcheck + Type: testRReferenceStaticType, + TargetPath: interpreter.PathValue{ + Domain: targetPath.Domain, + Identifier: targetPath.Identifier, }, }, - setupTransactionLocation, ) - } + } - // Create and store links. - // - // Equivalent to: - // // Working chain - // account.link<&Test.R>(/public/r, target: /private/r) - // account.link<&Test.R>(/private/r, target: /storage/r) - // // Cyclic chain - // account.link<&Test.R>(/public/r2, target: /private/r2) - // account.link<&Test.R>(/private/r2, target: /public/r2) - - storage, inter, err := rt.Storage(runtime.Context{ - Interface: runtimeInterface, - }) - require.NoError(t, err) + for _, sourcePath := range accountLinks { + storage.GetStorageMap(testAddress, sourcePath.Domain.Identifier(), true). + SetValue( + inter, + interpreter.StringStorageMapKey(sourcePath.Identifier), + interpreter.AccountLinkValue{}, //nolint:staticcheck + ) + } - for sourcePath, targetPath := range map[interpreter.PathValue]interpreter.PathValue{ - // Working chain - { - Domain: common.PathDomainPublic, - Identifier: pathIdentifierWorking, - }: { - Domain: common.PathDomainPrivate, - Identifier: pathIdentifierWorking, - }, - { - Domain: common.PathDomainPrivate, - Identifier: pathIdentifierWorking, - }: { - Domain: common.PathDomainStorage, - Identifier: pathIdentifierWorking, - }, - // Cyclic chain - { - Domain: common.PathDomainPublic, - Identifier: pathIdentifierCyclic, - }: { - Domain: common.PathDomainPrivate, - Identifier: pathIdentifierCyclic, - }, - { - Domain: common.PathDomainPrivate, - Identifier: pathIdentifierCyclic, - }: { - Domain: common.PathDomainPublic, - Identifier: pathIdentifierCyclic, - }, - // Dead end - { - Domain: common.PathDomainPublic, - Identifier: pathIdentifierDeadEnd, - }: { - Domain: common.PathDomainPrivate, - Identifier: pathIdentifierDeadEnd, + err = storage.Commit(inter, false) + require.NoError(t, err) + + // Save capability values into account + + setupTx := fmt.Sprintf( + // language=cadence + ` + import Test from 0x1 + + transaction { + prepare(signer: &Account) { + Test.saveExisting( + capability: cap, + wrapper: %s + ) + } + } + `, + setupFunction, + ) + + err = rt.ExecuteTransaction( + runtime.Script{ + Source: []byte(setupTx), + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: environment, + Location: setupTransactionLocation, + }, + ) + require.NoError(t, err) + + // Migrate + + migrator, err := NewMigration( + storage, + inter, + &AddressSliceIterator{ + Addresses: []common.Address{ + testAddress, }, - } { - storage.GetStorageMap(address, sourcePath.Domain.Identifier(), true). - SetValue( - inter, - interpreter.StringStorageMapKey(sourcePath.Identifier), - interpreter.PathLinkValue{ //nolint:staticcheck - Type: rReferenceStaticType, - TargetPath: interpreter.PathValue{ - Domain: targetPath.Domain, - Identifier: targetPath.Identifier, - }, - }, - ) - } + }, + &testAccountIDGenerator{}, + ) + require.NoError(t, err) - err = storage.Commit(inter, false) - require.NoError(t, err) + reporter := &testMigrationReporter{} + + err = migrator.Migrate(reporter) + require.NoError(t, err) + + // Check migrated capabilities + + assert.Equal(t, + expectedMigrations, + reporter.pathCapabilityMigrations, + ) + require.Equal(t, + expectedMissingCapabilityIDs, + reporter.missingCapabilityIDs, + ) + + if len(expectedMissingCapabilityIDs) == 0 { + + // Check - setupTx := fmt.Sprintf( + checkScript := fmt.Sprintf( // language=cadence ` - import Test from 0x1 - - transaction { - prepare(signer: &Account) { - Test.saveExisting( - publicCap: publicCap, - privateCap: privateCap, - wrapper: %s - ) - } - } - `, - setupFunction, - ) + import Test from 0x1 - err = rt.ExecuteTransaction( + access(all) + fun main() { + Test.checkMigratedValue(getter: %s) + } + `, + checkFunction, + ) + _, err = rt.ExecuteScript( runtime.Script{ - Source: []byte(setupTx), + Source: []byte(checkScript), }, runtime.Context{ Interface: runtimeInterface, Environment: environment, - Location: setupTransactionLocation, + Location: nextScriptLocation(), }, ) require.NoError(t, err) + } +} - // Migrate - - migrator, err := NewMigration( - storage, - inter, - &AddressSliceIterator{ - Addresses: []common.Address{ - address, - }, - }, - &testAccountIDGenerator{}, - ) - require.NoError(t, err) - - reporter := &testMigrationReporter{} +func TestPathCapabilityValueMigration(t *testing.T) { - err = migrator.Migrate(reporter) - require.NoError(t, err) + t.Parallel() - // Check migrated links + type linkTestCase struct { + name string + capabilityValue *interpreter.PathCapabilityValue + pathLinks map[interpreter.PathValue]interpreter.PathValue + accountLinks []interpreter.PathValue + expectedMigrations []testCapConsPathCapabilityMigration + expectedMissingCapabilityIDs []testCapConsMissingCapabilityID + } - assert.Equal(t, - []testCapConsLinkMigration{ + linkTestCases := []linkTestCase{ + { + name: "Path links, working chain (public -> private -> storage)", + // Equivalent to: getCapability<&Test.R>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + Address: interpreter.AddressValue(testAddress), + }, + pathLinks: map[interpreter.PathValue]interpreter.PathValue{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) + { + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }: { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }: { + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + }, + expectedMigrations: []testCapConsPathCapabilityMigration{ + { + accountAddress: testAddress, addressPath: interpreter.AddressPath{ - Address: address, + Address: testAddress, Path: interpreter.NewUnmeteredPathValue( common.PathDomainPublic, - pathIdentifierWorking, + testPathIdentifier, ), }, - capabilityID: 1, }, + }, + }, + { + name: "Path links, working chain (private -> storage)", + // Equivalent to: getCapability<&Test.R>(/private/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + Address: interpreter.AddressValue(testAddress), + }, + pathLinks: map[interpreter.PathValue]interpreter.PathValue{ + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }: { + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + }, + expectedMigrations: []testCapConsPathCapabilityMigration{ + { + accountAddress: testAddress, addressPath: interpreter.AddressPath{ - Address: address, + Address: testAddress, Path: interpreter.NewUnmeteredPathValue( common.PathDomainPrivate, - pathIdentifierWorking, + testPathIdentifier, ), }, - capabilityID: 2, }, }, - reporter.linkMigrations, - ) - assert.Equal( - t, - []CyclicLinkError{ - { - Paths: []interpreter.PathValue{ - {Domain: common.PathDomainPublic, Identifier: pathIdentifierCyclic}, - {Domain: common.PathDomainPrivate, Identifier: pathIdentifierCyclic}, - {Domain: common.PathDomainPublic, Identifier: pathIdentifierCyclic}, - }, - Address: address, + }, + { + name: "Path links, cyclic chain (public -> private -> public)", + // Equivalent to: getCapability<&Test.R>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, }, + Address: interpreter.AddressValue(testAddress), + }, + pathLinks: map[interpreter.PathValue]interpreter.PathValue{ + // Equivalent to: + // link<...>(/public/test, target: /private/test) { - Paths: []interpreter.PathValue{ - {Domain: common.PathDomainPrivate, Identifier: pathIdentifierCyclic}, - {Domain: common.PathDomainPublic, Identifier: pathIdentifierCyclic}, - {Domain: common.PathDomainPrivate, Identifier: pathIdentifierCyclic}, - }, - Address: address, + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }: { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, }, - }, - reporter.cyclicLinkCyclicLinkErrors, - ) - assert.Equal(t, - []testMissingTarget{ + // Equivalent to: + // link<...>(/private/test, target: /public/test) { - address: interpreter.AddressValue(address), - path: interpreter.NewUnmeteredPathValue( - common.PathDomainPublic, - pathIdentifierDeadEnd, - ), + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }: { + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, }, }, - reporter.missingTargets, - ) - - // Check migrated capabilities - - assert.Equal(t, - []testCapConsPathCapabilityMigration{ + expectedMigrations: nil, + expectedMissingCapabilityIDs: []testCapConsMissingCapabilityID{ { - address: address, + accountAddress: testAddress, addressPath: interpreter.AddressPath{ - Address: address, + Address: testAddress, Path: interpreter.NewUnmeteredPathValue( - common.PathDomainPrivate, - pathIdentifierWorking, + common.PathDomainPublic, + testPathIdentifier, ), }, }, + }, + }, + { + name: "Path links, missing target (public -> private)", + // Equivalent to: getCapability<&Test.R>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + Address: interpreter.AddressValue(testAddress), + }, + // Equivalent to: + // link<...>(/public/test, target: /private/test) + pathLinks: map[interpreter.PathValue]interpreter.PathValue{ { - address: address, + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }: { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + expectedMigrations: nil, + expectedMissingCapabilityIDs: []testCapConsMissingCapabilityID{ + { + accountAddress: testAddress, addressPath: interpreter.AddressPath{ - Address: address, + Address: testAddress, Path: interpreter.NewUnmeteredPathValue( common.PathDomainPublic, - pathIdentifierWorking, + testPathIdentifier, ), }, }, }, - reporter.pathCapabilityMigrations, - ) - require.Empty(t, reporter.missingCapabilityIDs) - - // Check - - checkScript := fmt.Sprintf( - // language=cadence - ` - import Test from 0x1 - - access(all) - fun main() { - Test.checkMigratedValues(getter: %s) - } - `, - checkFunction, - ) - _, err = rt.ExecuteScript( - runtime.Script{ - Source: []byte(checkScript), - }, - runtime.Context{ - Interface: runtimeInterface, - Location: nextScriptLocation(), - }, - ) - require.NoError(t, err) + }, } - t.Run("directly", func(t *testing.T) { - t.Parallel() + type valueTestCase struct { + name string + setupFunction string + checkFunction string + } - test(t, + valueTestCases := []valueTestCase{ + { + name: "directly", // language=cadence - ` + setupFunction: ` fun (cap: Capability): AnyStruct { return cap } `, // language=cadence - ` + checkFunction: ` fun (value: AnyStruct): Capability { return value as! Capability } `, - ) - }) - - t.Run("composite", func(t *testing.T) { - t.Parallel() - - test(t, + }, + { + name: "composite", // language=cadence - ` + setupFunction: ` fun (cap: Capability): AnyStruct { return Test.CapabilityWrapper(cap) } `, // language=cadence - ` - fun (value: AnyStruct): Capability { - let wrapper = value as! Test.CapabilityWrapper - return wrapper.capability - } - `, - ) - }) - - t.Run("optional", func(t *testing.T) { - t.Parallel() - - test(t, + checkFunction: ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityWrapper + return wrapper.capability + } + `, + }, + { + name: "optional", // language=cadence - ` + setupFunction: ` fun (cap: Capability): AnyStruct { return Test.CapabilityOptionalWrapper(cap) } `, // language=cadence - ` + checkFunction: ` fun (value: AnyStruct): Capability { let wrapper = value as! Test.CapabilityOptionalWrapper return wrapper.capability! } `, - ) - }) - - t.Run("array", func(t *testing.T) { - t.Parallel() - - test(t, + }, + { + name: "array", // language=cadence - ` + setupFunction: ` fun (cap: Capability): AnyStruct { return Test.CapabilityArrayWrapper([cap]) } `, // language=cadence - ` - fun (value: AnyStruct): Capability { - let wrapper = value as! Test.CapabilityArrayWrapper - return wrapper.capabilities[0] - } - `, - ) - }) - - t.Run("dictionary, value", func(t *testing.T) { - t.Parallel() + checkFunction: ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityArrayWrapper + return wrapper.capabilities[0] + } + `, + }, + { + name: "dictionary", - test(t, // language=cadence - ` + setupFunction: ` fun (cap: Capability): AnyStruct { return Test.CapabilityDictionaryWrapper({2: cap}) } `, // language=cadence - ` - fun (value: AnyStruct): Capability { - let wrapper = value as! Test.CapabilityDictionaryWrapper - return wrapper.capabilities[2]! - } - `, + checkFunction: ` + fun (value: AnyStruct): Capability { + let wrapper = value as! Test.CapabilityDictionaryWrapper + return wrapper.capabilities[2]! + } + `, + }, + } + + test := func(linkTestCase linkTestCase, valueTestCase valueTestCase) { + testName := fmt.Sprintf( + "%s, %s", + linkTestCase.name, + valueTestCase.name, ) - }) - // TODO: add more cases - // TODO: test non existing - // TODO: account link + t.Run(testName, func(t *testing.T) { + t.Parallel() + + testPathCapabilityValueMigration( + t, + linkTestCase.capabilityValue, + linkTestCase.pathLinks, + linkTestCase.accountLinks, + linkTestCase.expectedMigrations, + linkTestCase.expectedMissingCapabilityIDs, + valueTestCase.setupFunction, + valueTestCase.checkFunction, + ) + }) + } + for _, linkTestCase := range linkTestCases { + for _, valueTestCase := range valueTestCases { + test(linkTestCase, valueTestCase) + } + } } + +// TODO: add more cases +// TODO: test non existing +// TODO: account link From b2969afdbe5f5801e81f68815eebe94467cc7edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Nov 2023 13:23:57 -0800 Subject: [PATCH 21/51] test missing source --- migration/capcons/migration_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index dedfa61557..1481d127eb 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -572,6 +572,32 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, }, }, + { + name: "Path links, missing source (public -> private)", + // Equivalent to: getCapability<&Test.R>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + Address: interpreter.AddressValue(testAddress), + }, + pathLinks: nil, + expectedMigrations: nil, + expectedMissingCapabilityIDs: []testCapConsMissingCapabilityID{ + { + accountAddress: testAddress, + addressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + testPathIdentifier, + ), + }, + }, + }, + }, { name: "Path links, missing target (public -> private)", // Equivalent to: getCapability<&Test.R>(/public/test) From 4d56a32bf26d61ab96ad86e0562c7bff56162f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Nov 2023 17:08:57 -0800 Subject: [PATCH 22/51] handle account links --- migration/capcons/migration.go | 15 ++++++- migration/capcons/migration_test.go | 62 +++++++++++++++++++++++++---- runtime/interpreter/value_link.go | 13 +++--- 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index dccde1845d..515d7c3e47 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -284,6 +284,11 @@ func (m *Migration) migratePathCapabilitiesInAccount( } } +var fullyEntitledAccountReferenceStaticType = interpreter.ConvertSemaReferenceTypeToStaticReferenceType( + nil, + sema.FullyEntitledAccountReferenceType, +) + // migratePathCapability migrates a path capability to an ID capability in the given value. // If a value is returned, the value must be updated with the replacement in the parent. // If nil is returned, the value was not updated and no operation has to be performed. @@ -310,10 +315,18 @@ func (m *Migration) migratePathCapability( break } + newBorrowType, ok := oldCapability.BorrowType.(*interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + if newBorrowType.ReferencedType == interpreter.PrimitiveStaticTypeAuthAccount { + newBorrowType = fullyEntitledAccountReferenceStaticType + } + newCapability := interpreter.NewUnmeteredCapabilityValue( capabilityID, oldCapability.Address, - oldCapability.BorrowType, + newBorrowType, ) if reporter != nil { diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 1481d127eb..b7f49d0836 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -232,13 +232,21 @@ func testPathCapabilityValueMigration( } access(all) - fun checkMigratedValue(getter: fun(AnyStruct): Capability) { + fun checkMigratedCapabilityValueWithPathLink(getter: fun(AnyStruct): Capability) { self.account.storage.save(<-create R(), to: /storage/test) let capValue = self.account.storage.copy(from: /storage/wrappedCapability)! let cap = getter(capValue) assert(cap.id != 0) let ref = cap.borrow<&R>()! } + + access(all) + fun checkMigratedCapabilityValueWithAccountLink(getter: fun(AnyStruct): Capability) { + let capValue = self.account.storage.copy(from: /storage/wrappedCapability)! + let cap = getter(capValue) + assert(cap.id != 0) + let ref = cap.borrow<&Account>()! + } } ` @@ -406,18 +414,24 @@ func testPathCapabilityValueMigration( if len(expectedMissingCapabilityIDs) == 0 { + checkFunctionName := "checkMigratedCapabilityValueWithPathLink" + if len(accountLinks) > 0 { + checkFunctionName = "checkMigratedCapabilityValueWithAccountLink" + } + // Check checkScript := fmt.Sprintf( // language=cadence ` - import Test from 0x1 - - access(all) - fun main() { - Test.checkMigratedValue(getter: %s) - } - `, + import Test from 0x1 + + access(all) + fun main() { + Test.%s(getter: %s) + } + `, + checkFunctionName, checkFunction, ) _, err = rt.ExecuteScript( @@ -634,6 +648,38 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, }, }, + { + name: "Account link, working chain (public)", + // Equivalent to: getCapability<&AuthAccount>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: authAccountReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + Address: interpreter.AddressValue(testAddress), + }, + accountLinks: []interpreter.PathValue{ + // Equivalent to: + // linkAccount(/public/test) + { + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + expectedMigrations: []testCapConsPathCapabilityMigration{ + { + accountAddress: testAddress, + addressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + testPathIdentifier, + ), + }, + }, + }, + }, } type valueTestCase struct { diff --git a/runtime/interpreter/value_link.go b/runtime/interpreter/value_link.go index f4b4ec8390..30de6de9c2 100644 --- a/runtime/interpreter/value_link.go +++ b/runtime/interpreter/value_link.go @@ -196,16 +196,17 @@ func (v AccountLinkValue) ConformsToStaticType( panic(errors.NewUnreachableError()) } -func (v AccountLinkValue) Equal(_ *Interpreter, _ LocationRange, _ Value) bool { - panic(errors.NewUnreachableError()) +func (v AccountLinkValue) Equal(_ *Interpreter, _ LocationRange, other Value) bool { + _, ok := other.(AccountLinkValue) + return ok } func (AccountLinkValue) IsStorable() bool { panic(errors.NewUnreachableError()) } -func (v AccountLinkValue) Storable(_ atree.SlabStorage, _ atree.Address, _ uint64) (atree.Storable, error) { - panic(errors.NewUnreachableError()) +func (v AccountLinkValue) Storable(storage atree.SlabStorage, address atree.Address, maxInlineSize uint64) (atree.Storable, error) { + return maybeLargeImmutableStorable(v, storage, address, maxInlineSize) } func (AccountLinkValue) NeedsStoreTo(_ atree.Address) bool { @@ -239,11 +240,11 @@ func (AccountLinkValue) DeepRemove(_ *Interpreter) { } func (v AccountLinkValue) ByteSize() uint32 { - panic(errors.NewUnreachableError()) + return mustStorableSize(v) } func (v AccountLinkValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { - panic(errors.NewUnreachableError()) + return v, nil } func (v AccountLinkValue) ChildStorables() []atree.Storable { From 16e249bf66bd4b5a811e943cefba510e96f566b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Nov 2023 17:10:14 -0800 Subject: [PATCH 23/51] test private account link --- migration/capcons/migration_test.go | 35 +++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index b7f49d0836..3fb0cfa2e0 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -680,6 +680,38 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, }, }, + { + name: "Account link, working chain (private)", + // Equivalent to: getCapability<&AuthAccount>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: authAccountReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + Address: interpreter.AddressValue(testAddress), + }, + accountLinks: []interpreter.PathValue{ + // Equivalent to: + // linkAccount(/private/test) + { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + expectedMigrations: []testCapConsPathCapabilityMigration{ + { + accountAddress: testAddress, + addressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + testPathIdentifier, + ), + }, + }, + }, + }, } type valueTestCase struct { @@ -802,5 +834,4 @@ func TestPathCapabilityValueMigration(t *testing.T) { } // TODO: add more cases -// TODO: test non existing -// TODO: account link +// TODO: test migrated links From 08fb111edd5d76b641e99a6f03c09c3fb98d7cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 8 Nov 2023 08:27:12 -0800 Subject: [PATCH 24/51] support migrated links, do not require storage target to exists --- migration/capcons/migration.go | 5 +-- migration/capcons/migration_test.go | 49 +++++++++++++++++++++++++++++ runtime/stdlib/account.go | 6 ++-- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index 515d7c3e47..8b5ab896cb 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -683,9 +683,10 @@ func (m *Migration) getPathCapabilityFinalTarget( panic(errors.NewUnreachableError()) } - reference := m.interpreter.SharedState.Config.CapabilityBorrowHandler( + // Do not borrow final target (i.e. do not require target to exist), + // just get target address/path + reference := stdlib.GetCheckedCapabilityControllerReference( m.interpreter, - interpreter.EmptyLocationRange, value.Address, value.ID, wantedBorrowType, diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 3fb0cfa2e0..2b3d86cb92 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -541,6 +541,55 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, }, }, + // Test that the migration also follows capability controller, + // which were already previously migrated from links. + // Following the (capability value) should not borrow it, + // i.e. require the storage target to exist, + // but rather just get the storage target + { + name: "Path links, working chain (private -> private -> storage)", + // Equivalent to: getCapability<&Test.R>(/private/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + Address: interpreter.AddressValue(testAddress), + }, + pathLinks: map[interpreter.PathValue]interpreter.PathValue{ + // Equivalent to: + // link<&Test.R>(/private/test, target: /private/test2) + { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }: { + Domain: common.PathDomainPrivate, + Identifier: "test2", + }, + // Equivalent to: + // link<&Test.R>(/private/test2, target: /storage/test) + { + Domain: common.PathDomainPrivate, + Identifier: "test2", + }: { + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + }, + expectedMigrations: []testCapConsPathCapabilityMigration{ + { + accountAddress: testAddress, + addressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPrivate, + testPathIdentifier, + ), + }, + }, + }, + }, { name: "Path links, cyclic chain (public -> private -> public)", // Equivalent to: getCapability<&Test.R>(/public/test) diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index 4bdff82d54..a45d54e591 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -3227,7 +3227,7 @@ func getCheckedCapabilityController( return controller, wantedBorrowType } -func getCheckedCapabilityControllerReference( +func GetCheckedCapabilityControllerReference( inter *interpreter.Interpreter, capabilityAddressValue interpreter.AddressValue, capabilityIDValue interpreter.UInt64Value, @@ -3262,7 +3262,7 @@ func BorrowCapabilityController( wantedBorrowType *sema.ReferenceType, capabilityBorrowType *sema.ReferenceType, ) interpreter.ReferenceValue { - referenceValue := getCheckedCapabilityControllerReference( + referenceValue := GetCheckedCapabilityControllerReference( inter, capabilityAddress, capabilityID, @@ -3297,7 +3297,7 @@ func CheckCapabilityController( wantedBorrowType *sema.ReferenceType, capabilityBorrowType *sema.ReferenceType, ) interpreter.BoolValue { - referenceValue := getCheckedCapabilityControllerReference( + referenceValue := GetCheckedCapabilityControllerReference( inter, capabilityAddress, capabilityID, From 190b5357879e2ce3b4bf0b347281b75b26d4fe49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 8 Nov 2023 08:46:03 -0800 Subject: [PATCH 25/51] report and assert new borrow type --- migration/capcons/migration.go | 7 ++++++- migration/capcons/migration_test.go | 14 +++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index 8b5ab896cb..33292e9498 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -78,6 +78,7 @@ type PathCapabilityMigrationReporter interface { MigratedPathCapability( accountAddress common.Address, addressPath interpreter.AddressPath, + borrowType *interpreter.ReferenceStaticType, ) MissingCapabilityID( accountAddress common.Address, @@ -330,7 +331,11 @@ func (m *Migration) migratePathCapability( ) if reporter != nil { - reporter.MigratedPathCapability(accountAddress, addressPath) + reporter.MigratedPathCapability( + accountAddress, + addressPath, + newBorrowType, + ) } return newCapability diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 2b3d86cb92..04211c51d4 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -55,6 +55,7 @@ type testCapConsLinkMigration struct { type testCapConsPathCapabilityMigration struct { accountAddress common.Address addressPath interpreter.AddressPath + borrowType *interpreter.ReferenceStaticType } type testCapConsMissingCapabilityID struct { @@ -93,12 +94,14 @@ func (t *testMigrationReporter) MigratedLink( func (t *testMigrationReporter) MigratedPathCapability( accountAddress common.Address, addressPath interpreter.AddressPath, + borrowType *interpreter.ReferenceStaticType, ) { t.pathCapabilityMigrations = append( t.pathCapabilityMigrations, testCapConsPathCapabilityMigration{ accountAddress: accountAddress, addressPath: addressPath, + borrowType: borrowType, }, ) } @@ -503,6 +506,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { testPathIdentifier, ), }, + borrowType: testRReferenceStaticType, }, }, }, @@ -538,6 +542,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { testPathIdentifier, ), }, + borrowType: testRReferenceStaticType, }, }, }, @@ -587,6 +592,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { testPathIdentifier, ), }, + borrowType: testRReferenceStaticType, }, }, }, @@ -603,7 +609,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, pathLinks: map[interpreter.PathValue]interpreter.PathValue{ // Equivalent to: - // link<...>(/public/test, target: /private/test) + // link<&Test.R>(/public/test, target: /private/test) { Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -612,7 +618,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { Identifier: testPathIdentifier, }, // Equivalent to: - // link<...>(/private/test, target: /public/test) + // link<&Test.R>(/private/test, target: /public/test) { Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -673,7 +679,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, // Equivalent to: - // link<...>(/public/test, target: /private/test) + // link<&Test.R>(/public/test, target: /private/test) pathLinks: map[interpreter.PathValue]interpreter.PathValue{ { Domain: common.PathDomainPublic, @@ -726,6 +732,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { testPathIdentifier, ), }, + borrowType: fullyEntitledAccountReferenceStaticType, }, }, }, @@ -758,6 +765,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { testPathIdentifier, ), }, + borrowType: fullyEntitledAccountReferenceStaticType, }, }, }, From 0ffefdc8fbf02f39774207070316360779e05456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 8 Nov 2023 10:33:36 -0800 Subject: [PATCH 26/51] do not mutate while iterating --- migration/capcons/migration.go | 93 ++++++++++++++++++++++++++-------- runtime/interpreter/value.go | 57 +++++++++++++++++++++ 2 files changed, 128 insertions(+), 22 deletions(-) diff --git a/migration/capcons/migration.go b/migration/capcons/migration.go index 33292e9498..f8832c0227 100644 --- a/migration/capcons/migration.go +++ b/migration/capcons/migration.go @@ -187,10 +187,15 @@ func (m *Migration) migrateAccountLinksInAccountDomain( count := storageMap.Count() if count > 0 { + var identifiers []string + for key := iterator.NextKey(); key != nil; key = iterator.NextKey() { // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey identifier := string(key.(interpreter.StringAtreeValue)) + identifiers = append(identifiers, identifier) + } + for _, identifier := range identifiers { pathValue := interpreter.NewUnmeteredPathValue(domain, identifier) m.migrateLink( @@ -264,7 +269,21 @@ func (m *Migration) migratePathCapabilitiesInAccount( count := storageMap.Count() if count > 0 { - for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { + + var keys []string + + // Read all keys first, then migrate. + // Migrating (mutating) during iteration is not supported. + for key, _ := iterator.Next(); key != nil; key, _ = iterator.Next() { + // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey + identifier := string(key.(interpreter.StringAtreeValue)) + keys = append(keys, identifier) + } + + for _, key := range keys { + storageKey := interpreter.StringStorageMapKey(key) + + value := storageMap.ReadValue(nil, storageKey) newValue := m.migratePathCapability( accountAddress, @@ -273,11 +292,9 @@ func (m *Migration) migratePathCapabilitiesInAccount( ) if newValue != nil { - // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey - identifier := string(key.(interpreter.StringAtreeValue)) storageMap.SetValue( m.interpreter, - interpreter.StringStorageMapKey(identifier), + storageKey, newValue, ) } @@ -320,7 +337,9 @@ func (m *Migration) migratePathCapability( if !ok { panic(errors.NewUnreachableError()) } - if newBorrowType.ReferencedType == interpreter.PrimitiveStaticTypeAuthAccount { + + // Convert the old AuthAccount type to the new fully-entitled Account type + if newBorrowType.ReferencedType == interpreter.PrimitiveStaticTypeAuthAccount { //nolint:staticcheck newBorrowType = fullyEntitledAccountReferenceStaticType } @@ -345,7 +364,21 @@ func (m *Migration) migratePathCapability( // Migrate composite's fields - composite.ForEachField(nil, func(fieldName string, fieldValue interpreter.Value) (resume bool) { + // Read all keys first, then migrate. + // Migrating (mutating) during iteration is not supported. + + var fieldNames []string + + composite.ForEachFieldName(func(fieldName string) (resume bool) { + fieldNames = append(fieldNames, fieldName) + + // Continue iteration + return true + }) + + for _, fieldName := range fieldNames { + fieldValue := composite.GetField(m.interpreter, interpreter.EmptyLocationRange, fieldName) + newFieldValue := m.migratePathCapability(accountAddress, fieldValue, reporter) if newFieldValue != nil { composite.SetMember( @@ -355,10 +388,7 @@ func (m *Migration) migratePathCapability( newFieldValue, ) } - - // continue iteration - return true - }) + } // The composite itself does not have to be replaced @@ -375,11 +405,16 @@ func (m *Migration) migratePathCapability( case *interpreter.ArrayValue: array := value - var index int // Migrate array's elements - array.Iterate(m.interpreter, func(element interpreter.Value) (resume bool) { + // Do not use iteration using the array iterator, + // migrating (mutating) during iteration is not supported. + + count := array.Count() + for index := 0; index < count; index++ { + element := array.Get(m.interpreter, locationRange, index) + newElement := m.migratePathCapability(accountAddress, element, reporter) if newElement != nil { array.Set( @@ -389,11 +424,7 @@ func (m *Migration) migratePathCapability( newElement, ) } - - index++ - - return true - }) + } // The array itself does not have to be replaced @@ -404,7 +435,22 @@ func (m *Migration) migratePathCapability( // Migrate dictionary's values - dictionary.Iterate(m.interpreter, func(key, value interpreter.Value) (resume bool) { + // Read all keys first, then migrate. + // Migrating (mutating) during iteration is not supported. + + var keys []interpreter.Value + + dictionary.IterateKeys( + m.interpreter, + func(key interpreter.Value) (resume bool) { + keys = append(keys, key) + + // Continue iteration + return true + }, + ) + + for _, key := range keys { // Keys cannot be capabilities at the moment, // so this should never occur in stored data @@ -416,7 +462,12 @@ func (m *Migration) migratePathCapability( panic(errors.NewUnreachableError()) } - // Migrate the value of the key-value pair + // Migrate the value + + value, ok := dictionary.Get(m.interpreter, locationRange, key) + if !ok { + panic(errors.NewUnreachableError()) + } newValue := m.migratePathCapability(accountAddress, value, reporter) @@ -428,9 +479,7 @@ func (m *Migration) migratePathCapability( newValue, ) } - - return true - }) + } // The dictionary itself does not have to be replaced diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 8e46111afa..8729722b9c 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17620,6 +17620,29 @@ func (v *CompositeValue) GetOwner() common.Address { return common.Address(v.StorageAddress()) } +// ForEachFieldName iterates over all field names of the composite value. +// It does NOT iterate over computed fields and functions! +func (v *CompositeValue) ForEachFieldName( + f func(fieldName string) (resume bool), +) { + v.forEachFieldName(v.dictionary.IterateKeys, f) +} + +func (v *CompositeValue) forEachFieldName( + atreeIterate func(fn atree.MapElementIterationFunc) error, + f func(fieldName string) (resume bool), +) { + err := atreeIterate(func(key atree.Value) (resume bool, err error) { + resume = f( + string(key.(StringAtreeValue)), + ) + return + }) + if err != nil { + panic(errors.NewExternalError(err)) + } +} + // ForEachField iterates over all field-name field-value pairs of the composite value. // It does NOT iterate over computed fields and functions! func (v *CompositeValue) ForEachField( @@ -18208,6 +18231,40 @@ func (v *DictionaryValue) Accept(interpreter *Interpreter, visitor Visitor) { }) } +func (v *DictionaryValue) IterateKeys( + interpreter *Interpreter, + f func(key Value) (resume bool), +) { + v.iterateKeys(interpreter, v.dictionary.IterateKeys, f) +} + +func (v *DictionaryValue) iterateKeys( + interpreter *Interpreter, + atreeIterate func(fn atree.MapElementIterationFunc) error, + f func(key Value) (resume bool), +) { + iterate := func() { + err := atreeIterate(func(key atree.Value) (resume bool, err error) { + // atree.OrderedMap iteration provides low-level atree.Value, + // convert to high-level interpreter.Value + + resume = f( + MustConvertStoredValue(interpreter, key), + ) + + return resume, nil + }) + if err != nil { + panic(errors.NewExternalError(err)) + } + } + if v.IsResourceKinded(interpreter) { + interpreter.withMutationPrevention(v.StorageID(), iterate) + } else { + iterate() + } +} + func (v *DictionaryValue) Iterate(interpreter *Interpreter, f func(key, value Value) (resume bool)) { v.iterate(interpreter, v.dictionary.Iterate, f) } From 6161d64de6575d93c41bf3327ae78d5f085d48c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 8 Nov 2023 15:56:32 -0800 Subject: [PATCH 27/51] test migration for type mismatch between capability and link chain --- migration/capcons/migration_test.go | 289 +++++++++++++++++++++------- 1 file changed, 222 insertions(+), 67 deletions(-) diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 04211c51d4..4762c68939 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -155,14 +155,33 @@ var testRReferenceStaticType = interpreter.NewReferenceStaticType( testRCompositeStaticType, ) +var testSCompositeStaticType = interpreter.NewCompositeStaticTypeComputeTypeID( + nil, + common.NewAddressLocation(nil, testAddress, "Test"), + "Test.S", +) + +var testSReferenceStaticType = interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + testSCompositeStaticType, +) + +type testLink struct { + sourcePath interpreter.PathValue + targetPath interpreter.PathValue + borrowType *interpreter.ReferenceStaticType +} + func testPathCapabilityValueMigration( t *testing.T, capabilityValue *interpreter.PathCapabilityValue, - pathLinks map[interpreter.PathValue]interpreter.PathValue, + pathLinks []testLink, accountLinks []interpreter.PathValue, expectedMigrations []testCapConsPathCapabilityMigration, expectedMissingCapabilityIDs []testCapConsMissingCapabilityID, setupFunction, checkFunction string, + borrowShouldFail bool, ) { require.True(t, len(expectedMigrations) == 0 || @@ -179,6 +198,9 @@ func testPathCapabilityValueMigration( access(all) resource R {} + access(all) + struct S {} + access(all) struct CapabilityWrapper { @@ -235,20 +257,30 @@ func testPathCapabilityValueMigration( } access(all) - fun checkMigratedCapabilityValueWithPathLink(getter: fun(AnyStruct): Capability) { + fun checkMigratedCapabilityValueWithPathLink(getter: fun(AnyStruct): Capability, borrowShouldFail: Bool) { self.account.storage.save(<-create R(), to: /storage/test) let capValue = self.account.storage.copy(from: /storage/wrappedCapability)! let cap = getter(capValue) assert(cap.id != 0) - let ref = cap.borrow<&R>()! + let ref = cap.borrow<&R>() + if borrowShouldFail { + assert(ref == nil) + } else { + assert(ref != nil) + } } access(all) - fun checkMigratedCapabilityValueWithAccountLink(getter: fun(AnyStruct): Capability) { + fun checkMigratedCapabilityValueWithAccountLink(getter: fun(AnyStruct): Capability, borrowShouldFail: Bool) { let capValue = self.account.storage.copy(from: /storage/wrappedCapability)! let cap = getter(capValue) assert(cap.id != 0) - let ref = cap.borrow<&Account>()! + let ref = cap.check<&Account>() + if borrowShouldFail { + assert(ref == nil) + } else { + assert(ref != nil) + } } } ` @@ -327,13 +359,18 @@ func testPathCapabilityValueMigration( }) require.NoError(t, err) - for sourcePath, targetPath := range pathLinks { + for _, testLink := range pathLinks { + sourcePath := testLink.sourcePath + targetPath := testLink.targetPath + + require.NotNil(t, testLink.borrowType) + storage.GetStorageMap(testAddress, sourcePath.Domain.Identifier(), true). SetValue( inter, interpreter.StringStorageMapKey(sourcePath.Identifier), interpreter.PathLinkValue{ //nolint:staticcheck - Type: testRReferenceStaticType, + Type: testLink.borrowType, TargetPath: interpreter.PathValue{ Domain: targetPath.Domain, Identifier: targetPath.Identifier, @@ -431,11 +468,12 @@ func testPathCapabilityValueMigration( access(all) fun main() { - Test.%s(getter: %s) + Test.%s(getter: %s, borrowShouldFail: %v) } `, checkFunctionName, checkFunction, + borrowShouldFail, ) _, err = rt.ExecuteScript( runtime.Script{ @@ -458,10 +496,11 @@ func TestPathCapabilityValueMigration(t *testing.T) { type linkTestCase struct { name string capabilityValue *interpreter.PathCapabilityValue - pathLinks map[interpreter.PathValue]interpreter.PathValue + pathLinks []testLink accountLinks []interpreter.PathValue expectedMigrations []testCapConsPathCapabilityMigration expectedMissingCapabilityIDs []testCapConsMissingCapabilityID + borrowShouldFail bool } linkTestCases := []linkTestCase{ @@ -476,24 +515,72 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, Address: interpreter.AddressValue(testAddress), }, - pathLinks: map[interpreter.PathValue]interpreter.PathValue{ - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + { + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + }, + expectedMigrations: []testCapConsPathCapabilityMigration{ { + accountAddress: testAddress, + addressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + testPathIdentifier, + ), + }, + borrowType: testRReferenceStaticType, + }, + }, + }, + { + name: "Path links, working chain (public -> storage)", + // Equivalent to: getCapability<&Test.R>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, - }: { - Domain: common.PathDomainPrivate, - Identifier: testPathIdentifier, }, - // Equivalent to: - // link<&Test.R>(/private/test, target: /storage/test) + Address: interpreter.AddressValue(testAddress), + }, + pathLinks: []testLink{ { - Domain: common.PathDomainPrivate, - Identifier: testPathIdentifier, - }: { - Domain: common.PathDomainStorage, - Identifier: testPathIdentifier, + // Equivalent to: + // link<&Test.R>(/public/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, }, }, expectedMigrations: []testCapConsPathCapabilityMigration{ @@ -521,15 +608,19 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, Address: interpreter.AddressValue(testAddress), }, - pathLinks: map[interpreter.PathValue]interpreter.PathValue{ - // Equivalent to: - // link<&Test.R>(/private/test, target: /storage/test) + pathLinks: []testLink{ { - Domain: common.PathDomainPrivate, - Identifier: testPathIdentifier, - }: { - Domain: common.PathDomainStorage, - Identifier: testPathIdentifier, + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, }, }, expectedMigrations: []testCapConsPathCapabilityMigration{ @@ -562,24 +653,32 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, Address: interpreter.AddressValue(testAddress), }, - pathLinks: map[interpreter.PathValue]interpreter.PathValue{ - // Equivalent to: - // link<&Test.R>(/private/test, target: /private/test2) + pathLinks: []testLink{ { - Domain: common.PathDomainPrivate, - Identifier: testPathIdentifier, - }: { - Domain: common.PathDomainPrivate, - Identifier: "test2", + // Equivalent to: + // link<&Test.R>(/private/test, target: /private/test2) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: "test2", + }, + borrowType: testRReferenceStaticType, }, - // Equivalent to: - // link<&Test.R>(/private/test2, target: /storage/test) { - Domain: common.PathDomainPrivate, - Identifier: "test2", - }: { - Domain: common.PathDomainStorage, - Identifier: testPathIdentifier, + // Equivalent to: + // link<&Test.R>(/private/test2, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: "test2", + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, }, }, expectedMigrations: []testCapConsPathCapabilityMigration{ @@ -596,8 +695,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, }, }, + // TODO: verify { - name: "Path links, cyclic chain (public -> private -> public)", + name: "Path links, valid chain (public -> storage), different borrow type", // Equivalent to: getCapability<&Test.R>(/public/test) capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck BorrowType: testRReferenceStaticType, @@ -607,25 +707,75 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, Address: interpreter.AddressValue(testAddress), }, - pathLinks: map[interpreter.PathValue]interpreter.PathValue{ - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) + pathLinks: []testLink{ { - Domain: common.PathDomainPublic, - Identifier: testPathIdentifier, - }: { - Domain: common.PathDomainPrivate, - Identifier: testPathIdentifier, + // Equivalent to: + // link<&Test.S>(/public/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + // + borrowType: testSReferenceStaticType, }, - // Equivalent to: - // link<&Test.R>(/private/test, target: /public/test) + }, + expectedMigrations: []testCapConsPathCapabilityMigration{ { - Domain: common.PathDomainPrivate, - Identifier: testPathIdentifier, - }: { + accountAddress: testAddress, + addressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.NewUnmeteredPathValue( + common.PathDomainPublic, + testPathIdentifier, + ), + }, + borrowType: testRReferenceStaticType, + }, + }, + borrowShouldFail: true, + }, + { + name: "Path links, cyclic chain (public -> private -> public)", + // Equivalent to: getCapability<&Test.R>(/public/test) + capabilityValue: &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: testRReferenceStaticType, + Path: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, }, + Address: interpreter.AddressValue(testAddress), + }, + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + { + // Equivalent to: + // link<&Test.R>(/private/test, target: /public/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, }, expectedMigrations: nil, expectedMissingCapabilityIDs: []testCapConsMissingCapabilityID{ @@ -678,15 +828,19 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, Address: interpreter.AddressValue(testAddress), }, - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) - pathLinks: map[interpreter.PathValue]interpreter.PathValue{ + pathLinks: []testLink{ { - Domain: common.PathDomainPublic, - Identifier: testPathIdentifier, - }: { - Domain: common.PathDomainPrivate, - Identifier: testPathIdentifier, + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, }, }, expectedMigrations: nil, @@ -879,6 +1033,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { linkTestCase.expectedMissingCapabilityIDs, valueTestCase.setupFunction, valueTestCase.checkFunction, + linkTestCase.borrowShouldFail, ) }) } From 54bb96ad37275161df968bf0c92cf10cd47e2359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 8 Nov 2023 16:36:11 -0800 Subject: [PATCH 28/51] test link migrations --- migration/capcons/migration_test.go | 579 ++++++++++++++++++++++++++-- 1 file changed, 543 insertions(+), 36 deletions(-) diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index 4762c68939..b230f915df 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -69,11 +69,11 @@ type testMissingTarget struct { } type testMigrationReporter struct { - linkMigrations []testCapConsLinkMigration - pathCapabilityMigrations []testCapConsPathCapabilityMigration - missingCapabilityIDs []testCapConsMissingCapabilityID - cyclicLinkCyclicLinkErrors []CyclicLinkError - missingTargets []testMissingTarget + linkMigrations []testCapConsLinkMigration + pathCapabilityMigrations []testCapConsPathCapabilityMigration + missingCapabilityIDs []testCapConsMissingCapabilityID + cyclicLinkErrors []CyclicLinkError + missingTargets []testMissingTarget } var _ MigrationReporter = &testMigrationReporter{} @@ -120,8 +120,8 @@ func (t *testMigrationReporter) MissingCapabilityID( } func (t *testMigrationReporter) CyclicLink(cyclicLinkError CyclicLinkError) { - t.cyclicLinkCyclicLinkErrors = append( - t.cyclicLinkCyclicLinkErrors, + t.cyclicLinkErrors = append( + t.cyclicLinkErrors, cyclicLinkError, ) } @@ -173,6 +173,39 @@ type testLink struct { borrowType *interpreter.ReferenceStaticType } +func storeTestAccountLinks(accountLinks []interpreter.PathValue, storage *runtime.Storage, inter *interpreter.Interpreter) { + for _, sourcePath := range accountLinks { + storage.GetStorageMap(testAddress, sourcePath.Domain.Identifier(), true). + SetValue( + inter, + interpreter.StringStorageMapKey(sourcePath.Identifier), + interpreter.AccountLinkValue{}, //nolint:staticcheck + ) + } +} + +func storeTestPathLinks(t *testing.T, pathLinks []testLink, storage *runtime.Storage, inter *interpreter.Interpreter) { + for _, testLink := range pathLinks { + sourcePath := testLink.sourcePath + targetPath := testLink.targetPath + + require.NotNil(t, testLink.borrowType) + + storage.GetStorageMap(testAddress, sourcePath.Domain.Identifier(), true). + SetValue( + inter, + interpreter.StringStorageMapKey(sourcePath.Identifier), + interpreter.PathLinkValue{ //nolint:staticcheck + Type: testLink.borrowType, + TargetPath: interpreter.PathValue{ + Domain: targetPath.Domain, + Identifier: targetPath.Identifier, + }, + }, + ) + } +} + func testPathCapabilityValueMigration( t *testing.T, capabilityValue *interpreter.PathCapabilityValue, @@ -359,34 +392,9 @@ func testPathCapabilityValueMigration( }) require.NoError(t, err) - for _, testLink := range pathLinks { - sourcePath := testLink.sourcePath - targetPath := testLink.targetPath - - require.NotNil(t, testLink.borrowType) + storeTestPathLinks(t, pathLinks, storage, inter) - storage.GetStorageMap(testAddress, sourcePath.Domain.Identifier(), true). - SetValue( - inter, - interpreter.StringStorageMapKey(sourcePath.Identifier), - interpreter.PathLinkValue{ //nolint:staticcheck - Type: testLink.borrowType, - TargetPath: interpreter.PathValue{ - Domain: targetPath.Domain, - Identifier: targetPath.Identifier, - }, - }, - ) - } - - for _, sourcePath := range accountLinks { - storage.GetStorageMap(testAddress, sourcePath.Domain.Identifier(), true). - SetValue( - inter, - interpreter.StringStorageMapKey(sourcePath.Identifier), - interpreter.AccountLinkValue{}, //nolint:staticcheck - ) - } + storeTestAccountLinks(accountLinks, storage, inter) err = storage.Commit(inter, false) require.NoError(t, err) @@ -1045,5 +1053,504 @@ func TestPathCapabilityValueMigration(t *testing.T) { } } -// TODO: add more cases -// TODO: test migrated links +func testLinkMigration( + t *testing.T, + pathLinks []testLink, + accountLinks []interpreter.PathValue, + expectedLinkMigrations []testCapConsLinkMigration, + expectedCyclicLinkErrors []CyclicLinkError, + expectedMissingTargets []testMissingTarget, +) { + require.True(t, + len(expectedLinkMigrations) == 0 || + (len(expectedCyclicLinkErrors) == 0 && len(expectedMissingTargets) == 0), + ) + + // language=cadence + contract := ` + access(all) + contract Test { + + access(all) + resource R {} + + access(all) + struct S {} + } + ` + + rt := NewTestInterpreterRuntime() + + accountCodes := map[runtime.Location][]byte{} + var events []cadence.Event + var loggedMessages []string + + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: func(location runtime.Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{testAddress}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnProgramLog: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + // Deploy contract + + deployTransaction := utils.DeploymentTransaction("Test", []byte(contract)) + err := rt.ExecuteTransaction( + runtime.Script{ + Source: deployTransaction, + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Create and store path and account links + + storage, inter, err := rt.Storage(runtime.Context{ + Interface: runtimeInterface, + }) + require.NoError(t, err) + + storeTestPathLinks(t, pathLinks, storage, inter) + + storeTestAccountLinks(accountLinks, storage, inter) + + err = storage.Commit(inter, false) + require.NoError(t, err) + + // Migrate + + migrator, err := NewMigration( + storage, + inter, + &AddressSliceIterator{ + Addresses: []common.Address{ + testAddress, + }, + }, + &testAccountIDGenerator{}, + ) + require.NoError(t, err) + + reporter := &testMigrationReporter{} + + err = migrator.Migrate(reporter) + require.NoError(t, err) + + // Assert + + assert.Equal(t, + expectedLinkMigrations, + reporter.linkMigrations, + ) + assert.Equal(t, + expectedCyclicLinkErrors, + reporter.cyclicLinkErrors, + ) + assert.Equal(t, + expectedMissingTargets, + reporter.missingTargets, + ) +} + +func TestLinkMigration(t *testing.T) { + + t.Parallel() + + type linkTestCase struct { + name string + pathLinks []testLink + accountLinks []interpreter.PathValue + expectedLinkMigrations []testCapConsLinkMigration + expectedCyclicLinkErrors []CyclicLinkError + expectedMissingTargets []testMissingTarget + } + + linkTestCases := []linkTestCase{ + { + name: "Path links, working chain (public -> private -> storage)", + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + { + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 1, + }, + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 2, + }, + }, + }, + { + name: "Path links, working chain (public -> storage)", + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/public/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 1, + }, + }, + }, + { + name: "Path links, working chain (private -> storage)", + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 1, + }, + }, + }, + // Test that the migration also follows capability controller, + // which were already previously migrated from links. + // Following the (capability value) should not borrow it, + // i.e. require the storage target to exist, + // but rather just get the storage target + { + name: "Path links, working chain (private -> private -> storage)", + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/private/test, target: /private/test2) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: "test2", + }, + borrowType: testRReferenceStaticType, + }, + { + // Equivalent to: + // link<&Test.R>(/private/test2, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: "test2", + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: "test2", + }, + }, + capabilityID: 1, + }, + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 2, + }, + }, + }, + // TODO: verify + { + name: "Path links, valid chain (public -> storage), different borrow type", + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.S>(/public/test, target: /storage/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: testPathIdentifier, + }, + // + borrowType: testSReferenceStaticType, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 1, + }, + }, + }, + { + name: "Path links, cyclic chain (public -> private -> public)", + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + { + // Equivalent to: + // link<&Test.R>(/private/test, target: /public/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + }, + expectedCyclicLinkErrors: []CyclicLinkError{ + { + Address: testAddress, + Paths: []interpreter.PathValue{ + { + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + { + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + }, + { + Address: testAddress, + Paths: []interpreter.PathValue{ + { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + { + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + }, + }, + }, + { + name: "Path links, missing target (public -> private)", + pathLinks: []testLink{ + { + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + borrowType: testRReferenceStaticType, + }, + }, + expectedMissingTargets: []testMissingTarget{ + { + accountAddressValue: interpreter.AddressValue(testAddress), + path: interpreter.PathValue{ + Identifier: testPathIdentifier, + Domain: common.PathDomainPublic, + }, + }, + }, + }, + { + name: "Account link, working chain (public)", + accountLinks: []interpreter.PathValue{ + // Equivalent to: + // linkAccount(/public/test) + { + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 1, + }, + }, + }, + { + name: "Account link, working chain (private)", + accountLinks: []interpreter.PathValue{ + // Equivalent to: + // linkAccount(/private/test) + { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 1, + }, + }, + }, + } + + test := func(linkTestCase linkTestCase) { + + t.Run(linkTestCase.name, func(t *testing.T) { + t.Parallel() + + testLinkMigration( + t, + linkTestCase.pathLinks, + linkTestCase.accountLinks, + linkTestCase.expectedLinkMigrations, + linkTestCase.expectedCyclicLinkErrors, + linkTestCase.expectedMissingTargets, + ) + }) + } + + for _, linkTestCase := range linkTestCases { + test(linkTestCase) + } +} From 4040bc55146a9709f2de295e7c8f382529b275fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 8 Nov 2023 16:46:25 -0800 Subject: [PATCH 29/51] lint --- migration/capcons/migration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migration/capcons/migration_test.go b/migration/capcons/migration_test.go index b230f915df..cd928f8859 100644 --- a/migration/capcons/migration_test.go +++ b/migration/capcons/migration_test.go @@ -208,7 +208,7 @@ func storeTestPathLinks(t *testing.T, pathLinks []testLink, storage *runtime.Sto func testPathCapabilityValueMigration( t *testing.T, - capabilityValue *interpreter.PathCapabilityValue, + capabilityValue *interpreter.PathCapabilityValue, //nolint:staticcheck pathLinks []testLink, accountLinks []interpreter.PathValue, expectedMigrations []testCapConsPathCapabilityMigration, @@ -503,7 +503,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { type linkTestCase struct { name string - capabilityValue *interpreter.PathCapabilityValue + capabilityValue *interpreter.PathCapabilityValue //nolint:staticcheck pathLinks []testLink accountLinks []interpreter.PathValue expectedMigrations []testCapConsPathCapabilityMigration From 0aec1e765d966d26eb32a63ca4876325ba6d1491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 5 Dec 2023 14:19:58 -0800 Subject: [PATCH 30/51] move --- {migration => migrations}/capcons/error.go | 0 {migration => migrations}/capcons/migration.go | 0 {migration => migrations}/capcons/migration_test.go | 0 {migration => migrations}/capcons/target.go | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {migration => migrations}/capcons/error.go (100%) rename {migration => migrations}/capcons/migration.go (100%) rename {migration => migrations}/capcons/migration_test.go (100%) rename {migration => migrations}/capcons/target.go (100%) diff --git a/migration/capcons/error.go b/migrations/capcons/error.go similarity index 100% rename from migration/capcons/error.go rename to migrations/capcons/error.go diff --git a/migration/capcons/migration.go b/migrations/capcons/migration.go similarity index 100% rename from migration/capcons/migration.go rename to migrations/capcons/migration.go diff --git a/migration/capcons/migration_test.go b/migrations/capcons/migration_test.go similarity index 100% rename from migration/capcons/migration_test.go rename to migrations/capcons/migration_test.go diff --git a/migration/capcons/target.go b/migrations/capcons/target.go similarity index 100% rename from migration/capcons/target.go rename to migrations/capcons/target.go From 601d5a2dce404a43592df93072d83c9661b3c187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 5 Dec 2023 14:21:31 -0800 Subject: [PATCH 31/51] use common address iterator --- migrations/capcons/migration.go | 31 +++------------------------- migrations/capcons/migration_test.go | 5 +++-- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/migrations/capcons/migration.go b/migrations/capcons/migration.go index f8832c0227..da1b77b6ec 100644 --- a/migrations/capcons/migration.go +++ b/migrations/capcons/migration.go @@ -23,6 +23,7 @@ import ( "github.com/onflow/atree" + "github.com/onflow/cadence/migrations" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" @@ -31,32 +32,6 @@ import ( "github.com/onflow/cadence/runtime/stdlib" ) -type AddressIterator interface { - NextAddress() common.Address - Reset() -} - -type AddressSliceIterator struct { - Addresses []common.Address - index int -} - -var _ AddressIterator = &AddressSliceIterator{} - -func (a *AddressSliceIterator) NextAddress() common.Address { - index := a.index - if index >= len(a.Addresses) { - return common.ZeroAddress - } - address := a.Addresses[index] - a.index++ - return address -} - -func (a *AddressSliceIterator) Reset() { - a.index = 0 -} - type MigrationReporter interface { LinkMigrationReporter PathCapabilityMigrationReporter @@ -90,14 +65,14 @@ type Migration struct { storage *runtime.Storage interpreter *interpreter.Interpreter capabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value - addressIterator AddressIterator + addressIterator migrations.AddressIterator accountIDGenerator stdlib.AccountIDGenerator } func NewMigration( storage *runtime.Storage, interpreter *interpreter.Interpreter, - addressIterator AddressIterator, + addressIterator migrations.AddressIterator, accountIDGenerator stdlib.AccountIDGenerator, ) (*Migration, error) { diff --git a/migrations/capcons/migration_test.go b/migrations/capcons/migration_test.go index cd928f8859..80e137e453 100644 --- a/migrations/capcons/migration_test.go +++ b/migrations/capcons/migration_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence" + "github.com/onflow/cadence/migrations" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" @@ -435,7 +436,7 @@ func testPathCapabilityValueMigration( migrator, err := NewMigration( storage, inter, - &AddressSliceIterator{ + &migrations.AddressSliceIterator{ Addresses: []common.Address{ testAddress, }, @@ -1145,7 +1146,7 @@ func testLinkMigration( migrator, err := NewMigration( storage, inter, - &AddressSliceIterator{ + &migrations.AddressSliceIterator{ Addresses: []common.Address{ testAddress, }, From d0330d5de3bd791013da985a8cb4f0e00182e83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 5 Dec 2023 14:45:40 -0800 Subject: [PATCH 32/51] pass account address to value migration function --- migrations/account_storage.go | 14 ++++++++------ migrations/account_type/migration.go | 6 +++--- migrations/migration.go | 4 ++-- migrations/migration_test.go | 6 +++--- migrations/string_normalization/migration.go | 4 +++- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/migrations/account_storage.go b/migrations/account_storage.go index bfce7a6304..3940881259 100644 --- a/migrations/account_storage.go +++ b/migrations/account_storage.go @@ -37,18 +37,20 @@ func NewAccountStorage(storage *runtime.Storage, address common.Address) Account } } +type ValueConverter func( + value interpreter.Value, + address common.Address, + domain common.PathDomain, + key string, +) interpreter.Value + // ForEachValue iterates over the values in the account. // The `valueConverter takes a function to be applied to each value. // It returns the converted, if a new value was created during conversion. func (i *AccountStorage) ForEachValue( inter *interpreter.Interpreter, domains []common.PathDomain, - valueConverter func( - value interpreter.Value, - address common.Address, - domain common.PathDomain, - key string, - ) interpreter.Value, + valueConverter ValueConverter, ) { for _, domain := range domains { storageMap := i.storage.GetStorageMap(i.address, domain.Identifier(), false) diff --git a/migrations/account_type/migration.go b/migrations/account_type/migration.go index e7454e55b8..f2a94e1dbb 100644 --- a/migrations/account_type/migration.go +++ b/migrations/account_type/migration.go @@ -39,19 +39,19 @@ func (AccountTypeMigration) Name() string { // Migrate migrates `AuthAccount` and `PublicAccount` types inside `TypeValue`s, // to the account reference type (&Account). -func (AccountTypeMigration) Migrate(value interpreter.Value) (newValue interpreter.Value) { +func (AccountTypeMigration) Migrate(_ common.Address, value interpreter.Value) interpreter.Value { switch value := value.(type) { case interpreter.TypeValue: convertedType := maybeConvertAccountType(value.Type) if convertedType == nil { - return + return nil } return interpreter.NewTypeValue(nil, convertedType) case *interpreter.CapabilityValue: convertedBorrowType := maybeConvertAccountType(value.BorrowType) if convertedBorrowType == nil { - return + return nil } return interpreter.NewUnmeteredCapabilityValue(value.ID, value.Address, convertedBorrowType) diff --git a/migrations/migration.go b/migrations/migration.go index 9851c722ca..ce2233bf26 100644 --- a/migrations/migration.go +++ b/migrations/migration.go @@ -9,7 +9,7 @@ import ( type Migration interface { Name() string - Migrate(value interpreter.Value) (newValue interpreter.Value) + Migrate(accountAddress common.Address, value interpreter.Value) (newValue interpreter.Value) } type StorageMigration struct { @@ -195,7 +195,7 @@ func (m *StorageMigration) migrateNestedValue( default: // Assumption: all migrations only migrate non-container typed values. for _, migration := range migrations { - converted := migration.Migrate(value) + converted := migration.Migrate(address, value) if converted != nil { // Chain the migrations. diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 4dd996dbb2..2065e2a31c 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -60,7 +60,7 @@ func (testStringMigration) Name() string { return "testStringMigration" } -func (testStringMigration) Migrate(value interpreter.Value) (newValue interpreter.Value) { +func (testStringMigration) Migrate(_ common.Address, value interpreter.Value) interpreter.Value { if value, ok := value.(*interpreter.StringValue); ok { return interpreter.NewUnmeteredStringValue(fmt.Sprintf("updated_%s", value.Str)) } @@ -76,7 +76,7 @@ func (testInt8Migration) Name() string { return "testInt8Migration" } -func (testInt8Migration) Migrate(value interpreter.Value) (newValue interpreter.Value) { +func (testInt8Migration) Migrate(_ common.Address, value interpreter.Value) interpreter.Value { if value, ok := value.(interpreter.Int8Value); ok { return interpreter.NewUnmeteredInt8Value(int8(value) + 10) } @@ -194,6 +194,6 @@ func TestMultipleMigrations(t *testing.T) { require.Equal(t, "testStringMigration", reporter.migratedPaths["string_value"]) require.Equal(t, "testInt8Migration", reporter.migratedPaths["int8_value"]) - // int16 value must notbe reported as migrated. + // int16 value must not be reported as migrated. require.NotContains(t, reporter.migratedPaths, "int16_value") } diff --git a/migrations/string_normalization/migration.go b/migrations/string_normalization/migration.go index eb2304e825..fb15effd88 100644 --- a/migrations/string_normalization/migration.go +++ b/migrations/string_normalization/migration.go @@ -20,6 +20,7 @@ package string_normalization import ( "github.com/onflow/cadence/migrations" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" ) @@ -36,8 +37,9 @@ func (StringNormalizingMigration) Name() string { } func (StringNormalizingMigration) Migrate( + _ common.Address, value interpreter.Value, -) (newValue interpreter.Value) { +) interpreter.Value { switch value := value.(type) { case *interpreter.StringValue: return interpreter.NewUnmeteredStringValue(value.Str) From 77ffa5fd899f5e27b93dc6ecfaa92a107436d3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 5 Dec 2023 14:48:59 -0800 Subject: [PATCH 33/51] simplify account type value migration --- migrations/account_type/migration.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/migrations/account_type/migration.go b/migrations/account_type/migration.go index f2a94e1dbb..bf15e22212 100644 --- a/migrations/account_type/migration.go +++ b/migrations/account_type/migration.go @@ -43,21 +43,18 @@ func (AccountTypeMigration) Migrate(_ common.Address, value interpreter.Value) i switch value := value.(type) { case interpreter.TypeValue: convertedType := maybeConvertAccountType(value.Type) - if convertedType == nil { - return nil + if convertedType != nil { + return interpreter.NewTypeValue(nil, convertedType) } - return interpreter.NewTypeValue(nil, convertedType) case *interpreter.CapabilityValue: convertedBorrowType := maybeConvertAccountType(value.BorrowType) - if convertedBorrowType == nil { - return nil + if convertedBorrowType != nil { + return interpreter.NewUnmeteredCapabilityValue(value.ID, value.Address, convertedBorrowType) } - return interpreter.NewUnmeteredCapabilityValue(value.ID, value.Address, convertedBorrowType) - - default: - return nil } + + return nil } func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.StaticType { From 781e1579be5ca4f0f42c77fd00308fb060b3d25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 5 Dec 2023 16:36:35 -0800 Subject: [PATCH 34/51] pass address path in one parameter, instead of three --- migrations/account_storage.go | 16 ++++-- migrations/account_type/migration.go | 6 ++- migrations/account_type/migration_test.go | 51 +++++++------------ migrations/migration.go | 30 +++++------ migrations/migration_reporter.go | 6 +-- migrations/migration_test.go | 53 +++++++++++++++----- migrations/string_normalization/migration.go | 6 ++- 7 files changed, 96 insertions(+), 72 deletions(-) diff --git a/migrations/account_storage.go b/migrations/account_storage.go index 3940881259..6da4ed0625 100644 --- a/migrations/account_storage.go +++ b/migrations/account_storage.go @@ -38,10 +38,8 @@ func NewAccountStorage(storage *runtime.Storage, address common.Address) Account } type ValueConverter func( + addressPath interpreter.AddressPath, value interpreter.Value, - address common.Address, - domain common.PathDomain, - key string, ) interpreter.Value // ForEachValue iterates over the values in the account. @@ -71,9 +69,19 @@ func (i *AccountStorage) ForEachValue( for _, key := range keys { storageKey := interpreter.StringStorageMapKey(key) + path := interpreter.PathValue{ + Identifier: key, + Domain: domain, + } + + addressPath := interpreter.AddressPath{ + Address: i.address, + Path: path, + } + value := storageMap.ReadValue(nil, storageKey) - newValue := valueConverter(value, i.address, domain, key) + newValue := valueConverter(addressPath, value) if newValue == nil { continue } diff --git a/migrations/account_type/migration.go b/migrations/account_type/migration.go index bf15e22212..4dfde168e5 100644 --- a/migrations/account_type/migration.go +++ b/migrations/account_type/migration.go @@ -39,7 +39,11 @@ func (AccountTypeMigration) Name() string { // Migrate migrates `AuthAccount` and `PublicAccount` types inside `TypeValue`s, // to the account reference type (&Account). -func (AccountTypeMigration) Migrate(_ common.Address, value interpreter.Value) interpreter.Value { +func (AccountTypeMigration) Migrate( + _ interpreter.AddressPath, + value interpreter.Value, + _ *interpreter.Interpreter, +) interpreter.Value { switch value := value.(type) { case interpreter.TypeValue: convertedType := maybeConvertAccountType(value.Type) diff --git a/migrations/account_type/migration_test.go b/migrations/account_type/migration_test.go index bf2c95bc7c..f80aedb3fd 100644 --- a/migrations/account_type/migration_test.go +++ b/migrations/account_type/migration_test.go @@ -19,9 +19,9 @@ package account_type import ( - "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/atree" @@ -37,34 +37,20 @@ import ( var _ migrations.Reporter = &testReporter{} type testReporter struct { - migratedPaths map[common.Address]map[common.PathDomain]map[string]struct{} + migratedPaths map[interpreter.AddressPath]struct{} } func newTestReporter() *testReporter { return &testReporter{ - migratedPaths: map[common.Address]map[common.PathDomain]map[string]struct{}{}, + migratedPaths: map[interpreter.AddressPath]struct{}{}, } } func (t *testReporter) Report( - address common.Address, - domain common.PathDomain, - identifier string, + addressPath interpreter.AddressPath, _ string, ) { - migratedPathsInAddress, ok := t.migratedPaths[address] - if !ok { - migratedPathsInAddress = make(map[common.PathDomain]map[string]struct{}) - t.migratedPaths[address] = migratedPathsInAddress - } - - migratedPathsInDomain, ok := migratedPathsInAddress[domain] - if !ok { - migratedPathsInDomain = make(map[string]struct{}) - migratedPathsInAddress[domain] = migratedPathsInDomain - } - - migratedPathsInDomain[identifier] = struct{}{} + t.migratedPaths[addressPath] = struct{}{} } func TestTypeValueMigration(t *testing.T) { @@ -357,21 +343,20 @@ func TestTypeValueMigration(t *testing.T) { ) // Check reported migrated paths + for identifier, test := range testCases { + addressPath := interpreter.AddressPath{ + Address: account, + Path: interpreter.PathValue{ + Domain: pathDomain, + Identifier: identifier, + }, + } - migratedPathsInDomain := reporter.migratedPaths[account][pathDomain] - for path, test := range testCases { - t.Run(fmt.Sprintf("reported_%s", path), func(t *testing.T) { - test := test - path := path - - t.Parallel() - - if test.expectedType == nil { - require.NotContains(t, migratedPathsInDomain, path) - } else { - require.Contains(t, migratedPathsInDomain, path) - } - }) + if test.expectedType == nil { + assert.NotContains(t, reporter.migratedPaths, addressPath) + } else { + assert.Contains(t, reporter.migratedPaths, addressPath) + } } // Assert the migrated values. diff --git a/migrations/migration.go b/migrations/migration.go index ce2233bf26..1aa6ba93cd 100644 --- a/migrations/migration.go +++ b/migrations/migration.go @@ -9,7 +9,11 @@ import ( type Migration interface { Name() string - Migrate(accountAddress common.Address, value interpreter.Value) (newValue interpreter.Value) + Migrate( + addressPath interpreter.AddressPath, + value interpreter.Value, + interpreter *interpreter.Interpreter, + ) (newValue interpreter.Value) } type StorageMigration struct { @@ -60,12 +64,10 @@ func (m *StorageMigration) migrateValuesInAccount( accountStorage := NewAccountStorage(m.storage, address) migrateValue := func( + addressPath interpreter.AddressPath, value interpreter.Value, - address common.Address, - domain common.PathDomain, - key string, ) interpreter.Value { - return m.migrateNestedValue(value, address, domain, key, migrations, reporter) + return m.migrateNestedValue(addressPath, value, migrations, reporter) } accountStorage.ForEachValue( @@ -78,17 +80,15 @@ func (m *StorageMigration) migrateValuesInAccount( var emptyLocationRange = interpreter.EmptyLocationRange func (m *StorageMigration) migrateNestedValue( + addressPath interpreter.AddressPath, value interpreter.Value, - address common.Address, - domain common.PathDomain, - key string, migrations []Migration, reporter Reporter, ) (newValue interpreter.Value) { switch value := value.(type) { case *interpreter.SomeValue: innerValue := value.InnerValue(m.interpreter, emptyLocationRange) - newInnerValue := m.migrateNestedValue(innerValue, address, domain, key, migrations, reporter) + newInnerValue := m.migrateNestedValue(addressPath, innerValue, migrations, reporter) if newInnerValue != nil { return interpreter.NewSomeValueNonCopying(m.interpreter, newInnerValue) } @@ -102,7 +102,7 @@ func (m *StorageMigration) migrateNestedValue( count := array.Count() for index := 0; index < count; index++ { element := array.Get(m.interpreter, emptyLocationRange, index) - newElement := m.migrateNestedValue(element, address, domain, key, migrations, reporter) + newElement := m.migrateNestedValue(addressPath, element, migrations, reporter) if newElement != nil { array.Set( m.interpreter, @@ -130,7 +130,7 @@ func (m *StorageMigration) migrateNestedValue( for _, fieldName := range fieldNames { existingValue := composite.GetField(m.interpreter, interpreter.EmptyLocationRange, fieldName) - migratedValue := m.migrateNestedValue(existingValue, address, domain, key, migrations, reporter) + migratedValue := m.migrateNestedValue(addressPath, existingValue, migrations, reporter) if migratedValue == nil { continue } @@ -158,8 +158,8 @@ func (m *StorageMigration) migrateNestedValue( panic(errors.NewUnreachableError()) } - newKey := m.migrateNestedValue(existingKey, address, domain, key, migrations, reporter) - newValue := m.migrateNestedValue(existingValue, address, domain, key, migrations, reporter) + newKey := m.migrateNestedValue(addressPath, existingKey, migrations, reporter) + newValue := m.migrateNestedValue(addressPath, existingValue, migrations, reporter) if newKey == nil && newValue == nil { continue } @@ -195,7 +195,7 @@ func (m *StorageMigration) migrateNestedValue( default: // Assumption: all migrations only migrate non-container typed values. for _, migration := range migrations { - converted := migration.Migrate(address, value) + converted := migration.Migrate(addressPath, value, m.interpreter) if converted != nil { // Chain the migrations. @@ -207,7 +207,7 @@ func (m *StorageMigration) migrateNestedValue( newValue = converted if reporter != nil { - reporter.Report(address, domain, key, migration.Name()) + reporter.Report(addressPath, migration.Name()) } } } diff --git a/migrations/migration_reporter.go b/migrations/migration_reporter.go index 4ee44b5e62..662188a9fb 100644 --- a/migrations/migration_reporter.go +++ b/migrations/migration_reporter.go @@ -18,10 +18,8 @@ package migrations -import ( - "github.com/onflow/cadence/runtime/common" -) +import "github.com/onflow/cadence/runtime/interpreter" type Reporter interface { - Report(address common.Address, domain common.PathDomain, key string, migration string) + Report(addressPath interpreter.AddressPath, migration string) } diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 2065e2a31c..02f76597db 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -34,22 +34,23 @@ import ( ) type testReporter struct { - migratedPaths map[string]string + migratedPaths map[interpreter.AddressPath][]string } func newTestReporter() *testReporter { return &testReporter{ - migratedPaths: map[string]string{}, + migratedPaths: map[interpreter.AddressPath][]string{}, } } func (t *testReporter) Report( - _ common.Address, - _ common.PathDomain, - key string, + addressPath interpreter.AddressPath, migration string, ) { - t.migratedPaths[key] = migration + t.migratedPaths[addressPath] = append( + t.migratedPaths[addressPath], + migration, + ) } // testStringMigration @@ -60,7 +61,11 @@ func (testStringMigration) Name() string { return "testStringMigration" } -func (testStringMigration) Migrate(_ common.Address, value interpreter.Value) interpreter.Value { +func (testStringMigration) Migrate( + _ interpreter.AddressPath, + value interpreter.Value, + _ *interpreter.Interpreter, +) interpreter.Value { if value, ok := value.(*interpreter.StringValue); ok { return interpreter.NewUnmeteredStringValue(fmt.Sprintf("updated_%s", value.Str)) } @@ -76,7 +81,11 @@ func (testInt8Migration) Name() string { return "testInt8Migration" } -func (testInt8Migration) Migrate(_ common.Address, value interpreter.Value) interpreter.Value { +func (testInt8Migration) Migrate( + _ interpreter.AddressPath, + value interpreter.Value, + _ *interpreter.Interpreter, +) interpreter.Value { if value, ok := value.(interpreter.Int8Value); ok { return interpreter.NewUnmeteredInt8Value(int8(value) + 10) } @@ -191,9 +200,27 @@ func TestMultipleMigrations(t *testing.T) { } // Check the reporter - require.Equal(t, "testStringMigration", reporter.migratedPaths["string_value"]) - require.Equal(t, "testInt8Migration", reporter.migratedPaths["int8_value"]) - - // int16 value must not be reported as migrated. - require.NotContains(t, reporter.migratedPaths, "int16_value") + require.Equal(t, + map[interpreter.AddressPath][]string{ + interpreter.AddressPath{ + Address: account, + Path: interpreter.PathValue{ + Domain: pathDomain, + Identifier: "int8_value", + }, + }: { + "testInt8Migration", + }, + interpreter.AddressPath{ + Address: account, + Path: interpreter.PathValue{ + Domain: pathDomain, + Identifier: "string_value", + }, + }: { + "testStringMigration", + }, + }, + reporter.migratedPaths, + ) } diff --git a/migrations/string_normalization/migration.go b/migrations/string_normalization/migration.go index fb15effd88..1a52b9344d 100644 --- a/migrations/string_normalization/migration.go +++ b/migrations/string_normalization/migration.go @@ -20,7 +20,6 @@ package string_normalization import ( "github.com/onflow/cadence/migrations" - "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" ) @@ -37,12 +36,15 @@ func (StringNormalizingMigration) Name() string { } func (StringNormalizingMigration) Migrate( - _ common.Address, + _ interpreter.AddressPath, value interpreter.Value, + _ *interpreter.Interpreter, ) interpreter.Value { + switch value := value.(type) { case *interpreter.StringValue: return interpreter.NewUnmeteredStringValue(value.Str) + case interpreter.CharacterValue: return interpreter.NewUnmeteredCharacterValue(string(value)) } From 83564de6bba5695026a52f9f21a42be3afc45142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 5 Dec 2023 16:37:39 -0800 Subject: [PATCH 35/51] split combined link and capability migration into two separate migrations --- migrations/capcons/capabilitymigration.go | 114 ++++ migrations/capcons/linkmigration.go | 317 +++++++++ migrations/capcons/migration.go | 752 ---------------------- migrations/capcons/migration_test.go | 98 +-- 4 files changed, 487 insertions(+), 794 deletions(-) create mode 100644 migrations/capcons/capabilitymigration.go create mode 100644 migrations/capcons/linkmigration.go delete mode 100644 migrations/capcons/migration.go diff --git a/migrations/capcons/capabilitymigration.go b/migrations/capcons/capabilitymigration.go new file mode 100644 index 0000000000..5e16c82623 --- /dev/null +++ b/migrations/capcons/capabilitymigration.go @@ -0,0 +1,114 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + "github.com/onflow/cadence/migrations" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +type CapabilityMigrationReporter interface { + MigratedPathCapability( + accountAddress common.Address, + addressPath interpreter.AddressPath, + borrowType *interpreter.ReferenceStaticType, + ) + MissingCapabilityID( + accountAddress common.Address, + addressPath interpreter.AddressPath, + ) +} + +type CapabilityMigration struct { + CapabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value + Reporter CapabilityMigrationReporter +} + +var _ migrations.Migration = &CapabilityMigration{} + +func (*CapabilityMigration) Name() string { + return "CapabilityMigration" +} + +var fullyEntitledAccountReferenceStaticType = interpreter.ConvertSemaReferenceTypeToStaticReferenceType( + nil, + sema.FullyEntitledAccountReferenceType, +) + +// Migrate migrates a path capability to an ID capability in the given value. +// If a value is returned, the value must be updated with the replacement in the parent. +// If nil is returned, the value was not updated and no operation has to be performed. +func (m *CapabilityMigration) Migrate( + addressPath interpreter.AddressPath, + value interpreter.Value, + _ *interpreter.Interpreter, +) interpreter.Value { + reporter := m.Reporter + + switch value := value.(type) { + case *interpreter.PathCapabilityValue: //nolint:staticcheck + + // Migrate the path capability to an ID capability + + oldCapability := value + + capabilityAddressPath := oldCapability.AddressPath() + capabilityID, ok := m.CapabilityIDs[capabilityAddressPath] + if !ok { + if reporter != nil { + reporter.MissingCapabilityID( + addressPath.Address, + capabilityAddressPath, + ) + } + break + } + + newBorrowType, ok := oldCapability.BorrowType.(*interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Convert the old AuthAccount type to the new fully-entitled Account type + if newBorrowType.ReferencedType == interpreter.PrimitiveStaticTypeAuthAccount { //nolint:staticcheck + newBorrowType = fullyEntitledAccountReferenceStaticType + } + + newCapability := interpreter.NewUnmeteredCapabilityValue( + capabilityID, + oldCapability.Address, + newBorrowType, + ) + + if reporter != nil { + reporter.MigratedPathCapability( + addressPath.Address, + capabilityAddressPath, + newBorrowType, + ) + } + + return newCapability + } + + return nil +} diff --git a/migrations/capcons/linkmigration.go b/migrations/capcons/linkmigration.go new file mode 100644 index 0000000000..39a169ff97 --- /dev/null +++ b/migrations/capcons/linkmigration.go @@ -0,0 +1,317 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + goerrors "errors" + + "github.com/onflow/cadence/migrations" + "github.com/onflow/cadence/runtime/common" + "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 LinkMigrationReporter interface { + MigratedLink( + accountAddressPath interpreter.AddressPath, + capabilityID interpreter.UInt64Value, + ) + CyclicLink(err CyclicLinkError) + MissingTarget(accountAddressPath interpreter.AddressPath) +} + +// LinkMigration migrates all links to capability controllers. +type LinkMigration struct { + CapabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value + AccountIDGenerator stdlib.AccountIDGenerator + Reporter LinkMigrationReporter +} + +func (*LinkMigration) Name() string { + return "LinkMigration" +} + +var _ migrations.Migration = &LinkMigration{} + +func (m *LinkMigration) Migrate( + addressPath interpreter.AddressPath, + value interpreter.Value, + inter *interpreter.Interpreter, +) interpreter.Value { + + pathDomain := addressPath.Path.Domain + if pathDomain != common.PathDomainPublic && + pathDomain != common.PathDomainPrivate { + + return nil + } + + accountAddress := addressPath.Address + pathValue := addressPath.Path + + reporter := m.Reporter + accountIDGenerator := m.AccountIDGenerator + + locationRange := interpreter.EmptyLocationRange + + var borrowStaticType *interpreter.ReferenceStaticType + + switch readValue := value.(type) { + case *interpreter.CapabilityValue: + // Already migrated + return nil + + case interpreter.PathLinkValue: //nolint:staticcheck + var ok bool + borrowStaticType, ok = readValue.Type.(*interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + case interpreter.AccountLinkValue: //nolint:staticcheck + borrowStaticType = interpreter.NewReferenceStaticType( + nil, + interpreter.FullyEntitledAccountAccess, + interpreter.PrimitiveStaticTypeAccount, + ) + + default: + panic(errors.NewUnreachableError()) + } + + borrowType, ok := inter.MustConvertStaticToSemaType(borrowStaticType).(*sema.ReferenceType) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Get target + + target, _, err := m.getPathCapabilityFinalTarget( + inter, + accountAddress, + pathValue, + // TODO: + // Use top-most type to follow link all the way to final target + &sema.ReferenceType{ + Authorization: sema.UnauthorizedAccess, + Type: sema.AnyType, + }, + ) + if err != nil { + var cyclicLinkErr CyclicLinkError + if goerrors.As(err, &cyclicLinkErr) { + if reporter != nil { + reporter.CyclicLink(cyclicLinkErr) + } + + // TODO: really leave as-is? or still convert? + return nil + } + + panic(err) + } + + // Issue appropriate capability controller + + var capabilityID interpreter.UInt64Value + + switch target := target.(type) { + case nil: + reporter.MissingTarget(addressPath) + + // TODO: really leave as-is? or still convert? + return nil + + case pathCapabilityTarget: + + targetPath := interpreter.PathValue(target) + + capabilityID, _ = stdlib.IssueStorageCapabilityController( + inter, + locationRange, + accountIDGenerator, + accountAddress, + borrowType, + targetPath, + ) + + case accountCapabilityTarget: + capabilityID, _ = stdlib.IssueAccountCapabilityController( + inter, + locationRange, + accountIDGenerator, + accountAddress, + borrowType, + ) + + default: + panic(errors.NewUnreachableError()) + } + + // Record new capability ID in source path mapping. + // The mapping is used later for migrating path capabilities to ID capabilities, + // see CapabilityMigration. + + m.CapabilityIDs[addressPath] = capabilityID + + if reporter != nil { + reporter.MigratedLink(addressPath, capabilityID) + } + + addressValue := interpreter.AddressValue(addressPath.Address) + + return interpreter.NewCapabilityValue( + inter, + capabilityID, + addressValue, + borrowStaticType, + ) +} + +var authAccountReferenceStaticType = interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.PrimitiveStaticTypeAuthAccount, //nolint:staticcheck +) + +func (m *LinkMigration) getPathCapabilityFinalTarget( + inter *interpreter.Interpreter, + accountAddress common.Address, + pathValue interpreter.PathValue, + wantedBorrowType *sema.ReferenceType, +) ( + target capabilityTarget, + authorization interpreter.Authorization, + err error, +) { + + seenPaths := map[interpreter.PathValue]struct{}{} + paths := []interpreter.PathValue{pathValue} + + for { + // Detect cyclic links + + if _, ok := seenPaths[pathValue]; ok { + return nil, + interpreter.UnauthorizedAccess, + CyclicLinkError{ + Address: accountAddress, + Paths: paths, + } + } else { + seenPaths[pathValue] = struct{}{} + } + + domain := pathValue.Domain.Identifier() + identifier := pathValue.Identifier + + storageMapKey := interpreter.StringStorageMapKey(identifier) + + switch pathValue.Domain { + case common.PathDomainStorage: + + return pathCapabilityTarget(pathValue), + interpreter.ConvertSemaAccessToStaticAuthorization( + inter, + wantedBorrowType.Authorization, + ), + nil + + case common.PathDomainPublic, + common.PathDomainPrivate: + + value := inter.ReadStored(accountAddress, domain, storageMapKey) + if value == nil { + return nil, interpreter.UnauthorizedAccess, nil + } + + switch value := value.(type) { + case interpreter.PathLinkValue: //nolint:staticcheck + allowedType := inter.MustConvertStaticToSemaType(value.Type) + + if !sema.IsSubType(allowedType, wantedBorrowType) { + return nil, interpreter.UnauthorizedAccess, nil + } + + targetPath := value.TargetPath + paths = append(paths, targetPath) + pathValue = targetPath + + case interpreter.AccountLinkValue: //nolint:staticcheck + if !inter.IsSubTypeOfSemaType( + authAccountReferenceStaticType, + wantedBorrowType, + ) { + return nil, interpreter.UnauthorizedAccess, nil + } + + return accountCapabilityTarget(accountAddress), + interpreter.UnauthorizedAccess, + nil + + case *interpreter.CapabilityValue: + + // For backwards-compatibility, follow ID capability values + // which are published in the public or private domain + + capabilityBorrowType, ok := inter.MustConvertStaticToSemaType(value.BorrowType).(*sema.ReferenceType) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Do not borrow final target (i.e. do not require target to exist), + // just get target address/path + reference := stdlib.GetCheckedCapabilityControllerReference( + inter, + value.Address, + value.ID, + wantedBorrowType, + capabilityBorrowType, + ) + if reference == nil { + return nil, interpreter.UnauthorizedAccess, nil + } + + switch reference := reference.(type) { + case *interpreter.StorageReferenceValue: + accountAddress = reference.TargetStorageAddress + targetPath := reference.TargetPath + paths = append(paths, targetPath) + pathValue = targetPath + + case *interpreter.EphemeralReferenceValue: + accountValue := reference.Value.(*interpreter.SimpleCompositeValue) + address := accountValue.Fields[sema.AccountTypeAddressFieldName].(interpreter.AddressValue) + + return accountCapabilityTarget(address), + interpreter.UnauthorizedAccess, + nil + + default: + return nil, interpreter.UnauthorizedAccess, nil + } + + default: + panic(errors.NewUnreachableError()) + } + } + } +} diff --git a/migrations/capcons/migration.go b/migrations/capcons/migration.go deleted file mode 100644 index da1b77b6ec..0000000000 --- a/migrations/capcons/migration.go +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Cadence - The resource-oriented smart contract programming language - * - * Copyright Dapper Labs, Inc. - * - * 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 ( - goerrors "errors" - - "github.com/onflow/atree" - - "github.com/onflow/cadence/migrations" - "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/runtime/common" - "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 MigrationReporter interface { - LinkMigrationReporter - PathCapabilityMigrationReporter -} - -type LinkMigrationReporter interface { - MigratedLink( - accountAddressPath interpreter.AddressPath, - capabilityID interpreter.UInt64Value, - ) - CyclicLink(err CyclicLinkError) - MissingTarget( - accountAddress interpreter.AddressValue, - path interpreter.PathValue, - ) -} - -type PathCapabilityMigrationReporter interface { - MigratedPathCapability( - accountAddress common.Address, - addressPath interpreter.AddressPath, - borrowType *interpreter.ReferenceStaticType, - ) - MissingCapabilityID( - accountAddress common.Address, - addressPath interpreter.AddressPath, - ) -} - -type Migration struct { - storage *runtime.Storage - interpreter *interpreter.Interpreter - capabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value - addressIterator migrations.AddressIterator - accountIDGenerator stdlib.AccountIDGenerator -} - -func NewMigration( - storage *runtime.Storage, - interpreter *interpreter.Interpreter, - addressIterator migrations.AddressIterator, - accountIDGenerator stdlib.AccountIDGenerator, -) (*Migration, error) { - - return &Migration{ - storage: storage, - interpreter: interpreter, - addressIterator: addressIterator, - accountIDGenerator: accountIDGenerator, - }, nil -} - -// Migrate migrates the links to capability controllers, -// and all path capabilities and account capabilities to ID capabilities, -// in all accounts of the given iterator. -func (m *Migration) Migrate( - reporter MigrationReporter, -) error { - m.capabilityIDs = make(map[interpreter.AddressPath]interpreter.UInt64Value) - defer func() { - m.capabilityIDs = nil - }() - m.migrateLinks(reporter) - - m.addressIterator.Reset() - m.migratePathCapabilities(reporter) - - return m.storage.Commit(m.interpreter, false) -} - -// migrateLinks migrates the links to capability controllers -// in all accounts of the given iterator. -// It constructs a source path to capability ID mapping, -// which is later needed to path capabilities to ID capabilities. -func (m *Migration) migrateLinks( - reporter LinkMigrationReporter, -) { - for { - address := m.addressIterator.NextAddress() - if address == common.ZeroAddress { - break - } - - m.migrateLinksInAccount( - address, - reporter, - ) - } -} - -// migrateLinksInAccount migrates the links in the given account to capability controllers -// It records an entry in the source path to capability ID mapping, -// which is later needed to migrate path capabilities to ID capabilities. -func (m *Migration) migrateLinksInAccount( - accountAddress common.Address, - reporter LinkMigrationReporter, -) { - - migrateDomain := func(domain common.PathDomain) { - m.migrateAccountLinksInAccountDomain( - accountAddress, - domain, - reporter, - ) - } - - migrateDomain(common.PathDomainPublic) - migrateDomain(common.PathDomainPrivate) -} - -// migrateAccountLinksInAccountDomain migrates the links in the given account's storage domain -// to capability controllers. -// It records an entry in the source path to capability ID mapping, -// which is later needed to migrate path capabilities to ID capabilities. -func (m *Migration) migrateAccountLinksInAccountDomain( - accountAddress common.Address, - domain common.PathDomain, - reporter LinkMigrationReporter, -) { - accountAddressValue := interpreter.AddressValue(accountAddress) - - storageMap := m.storage.GetStorageMap(accountAddress, domain.Identifier(), false) - if storageMap == nil { - return - } - - iterator := storageMap.Iterator(m.interpreter) - - count := storageMap.Count() - if count > 0 { - var identifiers []string - - for key := iterator.NextKey(); key != nil; key = iterator.NextKey() { - // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey - identifier := string(key.(interpreter.StringAtreeValue)) - identifiers = append(identifiers, identifier) - } - - for _, identifier := range identifiers { - pathValue := interpreter.NewUnmeteredPathValue(domain, identifier) - - m.migrateLink( - accountAddressValue, - pathValue, - reporter, - ) - } - } -} - -// migrateAccountLinksInAccountDomain migrates the links in the given account's storage domain -// to capability controllers. -// It constructs a source path to ID mapping, -// which is later needed to migrate path capabilities to ID capabilities. -func (m *Migration) migrateLink( - accountAddressValue interpreter.AddressValue, - pathValue interpreter.PathValue, - reporter LinkMigrationReporter, -) { - capabilityID := m.migrateLinkToCapabilityController(accountAddressValue, pathValue, reporter) - if capabilityID == 0 { - return - } - - // Record new capability ID in source path mapping. - // The mapping is used later for migrating path capabilities to ID capabilities. - - accountAddress := accountAddressValue.ToAddress() - accountAddressPath := interpreter.AddressPath{ - Address: accountAddress, - Path: pathValue, - } - - m.capabilityIDs[accountAddressPath] = capabilityID - - if reporter != nil { - reporter.MigratedLink(accountAddressPath, capabilityID) - } -} - -// migratePathCapabilities migrates the path capabilities to ID capabilities -// in all accounts of the given iterator. -// It uses the source path to capability ID mapping which was constructed in migrateLinks. -func (m *Migration) migratePathCapabilities( - reporter PathCapabilityMigrationReporter, -) { - for { - address := m.addressIterator.NextAddress() - if address == common.ZeroAddress { - break - } - - m.migratePathCapabilitiesInAccount(address, reporter) - } -} - -var pathDomainStorage = common.PathDomainStorage.Identifier() - -func (m *Migration) migratePathCapabilitiesInAccount( - accountAddress common.Address, - reporter PathCapabilityMigrationReporter, -) { - - storageMap := m.storage.GetStorageMap(accountAddress, pathDomainStorage, false) - if storageMap == nil { - return - } - - iterator := storageMap.Iterator(m.interpreter) - - count := storageMap.Count() - if count > 0 { - - var keys []string - - // Read all keys first, then migrate. - // Migrating (mutating) during iteration is not supported. - for key, _ := iterator.Next(); key != nil; key, _ = iterator.Next() { - // TODO: unfortunately, the iterator only returns an atree.Value, not a StorageMapKey - identifier := string(key.(interpreter.StringAtreeValue)) - keys = append(keys, identifier) - } - - for _, key := range keys { - storageKey := interpreter.StringStorageMapKey(key) - - value := storageMap.ReadValue(nil, storageKey) - - newValue := m.migratePathCapability( - accountAddress, - value, - reporter, - ) - - if newValue != nil { - storageMap.SetValue( - m.interpreter, - storageKey, - newValue, - ) - } - } - } -} - -var fullyEntitledAccountReferenceStaticType = interpreter.ConvertSemaReferenceTypeToStaticReferenceType( - nil, - sema.FullyEntitledAccountReferenceType, -) - -// migratePathCapability migrates a path capability to an ID capability in the given value. -// If a value is returned, the value must be updated with the replacement in the parent. -// If nil is returned, the value was not updated and no operation has to be performed. -func (m *Migration) migratePathCapability( - accountAddress common.Address, - value interpreter.Value, - reporter PathCapabilityMigrationReporter, -) interpreter.Value { - locationRange := interpreter.EmptyLocationRange - - switch value := value.(type) { - case *interpreter.PathCapabilityValue: //nolint:staticcheck - - // Migrate the path capability to an ID capability - - oldCapability := value - - addressPath := oldCapability.AddressPath() - capabilityID, ok := m.capabilityIDs[addressPath] - if !ok { - if reporter != nil { - reporter.MissingCapabilityID(accountAddress, addressPath) - } - break - } - - newBorrowType, ok := oldCapability.BorrowType.(*interpreter.ReferenceStaticType) - if !ok { - panic(errors.NewUnreachableError()) - } - - // Convert the old AuthAccount type to the new fully-entitled Account type - if newBorrowType.ReferencedType == interpreter.PrimitiveStaticTypeAuthAccount { //nolint:staticcheck - newBorrowType = fullyEntitledAccountReferenceStaticType - } - - newCapability := interpreter.NewUnmeteredCapabilityValue( - capabilityID, - oldCapability.Address, - newBorrowType, - ) - - if reporter != nil { - reporter.MigratedPathCapability( - accountAddress, - addressPath, - newBorrowType, - ) - } - - return newCapability - - case *interpreter.CompositeValue: - composite := value - - // Migrate composite's fields - - // Read all keys first, then migrate. - // Migrating (mutating) during iteration is not supported. - - var fieldNames []string - - composite.ForEachFieldName(func(fieldName string) (resume bool) { - fieldNames = append(fieldNames, fieldName) - - // Continue iteration - return true - }) - - for _, fieldName := range fieldNames { - fieldValue := composite.GetField(m.interpreter, interpreter.EmptyLocationRange, fieldName) - - newFieldValue := m.migratePathCapability(accountAddress, fieldValue, reporter) - if newFieldValue != nil { - composite.SetMember( - m.interpreter, - locationRange, - fieldName, - newFieldValue, - ) - } - } - - // The composite itself does not have to be replaced - - return nil - - case *interpreter.SomeValue: - innerValue := value.InnerValue(m.interpreter, locationRange) - newInnerValue := m.migratePathCapability(accountAddress, innerValue, reporter) - if newInnerValue != nil { - return interpreter.NewSomeValueNonCopying(m.interpreter, newInnerValue) - } - - return nil - - case *interpreter.ArrayValue: - array := value - - // Migrate array's elements - - // Do not use iteration using the array iterator, - // migrating (mutating) during iteration is not supported. - - count := array.Count() - for index := 0; index < count; index++ { - element := array.Get(m.interpreter, locationRange, index) - - newElement := m.migratePathCapability(accountAddress, element, reporter) - if newElement != nil { - array.Set( - m.interpreter, - locationRange, - index, - newElement, - ) - } - } - - // The array itself does not have to be replaced - - return nil - - case *interpreter.DictionaryValue: - dictionary := value - - // Migrate dictionary's values - - // Read all keys first, then migrate. - // Migrating (mutating) during iteration is not supported. - - var keys []interpreter.Value - - dictionary.IterateKeys( - m.interpreter, - func(key interpreter.Value) (resume bool) { - keys = append(keys, key) - - // Continue iteration - return true - }, - ) - - for _, key := range keys { - - // Keys cannot be capabilities at the moment, - // so this should never occur in stored data - - switch key.(type) { - case *interpreter.CapabilityValue, - *interpreter.PathCapabilityValue: //nolint:staticcheck - - panic(errors.NewUnreachableError()) - } - - // Migrate the value - - value, ok := dictionary.Get(m.interpreter, locationRange, key) - if !ok { - panic(errors.NewUnreachableError()) - } - - newValue := m.migratePathCapability(accountAddress, value, reporter) - - if newValue != nil { - dictionary.Insert( - m.interpreter, - locationRange, - key, - newValue, - ) - } - } - - // The dictionary itself does not have to be replaced - - return nil - - case interpreter.NumberValue, - *interpreter.StringValue, - interpreter.CharacterValue, - interpreter.BoolValue, - interpreter.TypeValue, - interpreter.PathValue, - interpreter.NilValue: - - // Primitive values do not have to be updated, - // as they do not contain path capabilities. - - return nil - - case *interpreter.CapabilityValue: - // Already migrated - return nil - - default: - panic(errors.NewUnexpectedError("unsupported value type: %T", value)) - } - - return nil -} - -func (m *Migration) migrateLinkToCapabilityController( - accountAddressValue interpreter.AddressValue, - pathValue interpreter.PathValue, - reporter LinkMigrationReporter, -) interpreter.UInt64Value { - - locationRange := interpreter.EmptyLocationRange - - address := accountAddressValue.ToAddress() - - domain := pathValue.Domain.Identifier() - identifier := pathValue.Identifier - - storageMapKey := interpreter.StringStorageMapKey(identifier) - - readValue := m.interpreter.ReadStored(address, domain, storageMapKey) - if readValue == nil { - return 0 - } - - var borrowStaticType *interpreter.ReferenceStaticType - - switch readValue := readValue.(type) { - case *interpreter.CapabilityValue: - // Already migrated - return 0 - - case interpreter.PathLinkValue: //nolint:staticcheck - var ok bool - borrowStaticType, ok = readValue.Type.(*interpreter.ReferenceStaticType) - if !ok { - panic(errors.NewUnreachableError()) - } - - case interpreter.AccountLinkValue: //nolint:staticcheck - borrowStaticType = interpreter.NewReferenceStaticType( - nil, - interpreter.FullyEntitledAccountAccess, - interpreter.PrimitiveStaticTypeAccount, - ) - - default: - panic(errors.NewUnreachableError()) - } - - borrowType, ok := m.interpreter.MustConvertStaticToSemaType(borrowStaticType).(*sema.ReferenceType) - if !ok { - panic(errors.NewUnreachableError()) - } - - // Get target - - target, _, err := m.getPathCapabilityFinalTarget( - address, - pathValue, - // TODO: - // Use top-most type to follow link all the way to final target - &sema.ReferenceType{ - Authorization: sema.UnauthorizedAccess, - Type: sema.AnyType, - }, - ) - if err != nil { - var cyclicLinkErr CyclicLinkError - if goerrors.As(err, &cyclicLinkErr) { - reporter.CyclicLink(cyclicLinkErr) - return 0 - } - panic(err) - } - - // Issue appropriate capability controller - - var capabilityID interpreter.UInt64Value - - switch target := target.(type) { - case nil: - reporter.MissingTarget(accountAddressValue, pathValue) - return 0 - - case pathCapabilityTarget: - - targetPath := interpreter.PathValue(target) - - capabilityID, _ = stdlib.IssueStorageCapabilityController( - m.interpreter, - locationRange, - m.accountIDGenerator, - address, - borrowType, - targetPath, - ) - - case accountCapabilityTarget: - capabilityID, _ = stdlib.IssueAccountCapabilityController( - m.interpreter, - locationRange, - m.accountIDGenerator, - address, - borrowType, - ) - - default: - panic(errors.NewUnreachableError()) - } - - // Publish: overwrite link value with capability - - capabilityValue := interpreter.NewCapabilityValue( - m.interpreter, - capabilityID, - accountAddressValue, - borrowStaticType, - ) - - capabilityValue, ok = capabilityValue.Transfer( - m.interpreter, - locationRange, - atree.Address(address), - true, - nil, - nil, - ).(*interpreter.CapabilityValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - m.interpreter.WriteStored( - address, - domain, - storageMapKey, - capabilityValue, - ) - - return capabilityID -} - -var authAccountReferenceStaticType = interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeAuthAccount, //nolint:staticcheck -) - -func (m *Migration) getPathCapabilityFinalTarget( - accountAddress common.Address, - pathValue interpreter.PathValue, - wantedBorrowType *sema.ReferenceType, -) ( - target capabilityTarget, - authorization interpreter.Authorization, - err error, -) { - - seenPaths := map[interpreter.PathValue]struct{}{} - paths := []interpreter.PathValue{pathValue} - - for { - // Detect cyclic links - - if _, ok := seenPaths[pathValue]; ok { - return nil, - interpreter.UnauthorizedAccess, - CyclicLinkError{ - Address: accountAddress, - Paths: paths, - } - } else { - seenPaths[pathValue] = struct{}{} - } - - domain := pathValue.Domain.Identifier() - identifier := pathValue.Identifier - - storageMapKey := interpreter.StringStorageMapKey(identifier) - - switch pathValue.Domain { - case common.PathDomainStorage: - - return pathCapabilityTarget(pathValue), - interpreter.ConvertSemaAccessToStaticAuthorization( - m.interpreter, - wantedBorrowType.Authorization, - ), - nil - - case common.PathDomainPublic, - common.PathDomainPrivate: - - value := m.interpreter.ReadStored(accountAddress, domain, storageMapKey) - if value == nil { - return nil, interpreter.UnauthorizedAccess, nil - } - - switch value := value.(type) { - case interpreter.PathLinkValue: //nolint:staticcheck - allowedType := m.interpreter.MustConvertStaticToSemaType(value.Type) - - if !sema.IsSubType(allowedType, wantedBorrowType) { - return nil, interpreter.UnauthorizedAccess, nil - } - - targetPath := value.TargetPath - paths = append(paths, targetPath) - pathValue = targetPath - - case interpreter.AccountLinkValue: //nolint:staticcheck - if !m.interpreter.IsSubTypeOfSemaType( - authAccountReferenceStaticType, - wantedBorrowType, - ) { - return nil, interpreter.UnauthorizedAccess, nil - } - - return accountCapabilityTarget(accountAddress), - interpreter.UnauthorizedAccess, - nil - - case *interpreter.CapabilityValue: - - // For backwards-compatibility, follow ID capability values - // which are published in the public or private domain - - capabilityBorrowType, ok := - m.interpreter.MustConvertStaticToSemaType(value.BorrowType).(*sema.ReferenceType) - if !ok { - panic(errors.NewUnreachableError()) - } - - // Do not borrow final target (i.e. do not require target to exist), - // just get target address/path - reference := stdlib.GetCheckedCapabilityControllerReference( - m.interpreter, - value.Address, - value.ID, - wantedBorrowType, - capabilityBorrowType, - ) - if reference == nil { - return nil, interpreter.UnauthorizedAccess, nil - } - - switch reference := reference.(type) { - case *interpreter.StorageReferenceValue: - accountAddress = reference.TargetStorageAddress - targetPath := reference.TargetPath - paths = append(paths, targetPath) - pathValue = targetPath - - case *interpreter.EphemeralReferenceValue: - accountValue := reference.Value.(*interpreter.SimpleCompositeValue) - address := accountValue.Fields[sema.AccountTypeAddressFieldName].(interpreter.AddressValue) - - return accountCapabilityTarget(address), - interpreter.UnauthorizedAccess, - nil - - default: - return nil, interpreter.UnauthorizedAccess, nil - } - - default: - panic(errors.NewUnreachableError()) - } - } - } -} diff --git a/migrations/capcons/migration_test.go b/migrations/capcons/migration_test.go index 80e137e453..0478d90f2d 100644 --- a/migrations/capcons/migration_test.go +++ b/migrations/capcons/migration_test.go @@ -64,20 +64,16 @@ type testCapConsMissingCapabilityID struct { addressPath interpreter.AddressPath } -type testMissingTarget struct { - accountAddressValue interpreter.AddressValue - path interpreter.PathValue -} - type testMigrationReporter struct { linkMigrations []testCapConsLinkMigration pathCapabilityMigrations []testCapConsPathCapabilityMigration missingCapabilityIDs []testCapConsMissingCapabilityID cyclicLinkErrors []CyclicLinkError - missingTargets []testMissingTarget + missingTargets []interpreter.AddressPath } -var _ MigrationReporter = &testMigrationReporter{} +var _ LinkMigrationReporter = &testMigrationReporter{} +var _ CapabilityMigrationReporter = &testMigrationReporter{} func (t *testMigrationReporter) MigratedLink( accountAddressPath interpreter.AddressPath, @@ -128,15 +124,11 @@ func (t *testMigrationReporter) CyclicLink(cyclicLinkError CyclicLinkError) { } func (t *testMigrationReporter) MissingTarget( - accountAddressValue interpreter.AddressValue, - path interpreter.PathValue, + accountAddressPath interpreter.AddressPath, ) { t.missingTargets = append( t.missingTargets, - testMissingTarget{ - accountAddressValue: accountAddressValue, - path: path, - }, + accountAddressPath, ) } @@ -433,21 +425,39 @@ func testPathCapabilityValueMigration( // Migrate - migrator, err := NewMigration( - storage, - inter, + migration := migrations.NewStorageMigration(inter, storage) + + capabilityIDs := map[interpreter.AddressPath]interpreter.UInt64Value{} + + reporter := &testMigrationReporter{} + + migration.Migrate( &migrations.AddressSliceIterator{ Addresses: []common.Address{ testAddress, }, }, - &testAccountIDGenerator{}, + nil, + &LinkMigration{ + CapabilityIDs: capabilityIDs, + AccountIDGenerator: &testAccountIDGenerator{}, + Reporter: reporter, + }, ) require.NoError(t, err) - reporter := &testMigrationReporter{} - - err = migrator.Migrate(reporter) + migration.Migrate( + &migrations.AddressSliceIterator{ + Addresses: []common.Address{ + testAddress, + }, + }, + nil, + &CapabilityMigration{ + CapabilityIDs: capabilityIDs, + Reporter: reporter, + }, + ) require.NoError(t, err) // Check migrated capabilities @@ -1060,7 +1070,7 @@ func testLinkMigration( accountLinks []interpreter.PathValue, expectedLinkMigrations []testCapConsLinkMigration, expectedCyclicLinkErrors []CyclicLinkError, - expectedMissingTargets []testMissingTarget, + expectedMissingTargets []interpreter.AddressPath, ) { require.True(t, len(expectedLinkMigrations) == 0 || @@ -1143,23 +1153,27 @@ func testLinkMigration( // Migrate - migrator, err := NewMigration( - storage, - inter, + migration := migrations.NewStorageMigration(inter, storage) + + capabilityIDs := map[interpreter.AddressPath]interpreter.UInt64Value{} + + reporter := &testMigrationReporter{} + + migration.Migrate( &migrations.AddressSliceIterator{ Addresses: []common.Address{ testAddress, }, }, - &testAccountIDGenerator{}, + nil, + &LinkMigration{ + CapabilityIDs: capabilityIDs, + AccountIDGenerator: &testAccountIDGenerator{}, + Reporter: reporter, + }, ) require.NoError(t, err) - reporter := &testMigrationReporter{} - - err = migrator.Migrate(reporter) - require.NoError(t, err) - // Assert assert.Equal(t, @@ -1186,7 +1200,7 @@ func TestLinkMigration(t *testing.T) { accountLinks []interpreter.PathValue expectedLinkMigrations []testCapConsLinkMigration expectedCyclicLinkErrors []CyclicLinkError - expectedMissingTargets []testMissingTarget + expectedMissingTargets []interpreter.AddressPath } linkTestCases := []linkTestCase{ @@ -1225,7 +1239,7 @@ func TestLinkMigration(t *testing.T) { accountAddressPath: interpreter.AddressPath{ Address: testAddress, Path: interpreter.PathValue{ - Domain: common.PathDomainPublic, + Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, }, }, @@ -1235,7 +1249,7 @@ func TestLinkMigration(t *testing.T) { accountAddressPath: interpreter.AddressPath{ Address: testAddress, Path: interpreter.PathValue{ - Domain: common.PathDomainPrivate, + Domain: common.PathDomainPublic, Identifier: testPathIdentifier, }, }, @@ -1428,15 +1442,15 @@ func TestLinkMigration(t *testing.T) { Address: testAddress, Paths: []interpreter.PathValue{ { - Domain: common.PathDomainPublic, + Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, }, { - Domain: common.PathDomainPrivate, + Domain: common.PathDomainPublic, Identifier: testPathIdentifier, }, { - Domain: common.PathDomainPublic, + Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, }, }, @@ -1445,15 +1459,15 @@ func TestLinkMigration(t *testing.T) { Address: testAddress, Paths: []interpreter.PathValue{ { - Domain: common.PathDomainPrivate, + Domain: common.PathDomainPublic, Identifier: testPathIdentifier, }, { - Domain: common.PathDomainPublic, + Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, }, { - Domain: common.PathDomainPrivate, + Domain: common.PathDomainPublic, Identifier: testPathIdentifier, }, }, @@ -1477,10 +1491,10 @@ func TestLinkMigration(t *testing.T) { borrowType: testRReferenceStaticType, }, }, - expectedMissingTargets: []testMissingTarget{ + expectedMissingTargets: []interpreter.AddressPath{ { - accountAddressValue: interpreter.AddressValue(testAddress), - path: interpreter.PathValue{ + Address: testAddress, + Path: interpreter.PathValue{ Identifier: testPathIdentifier, Domain: common.PathDomainPublic, }, From 6c9ef05249d3b183b9fb9b72215b7b0d74f53bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 6 Dec 2023 09:20:32 -0800 Subject: [PATCH 36/51] add new location range argument --- migrations/capcons/linkmigration.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/migrations/capcons/linkmigration.go b/migrations/capcons/linkmigration.go index 39a169ff97..95a8468d7b 100644 --- a/migrations/capcons/linkmigration.go +++ b/migrations/capcons/linkmigration.go @@ -203,6 +203,8 @@ func (m *LinkMigration) getPathCapabilityFinalTarget( err error, ) { + locationRange := interpreter.EmptyLocationRange + seenPaths := map[interpreter.PathValue]struct{}{} paths := []interpreter.PathValue{pathValue} @@ -285,6 +287,7 @@ func (m *LinkMigration) getPathCapabilityFinalTarget( value.ID, wantedBorrowType, capabilityBorrowType, + locationRange, ) if reference == nil { return nil, interpreter.UnauthorizedAccess, nil From 92b13a4abff134e0c335cc0b25285270df29ed17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 6 Dec 2023 09:55:19 -0800 Subject: [PATCH 37/51] fix lint --- migrations/migration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 02f76597db..8d48ad4423 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -202,7 +202,7 @@ func TestMultipleMigrations(t *testing.T) { // Check the reporter require.Equal(t, map[interpreter.AddressPath][]string{ - interpreter.AddressPath{ + { Address: account, Path: interpreter.PathValue{ Domain: pathDomain, @@ -211,7 +211,7 @@ func TestMultipleMigrations(t *testing.T) { }: { "testInt8Migration", }, - interpreter.AddressPath{ + { Address: account, Path: interpreter.PathValue{ Domain: pathDomain, From 0292cfe375fb46c9362625486861fbfe5c83970e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Dec 2023 13:10:53 -0800 Subject: [PATCH 38/51] remove duplicated test case --- migrations/capcons/migration_test.go | 32 ---------------------------- 1 file changed, 32 deletions(-) diff --git a/migrations/capcons/migration_test.go b/migrations/capcons/migration_test.go index 0478d90f2d..613cf105be 100644 --- a/migrations/capcons/migration_test.go +++ b/migrations/capcons/migration_test.go @@ -1375,38 +1375,6 @@ func TestLinkMigration(t *testing.T) { }, }, }, - // TODO: verify - { - name: "Path links, valid chain (public -> storage), different borrow type", - pathLinks: []testLink{ - { - // Equivalent to: - // link<&Test.S>(/public/test, target: /storage/test) - sourcePath: interpreter.PathValue{ - Domain: common.PathDomainPublic, - Identifier: testPathIdentifier, - }, - targetPath: interpreter.PathValue{ - Domain: common.PathDomainStorage, - Identifier: testPathIdentifier, - }, - // - borrowType: testSReferenceStaticType, - }, - }, - expectedLinkMigrations: []testCapConsLinkMigration{ - { - accountAddressPath: interpreter.AddressPath{ - Address: testAddress, - Path: interpreter.PathValue{ - Domain: common.PathDomainPublic, - Identifier: testPathIdentifier, - }, - }, - capabilityID: 1, - }, - }, - }, { name: "Path links, cyclic chain (public -> private -> public)", pathLinks: []testLink{ From 3a0bff5ad5fdf2ab3f73e3aefcb6fc8ade5be515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Dec 2023 13:11:13 -0800 Subject: [PATCH 39/51] turn TODO comment into explanation --- migrations/capcons/migration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/capcons/migration_test.go b/migrations/capcons/migration_test.go index 613cf105be..c1b7114fbe 100644 --- a/migrations/capcons/migration_test.go +++ b/migrations/capcons/migration_test.go @@ -714,7 +714,7 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, }, }, - // TODO: verify + // NOTE: this migrates a broken capability to a broken capability { name: "Path links, valid chain (public -> storage), different borrow type", // Equivalent to: getCapability<&Test.R>(/public/test) From 297682f6b53b9867ba92d59a8dc2b4658dff4fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Dec 2023 13:13:03 -0800 Subject: [PATCH 40/51] check if reporter exists --- migrations/capcons/linkmigration.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/migrations/capcons/linkmigration.go b/migrations/capcons/linkmigration.go index 95a8468d7b..392edfc8db 100644 --- a/migrations/capcons/linkmigration.go +++ b/migrations/capcons/linkmigration.go @@ -135,7 +135,9 @@ func (m *LinkMigration) Migrate( switch target := target.(type) { case nil: - reporter.MissingTarget(addressPath) + if reporter != nil { + reporter.MissingTarget(addressPath) + } // TODO: really leave as-is? or still convert? return nil From 3784a40f4f774854a068e0f7a35068a3ca938b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Dec 2023 13:18:58 -0800 Subject: [PATCH 41/51] improve comment --- migrations/capcons/linkmigration.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/migrations/capcons/linkmigration.go b/migrations/capcons/linkmigration.go index 392edfc8db..30d5365c91 100644 --- a/migrations/capcons/linkmigration.go +++ b/migrations/capcons/linkmigration.go @@ -273,8 +273,13 @@ func (m *LinkMigration) getPathCapabilityFinalTarget( case *interpreter.CapabilityValue: - // For backwards-compatibility, follow ID capability values - // which are published in the public or private domain + // Follow ID capability values which are published in the public or private domain. + // This is needed for two reasons: + // 1. Support for migrating path capabilities to ID capabilities was already enabled on Testnet + // 2. During the migration of a whole link chain, + // the order of the migration of the individual links is undefined, + // so it's possible that a capability value is encountered when determining the final target, + // when a part of the full link chain was already previously migrated. capabilityBorrowType, ok := inter.MustConvertStaticToSemaType(value.BorrowType).(*sema.ReferenceType) if !ok { From b3778ebfa513f02c7ca2398b3ddbcfa82f362e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Dec 2023 13:23:02 -0800 Subject: [PATCH 42/51] use new CompositeValue.ForEachFieldName and DictionaryValue.IterateKeys functions --- migrations/migration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/migration.go b/migrations/migration.go index c4b6841c4e..362e0c7e32 100644 --- a/migrations/migration.go +++ b/migrations/migration.go @@ -140,7 +140,7 @@ func (m *StorageMigration) migrateNestedValue( // Read the field names first, so the iteration wouldn't be affected // by the modification of the nested values. var fieldNames []string - composite.ForEachField(nil, func(fieldName string, fieldValue interpreter.Value) (resume bool) { + composite.ForEachFieldName(func(fieldName string) (resume bool) { fieldNames = append(fieldNames, fieldName) return true }) @@ -165,7 +165,7 @@ func (m *StorageMigration) migrateNestedValue( // Read the keys first, so the iteration wouldn't be affected // by the modification of the nested values. var existingKeys []interpreter.Value - dictionary.Iterate(m.interpreter, func(key, _ interpreter.Value) (resume bool) { + dictionary.IterateKeys(m.interpreter, func(key interpreter.Value) (resume bool) { existingKeys = append(existingKeys, key) return true }) From 676ad21401dc58a9055c8a288543b5b59fc72b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Dec 2023 13:59:24 -0800 Subject: [PATCH 43/51] separate commit from migration, so multiple sequential migrations can be performed --- migrations/account_type/migration_test.go | 6 ++++++ migrations/capcons/migration_test.go | 7 ++++--- migrations/migration.go | 2 ++ migrations/migration_test.go | 2 ++ migrations/string_normalization/migration_test.go | 2 ++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/migrations/account_type/migration_test.go b/migrations/account_type/migration_test.go index f80aedb3fd..2fd1f81804 100644 --- a/migrations/account_type/migration_test.go +++ b/migrations/account_type/migration_test.go @@ -342,6 +342,8 @@ func TestTypeValueMigration(t *testing.T) { NewAccountTypeMigration(), ) + migration.Commit() + // Check reported migrated paths for identifier, test := range testCases { addressPath := interpreter.AddressPath{ @@ -650,6 +652,8 @@ func TestNestedTypeValueMigration(t *testing.T) { NewAccountTypeMigration(), ) + migration.Commit() + // Assert: Traverse through the storage and see if the values are updated now. storageMap := storage.GetStorageMap(account, pathDomain.Identifier(), false) @@ -761,6 +765,8 @@ func TestValuesWithStaticTypeMigration(t *testing.T) { NewAccountTypeMigration(), ) + migration.Commit() + // Assert: Traverse through the storage and see if the values are updated now. storageMap := storage.GetStorageMap(account, pathDomain.Identifier(), false) diff --git a/migrations/capcons/migration_test.go b/migrations/capcons/migration_test.go index c1b7114fbe..8c268221fa 100644 --- a/migrations/capcons/migration_test.go +++ b/migrations/capcons/migration_test.go @@ -444,7 +444,6 @@ func testPathCapabilityValueMigration( Reporter: reporter, }, ) - require.NoError(t, err) migration.Migrate( &migrations.AddressSliceIterator{ @@ -458,7 +457,8 @@ func testPathCapabilityValueMigration( Reporter: reporter, }, ) - require.NoError(t, err) + + migration.Commit() // Check migrated capabilities @@ -1172,7 +1172,8 @@ func testLinkMigration( Reporter: reporter, }, ) - require.NoError(t, err) + + migration.Commit() // Assert diff --git a/migrations/migration.go b/migrations/migration.go index 362e0c7e32..8ac80a59d5 100644 --- a/migrations/migration.go +++ b/migrations/migration.go @@ -66,7 +66,9 @@ func (m *StorageMigration) Migrate( migrations, ) } +} +func (m *StorageMigration) Commit() { err := m.storage.Commit(m.interpreter, false) if err != nil { panic(err) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 8d48ad4423..628da4284b 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -175,6 +175,8 @@ func TestMultipleMigrations(t *testing.T) { testInt8Migration{}, ) + migration.Commit() + // Assert: Traverse through the storage and see if the values are updated now. storageMap := storage.GetStorageMap(account, pathDomain.Identifier(), false) diff --git a/migrations/string_normalization/migration_test.go b/migrations/string_normalization/migration_test.go index c6385e4a82..074adb4c45 100644 --- a/migrations/string_normalization/migration_test.go +++ b/migrations/string_normalization/migration_test.go @@ -253,6 +253,8 @@ func TestStringNormalizingMigration(t *testing.T) { NewStringNormalizingMigration(), ) + migration.Commit() + // Assert: Traverse through the storage and see if the values are updated now. storageMap := storage.GetStorageMap(account, pathDomain.Identifier(), false) From bd1c39ee10a43c9290b17590950e602163f18352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Dec 2023 13:59:35 -0800 Subject: [PATCH 44/51] clean up, comment --- migrations/capcons/capabilitymigration.go | 2 ++ migrations/capcons/linkmigration.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/migrations/capcons/capabilitymigration.go b/migrations/capcons/capabilitymigration.go index 5e16c82623..f2a4fcdc44 100644 --- a/migrations/capcons/capabilitymigration.go +++ b/migrations/capcons/capabilitymigration.go @@ -38,6 +38,8 @@ type CapabilityMigrationReporter interface { ) } +// CapabilityMigration migrates all path capabilities to ID capabilities, +// using the path to ID capability controller mapping generated by LinkMigration. type CapabilityMigration struct { CapabilityIDs map[interpreter.AddressPath]interpreter.UInt64Value Reporter CapabilityMigrationReporter diff --git a/migrations/capcons/linkmigration.go b/migrations/capcons/linkmigration.go index 30d5365c91..b3bc4f8846 100644 --- a/migrations/capcons/linkmigration.go +++ b/migrations/capcons/linkmigration.go @@ -45,12 +45,12 @@ type LinkMigration struct { Reporter LinkMigrationReporter } +var _ migrations.Migration = &LinkMigration{} + func (*LinkMigration) Name() string { return "LinkMigration" } -var _ migrations.Migration = &LinkMigration{} - func (m *LinkMigration) Migrate( addressPath interpreter.AddressPath, value interpreter.Value, From cec29254ea1b58f82aa3c35624816010f17e52f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Dec 2023 09:54:38 -0800 Subject: [PATCH 45/51] fix lint --- migrations/account_type/migration.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/migrations/account_type/migration.go b/migrations/account_type/migration.go index d6db867e8a..90113eb4c0 100644 --- a/migrations/account_type/migration.go +++ b/migrations/account_type/migration.go @@ -48,16 +48,16 @@ func (AccountTypeMigration) Migrate( case interpreter.TypeValue: convertedType := maybeConvertAccountType(value.Type) if convertedType == nil { - return + return } - return interpreter.NewTypeValue(nil, convertedType) + return interpreter.NewTypeValue(nil, convertedType) case *interpreter.CapabilityValue: convertedBorrowType := maybeConvertAccountType(value.BorrowType) if convertedBorrowType == nil { - return - } - return interpreter.NewUnmeteredCapabilityValue(value.ID, value.Address, convertedBorrowType) + return + } + return interpreter.NewUnmeteredCapabilityValue(value.ID, value.Address, convertedBorrowType) case *interpreter.AccountCapabilityControllerValue: convertedBorrowType := maybeConvertAccountType(value.BorrowType) @@ -74,7 +74,11 @@ func (AccountTypeMigration) Migrate( return } borrowType := convertedBorrowType.(*interpreter.ReferenceStaticType) - return interpreter.NewUnmeteredStorageCapabilityControllerValue(borrowType, value.CapabilityID, value.TargetPath) + return interpreter.NewUnmeteredStorageCapabilityControllerValue( + borrowType, + value.CapabilityID, + value.TargetPath, + ) } return From e46b8a9f9cf8047b7a0f2a8157cddcfe5a072740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Dec 2023 10:31:22 -0800 Subject: [PATCH 46/51] simplify --- runtime/interpreter/interpreter.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 8326abb795..5f5cbbc9da 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4528,12 +4528,8 @@ func (interpreter *Interpreter) ConvertStaticToSemaType(staticType StaticType) ( return ConvertStaticToSemaType( config.MemoryGauge, staticType, - func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.InterfaceType, error) { - return interpreter.GetInterfaceType(location, qualifiedIdentifier, typeID) - }, - func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.CompositeType, error) { - return interpreter.GetCompositeType(location, qualifiedIdentifier, typeID) - }, + interpreter.GetInterfaceType, + interpreter.GetCompositeType, interpreter.getEntitlement, interpreter.getEntitlementMapType, ) From e08a7907915b2260dad7c72dfa8e3d907401ab44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Dec 2023 10:36:06 -0800 Subject: [PATCH 47/51] also handle path to account link chains --- migrations/account_type/migration.go | 1 + migrations/capcons/migration_test.go | 124 ++++++++++++++------- runtime/interpreter/primitivestatictype.go | 26 +++-- 3 files changed, 101 insertions(+), 50 deletions(-) diff --git a/migrations/account_type/migration.go b/migrations/account_type/migration.go index 90113eb4c0..6d87e1991d 100644 --- a/migrations/account_type/migration.go +++ b/migrations/account_type/migration.go @@ -167,6 +167,7 @@ func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.Stat switch staticType { case interpreter.PrimitiveStaticTypePublicAccount: //nolint:staticcheck return unauthorizedAccountReferenceType + case interpreter.PrimitiveStaticTypeAuthAccount: //nolint:staticcheck return authAccountReferenceType diff --git a/migrations/capcons/migration_test.go b/migrations/capcons/migration_test.go index 8c268221fa..d70a7be610 100644 --- a/migrations/capcons/migration_test.go +++ b/migrations/capcons/migration_test.go @@ -535,9 +535,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -548,9 +548,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, borrowType: testRReferenceStaticType, }, + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -588,9 +588,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -628,9 +628,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -673,9 +673,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/private/test, target: /private/test2) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /private/test2) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -686,9 +686,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, borrowType: testRReferenceStaticType, }, + // Equivalent to: + // link<&Test.R>(/private/test2, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/private/test2, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: "test2", @@ -727,9 +727,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, pathLinks: []testLink{ + // Equivalent to: + // link<&Test.S>(/public/test, target: /storage/test) { - // Equivalent to: - // link<&Test.S>(/public/test, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -769,9 +769,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -782,9 +782,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { }, borrowType: testRReferenceStaticType, }, + // Equivalent to: + // link<&Test.R>(/private/test, target: /public/test) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /public/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -848,9 +848,9 @@ func TestPathCapabilityValueMigration(t *testing.T) { Address: interpreter.AddressValue(testAddress), }, pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -1208,9 +1208,9 @@ func TestLinkMigration(t *testing.T) { { name: "Path links, working chain (public -> private -> storage)", pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -1221,9 +1221,9 @@ func TestLinkMigration(t *testing.T) { }, borrowType: testRReferenceStaticType, }, + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -1261,9 +1261,9 @@ func TestLinkMigration(t *testing.T) { { name: "Path links, working chain (public -> storage)", pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -1291,9 +1291,9 @@ func TestLinkMigration(t *testing.T) { { name: "Path links, working chain (private -> storage)", pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/private/test, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -1326,9 +1326,9 @@ func TestLinkMigration(t *testing.T) { { name: "Path links, working chain (private -> private -> storage)", pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/private/test, target: /private/test2) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /private/test2) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -1339,9 +1339,9 @@ func TestLinkMigration(t *testing.T) { }, borrowType: testRReferenceStaticType, }, + // Equivalent to: + // link<&Test.R>(/private/test2, target: /storage/test) { - // Equivalent to: - // link<&Test.R>(/private/test2, target: /storage/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: "test2", @@ -1379,9 +1379,9 @@ func TestLinkMigration(t *testing.T) { { name: "Path links, cyclic chain (public -> private -> public)", pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -1392,9 +1392,9 @@ func TestLinkMigration(t *testing.T) { }, borrowType: testRReferenceStaticType, }, + // Equivalent to: + // link<&Test.R>(/private/test, target: /public/test) { - // Equivalent to: - // link<&Test.R>(/private/test, target: /public/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPrivate, Identifier: testPathIdentifier, @@ -1446,9 +1446,9 @@ func TestLinkMigration(t *testing.T) { { name: "Path links, missing target (public -> private)", pathLinks: []testLink{ + // Equivalent to: + // link<&Test.R>(/public/test, target: /private/test) { - // Equivalent to: - // link<&Test.R>(/public/test, target: /private/test) sourcePath: interpreter.PathValue{ Domain: common.PathDomainPublic, Identifier: testPathIdentifier, @@ -1516,6 +1516,54 @@ func TestLinkMigration(t *testing.T) { }, }, }, + { + name: "Account link, working chain (public -> private)", + pathLinks: []testLink{ + // Equivalent to: + // link<&AuthAccount>(/public/test, target: /private/test) + { + sourcePath: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + targetPath: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + borrowType: authAccountReferenceStaticType, + }, + }, + accountLinks: []interpreter.PathValue{ + // Equivalent to: + // linkAccount(/private/test) + { + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + expectedLinkMigrations: []testCapConsLinkMigration{ + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPrivate, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 1, + }, + { + accountAddressPath: interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: common.PathDomainPublic, + Identifier: testPathIdentifier, + }, + }, + capabilityID: 2, + }, + }, + }, } test := func(linkTestCase linkTestCase) { diff --git a/runtime/interpreter/primitivestatictype.go b/runtime/interpreter/primitivestatictype.go index d66ffb1d04..8c8e997441 100644 --- a/runtime/interpreter/primitivestatictype.go +++ b/runtime/interpreter/primitivestatictype.go @@ -566,18 +566,20 @@ func (t PrimitiveStaticType) SemaType() sema.Type { case PrimitiveStaticTypeAccountCapabilityController: return sema.AccountCapabilityControllerType - case PrimitiveStaticTypeAuthAccount, - PrimitiveStaticTypePublicAccount, - PrimitiveStaticTypeAuthAccountContracts, - PrimitiveStaticTypePublicAccountContracts, - PrimitiveStaticTypeAuthAccountKeys, - PrimitiveStaticTypePublicAccountKeys, - PrimitiveStaticTypeAuthAccountInbox, - PrimitiveStaticTypeAuthAccountStorageCapabilities, - PrimitiveStaticTypeAuthAccountAccountCapabilities, - PrimitiveStaticTypeAuthAccountCapabilities, - PrimitiveStaticTypePublicAccountCapabilities, - PrimitiveStaticTypeAccountKey: + case PrimitiveStaticTypeAuthAccount: //nolint:staticcheck + return sema.FullyEntitledAccountReferenceType + case PrimitiveStaticTypePublicAccount: //nolint:staticcheck + return sema.AccountReferenceType + case PrimitiveStaticTypeAuthAccountContracts, //nolint:staticcheck + PrimitiveStaticTypePublicAccountContracts, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountKeys, //nolint:staticcheck + PrimitiveStaticTypePublicAccountKeys, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountInbox, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountStorageCapabilities, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountAccountCapabilities, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountCapabilities, //nolint:staticcheck + PrimitiveStaticTypePublicAccountCapabilities, //nolint:staticcheck + PrimitiveStaticTypeAccountKey: //nolint:staticcheck // These types are deprecated, and only exist for migration purposes return nil From f5682217cddcdc51f3845db6742d030968fc2eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Dec 2023 11:47:21 -0800 Subject: [PATCH 48/51] fix tests for re-added AuthAccount/PublicAccount primitive static types --- encoding/ccf/simpletype_test.go | 4 +++ runtime/interpreter/primitivestatictype.go | 21 ++++++++++++++++ .../interpreter/primitivestatictype_test.go | 4 +++ runtime/interpreter/statictype_test.go | 25 +++++++++++-------- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/encoding/ccf/simpletype_test.go b/encoding/ccf/simpletype_test.go index ed05bd2404..09434bba55 100644 --- a/encoding/ccf/simpletype_test.go +++ b/encoding/ccf/simpletype_test.go @@ -65,6 +65,10 @@ func TestTypeConversion(t *testing.T) { continue } + if ty.IsDeprecated() { + continue + } + if _, ok := semaType.(*sema.CapabilityType); ok { continue } diff --git a/runtime/interpreter/primitivestatictype.go b/runtime/interpreter/primitivestatictype.go index 8c8e997441..159b04b150 100644 --- a/runtime/interpreter/primitivestatictype.go +++ b/runtime/interpreter/primitivestatictype.go @@ -674,6 +674,27 @@ func (t PrimitiveStaticType) IsDefined() bool { return ok } +// Deprecated: IsDeprecated only exists for migration purposes. +func (t PrimitiveStaticType) IsDeprecated() bool { + switch t { + case PrimitiveStaticTypeAuthAccount, //nolint:staticcheck + PrimitiveStaticTypePublicAccount, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountContracts, //nolint:staticcheck + PrimitiveStaticTypePublicAccountContracts, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountKeys, //nolint:staticcheck + PrimitiveStaticTypePublicAccountKeys, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountInbox, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountStorageCapabilities, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountAccountCapabilities, //nolint:staticcheck + PrimitiveStaticTypeAuthAccountCapabilities, //nolint:staticcheck + PrimitiveStaticTypePublicAccountCapabilities, //nolint:staticcheck + PrimitiveStaticTypeAccountKey: //nolint:staticcheck + return true + } + + return false +} + // ConvertSemaToPrimitiveStaticType converts a `sema.Type` to a `PrimitiveStaticType`. // // Returns `PrimitiveStaticTypeUnknown` if the given type is not a primitive type. diff --git a/runtime/interpreter/primitivestatictype_test.go b/runtime/interpreter/primitivestatictype_test.go index 9dca22c6d0..6bd7dd4037 100644 --- a/runtime/interpreter/primitivestatictype_test.go +++ b/runtime/interpreter/primitivestatictype_test.go @@ -41,6 +41,10 @@ func TestPrimitiveStaticTypeSemaTypeConversion(t *testing.T) { return } + if ty.IsDeprecated() { + return + } + ty2 := ConvertSemaToPrimitiveStaticType(nil, semaType) require.True(t, ty2.Equal(ty)) }) diff --git a/runtime/interpreter/statictype_test.go b/runtime/interpreter/statictype_test.go index d28ac7cf8b..249a3739a4 100644 --- a/runtime/interpreter/statictype_test.go +++ b/runtime/interpreter/statictype_test.go @@ -919,10 +919,11 @@ func TestStaticTypeConversion(t *testing.T) { testFunctionType := &sema.FunctionType{} type testCase struct { - name string - semaType sema.Type - staticType StaticType - getInterface func( + name string + semaType sema.Type + staticType StaticType + noSemaToStatic bool + getInterface func( t *testing.T, location common.Location, qualifiedIdentifier string, @@ -1554,14 +1555,16 @@ func TestStaticTypeConversion(t *testing.T) { // Deprecated primitive static types, only exist for migration purposes { - name: "AuthAccount", - semaType: nil, - staticType: PrimitiveStaticTypeAuthAccount, + name: "AuthAccount", + semaType: sema.FullyEntitledAccountReferenceType, + staticType: PrimitiveStaticTypeAuthAccount, + noSemaToStatic: true, }, { - name: "PublicAccount", - semaType: nil, - staticType: PrimitiveStaticTypePublicAccount, + name: "PublicAccount", + semaType: sema.AccountReferenceType, + staticType: PrimitiveStaticTypePublicAccount, + noSemaToStatic: true, }, { name: "AuthAccount.Contracts", @@ -1622,7 +1625,7 @@ func TestStaticTypeConversion(t *testing.T) { // Test sema to static - if test.semaType != nil { + if test.semaType != nil && !test.noSemaToStatic { convertedStaticType := ConvertSemaToStaticType(nil, test.semaType) require.Equal(t, test.staticType, From 2996575b5e2a0e6157234501b84f73d1bcb090e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 15 Dec 2023 11:19:53 -0800 Subject: [PATCH 49/51] Update migrations/capcons/linkmigration.go --- migrations/capcons/linkmigration.go | 1 - 1 file changed, 1 deletion(-) diff --git a/migrations/capcons/linkmigration.go b/migrations/capcons/linkmigration.go index b3bc4f8846..aef20aaede 100644 --- a/migrations/capcons/linkmigration.go +++ b/migrations/capcons/linkmigration.go @@ -108,7 +108,6 @@ func (m *LinkMigration) Migrate( inter, accountAddress, pathValue, - // TODO: // Use top-most type to follow link all the way to final target &sema.ReferenceType{ Authorization: sema.UnauthorizedAccess, From aee0846fccd04a345e114e569ac008afc42a7914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 15 Dec 2023 15:53:49 -0800 Subject: [PATCH 50/51] Update encoding/ccf/simpletype_test.go Co-authored-by: Supun Setunga --- encoding/ccf/simpletype_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encoding/ccf/simpletype_test.go b/encoding/ccf/simpletype_test.go index 09434bba55..cedc01faba 100644 --- a/encoding/ccf/simpletype_test.go +++ b/encoding/ccf/simpletype_test.go @@ -65,7 +65,7 @@ func TestTypeConversion(t *testing.T) { continue } - if ty.IsDeprecated() { + if ty.IsDeprecated() { //nolint:staticcheck continue } From 909282dfb99128d9bc45e632485043c1c4e7ae58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 19 Dec 2023 09:51:31 -0800 Subject: [PATCH 51/51] fix lint --- encoding/ccf/simpletype_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encoding/ccf/simpletype_test.go b/encoding/ccf/simpletype_test.go index cedc01faba..55460165ed 100644 --- a/encoding/ccf/simpletype_test.go +++ b/encoding/ccf/simpletype_test.go @@ -65,7 +65,7 @@ func TestTypeConversion(t *testing.T) { continue } - if ty.IsDeprecated() { //nolint:staticcheck + if ty.IsDeprecated() { //nolint:staticcheck continue }