Skip to content

Commit

Permalink
Merge pull request #2912 from onflow/bastian/cap-cons-state-migration
Browse files Browse the repository at this point in the history
  • Loading branch information
turbolent authored Dec 19, 2023
2 parents 77fc728 + 909282d commit 866822a
Show file tree
Hide file tree
Showing 26 changed files with 3,097 additions and 129 deletions.
4 changes: 4 additions & 0 deletions encoding/ccf/simpletype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ func TestTypeConversion(t *testing.T) {
continue
}

if ty.IsDeprecated() { //nolint:staticcheck
continue
}

if _, ok := semaType.(*sema.CapabilityType); ok {
continue
}
Expand Down
24 changes: 17 additions & 7 deletions migrations/account_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ func NewAccountStorage(storage *runtime.Storage, address common.Address) Account
}
}

type ValueConverter func(
addressPath interpreter.AddressPath,
value interpreter.Value,
) 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)
Expand All @@ -69,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
}
Expand Down
18 changes: 13 additions & 5 deletions migrations/account_type/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ 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(
_ interpreter.AddressPath,
value interpreter.Value,
_ *interpreter.Interpreter,
) (newValue interpreter.Value) {
switch value := value.(type) {
case interpreter.TypeValue:
convertedType := maybeConvertAccountType(value.Type)
Expand Down Expand Up @@ -71,11 +75,14 @@ func (AccountTypeMigration) Migrate(value interpreter.Value) (newValue interpret
return
}
borrowType := convertedBorrowType.(*interpreter.ReferenceStaticType)
return interpreter.NewUnmeteredStorageCapabilityControllerValue(borrowType, value.CapabilityID, value.TargetPath)

default:
return nil
return interpreter.NewUnmeteredStorageCapabilityControllerValue(
borrowType,
value.CapabilityID,
value.TargetPath,
)
}

return
}

func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.StaticType {
Expand Down Expand Up @@ -165,6 +172,7 @@ func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.Stat
switch staticType {
case interpreter.PrimitiveStaticTypePublicAccount: //nolint:staticcheck
return unauthorizedAccountReferenceType

case interpreter.PrimitiveStaticTypeAuthAccount: //nolint:staticcheck
return authAccountReferenceType

Expand Down
56 changes: 23 additions & 33 deletions migrations/account_type/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package account_type

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -38,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) {
Expand Down Expand Up @@ -379,22 +364,23 @@ func TestTypeValueMigration(t *testing.T) {
NewAccountTypeMigration(),
)

// Check reported migrated paths

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
migration.Commit()

t.Parallel()
// Check reported migrated paths
for identifier, test := range testCases {
addressPath := interpreter.AddressPath{
Address: account,
Path: interpreter.PathValue{
Domain: pathDomain,
Identifier: identifier,
},
}

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.
Expand Down Expand Up @@ -699,6 +685,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)
Expand Down Expand Up @@ -840,6 +828,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)
Expand Down
116 changes: 116 additions & 0 deletions migrations/capcons/capabilitymigration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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,
)
}

// 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
}

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
}
55 changes: 55 additions & 0 deletions migrations/capcons/error.go
Original file line number Diff line number Diff line change
@@ -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,
)
}
Loading

0 comments on commit 866822a

Please sign in to comment.