From 1f3d40e620950f261260ae29efc02b12ba501935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 11 Mar 2024 15:43:55 -0700 Subject: [PATCH] add missing interface type conversion rule, extend tests MetadataViews.ResolverCollection needs to get converted to ViewResolver.ResolverCollection --- cmd/util/ledger/migrations/cadence.go | 17 +- .../cadence_values_migration_test.go | 466 ++++++++++++++---- 2 files changed, 389 insertions(+), 94 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 0f282a254ee..dc6029021c9 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -2,6 +2,7 @@ package migrations import ( _ "embed" + "fmt" "github.com/onflow/cadence/migrations/capcons" "github.com/onflow/cadence/migrations/statictypes" @@ -18,10 +19,15 @@ import ( func NewInterfaceTypeConversionRules(chainID flow.ChainID) StaticTypeMigrationRules { systemContracts := systemcontracts.SystemContractsForChain(chainID) - oldFungibleTokenResolverType, newFungibleTokenResolverType := fungibleTokenResolverRule(systemContracts) + oldFungibleTokenResolverType, newFungibleTokenResolverType := + newFungibleTokenMetadataViewsToViewResolverRule(systemContracts, "Resolver") + + oldFungibleTokenResolverCollectionType, newFungibleTokenResolverCollectionType := + newFungibleTokenMetadataViewsToViewResolverRule(systemContracts, "ResolverCollection") return StaticTypeMigrationRules{ - oldFungibleTokenResolverType.ID(): newFungibleTokenResolverType, + oldFungibleTokenResolverType.ID(): newFungibleTokenResolverType, + oldFungibleTokenResolverCollectionType.ID(): newFungibleTokenResolverCollectionType, } } @@ -119,8 +125,9 @@ func fungibleTokenVaultRule( return oldType, newType } -func fungibleTokenResolverRule( +func newFungibleTokenMetadataViewsToViewResolverRule( systemContracts *systemcontracts.SystemContracts, + typeName string, ) ( *interpreter.InterfaceStaticType, *interpreter.InterfaceStaticType, @@ -138,8 +145,8 @@ func fungibleTokenResolverRule( Name: newContract.Name, } - oldQualifiedIdentifier := oldContract.Name + ".Resolver" - newQualifiedIdentifier := newContract.Name + ".Resolver" + oldQualifiedIdentifier := fmt.Sprintf("%s.%s", oldContract.Name, typeName) + newQualifiedIdentifier := fmt.Sprintf("%s.%s", newContract.Name, typeName) oldType := &interpreter.InterfaceStaticType{ Location: oldLocation, diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 6fdba6520e6..fb1d6ed6e2b 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -887,59 +887,179 @@ func TestProgramParsingError(t *testing.T) { func TestCoreContractUsage(t *testing.T) { t.Parallel() - rwf := &testReportWriterFactory{} + const chainID = flow.Emulator - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) + migrate := func(t *testing.T, staticType interpreter.StaticType) interpreter.StaticType { - const nWorker = 2 + rwf := &testReportWriterFactory{} - const chainID = flow.Emulator - chain := chainID.Chain() + logWriter := &writer{} + logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - testFlowAddress, err := chain.AddressAtIndex(1_000_000) - require.NoError(t, err) + const nWorker = 2 - testAddress := common.Address(testFlowAddress) + chain := chainID.Chain() - payloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) + testFlowAddress, err := chain.AddressAtIndex(1_000_000) + require.NoError(t, err) - runtime, err := NewMigratorRuntime( - testAddress, - payloads, - util.RuntimeInterfaceConfig{}, - ) - require.NoError(t, err) + testAddress := common.Address(testFlowAddress) - err = runtime.Accounts.Create(nil, testFlowAddress) - require.NoError(t, err) + payloads, err := newBootstrapPayloads(chainID) + require.NoError(t, err) - storage := runtime.Storage + runtime, err := NewMigratorRuntime( + testAddress, + payloads, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) - storageMap := storage.GetStorageMap( - testAddress, - common.PathDomainStorage.Identifier(), - true, - ) + err = runtime.Accounts.Create(nil, testFlowAddress) + require.NoError(t, err) - systemContracts := systemcontracts.SystemContractsForChain(chainID) + storage := runtime.Storage - const fungibleTokenContractName = "FungibleToken" - fungibleTokenContractLocation := common.NewAddressLocation( - nil, - common.Address(systemContracts.FungibleToken.Address), - fungibleTokenContractName, - ) + storageDomain := common.PathDomainStorage.Identifier() + storageMapKey := interpreter.StringStorageMapKey("test") - const fungibleTokenVaultTypeQualifiedIdentifier = fungibleTokenContractName + ".Vault" + storageMap := storage.GetStorageMap( + testAddress, + storageDomain, + true, + ) - capabilityValue := interpreter.NewUnmeteredCapabilityValue( - 0, - interpreter.AddressValue(testAddress), - interpreter.NewReferenceStaticType( + capabilityValue := interpreter.NewUnmeteredCapabilityValue( + 0, + interpreter.AddressValue(testAddress), + staticType, + ) + + storageMap.WriteValue( + runtime.Interpreter, + storageMapKey, + capabilityValue, + ) + + err = storage.Commit(runtime.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := runtime.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + // Merge the changes to the original payloads. + + expectedAddresses := map[flow.Address]struct{}{ + flow.Address(testAddress): {}, + } + + payloads, err = MergeRegisterChanges( + runtime.Snapshot.Payloads, + result.WriteSet, + expectedAddresses, + nil, + logger, + ) + require.NoError(t, err) + + // Migrate + + // TODO: EVM contract is not deployed in snapshot yet, so can't update it + const evmContractChange = EVMContractChangeNone + + const burnerContractChange = BurnerContractChangeUpdate + + migrations := NewCadence1Migrations( + logger, + rwf, + nWorker, + chainID, + false, + false, + evmContractChange, + burnerContractChange, + nil, + false, + 0, + ) + + for _, migration := range migrations { + payloads, err = migration.Migrate(payloads) + require.NoError( + t, + err, + "migration `%s` failed, logs: %v", + migration.Name, + logWriter.logs, + ) + } + + // Check error logs + require.Len(t, logWriter.logs, 0) + + // Get result + + mr, err := NewMigratorRuntime( + testAddress, + payloads, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + storageMap = mr.Storage.GetStorageMap( + testAddress, + storageDomain, + false, + ) + require.NotNil(t, storageMap) + + resultValue := storageMap.ReadValue(nil, storageMapKey) + require.NotNil(t, resultValue) + require.IsType(t, &interpreter.IDCapabilityValue{}, resultValue) + + resultCap := resultValue.(*interpreter.IDCapabilityValue) + return resultCap.BorrowType + } + + t.Run("&FungibleToken.Vault => auth(Withdraw) &{FungibleToken.Vault}", func(t *testing.T) { + t.Parallel() + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + + const fungibleTokenContractName = "FungibleToken" + fungibleTokenContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.FungibleToken.Address), + fungibleTokenContractName, + ) + + const fungibleTokenVaultTypeQualifiedIdentifier = fungibleTokenContractName + ".Vault" + + input := interpreter.NewReferenceStaticType( nil, interpreter.UnauthorizedAccess, + interpreter.NewCompositeStaticType( + nil, + fungibleTokenContractLocation, + fungibleTokenVaultTypeQualifiedIdentifier, + fungibleTokenContractLocation.TypeID(nil, fungibleTokenVaultTypeQualifiedIdentifier), + ), + ) + + const fungibleTokenWithdrawTypeQualifiedIdentifier = fungibleTokenContractName + ".Withdraw" + expected := interpreter.NewReferenceStaticType( + nil, + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { + return []common.TypeID{ + fungibleTokenContractLocation.TypeID(nil, fungibleTokenWithdrawTypeQualifiedIdentifier), + } + }, + 1, + sema.Conjunction, + ), interpreter.NewIntersectionStaticType( nil, []*interpreter.InterfaceStaticType{ @@ -951,69 +1071,237 @@ func TestCoreContractUsage(t *testing.T) { ), }, ), - ), - ) + ) - storageMap.WriteValue( - runtime.Interpreter, - interpreter.StringStorageMapKey("test"), - capabilityValue, - ) + actual := migrate(t, input) - err = storage.Commit(runtime.Interpreter, false) - require.NoError(t, err) + require.Equal(t, expected, actual) + }) - // finalize the transaction - result, err := runtime.TransactionState.FinalizeMainTransaction() - require.NoError(t, err) + t.Run("&FungibleToken.Vault{FungibleToken.Balance} => &{FungibleToken.Vault}", func(t *testing.T) { + t.Parallel() - // Merge the changes to the original payloads. + systemContracts := systemcontracts.SystemContractsForChain(chainID) - expectedAddresses := map[flow.Address]struct{}{ - flow.Address(testAddress): {}, - } + const fungibleTokenContractName = "FungibleToken" + fungibleTokenContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.FungibleToken.Address), + fungibleTokenContractName, + ) - payloads, err = MergeRegisterChanges( - runtime.Snapshot.Payloads, - result.WriteSet, - expectedAddresses, - nil, - logger, - ) - require.NoError(t, err) + const fungibleTokenVaultTypeQualifiedIdentifier = fungibleTokenContractName + ".Vault" + const fungibleTokenBalanceTypeQualifiedIdentifier = fungibleTokenContractName + ".Balance" - // Migrate + inputIntersectionType := interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + interpreter.NewInterfaceStaticType( + nil, + fungibleTokenContractLocation, + fungibleTokenBalanceTypeQualifiedIdentifier, + fungibleTokenContractLocation.TypeID(nil, fungibleTokenBalanceTypeQualifiedIdentifier), + ), + }, + ) + inputIntersectionType.LegacyType = interpreter.NewCompositeStaticType( + nil, + fungibleTokenContractLocation, + fungibleTokenVaultTypeQualifiedIdentifier, + fungibleTokenContractLocation.TypeID(nil, fungibleTokenVaultTypeQualifiedIdentifier), + ) - // TODO: EVM contract is not deployed in snapshot yet, so can't update it - const evmContractChange = EVMContractChangeNone + input := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + inputIntersectionType, + ) - const burnerContractChange = BurnerContractChangeUpdate + expected := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + interpreter.NewInterfaceStaticType( + nil, + fungibleTokenContractLocation, + fungibleTokenVaultTypeQualifiedIdentifier, + fungibleTokenContractLocation.TypeID(nil, fungibleTokenVaultTypeQualifiedIdentifier), + ), + }, + ), + ) - migrations := NewCadence1Migrations( - logger, - rwf, - nWorker, - chainID, - false, - false, - evmContractChange, - burnerContractChange, - nil, - false, - 0, - ) + actual := migrate(t, input) - for _, migration := range migrations { - payloads, err = migration.Migrate(payloads) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, + require.Equal(t, expected, actual) + }) + + t.Run("&NonFungibleToken.NFT => &{NonFungibleToken.NFT}", func(t *testing.T) { + t.Parallel() + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + + const nonFungibleTokenContractName = "NonFungibleToken" + nonFungibleTokenContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.NonFungibleToken.Address), + nonFungibleTokenContractName, ) - } - // Check error logs - require.Len(t, logWriter.logs, 0) + const nonFungibleTokenNFTTypeQualifiedIdentifier = nonFungibleTokenContractName + ".NFT" + + input := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewCompositeStaticType( + nil, + nonFungibleTokenContractLocation, + nonFungibleTokenNFTTypeQualifiedIdentifier, + nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenNFTTypeQualifiedIdentifier), + ), + ) + + expected := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + interpreter.NewInterfaceStaticType( + nil, + nonFungibleTokenContractLocation, + nonFungibleTokenNFTTypeQualifiedIdentifier, + nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenNFTTypeQualifiedIdentifier), + ), + }, + ), + ) + + actual := migrate(t, input) + + require.Equal(t, expected, actual) + }) + + t.Run("&{MetadataViews.Resolver} => &{ViewResolver.Resolver}", func(t *testing.T) { + t.Parallel() + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + + const metadataViewsContractName = "MetadataViews" + metadataViewsContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.MetadataViews.Address), + metadataViewsContractName, + ) + + const metadataViewsResolverTypeQualifiedIdentifier = metadataViewsContractName + ".Resolver" + + input := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + interpreter.NewInterfaceStaticType( + nil, + metadataViewsContractLocation, + metadataViewsResolverTypeQualifiedIdentifier, + metadataViewsContractLocation.TypeID(nil, metadataViewsResolverTypeQualifiedIdentifier), + ), + }, + ), + ) + + const viewResolverContractName = "ViewResolver" + viewResolverContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.MetadataViews.Address), + viewResolverContractName, + ) + + const viewResolverResolverTypeQualifiedIdentifier = viewResolverContractName + ".Resolver" + + expected := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + interpreter.NewInterfaceStaticType( + nil, + viewResolverContractLocation, + viewResolverResolverTypeQualifiedIdentifier, + viewResolverContractLocation.TypeID(nil, viewResolverResolverTypeQualifiedIdentifier), + ), + }, + ), + ) + + actual := migrate(t, input) + + require.Equal(t, expected, actual) + }) + + t.Run("&{MetadataViews.ResolverCollection} => &{ViewResolver.ResolverCollection}", func(t *testing.T) { + t.Parallel() + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + + const metadataViewsContractName = "MetadataViews" + metadataViewsContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.MetadataViews.Address), + metadataViewsContractName, + ) + + const metadataViewsResolverTypeQualifiedIdentifier = metadataViewsContractName + ".ResolverCollection" + + input := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + interpreter.NewInterfaceStaticType( + nil, + metadataViewsContractLocation, + metadataViewsResolverTypeQualifiedIdentifier, + metadataViewsContractLocation.TypeID(nil, metadataViewsResolverTypeQualifiedIdentifier), + ), + }, + ), + ) + + const viewResolverContractName = "ViewResolver" + viewResolverContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.MetadataViews.Address), + viewResolverContractName, + ) + + const viewResolverResolverTypeQualifiedIdentifier = viewResolverContractName + ".ResolverCollection" + + expected := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + interpreter.NewInterfaceStaticType( + nil, + viewResolverContractLocation, + viewResolverResolverTypeQualifiedIdentifier, + viewResolverContractLocation.TypeID(nil, viewResolverResolverTypeQualifiedIdentifier), + ), + }, + ), + ) + + actual := migrate(t, input) + + require.Equal(t, expected, actual) + }) + }