diff --git a/cmd/util/cmd/execution-state-extract/cmd.go b/cmd/util/cmd/execution-state-extract/cmd.go index b7edecd8936..96ca319e575 100644 --- a/cmd/util/cmd/execution-state-extract/cmd.go +++ b/cmd/util/cmd/execution-state-extract/cmd.go @@ -1,10 +1,8 @@ package extract import ( - "compress/gzip" "encoding/hex" "fmt" - "io" "os" "path" "runtime/pprof" @@ -326,7 +324,8 @@ func run(*cobra.Command, []string) { } } - chain := flow.ChainID(flagChain).Chain() + // Validate chain ID + _ = flow.ChainID(flagChain).Chain() if flagNoReport { log.Warn().Msgf("--no-report flag is deprecated") @@ -401,43 +400,6 @@ func run(*cobra.Command, []string) { log.Info().Msgf("state extraction plan: %s, %s", inputMsg, outputMsg) - chainID := chain.ChainID() - - burnerContractChange := migrations.BurnerContractChangeNone - evmContractChange := migrations.EVMContractChangeNone - switch chainID { - case flow.Emulator: - burnerContractChange = migrations.BurnerContractChangeDeploy - evmContractChange = migrations.EVMContractChangeDeployMinimalAndUpdateFull - case flow.Testnet, flow.Mainnet: - burnerContractChange = migrations.BurnerContractChangeUpdate - evmContractChange = migrations.EVMContractChangeUpdateFull - } - - stagedContracts, err := migrations.StagedContractsFromCSV(flagStagedContractsFile) - if err != nil { - log.Fatal().Err(err).Msgf("error loading staged contracts: %s", err.Error()) - } - - opts := migrations.Options{ - NWorker: flagNWorker, - DiffMigrations: flagDiffMigration, - LogVerboseDiff: flagLogVerboseDiff, - CheckStorageHealthBeforeMigration: flagCheckStorageHealthBeforeMigration, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - StagedContracts: stagedContracts, - Prune: flagPrune, - MaxAccountSize: flagMaxAccountSize, - VerboseErrorOutput: flagVerboseErrorOutput, - FixSlabsWithBrokenReferences: chainID == flow.Testnet && flagFixSlabsWithBrokenReferences, - FilterUnreferencedSlabs: flagFilterUnreferencedSlabs, - ReportMetrics: flagReportMetrics, - CacheStaticTypeMigrationResults: flagCacheStaticTypeMigrationResults, - CacheEntitlementsMigrationResults: flagCacheEntitlementsMigrationResults, - } - var extractor extractor if len(flagInputPayloadFileName) > 0 { extractor = newPayloadFileExtractor(log.Logger, flagInputPayloadFileName) @@ -460,21 +422,6 @@ func run(*cobra.Command, []string) { var migs []migrations.NamedMigration switch flagMigration { - case "cadence-1.0": - migs = newCadence1Migrations( - log.Logger, - flagOutputDir, - opts, - ) - - case "fix-authorizations": - migs = newFixAuthorizationsMigrations( - log.Logger, - flagAuthorizationFixes, - flagOutputDir, - opts, - ) - default: log.Fatal().Msgf("unknown migration: %s", flagMigration) } @@ -557,35 +504,3 @@ func ensureCheckpointFileExist(dir string) error { return fmt.Errorf("no checkpoint file was found, no root checkpoint file was found in %v, check the --execution-state-dir flag", dir) } - -func readAuthorizationFixes(path string) migrations.AuthorizationFixes { - - file, err := os.Open(path) - if err != nil { - log.Fatal().Err(err).Msgf("can't open authorization fixes: %s", path) - } - defer file.Close() - - var reader io.Reader = file - if isGzip(file) { - reader, err = gzip.NewReader(file) - if err != nil { - log.Fatal().Err(err).Msgf("failed to create gzip reader for %s", path) - } - } - - log.Info().Msgf("Reading authorization fixes from %s ...", path) - - fixes, err := migrations.ReadAuthorizationFixes(reader, nil) - if err != nil { - log.Fatal().Err(err).Msgf("failed to read authorization fixes %s", path) - } - - log.Info().Msgf("Read %d authorization fixes", len(fixes)) - - return fixes -} - -func isGzip(file *os.File) bool { - return strings.HasSuffix(file.Name(), ".gz") -} diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 49b1728bb69..1ddbc721276 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -11,7 +11,6 @@ import ( "golang.org/x/sync/errgroup" migrators "github.com/onflow/flow-go/cmd/util/ledger/migrations" - "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/hash" @@ -357,80 +356,3 @@ func createTrieFromPayloads(logger zerolog.Logger, payloads []*ledger.Payload) ( return newTrie, nil } - -func newCadence1Migrations( - log zerolog.Logger, - outputDir string, - opts migrators.Options, -) []migrators.NamedMigration { - - log.Info().Msg("initializing Cadence 1.0 migrations ...") - - rwf := reporters.NewReportFileWriterFactory(outputDir, log) - - namedMigrations := migrators.NewCadence1Migrations( - log, - outputDir, - rwf, - opts, - ) - - // At the end, fix up storage-used discrepancies - namedMigrations = append( - namedMigrations, - migrators.NamedMigration{ - Name: "account-usage-migration", - Migrate: migrators.NewAccountBasedMigration( - log, - opts.NWorker, - []migrators.AccountBasedMigration{ - migrators.NewAccountUsageMigration(rwf), - }, - ), - }, - ) - - log.Info().Msg("initialized migrations") - - return namedMigrations -} - -func newFixAuthorizationsMigrations( - log zerolog.Logger, - authorizationFixesPath string, - outputDir string, - opts migrators.Options, -) []migrators.NamedMigration { - - log.Info().Msg("initializing authorization fix migrations ...") - - rwf := reporters.NewReportFileWriterFactory(outputDir, log) - - authorizationFixes := readAuthorizationFixes(authorizationFixesPath) - - namedMigrations := migrators.NewFixAuthorizationsMigrations( - log, - rwf, - authorizationFixes, - opts, - ) - - // At the end, fix up storage-used discrepancies - namedMigrations = append( - namedMigrations, - migrators.NamedMigration{ - Name: "account-usage-migration", - Migrate: migrators.NewAccountBasedMigration( - log, - opts.NWorker, - []migrators.AccountBasedMigration{ - migrators.NewAccountUsageMigration(rwf), - }, - ), - }, - ) - - log.Info().Msg("initialized migrations") - - return namedMigrations -} diff --git a/cmd/util/ledger/migrations/burner.go b/cmd/util/ledger/migrations/burner.go deleted file mode 100644 index 726da8f4ea4..00000000000 --- a/cmd/util/ledger/migrations/burner.go +++ /dev/null @@ -1,27 +0,0 @@ -package migrations - -import ( - coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/model/flow" -) - -func NewBurnerDeploymentMigration( - chainID flow.ChainID, - logger zerolog.Logger, -) RegistersMigration { - address := BurnerAddressForChain(chainID) - return NewDeploymentMigration( - chainID, - Contract{ - Name: "Burner", - Code: coreContracts.Burner(), - }, - address, - map[flow.Address]struct{}{ - address: {}, - }, - logger, - ) -} diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go deleted file mode 100644 index 2cc5c9154ed..00000000000 --- a/cmd/util/ledger/migrations/cadence.go +++ /dev/null @@ -1,855 +0,0 @@ -package migrations - -import ( - "context" - _ "embed" - "fmt" - - "github.com/rs/zerolog" - - "github.com/onflow/cadence/migrations/capcons" - "github.com/onflow/cadence/migrations/statictypes" - "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/fvm/systemcontracts" - "github.com/onflow/flow-go/fvm/tracing" - "github.com/onflow/flow-go/model/flow" -) - -func NewInterfaceTypeConversionRules(chainID flow.ChainID) StaticTypeMigrationRules { - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - oldFungibleTokenResolverType, newFungibleTokenResolverType := - newFungibleTokenMetadataViewsToViewResolverRule(systemContracts, "Resolver") - - oldFungibleTokenResolverCollectionType, newFungibleTokenResolverCollectionType := - newFungibleTokenMetadataViewsToViewResolverRule(systemContracts, "ResolverCollection") - - oldNonFungibleTokenINFTType, newNonFungibleTokenNFTType := - nonFungibleTokenInterfaceToInterfaceRule(systemContracts, "INFT", "NFT") - - return StaticTypeMigrationRules{ - oldFungibleTokenResolverType.ID(): newFungibleTokenResolverType, - oldFungibleTokenResolverCollectionType.ID(): newFungibleTokenResolverCollectionType, - oldNonFungibleTokenINFTType.ID(): newNonFungibleTokenNFTType, - } -} - -func NewCompositeTypeConversionRules( - chainID flow.ChainID, - legacyTypeRequirements *LegacyTypeRequirements, -) StaticTypeMigrationRules { - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - oldFungibleTokenVaultCompositeType, newFungibleTokenVaultType := - fungibleTokenRule(systemContracts, "Vault") - oldNonFungibleTokenNFTCompositeType, newNonFungibleTokenNFTType := - nonFungibleTokenCompositeToInterfaceRule(systemContracts, "NFT") - oldNonFungibleTokenCollectionCompositeType, newNonFungibleTokenCollectionType := - nonFungibleTokenCompositeToInterfaceRule(systemContracts, "Collection") - - rules := StaticTypeMigrationRules{ - oldFungibleTokenVaultCompositeType.ID(): newFungibleTokenVaultType, - oldNonFungibleTokenNFTCompositeType.ID(): newNonFungibleTokenNFTType, - oldNonFungibleTokenCollectionCompositeType.ID(): newNonFungibleTokenCollectionType, - } - - for _, typeRequirement := range legacyTypeRequirements.typeRequirements { - oldType, newType := compositeToInterfaceRule( - typeRequirement.Address, - typeRequirement.ContractName, - typeRequirement.TypeName, - ) - - rules[oldType.ID()] = newType - } - - return rules -} - -func NewCadence1InterfaceStaticTypeConverter(chainID flow.ChainID) statictypes.InterfaceTypeConverterFunc { - return NewStaticTypeMigration[*interpreter.InterfaceStaticType]( - func() StaticTypeMigrationRules { - return NewInterfaceTypeConversionRules(chainID) - }, - ) -} - -func NewCadence1CompositeStaticTypeConverter( - chainID flow.ChainID, - legacyTypeRequirements *LegacyTypeRequirements, -) statictypes.CompositeTypeConverterFunc { - return NewStaticTypeMigration[*interpreter.CompositeStaticType]( - func() StaticTypeMigrationRules { - return NewCompositeTypeConversionRules(chainID, legacyTypeRequirements) - }, - ) -} - -func nonFungibleTokenCompositeToInterfaceRule( - systemContracts *systemcontracts.SystemContracts, - identifier string, -) ( - *interpreter.CompositeStaticType, - *interpreter.IntersectionStaticType, -) { - contract := systemContracts.NonFungibleToken - - return compositeToInterfaceRule( - common.Address(contract.Address), - contract.Name, - identifier, - ) -} - -func compositeToInterfaceRule( - address common.Address, - contractName string, - typeName string, -) ( - *interpreter.CompositeStaticType, - *interpreter.IntersectionStaticType, -) { - qualifiedIdentifier := fmt.Sprintf("%s.%s", contractName, typeName) - - location := common.AddressLocation{ - Address: address, - Name: contractName, - } - - nftTypeID := location.TypeID(nil, qualifiedIdentifier) - - oldType := &interpreter.CompositeStaticType{ - Location: location, - QualifiedIdentifier: qualifiedIdentifier, - TypeID: nftTypeID, - } - - newType := &interpreter.IntersectionStaticType{ - Types: []*interpreter.InterfaceStaticType{ - { - Location: location, - QualifiedIdentifier: qualifiedIdentifier, - TypeID: nftTypeID, - }, - }, - } - - return oldType, newType -} - -func nonFungibleTokenInterfaceToInterfaceRule( - systemContracts *systemcontracts.SystemContracts, - oldIdentifier string, - newIdentifier string, -) ( - *interpreter.InterfaceStaticType, - *interpreter.InterfaceStaticType, -) { - contract := systemContracts.NonFungibleToken - - oldQualifiedIdentifier := fmt.Sprintf("%s.%s", contract.Name, oldIdentifier) - newQualifiedIdentifier := fmt.Sprintf("%s.%s", contract.Name, newIdentifier) - - location := common.AddressLocation{ - Address: common.Address(contract.Address), - Name: contract.Name, - } - - oldTypeID := location.TypeID(nil, oldQualifiedIdentifier) - newTypeID := location.TypeID(nil, newQualifiedIdentifier) - - oldType := &interpreter.InterfaceStaticType{ - Location: location, - QualifiedIdentifier: oldQualifiedIdentifier, - TypeID: oldTypeID, - } - - newType := &interpreter.InterfaceStaticType{ - Location: location, - QualifiedIdentifier: newQualifiedIdentifier, - TypeID: newTypeID, - } - - return oldType, newType -} - -func fungibleTokenRule( - systemContracts *systemcontracts.SystemContracts, - identifier string, -) ( - *interpreter.CompositeStaticType, - *interpreter.IntersectionStaticType, -) { - contract := systemContracts.FungibleToken - - qualifiedIdentifier := fmt.Sprintf("%s.%s", contract.Name, identifier) - - location := common.AddressLocation{ - Address: common.Address(contract.Address), - Name: contract.Name, - } - - vaultTypeID := location.TypeID(nil, qualifiedIdentifier) - - oldType := &interpreter.CompositeStaticType{ - Location: location, - QualifiedIdentifier: qualifiedIdentifier, - TypeID: vaultTypeID, - } - - newType := &interpreter.IntersectionStaticType{ - Types: []*interpreter.InterfaceStaticType{ - { - Location: location, - QualifiedIdentifier: qualifiedIdentifier, - TypeID: vaultTypeID, - }, - }, - } - - return oldType, newType -} - -func newFungibleTokenMetadataViewsToViewResolverRule( - systemContracts *systemcontracts.SystemContracts, - typeName string, -) ( - *interpreter.InterfaceStaticType, - *interpreter.InterfaceStaticType, -) { - oldContract := systemContracts.MetadataViews - newContract := systemContracts.ViewResolver - - oldLocation := common.AddressLocation{ - Address: common.Address(oldContract.Address), - Name: oldContract.Name, - } - - newLocation := common.AddressLocation{ - Address: common.Address(newContract.Address), - Name: newContract.Name, - } - - oldQualifiedIdentifier := fmt.Sprintf("%s.%s", oldContract.Name, typeName) - newQualifiedIdentifier := fmt.Sprintf("%s.%s", newContract.Name, typeName) - - oldType := &interpreter.InterfaceStaticType{ - Location: oldLocation, - QualifiedIdentifier: oldQualifiedIdentifier, - TypeID: oldLocation.TypeID(nil, oldQualifiedIdentifier), - } - - newType := &interpreter.InterfaceStaticType{ - Location: newLocation, - QualifiedIdentifier: newQualifiedIdentifier, - TypeID: newLocation.TypeID(nil, newQualifiedIdentifier), - } - - return oldType, newType -} - -type NamedMigration struct { - Name string - Migrate RegistersMigration -} - -type IssueStorageCapConMigration struct { - name string - chainID flow.ChainID - accountsCapabilities *capcons.AccountsCapabilities - interpreterMigrationRuntimeConfig InterpreterMigrationRuntimeConfig - programs map[runtime.Location]*interpreter.Program - typedCapabilityMapping *capcons.PathTypeCapabilityMapping - untypedCapabilityMapping *capcons.PathCapabilityMapping - reporter reporters.ReportWriter - logVerboseDiff bool - verboseErrorOutput bool - errorMessageHandler *errorMessageHandler - log zerolog.Logger -} - -const issueStorageCapConMigrationReporterName = "cadence-storage-capcon-issue-migration" - -func NewIssueStorageCapConMigration( - rwf reporters.ReportWriterFactory, - errorMessageHandler *errorMessageHandler, - chainID flow.ChainID, - storageDomainCapabilities *capcons.AccountsCapabilities, - programs map[runtime.Location]*interpreter.Program, - typedStorageCapabilityMapping *capcons.PathTypeCapabilityMapping, - untypedStorageCapabilityMapping *capcons.PathCapabilityMapping, - opts Options, -) *IssueStorageCapConMigration { - return &IssueStorageCapConMigration{ - name: "cadence_storage_cap_con_issue_migration", - reporter: rwf.ReportWriter(issueStorageCapConMigrationReporterName), - chainID: chainID, - accountsCapabilities: storageDomainCapabilities, - programs: programs, - typedCapabilityMapping: typedStorageCapabilityMapping, - untypedCapabilityMapping: untypedStorageCapabilityMapping, - logVerboseDiff: opts.LogVerboseDiff, - verboseErrorOutput: opts.VerboseErrorOutput, - errorMessageHandler: errorMessageHandler, - } -} - -func (m *IssueStorageCapConMigration) InitMigration( - log zerolog.Logger, - _ *registers.ByAccount, - _ int, -) error { - m.log = log.With().Str("migration", m.name).Logger() - - // During the migration, we only provide already checked programs, - // no parsing/checking of contracts is expected. - - m.interpreterMigrationRuntimeConfig = InterpreterMigrationRuntimeConfig{ - GetOrLoadProgram: func( - location runtime.Location, - _ func() (*interpreter.Program, error), - ) (*interpreter.Program, error) { - program, ok := m.programs[location] - if !ok { - return nil, fmt.Errorf("program not found: %s", location) - } - return program, nil - }, - GetCode: func(_ common.AddressLocation) ([]byte, error) { - return nil, fmt.Errorf("unexpected call to GetCode") - }, - GetContractNames: func(address flow.Address) ([]string, error) { - return nil, fmt.Errorf("unexpected call to GetContractNames") - }, - } - - return nil -} - -func (m *IssueStorageCapConMigration) MigrateAccount( - _ context.Context, - address common.Address, - accountRegisters *registers.AccountRegisters, -) error { - accountCapabilities := m.accountsCapabilities.Get(address) - if accountCapabilities == nil { - return nil - } - - // Create all the runtime components we need for the migration - migrationRuntime, err := NewInterpreterMigrationRuntime( - accountRegisters, - m.chainID, - m.interpreterMigrationRuntimeConfig, - ) - if err != nil { - return fmt.Errorf("failed to create interpreter migration runtime: %w", err) - } - - idGenerator := environment.NewAccountLocalIDGenerator( - tracing.NewMockTracerSpan(), - util.NopMeter{}, - migrationRuntime.Accounts, - ) - - handler := capabilityControllerHandler{ - idGenerator: idGenerator, - } - - reporter := newValueMigrationReporter( - m.reporter, - m.log, - m.errorMessageHandler, - m.verboseErrorOutput, - ) - - inter := migrationRuntime.Interpreter - - capcons.IssueAccountCapabilities( - inter, - migrationRuntime.Storage, - reporter, - address, - accountCapabilities, - handler, - m.typedCapabilityMapping, - m.untypedCapabilityMapping, - func(valueType interpreter.StaticType) interpreter.Authorization { - // TODO: - return interpreter.UnauthorizedAccess - }, - ) - - err = migrationRuntime.Storage.NondeterministicCommit(inter, false) - if err != nil { - return fmt.Errorf("failed to commit changes: %w", err) - } - - // Commit/finalize the transaction - - expectedAddresses := map[flow.Address]struct{}{ - flow.Address(address): {}, - } - - err = migrationRuntime.Commit(expectedAddresses, m.log) - if err != nil { - return fmt.Errorf("failed to commit: %w", err) - } - - return nil -} - -func (m *IssueStorageCapConMigration) Close() error { - m.reporter.Close() - return nil -} - -var _ AccountBasedMigration = &IssueStorageCapConMigration{} - -func NewCadence1ValueMigrations( - log zerolog.Logger, - rwf reporters.ReportWriterFactory, - importantLocations map[common.AddressLocation]struct{}, - legacyTypeRequirements *LegacyTypeRequirements, - opts Options, -) (migs []NamedMigration) { - - // Populated by CadenceLinkValueMigration, - // used by CadenceCapabilityValueMigration - privatePublicCapabilityMapping := &capcons.PathCapabilityMapping{} - // Populated by IssueStorageCapConMigration - // used by CadenceCapabilityValueMigration - typedStorageCapabilityMapping := &capcons.PathTypeCapabilityMapping{} - untypedStorageCapabilityMapping := &capcons.PathCapabilityMapping{} - - // Populated by StorageCapMigration, - // used by IssueStorageCapConMigration - storageDomainCapabilities := &capcons.AccountsCapabilities{} - - errorMessageHandler := &errorMessageHandler{} - - // The value migrations are run as account-based migrations, - // i.e. the migrations are only given the payloads for the account to be migrated. - // However, the migrations need to be able to get the code for contracts of any account. - // - // To achieve this, the contracts are extracted from the payloads once, - // before the value migrations are run. - - programs := make(map[common.Location]*interpreter.Program, 1000) - - migs = []NamedMigration{ - { - Name: "cleanup-contracts", - Migrate: NewAccountBasedMigration( - log, - opts.NWorker, - []AccountBasedMigration{ - NewContractCleanupMigration(rwf), - }, - ), - }, - { - Name: "check-contracts", - Migrate: NewContractCheckingMigration( - log, - rwf, - opts.ChainID, - opts.VerboseErrorOutput, - importantLocations, - programs, - ), - }, - } - - for index, migrationConstructor := range []func(opts Options) (string, AccountBasedMigration){ - func(opts Options) (string, AccountBasedMigration) { - migration := NewCadence1ValueMigration( - rwf, - errorMessageHandler, - programs, - NewCadence1CompositeStaticTypeConverter(opts.ChainID, legacyTypeRequirements), - NewCadence1InterfaceStaticTypeConverter(opts.ChainID), - storageDomainCapabilities, - opts, - ) - return migration.name, migration - }, - func(opts Options) (string, AccountBasedMigration) { - migration := NewIssueStorageCapConMigration( - rwf, - errorMessageHandler, - opts.ChainID, - storageDomainCapabilities, - programs, - typedStorageCapabilityMapping, - untypedStorageCapabilityMapping, - opts, - ) - return migration.name, migration - - }, - func(opts Options) (string, AccountBasedMigration) { - migration := NewCadence1LinkValueMigration( - rwf, - errorMessageHandler, - programs, - privatePublicCapabilityMapping, - opts, - ) - return migration.name, migration - }, - func(opts Options) (string, AccountBasedMigration) { - migration := NewCadence1CapabilityValueMigration( - rwf, - errorMessageHandler, - programs, - privatePublicCapabilityMapping, - typedStorageCapabilityMapping, - untypedStorageCapabilityMapping, - opts, - ) - return migration.name, migration - }, - } { - opts := opts - // Only check storage health before the first migration - opts.CheckStorageHealthBeforeMigration = opts.CheckStorageHealthBeforeMigration && index == 0 - - name, accountBasedMigration := migrationConstructor(opts) - - migs = append( - migs, - NamedMigration{ - Name: name, - Migrate: NewAccountBasedMigration( - log, - opts.NWorker, - []AccountBasedMigration{ - accountBasedMigration, - }, - ), - }, - ) - } - - if opts.ReportMetrics { - migs = append(migs, NamedMigration{ - Name: metricsCollectingMigrationName, - Migrate: NewAccountBasedMigration( - log, - opts.NWorker, - []AccountBasedMigration{ - NewMetricsCollectingMigration( - log, - opts.ChainID, - rwf, - programs, - ), - }, - ), - }) - } - - return -} - -const stagedContractUpdateMigrationName = "staged-contracts-update-migration" - -func NewCadence1ContractsMigrations( - log zerolog.Logger, - rwf reporters.ReportWriterFactory, - importantLocations map[common.AddressLocation]struct{}, - legacyTypeRequirements *LegacyTypeRequirements, - opts Options, -) ( - migs []NamedMigration, -) { - - stagedContractsMigrationOptions := StagedContractsMigrationOptions{ - ChainID: opts.ChainID, - VerboseErrorOutput: opts.VerboseErrorOutput, - } - - systemContractsMigrationOptions := SystemContractsMigrationOptions{ - StagedContractsMigrationOptions: stagedContractsMigrationOptions, - EVM: opts.EVMContractChange, - Burner: opts.BurnerContractChange, - } - - systemContractsMigration := NewSystemContractsMigration( - log, - rwf, - importantLocations, - systemContractsMigrationOptions, - ) - - stagedContractsMigration := NewStagedContractsMigration( - "StagedContractsMigration", - "staged-contracts-migration", - log, - rwf, - legacyTypeRequirements, - stagedContractsMigrationOptions, - ).WithContractUpdateValidation(). - WithStagedContractUpdates(opts.StagedContracts) - - toAccountBasedMigration := func(migration AccountBasedMigration) RegistersMigration { - return NewAccountBasedMigration( - log, - opts.NWorker, - []AccountBasedMigration{ - migration, - }, - ) - } - - switch opts.EVMContractChange { - case EVMContractChangeNone: - // NO-OP - - case EVMContractChangeUpdateFull: - // handled in system contract updates (SystemContractChanges) - - case EVMContractChangeDeployFull, - EVMContractChangeDeployMinimalAndUpdateFull: - - full := opts.EVMContractChange == EVMContractChangeDeployFull - - migs = append( - migs, - NamedMigration{ - Name: "evm-deployment-migration", - Migrate: NewEVMDeploymentMigration(opts.ChainID, log, full), - }, - ) - } - - if opts.BurnerContractChange == BurnerContractChangeDeploy { - migs = append( - migs, - NamedMigration{ - Name: "burner-deployment-migration", - Migrate: NewBurnerDeploymentMigration(opts.ChainID, log), - }, - ) - } - - migs = append( - migs, - NamedMigration{ - Name: "system-contracts-update-migration", - Migrate: toAccountBasedMigration(systemContractsMigration), - }, - NamedMigration{ - Name: stagedContractUpdateMigrationName, - Migrate: toAccountBasedMigration(stagedContractsMigration), - }, - ) - - return migs -} - -var testnetAccountsWithBrokenSlabReferences = func() map[common.Address]struct{} { - testnetAddresses := map[common.Address]struct{}{ - mustHexToAddress("434a1f199a7ae3ba"): {}, - mustHexToAddress("454c9991c2b8d947"): {}, - mustHexToAddress("48602d8056ff9d93"): {}, - mustHexToAddress("5d63c34d7f05e5a4"): {}, - mustHexToAddress("5e3448b3cffb97f2"): {}, - mustHexToAddress("7d8c7e050c694eaa"): {}, - mustHexToAddress("ba53f16ede01972d"): {}, - mustHexToAddress("c843c1f5a4805c3a"): {}, - mustHexToAddress("48d3be92e6e4a973"): {}, - } - - for address := range testnetAddresses { - if !flow.Testnet.Chain().IsValid(flow.Address(address)) { - panic(fmt.Sprintf("invalid testnet address: %s", address.Hex())) - } - } - - return testnetAddresses -}() - -func mustHexToAddress(hex string) common.Address { - address, err := common.HexToAddress(hex) - if err != nil { - panic(err) - } - return address -} - -type Options struct { - NWorker int - DiffMigrations bool - LogVerboseDiff bool - CheckStorageHealthBeforeMigration bool - VerboseErrorOutput bool - ChainID flow.ChainID - EVMContractChange EVMContractChange - BurnerContractChange BurnerContractChange - StagedContracts []StagedContract - Prune bool - MaxAccountSize uint64 - FixSlabsWithBrokenReferences bool - FilterUnreferencedSlabs bool - ReportMetrics bool - CacheStaticTypeMigrationResults bool - CacheEntitlementsMigrationResults bool -} - -func NewCadence1Migrations( - log zerolog.Logger, - outputDir string, - rwf reporters.ReportWriterFactory, - opts Options, -) (migs []NamedMigration) { - - if opts.MaxAccountSize > 0 { - - maxSizeExceptions := map[string]struct{}{} - - systemContracts := systemcontracts.SystemContractsForChain(opts.ChainID) - for _, systemContract := range systemContracts.All() { - maxSizeExceptions[string(systemContract.Address.Bytes())] = struct{}{} - } - - migs = append( - migs, - NamedMigration{ - Name: "account-size-filter-migration", - Migrate: NewAccountSizeFilterMigration( - opts.MaxAccountSize, - maxSizeExceptions, - log, - ), - }, - ) - } - - if opts.FixSlabsWithBrokenReferences || opts.FilterUnreferencedSlabs { - - var accountBasedMigrations []AccountBasedMigration - - if opts.FixSlabsWithBrokenReferences { - accountBasedMigrations = append( - accountBasedMigrations, - NewFixBrokenReferencesInSlabsMigration(outputDir, rwf, testnetAccountsWithBrokenSlabReferences), - ) - } - - if opts.FilterUnreferencedSlabs { - accountBasedMigrations = append( - accountBasedMigrations, - // NOTE: migration to filter unreferenced slabs should happen - // after migration to fix slabs with references to nonexistent slabs. - NewFilterUnreferencedSlabsMigration(outputDir, rwf), - ) - } - - migs = append( - migs, - NamedMigration{ - Name: "fix-slabs-migration", - Migrate: NewAccountBasedMigration( - log, - opts.NWorker, - accountBasedMigrations, - ), - }, - ) - } - - if opts.Prune { - migration := NewCadence1PruneMigration(opts.ChainID, log, opts.NWorker) - if migration != nil { - migs = append( - migs, - NamedMigration{ - Name: "prune-migration", - Migrate: migration, - }, - ) - } - } - - importantLocations := make(map[common.AddressLocation]struct{}) - legacyTypeRequirements := &LegacyTypeRequirements{} - - cadenceTypeRequirementsExtractor := NewTypeRequirementsExtractingMigration( - log, - rwf, - importantLocations, - legacyTypeRequirements, - ) - - migs = append( - migs, - NamedMigration{ - Name: "extract-type-requirements", - Migrate: cadenceTypeRequirementsExtractor, - }, - ) - - cadence1ContractsMigrations := NewCadence1ContractsMigrations( - log, - rwf, - importantLocations, - legacyTypeRequirements, - opts, - ) - - migs = append( - migs, - cadence1ContractsMigrations..., - ) - - migs = append( - migs, - NewCadence1ValueMigrations( - log, - rwf, - importantLocations, - legacyTypeRequirements, - opts, - )..., - ) - - switch opts.EVMContractChange { - case EVMContractChangeNone, - EVMContractChangeDeployFull: - // NO-OP - case EVMContractChangeUpdateFull, EVMContractChangeDeployMinimalAndUpdateFull: - migs = append( - migs, - NamedMigration{ - Name: "evm-setup-migration", - Migrate: NewEVMSetupMigration(opts.ChainID, log), - }, - ) - if opts.ChainID == flow.Emulator { - - // In the Emulator the EVM storage account needs to be created - - systemContracts := systemcontracts.SystemContractsForChain(opts.ChainID) - evmStorageAddress := systemContracts.EVMStorage.Address - - migs = append( - migs, - NamedMigration{ - Name: "evm-storage-account-creation-migration", - Migrate: NewAccountCreationMigration(evmStorageAddress, log), - }, - ) - } - } - - return migs -} diff --git a/cmd/util/ledger/migrations/cadence_test.go b/cmd/util/ledger/migrations/cadence_test.go deleted file mode 100644 index bc2d02a0d6a..00000000000 --- a/cmd/util/ledger/migrations/cadence_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package migrations - -import ( - "testing" - - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/model/flow" -) - -func TestMigrateCadence1EmptyContract(t *testing.T) { - - t.Parallel() - - const chainID = flow.Testnet - - address, err := common.HexToAddress("0x4184b8bdf78db9eb") - require.NoError(t, err) - - const contractName = "FungibleToken" - - registersByAccount := registers.NewByAccount() - - err = registersByAccount.Set( - string(address[:]), - flow.ContractKey(contractName), - // some whitespace for testing purposes - []byte(" \t \n "), - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{contractName}) - require.NoError(t, err) - - err = registersByAccount.Set( - string(address[:]), - flow.ContractNamesKey, - encodedContractNames, - ) - require.NoError(t, err) - - programs := map[common.Location]*interpreter.Program{} - - rwf := &testReportWriterFactory{} - - // Run contract checking migration - - log := zerolog.Nop() - checkingMigration := NewContractCheckingMigration(log, rwf, chainID, false, nil, programs) - - err = checkingMigration(registersByAccount) - require.NoError(t, err) - - reporter := rwf.reportWriters[contractCheckingReporterName] - assert.Empty(t, reporter.entries) - - // Initialize metrics collecting migration (used to run into unexpected error) - - metricsCollectingMigration := NewMetricsCollectingMigration(log, chainID, rwf, programs) - - err = metricsCollectingMigration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) -} diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go deleted file mode 100644 index 4bd2c756cde..00000000000 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ /dev/null @@ -1,932 +0,0 @@ -package migrations - -import ( - "context" - "encoding/json" - "fmt" - "io" - "sync" - - "errors" - - "github.com/onflow/cadence/migrations" - "github.com/onflow/cadence/migrations/capcons" - "github.com/onflow/cadence/migrations/entitlements" - "github.com/onflow/cadence/migrations/statictypes" - "github.com/onflow/cadence/migrations/string_normalization" - "github.com/onflow/cadence/migrations/type_keys" - "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/runtime/common" - cadenceErrors "github.com/onflow/cadence/runtime/errors" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/sema" - "github.com/onflow/cadence/runtime/stdlib" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/fvm/tracing" - "github.com/onflow/flow-go/ledger" - "github.com/onflow/flow-go/model/flow" -) - -type CadenceBaseMigration struct { - name string - log zerolog.Logger - reporter reporters.ReportWriter - diffReporter reporters.ReportWriter - logVerboseDiff bool - verboseErrorOutput bool - checkStorageHealthBeforeMigration bool - valueMigrations func( - inter *interpreter.Interpreter, - accounts environment.Accounts, - reporter *cadenceValueMigrationReporter, - ) []migrations.ValueMigration - interpreterMigrationRuntimeConfig InterpreterMigrationRuntimeConfig - errorMessageHandler *errorMessageHandler - programs map[runtime.Location]*interpreter.Program - chainID flow.ChainID - nWorkers int -} - -var _ AccountBasedMigration = (*CadenceBaseMigration)(nil) -var _ io.Closer = (*CadenceBaseMigration)(nil) - -func (m *CadenceBaseMigration) Close() error { - // Close the report writer so it flushes to file. - m.reporter.Close() - - if m.diffReporter != nil { - m.diffReporter.Close() - } - - return nil -} - -func (m *CadenceBaseMigration) InitMigration( - log zerolog.Logger, - _ *registers.ByAccount, - nWorkers int, -) error { - m.log = log.With().Str("migration", m.name).Logger() - m.nWorkers = nWorkers - - // During the migration, we only provide already checked programs, - // no parsing/checking of contracts is expected. - - m.interpreterMigrationRuntimeConfig = InterpreterMigrationRuntimeConfig{ - GetOrLoadProgram: func( - location runtime.Location, - _ func() (*interpreter.Program, error), - ) (*interpreter.Program, error) { - program, ok := m.programs[location] - if !ok { - return nil, fmt.Errorf("program not found: %s", location) - } - return program, nil - }, - GetCode: func(_ common.AddressLocation) ([]byte, error) { - return nil, fmt.Errorf("unexpected call to GetCode") - }, - GetContractNames: func(address flow.Address) ([]string, error) { - return nil, fmt.Errorf("unexpected call to GetContractNames") - }, - } - - return nil -} - -func (m *CadenceBaseMigration) MigrateAccount( - _ context.Context, - address common.Address, - accountRegisters *registers.AccountRegisters, -) error { - var oldPayloadsForDiff []*ledger.Payload - if m.diffReporter != nil { - oldPayloadsForDiff = accountRegisters.Payloads() - } - - // Create all the runtime components we need for the migration - migrationRuntime, err := NewInterpreterMigrationRuntime( - accountRegisters, - m.chainID, - m.interpreterMigrationRuntimeConfig, - ) - if err != nil { - return fmt.Errorf("failed to create interpreter migration runtime: %w", err) - } - - storage := migrationRuntime.Storage - - // Check storage health before migration, if enabled. - var storageHealthErrorBefore error - if m.checkStorageHealthBeforeMigration { - - storageHealthErrorBefore = util.CheckStorageHealth(address, storage, accountRegisters, AllStorageMapDomains, m.nWorkers) - if storageHealthErrorBefore != nil { - m.log.Warn(). - Err(storageHealthErrorBefore). - Str("account", address.HexWithPrefix()). - Msg("storage health check before migration failed") - } - } - - migration, err := migrations.NewStorageMigration( - migrationRuntime.Interpreter, - storage, - m.name, - address, - ) - if err != nil { - return fmt.Errorf("failed to create storage migration: %w", err) - } - - reporter := newValueMigrationReporter( - m.reporter, - m.log, - m.errorMessageHandler, - m.verboseErrorOutput, - ) - - valueMigrations := m.valueMigrations( - migrationRuntime.Interpreter, - migrationRuntime.Accounts, - reporter, - ) - - migration.Migrate( - migration.NewValueMigrationsPathMigrator( - reporter, - valueMigrations..., - ), - ) - - err = migration.Commit() - if err != nil { - return fmt.Errorf("failed to commit changes: %w", err) - } - - // Check storage health after migration. - // If the storage health check failed before the migration, we don't need to check it again. - if storageHealthErrorBefore == nil { - storageHealthErrorAfter := storage.CheckHealth() - if storageHealthErrorAfter != nil { - m.log.Err(storageHealthErrorAfter). - Str("account", address.HexWithPrefix()). - Msg("storage health check after migration failed") - } - } - - // Commit/finalize the transaction - - expectedAddresses := map[flow.Address]struct{}{ - flow.Address(address): {}, - } - - err = migrationRuntime.Commit(expectedAddresses, m.log) - if err != nil { - return fmt.Errorf("failed to commit: %w", err) - } - - if m.diffReporter != nil { - newPayloadsForDiff := accountRegisters.Payloads() - - accountDiffReporter := NewCadenceValueDiffReporter( - address, - m.chainID, - m.diffReporter, - m.logVerboseDiff, - m.nWorkers, - ) - - owner := flow.AddressToRegisterOwner(flow.Address(address)) - - oldRegistersForDiff, err := registers.NewAccountRegistersFromPayloads(owner, oldPayloadsForDiff) - if err != nil { - return fmt.Errorf("failed to create registers from old payloads: %w", err) - } - - newRegistersForDiff, err := registers.NewAccountRegistersFromPayloads(owner, newPayloadsForDiff) - if err != nil { - return fmt.Errorf("failed to create registers from new payloads: %w", err) - } - - accountDiffReporter.DiffStates( - oldRegistersForDiff, - newRegistersForDiff, - AllStorageMapDomains, - ) - } - - return nil -} - -const cadenceValueMigrationReporterName = "cadence-value-migration" - -// NewCadence1ValueMigration creates a new CadenceBaseMigration -// which runs some of the Cadence value migrations (static types, entitlements, strings) -func NewCadence1ValueMigration( - rwf reporters.ReportWriterFactory, - errorMessageHandler *errorMessageHandler, - programs map[runtime.Location]*interpreter.Program, - compositeTypeConverter statictypes.CompositeTypeConverterFunc, - interfaceTypeConverter statictypes.InterfaceTypeConverterFunc, - storageDomainCapabilities *capcons.AccountsCapabilities, - opts Options, -) *CadenceBaseMigration { - - var diffReporter reporters.ReportWriter - if opts.DiffMigrations { - diffReporter = rwf.ReportWriter("cadence-value-migration-diff") - } - - var staticTypeMigrationCache migrations.StaticTypeCache - if opts.CacheStaticTypeMigrationResults { - staticTypeMigrationCache = migrations.NewDefaultStaticTypeCache() - } - - var entitlementsMigrationCache migrations.StaticTypeCache - if opts.CacheEntitlementsMigrationResults { - entitlementsMigrationCache = migrations.NewDefaultStaticTypeCache() - } - - return &CadenceBaseMigration{ - name: "cadence_value_migration", - reporter: rwf.ReportWriter(cadenceValueMigrationReporterName), - diffReporter: diffReporter, - logVerboseDiff: opts.LogVerboseDiff, - verboseErrorOutput: opts.VerboseErrorOutput, - checkStorageHealthBeforeMigration: opts.CheckStorageHealthBeforeMigration, - valueMigrations: func( - inter *interpreter.Interpreter, - _ environment.Accounts, - reporter *cadenceValueMigrationReporter, - ) []migrations.ValueMigration { - return []migrations.ValueMigration{ - statictypes.NewStaticTypeMigrationWithCache(staticTypeMigrationCache). - WithCompositeTypeConverter(compositeTypeConverter). - WithInterfaceTypeConverter(interfaceTypeConverter), - entitlements.NewEntitlementsMigrationWithCache(inter, entitlementsMigrationCache), - // After the static type and entitlements migration, we run the type key migration, - // which ensures that even if the previous migrations failed to migrate `Type` values - // used as dictionary keys, they will get re-stored and are accessible by users - // and the mutating iterator of the inlined version of atree - type_keys.NewTypeKeyMigration(), - string_normalization.NewStringNormalizingMigration(), - &capcons.StorageCapMigration{ - StorageDomainCapabilities: storageDomainCapabilities, - }, - } - }, - errorMessageHandler: errorMessageHandler, - programs: programs, - chainID: opts.ChainID, - } -} - -type capabilityControllerHandler struct { - idGenerator environment.AccountLocalIDGenerator -} - -var _ stdlib.CapabilityControllerIssueHandler = capabilityControllerHandler{} -var _ stdlib.CapabilityControllerHandler = capabilityControllerHandler{} - -func (c capabilityControllerHandler) GenerateAccountID(address common.Address) (uint64, error) { - return c.idGenerator.GenerateAccountID(address) -} - -func (capabilityControllerHandler) EmitEvent( - _ *interpreter.Interpreter, - _ interpreter.LocationRange, - _ *sema.CompositeType, - _ []interpreter.Value, -) { - // NO-OP -} - -// NewCadence1LinkValueMigration creates a new CadenceBaseMigration -// which migrates links to capability controllers. -// It populates the given map with the IDs of the capability controller it issues. -func NewCadence1LinkValueMigration( - rwf reporters.ReportWriterFactory, - errorMessageHandler *errorMessageHandler, - programs map[runtime.Location]*interpreter.Program, - capabilityMapping *capcons.PathCapabilityMapping, - opts Options, -) *CadenceBaseMigration { - var diffReporter reporters.ReportWriter - if opts.DiffMigrations { - diffReporter = rwf.ReportWriter("cadence-link-value-migration-diff") - } - - return &CadenceBaseMigration{ - name: "cadence_link_value_migration", - reporter: rwf.ReportWriter("cadence-link-value-migration"), - diffReporter: diffReporter, - logVerboseDiff: opts.LogVerboseDiff, - verboseErrorOutput: opts.VerboseErrorOutput, - checkStorageHealthBeforeMigration: opts.CheckStorageHealthBeforeMigration, - valueMigrations: func( - _ *interpreter.Interpreter, - accounts environment.Accounts, - reporter *cadenceValueMigrationReporter, - ) []migrations.ValueMigration { - - idGenerator := environment.NewAccountLocalIDGenerator( - tracing.NewMockTracerSpan(), - util.NopMeter{}, - accounts, - ) - - handler := capabilityControllerHandler{ - idGenerator: idGenerator, - } - - return []migrations.ValueMigration{ - &capcons.LinkValueMigration{ - CapabilityMapping: capabilityMapping, - IssueHandler: handler, - Handler: handler, - Reporter: reporter, - }, - } - }, - errorMessageHandler: errorMessageHandler, - programs: programs, - chainID: opts.ChainID, - } -} - -const capabilityValueMigrationReporterName = "cadence-capability-value-migration" - -// NewCadence1CapabilityValueMigration creates a new CadenceBaseMigration -// which migrates path capability values to ID capability values. -// It requires a map the IDs of the capability controllers, -// generated by the link value migration. -func NewCadence1CapabilityValueMigration( - rwf reporters.ReportWriterFactory, - errorMessageHandler *errorMessageHandler, - programs map[runtime.Location]*interpreter.Program, - privatePublicCapabilityMapping *capcons.PathCapabilityMapping, - typedStorageCapabilityMapping *capcons.PathTypeCapabilityMapping, - untypedStorageCapabilityMapping *capcons.PathCapabilityMapping, - opts Options, -) *CadenceBaseMigration { - var diffReporter reporters.ReportWriter - if opts.DiffMigrations { - diffReporter = rwf.ReportWriter("cadence-capability-value-migration-diff") - } - - return &CadenceBaseMigration{ - name: "cadence_capability_value_migration", - reporter: rwf.ReportWriter(capabilityValueMigrationReporterName), - diffReporter: diffReporter, - logVerboseDiff: opts.LogVerboseDiff, - verboseErrorOutput: opts.VerboseErrorOutput, - checkStorageHealthBeforeMigration: opts.CheckStorageHealthBeforeMigration, - valueMigrations: func( - _ *interpreter.Interpreter, - accounts environment.Accounts, - reporter *cadenceValueMigrationReporter, - ) []migrations.ValueMigration { - return []migrations.ValueMigration{ - &capcons.CapabilityValueMigration{ - PrivatePublicCapabilityMapping: privatePublicCapabilityMapping, - TypedStorageCapabilityMapping: typedStorageCapabilityMapping, - UntypedStorageCapabilityMapping: untypedStorageCapabilityMapping, - Reporter: reporter, - }, - } - }, - errorMessageHandler: errorMessageHandler, - programs: programs, - chainID: opts.ChainID, - } -} - -// errorMessageHandler formats error messages from errors. -// It only reports program loading errors once. -type errorMessageHandler struct { - // common.Location -> struct{} - reportedProgramLoadingErrors sync.Map -} - -func (t *errorMessageHandler) FormatError(err error) (message string, showStack bool) { - - // Only report program loading errors once, - // omit full error message for subsequent occurrences - - var programLoadingError environment.ProgramLoadingError - if errors.As(err, &programLoadingError) { - location := programLoadingError.Location - _, ok := t.reportedProgramLoadingErrors.LoadOrStore(location, struct{}{}) - if ok { - return "error getting program", false - } - - return err.Error(), false - } - - return err.Error(), true -} - -// cadenceValueMigrationReporter is the reporter for cadence value migrations -type cadenceValueMigrationReporter struct { - reportWriter reporters.ReportWriter - log zerolog.Logger - errorMessageHandler *errorMessageHandler - verboseErrorOutput bool -} - -var _ capcons.LinkMigrationReporter = &cadenceValueMigrationReporter{} -var _ capcons.CapabilityMigrationReporter = &cadenceValueMigrationReporter{} -var _ capcons.StorageCapabilityMigrationReporter = &cadenceValueMigrationReporter{} -var _ migrations.Reporter = &cadenceValueMigrationReporter{} - -func newValueMigrationReporter( - reportWriter reporters.ReportWriter, - log zerolog.Logger, - errorMessageHandler *errorMessageHandler, - verboseErrorOutput bool, -) *cadenceValueMigrationReporter { - return &cadenceValueMigrationReporter{ - reportWriter: reportWriter, - log: log, - errorMessageHandler: errorMessageHandler, - verboseErrorOutput: verboseErrorOutput, - } -} - -func (t *cadenceValueMigrationReporter) Migrated( - storageKey interpreter.StorageKey, - storageMapKey interpreter.StorageMapKey, - migration string, -) { - t.reportWriter.Write(cadenceValueMigrationEntry{ - StorageKey: storageKey, - StorageMapKey: storageMapKey, - Migration: migration, - }) -} - -func (t *cadenceValueMigrationReporter) Error(err error) { - - var migrationErr migrations.StorageMigrationError - - if !errors.As(err, &migrationErr) { - panic(cadenceErrors.NewUnreachableError()) - } - - message, showStack := t.errorMessageHandler.FormatError(migrationErr.Err) - - storageKey := migrationErr.StorageKey - storageMapKey := migrationErr.StorageMapKey - migration := migrationErr.Migration - - if showStack && len(migrationErr.Stack) > 0 { - message = fmt.Sprintf("%s\n%s", message, migrationErr.Stack) - } - - if t.verboseErrorOutput { - t.reportWriter.Write(cadenceValueMigrationFailureEntry{ - StorageKey: storageKey, - StorageMapKey: storageMapKey, - Migration: migration, - Message: message, - }) - } -} - -func (t *cadenceValueMigrationReporter) MigratedPathCapability( - accountAddress common.Address, - addressPath interpreter.AddressPath, - borrowType *interpreter.ReferenceStaticType, - capabilityID interpreter.UInt64Value, -) { - t.reportWriter.Write(capabilityMigrationEntry{ - AccountAddress: accountAddress, - AddressPath: addressPath, - BorrowType: borrowType, - CapabilityID: capabilityID, - }) -} - -func (t *cadenceValueMigrationReporter) MissingCapabilityID( - accountAddress common.Address, - addressPath interpreter.AddressPath, -) { - t.reportWriter.Write(capabilityMissingCapabilityIDEntry{ - AccountAddress: accountAddress, - AddressPath: addressPath, - }) -} - -func (t *cadenceValueMigrationReporter) MissingBorrowType( - targetPath interpreter.AddressPath, - storedPath interpreter.AddressPath, -) { - t.reportWriter.Write(storageCapConsMissingBorrowTypeEntry{ - TargetPath: targetPath, - StoredPath: storedPath, - }) -} - -func (t *cadenceValueMigrationReporter) InferredMissingBorrowType( - targetPath interpreter.AddressPath, - borrowType *interpreter.ReferenceStaticType, - storedPath interpreter.AddressPath, -) { - t.reportWriter.Write(storageCapConsInferredBorrowTypeEntry{ - TargetPath: targetPath, - BorrowType: borrowType, - StoredPath: storedPath, - }) -} - -func (t *cadenceValueMigrationReporter) IssuedStorageCapabilityController( - accountAddress common.Address, - addressPath interpreter.AddressPath, - borrowType *interpreter.ReferenceStaticType, - capabilityID interpreter.UInt64Value, -) { - t.reportWriter.Write(storageCapConIssuedEntry{ - AccountAddress: accountAddress, - AddressPath: addressPath, - BorrowType: borrowType, - CapabilityID: capabilityID, - }) -} - -func (t *cadenceValueMigrationReporter) MigratedLink( - accountAddressPath interpreter.AddressPath, - capabilityID interpreter.UInt64Value, -) { - t.reportWriter.Write(linkMigrationEntry{ - AccountAddressPath: accountAddressPath, - CapabilityID: uint64(capabilityID), - }) -} - -func (t *cadenceValueMigrationReporter) CyclicLink(err capcons.CyclicLinkError) { - t.reportWriter.Write(linkCyclicEntry{ - Address: err.Address, - Paths: err.Paths, - }) -} - -func (t *cadenceValueMigrationReporter) MissingTarget(accountAddressPath interpreter.AddressPath) { - t.reportWriter.Write(linkMissingTargetEntry{ - AddressPath: accountAddressPath, - }) -} - -func (t *cadenceValueMigrationReporter) DictionaryKeyConflict(accountAddressPath interpreter.AddressPath) { - t.reportWriter.Write(dictionaryKeyConflictEntry{ - AddressPath: accountAddressPath, - }) -} - -type valueMigrationReportEntry interface { - accountAddress() common.Address -} - -// cadenceValueMigrationReportEntry - -type cadenceValueMigrationEntry struct { - StorageKey interpreter.StorageKey - StorageMapKey interpreter.StorageMapKey - Migration string -} - -var _ valueMigrationReportEntry = cadenceValueMigrationEntry{} - -func (e cadenceValueMigrationEntry) accountAddress() common.Address { - return e.StorageKey.Address -} - -var _ json.Marshaler = cadenceValueMigrationEntry{} - -func (e cadenceValueMigrationEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - StorageDomain string `json:"domain"` - Key string `json:"key"` - Migration string `json:"migration"` - }{ - Kind: "cadence-value-migration-success", - AccountAddress: e.StorageKey.Address.HexWithPrefix(), - StorageDomain: e.StorageKey.Key, - Key: fmt.Sprintf("%s", e.StorageMapKey), - Migration: e.Migration, - }) -} - -// cadenceValueMigrationFailureEntry - -type cadenceValueMigrationFailureEntry struct { - StorageKey interpreter.StorageKey - StorageMapKey interpreter.StorageMapKey - Migration string - Message string -} - -var _ valueMigrationReportEntry = cadenceValueMigrationFailureEntry{} - -func (e cadenceValueMigrationFailureEntry) accountAddress() common.Address { - return e.StorageKey.Address -} - -var _ json.Marshaler = cadenceValueMigrationFailureEntry{} - -func (e cadenceValueMigrationFailureEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - StorageDomain string `json:"domain"` - Key string `json:"key"` - Migration string `json:"migration"` - Message string `json:"message"` - }{ - Kind: "cadence-value-migration-failure", - AccountAddress: e.StorageKey.Address.HexWithPrefix(), - StorageDomain: e.StorageKey.Key, - Key: fmt.Sprintf("%s", e.StorageMapKey), - Migration: e.Migration, - Message: e.Message, - }) -} - -// linkMigrationEntry - -type linkMigrationEntry struct { - AccountAddressPath interpreter.AddressPath - CapabilityID uint64 -} - -var _ valueMigrationReportEntry = linkMigrationEntry{} - -func (e linkMigrationEntry) accountAddress() common.Address { - return e.AccountAddressPath.Address -} - -var _ json.Marshaler = linkMigrationEntry{} - -func (e linkMigrationEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Path string `json:"path"` - CapabilityID uint64 `json:"capability_id"` - }{ - Kind: "link-migration-success", - AccountAddress: e.AccountAddressPath.Address.HexWithPrefix(), - Path: e.AccountAddressPath.Path.String(), - CapabilityID: e.CapabilityID, - }) -} - -// capabilityMigrationEntry - -type capabilityMigrationEntry struct { - AccountAddress common.Address - AddressPath interpreter.AddressPath - BorrowType *interpreter.ReferenceStaticType - CapabilityID interpreter.UInt64Value -} - -var _ valueMigrationReportEntry = capabilityMigrationEntry{} - -func (e capabilityMigrationEntry) accountAddress() common.Address { - return e.AccountAddress -} - -var _ json.Marshaler = capabilityMigrationEntry{} - -func (e capabilityMigrationEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Address string `json:"address"` - Path string `json:"path"` - BorrowType string `json:"borrow_type"` - CapabilityID string `json:"capability_id"` - }{ - Kind: "capability-migration-success", - AccountAddress: e.AccountAddress.HexWithPrefix(), - Address: e.AddressPath.Address.HexWithPrefix(), - Path: e.AddressPath.Path.String(), - BorrowType: string(e.BorrowType.ID()), - CapabilityID: e.CapabilityID.String(), - }) -} - -// capabilityMissingCapabilityIDEntry - -type capabilityMissingCapabilityIDEntry struct { - AccountAddress common.Address - AddressPath interpreter.AddressPath -} - -var _ valueMigrationReportEntry = capabilityMissingCapabilityIDEntry{} - -func (e capabilityMissingCapabilityIDEntry) accountAddress() common.Address { - return e.AccountAddress -} - -var _ json.Marshaler = capabilityMissingCapabilityIDEntry{} - -func (e capabilityMissingCapabilityIDEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Address string `json:"address"` - Path string `json:"path"` - }{ - Kind: "capability-missing-capability-id", - AccountAddress: e.AccountAddress.HexWithPrefix(), - Address: e.AddressPath.Address.HexWithPrefix(), - Path: e.AddressPath.Path.String(), - }) -} - -// linkMissingTargetEntry - -type linkMissingTargetEntry struct { - AddressPath interpreter.AddressPath -} - -var _ valueMigrationReportEntry = linkMissingTargetEntry{} - -func (e linkMissingTargetEntry) accountAddress() common.Address { - return e.AddressPath.Address -} - -var _ json.Marshaler = linkMissingTargetEntry{} - -func (e linkMissingTargetEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Path string `json:"path"` - }{ - Kind: "link-missing-target", - AccountAddress: e.AddressPath.Address.HexWithPrefix(), - Path: e.AddressPath.Path.String(), - }) -} - -// linkCyclicEntry - -type linkCyclicEntry struct { - Address common.Address - Paths []interpreter.PathValue -} - -var _ valueMigrationReportEntry = linkCyclicEntry{} - -func (e linkCyclicEntry) accountAddress() common.Address { - return e.Address -} - -var _ json.Marshaler = linkCyclicEntry{} - -func (e linkCyclicEntry) MarshalJSON() ([]byte, error) { - - pathStrings := make([]string, 0, len(e.Paths)) - for _, path := range e.Paths { - pathStrings = append(pathStrings, path.String()) - } - - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Paths []string `json:"paths"` - }{ - Kind: "link-cyclic", - AccountAddress: e.Address.HexWithPrefix(), - Paths: pathStrings, - }) -} - -// dictionaryKeyConflictEntry - -type dictionaryKeyConflictEntry struct { - AddressPath interpreter.AddressPath -} - -var _ json.Marshaler = dictionaryKeyConflictEntry{} - -func (e dictionaryKeyConflictEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Path string `json:"path"` - }{ - Kind: "dictionary-key-conflict", - AccountAddress: e.AddressPath.Address.HexWithPrefix(), - Path: e.AddressPath.Path.String(), - }) -} - -// storageCapConIssuedEntry - -type storageCapConIssuedEntry struct { - AccountAddress common.Address - AddressPath interpreter.AddressPath - BorrowType interpreter.StaticType - CapabilityID interpreter.UInt64Value -} - -var _ valueMigrationReportEntry = storageCapConIssuedEntry{} - -func (e storageCapConIssuedEntry) accountAddress() common.Address { - return e.AccountAddress -} - -var _ json.Marshaler = storageCapConIssuedEntry{} - -func (e storageCapConIssuedEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Address string `json:"address"` - Path string `json:"path"` - BorrowType string `json:"borrow_type"` - CapabilityID string `json:"capability_id"` - }{ - Kind: "storage-capcon-issued", - AccountAddress: e.AccountAddress.HexWithPrefix(), - Address: e.AddressPath.Address.HexWithPrefix(), - Path: e.AddressPath.Path.String(), - BorrowType: string(e.BorrowType.ID()), - CapabilityID: e.CapabilityID.String(), - }) -} - -// StorageCapConMissingBorrowType - -type storageCapConsMissingBorrowTypeEntry struct { - TargetPath interpreter.AddressPath - StoredPath interpreter.AddressPath -} - -var _ valueMigrationReportEntry = storageCapConsMissingBorrowTypeEntry{} - -func (e storageCapConsMissingBorrowTypeEntry) accountAddress() common.Address { - return e.StoredPath.Address -} - -var _ json.Marshaler = storageCapConsMissingBorrowTypeEntry{} - -func (e storageCapConsMissingBorrowTypeEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Address string `json:"address"` - TargetPath string `json:"target_path"` - StoredPath string `json:"stored_path"` - }{ - Kind: "storage-capcon-missing-borrow-type", - AccountAddress: e.StoredPath.Address.HexWithPrefix(), - Address: e.TargetPath.Address.HexWithPrefix(), - TargetPath: e.TargetPath.Path.String(), - StoredPath: e.StoredPath.Path.String(), - }) -} - -// StorageCapConMissingBorrowType - -type storageCapConsInferredBorrowTypeEntry struct { - TargetPath interpreter.AddressPath - BorrowType *interpreter.ReferenceStaticType - StoredPath interpreter.AddressPath -} - -var _ valueMigrationReportEntry = storageCapConsInferredBorrowTypeEntry{} -var _ json.Marshaler = storageCapConsInferredBorrowTypeEntry{} - -func (e storageCapConsInferredBorrowTypeEntry) accountAddress() common.Address { - return e.StoredPath.Address -} - -func (e storageCapConsInferredBorrowTypeEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - Address string `json:"address"` - TargetPath string `json:"target_path"` - BorrowType string `json:"borrow_type"` - StoredPath string `json:"stored_path"` - }{ - Kind: "storage-capcon-inferred-borrow-type", - AccountAddress: e.StoredPath.Address.HexWithPrefix(), - Address: e.TargetPath.Address.HexWithPrefix(), - TargetPath: e.TargetPath.Path.String(), - BorrowType: string(e.BorrowType.ID()), - StoredPath: e.StoredPath.Path.String(), - }) -} diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go deleted file mode 100644 index cad06ce594d..00000000000 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ /dev/null @@ -1,3142 +0,0 @@ -package migrations - -import ( - _ "embed" - "fmt" - "io" - "sort" - "sync" - "testing" - - _ "github.com/glebarez/go-sqlite" - "github.com/onflow/cadence" - migrations2 "github.com/onflow/cadence/migrations" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "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/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/engine/execution/computation" - "github.com/onflow/flow-go/fvm" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/fvm/systemcontracts" - "github.com/onflow/flow-go/model/flow" -) - -const snapshotPath = "test-data/cadence_values_migration/snapshot_cadence_v0.42.6" - -const testAccountAddress = "01cf0e2f2f715450" - -type writer struct { - logs []string -} - -var _ io.Writer = &writer{} - -func (w *writer) Write(p []byte) (n int, err error) { - w.logs = append(w.logs, string(p)) - return len(p), nil -} - -//go:embed test-data/cadence_values_migration/test_contract_upgraded.cdc -var testContractUpgraded []byte - -func TestCadenceValuesMigration(t *testing.T) { - - t.Parallel() - - address, err := common.HexToAddress(testAccountAddress) - require.NoError(t, err) - - // Get the old payloads - payloads, err := util.PayloadsFromEmulatorSnapshot(snapshotPath) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - const chainID = flow.Emulator - // TODO: EVM contract is not deployed in snapshot yet, so can't update it - const evmContractChange = EVMContractChangeNone - - const burnerContractChange = BurnerContractChangeDeploy - - stagedContracts := []StagedContract{ - { - Contract: Contract{ - Name: "Test", - Code: testContractUpgraded, - }, - Address: address, - }, - } - - migrations := NewCadence1Migrations( - logger, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - StagedContracts: stagedContracts, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - // Assert the migrated payloads - checkMigratedPayloads(t, address, registersByAccount, chainID) - - // Check reporters - checkReporters(t, rwf, address) - - // Check error logs. - require.Empty(t, logWriter.logs) - - checkMigratedState(t, address, registersByAccount, chainID) -} - -type migrationVisit struct { - storageKey interpreter.StorageKey - storageMapKey interpreter.StorageMapKey - value string -} - -type visitMigration struct { - visits []migrationVisit -} - -var _ migrations2.ValueMigration = &visitMigration{} - -func (*visitMigration) Name() string { - return "visit" -} - -func (m *visitMigration) Migrate( - storageKey interpreter.StorageKey, - storageMapKey interpreter.StorageMapKey, - value interpreter.Value, - _ *interpreter.Interpreter, - _ migrations2.ValueMigrationPosition, -) (newValue interpreter.Value, err error) { - - m.visits = append( - m.visits, - migrationVisit{ - storageKey: storageKey, - storageMapKey: storageMapKey, - value: value.String(), - }, - ) - - return nil, nil -} - -func (*visitMigration) CanSkip(_ interpreter.StaticType) bool { - return false -} - -func (*visitMigration) Domains() map[string]struct{} { - return nil -} - -func checkMigratedState( - t *testing.T, - address common.Address, - registersByAccount *registers.ByAccount, - chainID flow.ChainID, -) { - - mr, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - validationMigration, err := migrations2.NewStorageMigration( - mr.Interpreter, - mr.Storage, - "validation", - address, - ) - require.NoError(t, err) - - visitMigration := &visitMigration{} - - validationMigration.Migrate( - validationMigration.NewValueMigrationsPathMigrator(nil, visitMigration), - ) - - require.Equal(t, - []migrationVisit{ - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_string_keys"), - value: `"H\u{e9}llo"`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_string_keys"), - value: `"Caf\u{e9}"`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_string_keys"), - value: `2`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_string_keys"), - value: `1`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_string_keys"), - value: `{"H\u{e9}llo": 2, "Caf\u{e9}": 1}`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: `0.00100000`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: `8791026472627208194`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: `A.0ae53cb6e3f42a79.FlowToken.Vault(balance: 0.00100000, uuid: 8791026472627208194)`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_auth_reference_typed_key"), - value: `Type()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_auth_reference_typed_key"), - value: `"auth_ref"`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_auth_reference_typed_key"), - value: `{Type(): "auth_ref"}`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_reference_typed_key"), - value: `Type()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_reference_typed_key"), - value: `"non_auth_ref"`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_reference_typed_key"), - value: `{Type(): "non_auth_ref"}`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("type_value"), - value: "Type()", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "Type()", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "Type()", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "Type()", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "Type()", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "Type<&Account>()", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: `Type()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: `Type()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: `Type()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: `Type()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "4", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "6", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "5", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "7", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "8", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "2", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "3", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "9", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: "1", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - value: `{Type(): 4, Type(): 6, Type(): 5, Type(): 7, Type<&Account>(): 8, Type(): 2, Type(): 3, Type(): 9, Type(): 1}`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("capability"), - value: `Capability(address: 0x01cf0e2f2f715450, id: 2)`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("capability"), - value: `Capability(address: 0x01cf0e2f2f715450, id: 2)`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("string_value_1"), - value: `"Caf\u{e9}"`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("untyped_capability"), - value: `Capability(address: 0x01cf0e2f2f715450, id: 2)`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("r"), - value: `11457157452030541824`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("r"), - value: "A.01cf0e2f2f715450.Test.R(uuid: 11457157452030541824)", - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("string_value_2"), - value: `"Caf\u{e9}"`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - value: `Type<{A.01cf0e2f2f715450.Test.Bar, A.01cf0e2f2f715450.Test.Foo}>()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - value: `Type<{A.01cf0e2f2f715450.Test.Foo, A.01cf0e2f2f715450.Test.Bar, A.01cf0e2f2f715450.Test.Baz}>()`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - value: `1`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - value: `2`, - }, - { - storageKey: interpreter.StorageKey{Key: "storage", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - value: `{Type<{A.01cf0e2f2f715450.Test.Bar, A.01cf0e2f2f715450.Test.Foo}>(): 1, Type<{A.01cf0e2f2f715450.Test.Foo, A.01cf0e2f2f715450.Test.Bar, A.01cf0e2f2f715450.Test.Baz}>(): 2}`, - }, - { - storageKey: interpreter.StorageKey{Key: "public", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenReceiver"), - value: `Capability<&A.0ae53cb6e3f42a79.FlowToken.Vault>(address: 0x01cf0e2f2f715450, id: 1)`, - }, - { - storageKey: interpreter.StorageKey{Key: "public", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("linkR"), - value: `Capability(address: 0x01cf0e2f2f715450, id: 2)`, - }, - { - storageKey: interpreter.StorageKey{Key: "public", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenBalance"), - value: `Capability<&A.0ae53cb6e3f42a79.FlowToken.Vault>(address: 0x01cf0e2f2f715450, id: 3)`, - }, - { - storageKey: interpreter.StorageKey{Key: "contract", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("Test"), - value: `A.01cf0e2f2f715450.Test()`, - }, - { - storageKey: interpreter.StorageKey{Key: "cap_con", Address: address}, - storageMapKey: interpreter.Uint64StorageMapKey(0x2), - value: "StorageCapabilityController(borrowType: Type(), capabilityID: 2, target: /storage/r)", - }, - { - storageKey: interpreter.StorageKey{Key: "cap_con", Address: address}, - storageMapKey: interpreter.Uint64StorageMapKey(0x1), - value: "StorageCapabilityController(borrowType: Type<&A.0ae53cb6e3f42a79.FlowToken.Vault>(), capabilityID: 1, target: /storage/flowTokenVault)", - }, - { - storageKey: interpreter.StorageKey{Key: "cap_con", Address: address}, - storageMapKey: interpreter.Uint64StorageMapKey(0x3), - value: "StorageCapabilityController(borrowType: Type<&A.0ae53cb6e3f42a79.FlowToken.Vault>(), capabilityID: 3, target: /storage/flowTokenVault)", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: "3", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: "1", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: "nil", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: "nil", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("flowTokenVault"), - value: "{3: nil, 1: nil}", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("r"), - value: "2", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("r"), - value: "nil", - }, - { - storageKey: interpreter.StorageKey{Key: "path_cap", Address: address}, - storageMapKey: interpreter.StringStorageMapKey("r"), - value: "{2: nil}", - }, - }, - visitMigration.visits, - ) - -} - -var flowTokenAddress = func() common.Address { - address, _ := common.HexToAddress("0ae53cb6e3f42a79") - return address -}() - -func checkMigratedPayloads( - t *testing.T, - address common.Address, - registersByAccount *registers.ByAccount, - chainID flow.ChainID, -) { - mr, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - storageMap := mr.Storage.GetStorageMap(address, common.PathDomainStorage.Identifier(), false) - require.NotNil(t, storageMap) - require.Equal(t, 12, int(storageMap.Count())) - - iterator := storageMap.Iterator(mr.Interpreter) - - // Check whether the account ID is properly incremented. - checkAccountID(t, mr, address) - - fullyEntitledAccountReferenceType := interpreter.ConvertSemaToStaticType(nil, sema.FullyEntitledAccountReferenceType) - accountReferenceType := interpreter.ConvertSemaToStaticType(nil, sema.AccountReferenceType) - - var values []interpreter.Value - for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { - values = append(values, value) - } - - testContractLocation := common.NewAddressLocation( - nil, - address, - "Test", - ) - - flowTokenLocation := common.NewAddressLocation( - nil, - flowTokenAddress, - "FlowToken", - ) - - fooInterfaceType := interpreter.NewInterfaceStaticTypeComputeTypeID( - nil, - testContractLocation, - "Test.Foo", - ) - - barInterfaceType := interpreter.NewInterfaceStaticTypeComputeTypeID( - nil, - testContractLocation, - "Test.Bar", - ) - - bazInterfaceType := interpreter.NewInterfaceStaticTypeComputeTypeID( - nil, - testContractLocation, - "Test.Baz", - ) - - rResourceType := interpreter.NewCompositeStaticTypeComputeTypeID( - nil, - testContractLocation, - "Test.R", - ) - - entitlementAuthorization := func() interpreter.EntitlementSetAuthorization { - return interpreter.NewEntitlementSetAuthorization( - nil, - func() (entitlements []common.TypeID) { - return []common.TypeID{ - testContractLocation.TypeID(nil, "Test.E"), - } - }, - 1, - sema.Conjunction, - ) - } - - expectedValues := []interpreter.Value{ - interpreter.NewDictionaryValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - interpreter.NewDictionaryStaticType( - nil, - interpreter.PrimitiveStaticTypeString, - interpreter.PrimitiveStaticTypeInt, - ), - interpreter.NewUnmeteredStringValue("Caf\u00E9"), - interpreter.NewUnmeteredIntValueFromInt64(1), - interpreter.NewUnmeteredStringValue("H\u00E9llo"), - interpreter.NewUnmeteredIntValueFromInt64(2), - ), - - interpreter.NewCompositeValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - flowTokenLocation, - "FlowToken.Vault", - common.CompositeKindResource, - []interpreter.CompositeField{ - { - Value: interpreter.NewUnmeteredUFix64Value(0.001 * sema.Fix64Factor), - Name: "balance", - }, - { - Value: interpreter.NewUnmeteredUInt64Value(8791026472627208194), - Name: "uuid", - }, - }, - address, - ), - interpreter.NewDictionaryValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - interpreter.NewDictionaryStaticType( - nil, - interpreter.PrimitiveStaticTypeMetaType, - interpreter.PrimitiveStaticTypeString, - ), - interpreter.NewUnmeteredTypeValue( - interpreter.NewReferenceStaticType( - nil, - entitlementAuthorization(), - rResourceType, - ), - ), - interpreter.NewUnmeteredStringValue("auth_ref"), - ), - interpreter.NewDictionaryValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - interpreter.NewDictionaryStaticType( - nil, - interpreter.PrimitiveStaticTypeMetaType, - interpreter.PrimitiveStaticTypeString, - ), - interpreter.NewUnmeteredTypeValue( - interpreter.NewReferenceStaticType( - nil, - entitlementAuthorization(), - rResourceType, - ), - ), - interpreter.NewUnmeteredStringValue("non_auth_ref"), - ), - interpreter.NewUnmeteredTypeValue(fullyEntitledAccountReferenceType), - interpreter.NewDictionaryValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - interpreter.NewDictionaryStaticType( - nil, - interpreter.PrimitiveStaticTypeMetaType, - interpreter.PrimitiveStaticTypeInt, - ), - interpreter.NewUnmeteredTypeValue(fullyEntitledAccountReferenceType), - interpreter.NewUnmeteredIntValueFromInt64(1), - interpreter.NewUnmeteredTypeValue(interpreter.PrimitiveStaticTypeAccount_Capabilities), - interpreter.NewUnmeteredIntValueFromInt64(2), - interpreter.NewUnmeteredTypeValue(interpreter.PrimitiveStaticTypeAccount_AccountCapabilities), - interpreter.NewUnmeteredIntValueFromInt64(3), - interpreter.NewUnmeteredTypeValue(interpreter.PrimitiveStaticTypeAccount_StorageCapabilities), - interpreter.NewUnmeteredIntValueFromInt64(4), - interpreter.NewUnmeteredTypeValue(interpreter.PrimitiveStaticTypeAccount_Contracts), - interpreter.NewUnmeteredIntValueFromInt64(5), - interpreter.NewUnmeteredTypeValue(interpreter.PrimitiveStaticTypeAccount_Keys), - interpreter.NewUnmeteredIntValueFromInt64(6), - interpreter.NewUnmeteredTypeValue(interpreter.PrimitiveStaticTypeAccount_Inbox), - interpreter.NewUnmeteredIntValueFromInt64(7), - interpreter.NewUnmeteredTypeValue(accountReferenceType), - interpreter.NewUnmeteredIntValueFromInt64(8), - interpreter.NewUnmeteredTypeValue(interpreter.AccountKeyStaticType), - interpreter.NewUnmeteredIntValueFromInt64(9), - ), - interpreter.NewUnmeteredSomeValueNonCopying( - interpreter.NewUnmeteredCapabilityValue( - 2, - interpreter.NewAddressValue(nil, address), - interpreter.NewReferenceStaticType(nil, entitlementAuthorization(), rResourceType), - ), - ), - - // String value should be in the normalized form. - interpreter.NewUnmeteredStringValue("Caf\u00E9"), - - interpreter.NewUnmeteredCapabilityValue( - 2, - interpreter.NewAddressValue(nil, address), - interpreter.NewReferenceStaticType(nil, entitlementAuthorization(), rResourceType), - ), - - interpreter.NewCompositeValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - testContractLocation, - "Test.R", - common.CompositeKindResource, - []interpreter.CompositeField{ - { - Value: interpreter.NewUnmeteredUInt64Value(11457157452030541824), - Name: "uuid", - }, - }, - address, - ), - - // String value should be in the normalized form. - interpreter.NewUnmeteredStringValue("Caf\u00E9"), - - interpreter.NewDictionaryValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - interpreter.NewDictionaryStaticType( - nil, - interpreter.PrimitiveStaticTypeMetaType, - interpreter.PrimitiveStaticTypeInt, - ), - interpreter.NewUnmeteredTypeValue( - &interpreter.IntersectionStaticType{ - Types: []*interpreter.InterfaceStaticType{ - fooInterfaceType, - barInterfaceType, - }, - LegacyType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - ), - interpreter.NewUnmeteredIntValueFromInt64(1), - interpreter.NewUnmeteredTypeValue( - &interpreter.IntersectionStaticType{ - Types: []*interpreter.InterfaceStaticType{ - fooInterfaceType, - barInterfaceType, - bazInterfaceType, - }, - LegacyType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - ), - interpreter.NewUnmeteredIntValueFromInt64(2), - ), - } - - require.Equal(t, len(expectedValues), len(values)) - - for index, value := range values { - actualValue := value.(interpreter.EquatableValue) - expectedValue := expectedValues[index] - - assert.True(t, - actualValue.Equal(mr.Interpreter, interpreter.EmptyLocationRange, expectedValue), - "values at index %d are not equal: %s != %s", - index, - actualValue, - expectedValue, - ) - } -} - -func checkAccountID(t *testing.T, mr *InterpreterMigrationRuntime, address common.Address) { - id := flow.AccountStatusRegisterID(flow.Address(address)) - statusBytes, err := mr.Accounts.GetValue(id) - require.NoError(t, err) - - accountStatus, err := environment.AccountStatusFromBytes(statusBytes) - require.NoError(t, err) - - assert.Equal(t, uint64(3), accountStatus.AccountIdCounter()) -} - -func checkReporters( - t *testing.T, - rwf *testReportWriterFactory, - address common.Address, -) { - - testContractLocation := common.NewAddressLocation( - nil, - address, - "Test", - ) - - rResourceType := interpreter.NewCompositeStaticTypeComputeTypeID( - nil, - testContractLocation, - "Test.R", - ) - - entitlementAuthorization := func() interpreter.EntitlementSetAuthorization { - return interpreter.NewEntitlementSetAuthorization( - nil, - func() (entitlements []common.TypeID) { - return []common.TypeID{ - testContractLocation.TypeID(nil, "Test.E"), - } - }, - 1, - sema.Conjunction, - ) - } - - var reporterNames []string - for reporterName := range rwf.reportWriters { - reporterNames = append(reporterNames, reporterName) - } - sort.Strings(reporterNames) - - var accountReportEntries []valueMigrationReportEntry - - for _, reporterName := range reporterNames { - reportWriter := rwf.reportWriters[reporterName] - - for _, entry := range reportWriter.entries { - - e, ok := entry.(valueMigrationReportEntry) - if !ok || e.accountAddress() != address { - continue - } - - accountReportEntries = append(accountReportEntries, e) - } - } - - assert.Equal( - t, - []valueMigrationReportEntry{ - capabilityMigrationEntry{ - AccountAddress: address, - AddressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.PathValue{ - Identifier: "linkR", - Domain: common.PathDomainPublic, - }, - }, - BorrowType: interpreter.NewReferenceStaticType( - nil, - entitlementAuthorization(), - rResourceType, - ), - CapabilityID: 2, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("capability"), - Migration: "CapabilityValueMigration", - }, - capabilityMigrationEntry{ - AccountAddress: address, - AddressPath: interpreter.AddressPath{ - Address: address, Path: interpreter.PathValue{ - Identifier: "linkR", - Domain: common.PathDomainPublic, - }, - }, - BorrowType: interpreter.NewReferenceStaticType( - nil, - entitlementAuthorization(), - rResourceType, - ), - CapabilityID: 2, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("untyped_capability"), - Migration: "CapabilityValueMigration", - }, - linkMigrationEntry{ - AccountAddressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.PathValue{ - Identifier: "flowTokenReceiver", - Domain: common.PathDomainPublic, - }, - }, - CapabilityID: 1, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "public", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("flowTokenReceiver"), - Migration: "LinkValueMigration", - }, - linkMigrationEntry{ - AccountAddressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.PathValue{ - Identifier: "linkR", - Domain: common.PathDomainPublic, - }, - }, - CapabilityID: 2, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "public", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("linkR"), - Migration: "LinkValueMigration", - }, - linkMigrationEntry{ - AccountAddressPath: interpreter.AddressPath{ - Address: address, - Path: interpreter.PathValue{ - Identifier: "flowTokenBalance", - Domain: common.PathDomainPublic, - }, - }, - CapabilityID: 3, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "public", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("flowTokenBalance"), - Migration: "LinkValueMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_string_keys"), - Migration: "StringNormalizingMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_auth_reference_typed_key"), - Migration: "EntitlementsMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_auth_reference_typed_key"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_reference_typed_key"), - Migration: "EntitlementsMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_reference_typed_key"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("type_value"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_account_type_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("capability"), - Migration: "EntitlementsMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("string_value_1"), - Migration: "StringNormalizingMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - Migration: "StaticTypeMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "storage", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("dictionary_with_restricted_typed_keys"), - Migration: "TypeKeyMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "public", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("flowTokenReceiver"), - Migration: "EntitlementsMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "public", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("linkR"), - Migration: "EntitlementsMigration", - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{Key: "public", Address: address}, - StorageMapKey: interpreter.StringStorageMapKey("flowTokenBalance"), - Migration: "EntitlementsMigration", - }, - }, - - accountReportEntries, - ) -} - -type testReportWriterFactory struct { - lock sync.Mutex - reportWriters map[string]*testReportWriter -} - -func (f *testReportWriterFactory) ReportWriter(dataNamespace string) reporters.ReportWriter { - f.lock.Lock() - defer f.lock.Unlock() - - if f.reportWriters == nil { - f.reportWriters = make(map[string]*testReportWriter) - } - reportWriter := &testReportWriter{} - if _, ok := f.reportWriters[dataNamespace]; ok { - panic(fmt.Sprintf("report writer already exists for namespace %s", dataNamespace)) - } - f.reportWriters[dataNamespace] = reportWriter - return reportWriter -} - -type testReportWriter struct { - lock sync.Mutex - entries []any -} - -var _ reporters.ReportWriter = &testReportWriter{} - -func (r *testReportWriter) Write(entry any) { - r.lock.Lock() - defer r.lock.Unlock() - - r.entries = append(r.entries, entry) -} - -func (r *testReportWriter) Close() {} - -func TestBootstrappedStateMigration(t *testing.T) { - t.Parallel() - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - const chainID = flow.Emulator - // TODO: EVM contract is not deployed in snapshot yet, so can't update it - const evmContractChange = EVMContractChangeNone - - const burnerContractChange = BurnerContractChangeUpdate - - payloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - migrations := NewCadence1Migrations( - logger, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - // Check error logs. - require.Empty(t, logWriter.logs) -} - -func TestProgramParsingError(t *testing.T) { - t.Parallel() - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - const chainID = flow.Emulator - chain := chainID.Chain() - - testAddress := common.Address(chain.ServiceAddress()) - - payloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - runtime, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - storage := runtime.Storage - - storageMap := storage.GetStorageMap( - testAddress, - common.PathDomainStorage.Identifier(), - true, - ) - - const contractName = "C" - contractLocation := common.NewAddressLocation(nil, testAddress, contractName) - - const nonExistingStructQualifiedIdentifier = contractName + ".NonExistingStruct" - - capabilityValue := interpreter.NewUnmeteredCapabilityValue( - 1, - interpreter.AddressValue(testAddress), - interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.NewCompositeStaticType( - nil, - contractLocation, - nonExistingStructQualifiedIdentifier, - contractLocation.TypeID(nil, nonExistingStructQualifiedIdentifier), - ), - ), - ) - - storageMap.WriteValue( - runtime.Interpreter, - interpreter.StringStorageMapKey("test"), - capabilityValue, - ) - - err = storage.NondeterministicCommit(runtime.Interpreter, false) - require.NoError(t, err) - - // finalize the transaction - result, err := runtime.TransactionState.FinalizeMainTransaction() - require.NoError(t, err) - - // Merge the changes into the registers - - expectedAddresses := map[flow.Address]struct{}{ - flow.Address(testAddress): {}, - } - - err = registers.ApplyChanges( - registersByAccount, - result.WriteSet, - expectedAddresses, - logger, - ) - require.NoError(t, err) - - // Set the code for the old program - - err = registersByAccount.Set( - string(testAddress[:]), - flow.ContractKey(contractName), - []byte(`pub contract C {}`), - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{contractName}) - require.NoError(t, err) - - err = registersByAccount.Set( - string(testAddress[:]), - flow.ContractNamesKey, - encodedContractNames, - ) - 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, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - reporter := rwf.reportWriters[contractCheckingReporterName] - require.NotNil(t, reporter) - - var messages []string - - for _, entry := range reporter.entries { - if errorEntry, isErrorEntry := entry.(contractCheckingFailure); isErrorEntry { - messages = append(messages, errorEntry.Error) - break - } - } - - require.Len(t, messages, 1) - - assert.Contains(t, messages[0], "`pub` is no longer a valid access keyword") - assert.NotContains(t, messages[0], "runtime/debug.Stack()") -} - -func TestCoreContractUsage(t *testing.T) { - t.Parallel() - - const chainID = flow.Emulator - - migrate := func(t *testing.T, staticType interpreter.StaticType) interpreter.StaticType { - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - chain := chainID.Chain() - - testFlowAddress, err := chain.AddressAtIndex(1_000_000) - require.NoError(t, err) - - testAddress := common.Address(testFlowAddress) - - payloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - runtime, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - err = runtime.Accounts.Create(nil, testFlowAddress) - require.NoError(t, err) - - storage := runtime.Storage - - storageDomain := common.PathDomainStorage.Identifier() - storageMapKey := interpreter.StringStorageMapKey("test") - - storageMap := storage.GetStorageMap( - testAddress, - storageDomain, - true, - ) - - capabilityValue := interpreter.NewUnmeteredCapabilityValue( - 1, - interpreter.AddressValue(testAddress), - staticType, - ) - - storageMap.WriteValue( - runtime.Interpreter, - storageMapKey, - capabilityValue, - ) - - err = storage.NondeterministicCommit(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): {}, - } - - err = registers.ApplyChanges( - registersByAccount, - result.WriteSet, - expectedAddresses, - 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, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - 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 := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - 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{ - interpreter.NewInterfaceStaticType( - nil, - fungibleTokenContractLocation, - fungibleTokenVaultTypeQualifiedIdentifier, - fungibleTokenContractLocation.TypeID(nil, fungibleTokenVaultTypeQualifiedIdentifier), - ), - }, - ), - ) - - actual := migrate(t, input) - - require.Equal(t, expected, actual) - }) - - t.Run("&FungibleToken.Vault{FungibleToken.Balance} => &{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" - const fungibleTokenBalanceTypeQualifiedIdentifier = fungibleTokenContractName + ".Balance" - - 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), - ) - - input := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - inputIntersectionType, - ) - - expected := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.NewIntersectionStaticType( - nil, - []*interpreter.InterfaceStaticType{ - interpreter.NewInterfaceStaticType( - nil, - fungibleTokenContractLocation, - fungibleTokenVaultTypeQualifiedIdentifier, - fungibleTokenContractLocation.TypeID(nil, fungibleTokenVaultTypeQualifiedIdentifier), - ), - }, - ), - ) - - actual := migrate(t, input) - - 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, - ) - - 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("&NonFungibleToken.Collection => auth(Withdraw) &{NonFungibleToken.Collection}", func(t *testing.T) { - t.Parallel() - - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - const nonFungibleTokenContractName = "NonFungibleToken" - nonFungibleTokenContractLocation := common.NewAddressLocation( - nil, - common.Address(systemContracts.NonFungibleToken.Address), - nonFungibleTokenContractName, - ) - - const nonFungibleTokenCollectionTypeQualifiedIdentifier = nonFungibleTokenContractName + ".Collection" - - input := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.NewCompositeStaticType( - nil, - nonFungibleTokenContractLocation, - nonFungibleTokenCollectionTypeQualifiedIdentifier, - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenCollectionTypeQualifiedIdentifier), - ), - ) - - const nonFungibleTokenWithdrawTypeQualifiedIdentifier = nonFungibleTokenContractName + ".Withdraw" - expected := interpreter.NewReferenceStaticType( - nil, - interpreter.NewEntitlementSetAuthorization( - nil, - func() []common.TypeID { - return []common.TypeID{ - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenWithdrawTypeQualifiedIdentifier), - } - }, - 1, - sema.Conjunction, - ), - interpreter.NewIntersectionStaticType( - nil, - []*interpreter.InterfaceStaticType{ - interpreter.NewInterfaceStaticType( - nil, - nonFungibleTokenContractLocation, - nonFungibleTokenCollectionTypeQualifiedIdentifier, - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenCollectionTypeQualifiedIdentifier), - ), - }, - ), - ) - - actual := migrate(t, input) - - require.Equal(t, expected, actual) - }) - - t.Run("&NonFungibleToken.Provider => auth(Withdraw) &{NonFungibleToken.Provider}", func(t *testing.T) { - t.Parallel() - - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - const nonFungibleTokenContractName = "NonFungibleToken" - nonFungibleTokenContractLocation := common.NewAddressLocation( - nil, - common.Address(systemContracts.NonFungibleToken.Address), - nonFungibleTokenContractName, - ) - - const nonFungibleTokenProviderTypeQualifiedIdentifier = nonFungibleTokenContractName + ".Provider" - - input := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.NewInterfaceStaticType( - nil, - nonFungibleTokenContractLocation, - nonFungibleTokenProviderTypeQualifiedIdentifier, - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenProviderTypeQualifiedIdentifier), - ), - ) - - const nonFungibleTokenWithdrawTypeQualifiedIdentifier = nonFungibleTokenContractName + ".Withdraw" - expected := interpreter.NewReferenceStaticType( - nil, - interpreter.NewEntitlementSetAuthorization( - nil, - func() []common.TypeID { - return []common.TypeID{ - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenWithdrawTypeQualifiedIdentifier), - } - }, - 1, - sema.Conjunction, - ), - interpreter.NewIntersectionStaticType( - nil, - []*interpreter.InterfaceStaticType{ - interpreter.NewInterfaceStaticType( - nil, - nonFungibleTokenContractLocation, - nonFungibleTokenProviderTypeQualifiedIdentifier, - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenProviderTypeQualifiedIdentifier), - ), - }, - ), - ) - - actual := migrate(t, input) - - require.Equal(t, expected, actual) - }) - - t.Run("&NonFungibleToken.Collection{NonFungibleToken.CollectionPublic} => &{NonFungibleToken.Collection}", func(t *testing.T) { - t.Parallel() - - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - const nonFungibleTokenContractName = "NonFungibleToken" - nonFungibleTokenContractLocation := common.NewAddressLocation( - nil, - common.Address(systemContracts.NonFungibleToken.Address), - nonFungibleTokenContractName, - ) - - const nonFungibleTokenVaultTypeQualifiedIdentifier = nonFungibleTokenContractName + ".Collection" - const nonFungibleTokenCollectionPublicTypeQualifiedIdentifier = nonFungibleTokenContractName + ".CollectionPublic" - - inputIntersectionType := interpreter.NewIntersectionStaticType( - nil, - []*interpreter.InterfaceStaticType{ - interpreter.NewInterfaceStaticType( - nil, - nonFungibleTokenContractLocation, - nonFungibleTokenCollectionPublicTypeQualifiedIdentifier, - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenCollectionPublicTypeQualifiedIdentifier), - ), - }, - ) - inputIntersectionType.LegacyType = interpreter.NewCompositeStaticType( - nil, - nonFungibleTokenContractLocation, - nonFungibleTokenVaultTypeQualifiedIdentifier, - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenVaultTypeQualifiedIdentifier), - ) - - input := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - inputIntersectionType, - ) - - expected := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.NewIntersectionStaticType( - nil, - []*interpreter.InterfaceStaticType{ - interpreter.NewInterfaceStaticType( - nil, - nonFungibleTokenContractLocation, - nonFungibleTokenVaultTypeQualifiedIdentifier, - nonFungibleTokenContractLocation.TypeID(nil, nonFungibleTokenVaultTypeQualifiedIdentifier), - ), - }, - ), - ) - - 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) - }) - -} - -func TestDictionaryKeyConflictEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := dictionaryKeyConflictEntry{ - AddressPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainPublic, - Identifier: "test", - }, - }, - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "dictionary-key-conflict", - "account_address": "0x0000000000000001", - "path": "/public/test" - }`, - string(actual), - ) -} - -func TestLinkMissingTargetEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := linkMissingTargetEntry{ - AddressPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainPublic, - Identifier: "test", - }, - }, - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "link-missing-target", - "account_address": "0x0000000000000001", - "path": "/public/test" - }`, - string(actual), - ) -} - -func TestCapabilityMissingCapabilityIDEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := capabilityMissingCapabilityIDEntry{ - AccountAddress: common.MustBytesToAddress([]byte{0x2}), - AddressPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainPublic, - Identifier: "test", - }, - }, - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "capability-missing-capability-id", - "account_address": "0x0000000000000002", - "address": "0x0000000000000001", - "path": "/public/test" - }`, - string(actual), - ) -} - -func TestCapabilityMigrationEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := capabilityMigrationEntry{ - AccountAddress: common.MustBytesToAddress([]byte{0x2}), - AddressPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainPublic, - Identifier: "test", - }, - }, - BorrowType: interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeInt, - ), - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "capability-migration-success", - "account_address": "0x0000000000000002", - "address": "0x0000000000000001", - "path": "/public/test", - "borrow_type": "&Int", - "capability_id": "0" - }`, - string(actual), - ) -} - -func TestLinkMigrationEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := linkMigrationEntry{ - AccountAddressPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainPublic, - Identifier: "test", - }, - }, - CapabilityID: 42, - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "link-migration-success", - "account_address": "0x0000000000000001", - "path": "/public/test", - "capability_id": 42 - }`, - string(actual), - ) -} - -func TestCadenceValueMigrationFailureEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := cadenceValueMigrationFailureEntry{ - StorageKey: interpreter.StorageKey{ - Address: common.MustBytesToAddress([]byte{0x1}), - Key: "storage", - }, - StorageMapKey: interpreter.StringStorageMapKey("test"), - Migration: "test-migration", - Message: "unknown", - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "cadence-value-migration-failure", - "account_address": "0x0000000000000001", - "domain": "storage", - "key": "test", - "migration": "test-migration", - "message": "unknown" - }`, - string(actual), - ) -} - -func TestCadenceValueMigrationEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{ - Address: common.MustBytesToAddress([]byte{0x1}), - Key: "storage", - }, - StorageMapKey: interpreter.StringStorageMapKey("test"), - Migration: "test-migration", - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "cadence-value-migration-success", - "account_address": "0x0000000000000001", - "domain": "storage", - "key": "test", - "migration": "test-migration" - }`, - string(actual), - ) -} - -func TestStoragePathCapabilityMigration(t *testing.T) { - t.Parallel() - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - const chainID = flow.Emulator - - payloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - addressA := common.Address(chainID.Chain().ServiceAddress()) - - // TODO: is there a way to read this address from bootstrapped payloads - // rather than hard-coding it here? - addressB, err := common.HexToAddress("0xe5a8b7f23e8b548f") - require.NoError(t, err) - - addressC, err := common.HexToAddress("0x1") - require.NoError(t, err) - - // Store a contract in `addressC`. - - const contractName = "Test" - contractAddress := addressC - - err = registersByAccount.Set( - string(contractAddress[:]), - flow.ContractKey(contractName), - []byte(` - pub contract Test { - access(all) resource R {} - } - `), - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{contractName}) - require.NoError(t, err) - - err = registersByAccount.Set( - string(contractAddress[:]), - flow.ContractNamesKey, - encodedContractNames, - ) - require.NoError(t, err) - - migrationRuntime, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - storage := migrationRuntime.Storage - storageDomain := common.PathDomainStorage.Identifier() - - // Store a typed-capability with storage path - - storageMapForAddressA := storage.GetStorageMap( - addressA, - storageDomain, - true, - ) - - borrowType := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeAnyStruct, - ) - - aCapStorageMapKey := interpreter.StringStorageMapKey("aCap") - - aCapability := interpreter.NewUnmeteredPathCapabilityValue( - borrowType, - // Important: Capability must be for a different address, - // compared to where the capability is stored. - interpreter.AddressValue(addressB), - interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "a"), - ) - - storageMapForAddressA.WriteValue( - migrationRuntime.Interpreter, - aCapStorageMapKey, - aCapability, - ) - - // Store another capability with storage path, but without a borrow type. - // But the target path does not contain a value. - - bCapStorageMapKey := interpreter.StringStorageMapKey("bCap") - - bCapability := interpreter.NewUnmeteredPathCapabilityValue( - // NOTE: no borrow type - nil, - // Important: Capability must be for a different address, - // compared to where the capability is stored. - interpreter.AddressValue(addressB), - interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "b"), - ) - - storageMapForAddressA.WriteValue( - migrationRuntime.Interpreter, - bCapStorageMapKey, - bCapability, - ) - - // Store a third capability with storage path, without a borrow type. - // But the target path contains a value. - - storageMapForAddressB := storage.GetStorageMap( - addressB, - storageDomain, - true, - ) - - storageMapForAddressB.WriteValue( - migrationRuntime.Interpreter, - interpreter.StringStorageMapKey("c"), - interpreter.NewUnmeteredStringValue("This is the bar value"), - ) - - cCapStorageMapKey := interpreter.StringStorageMapKey("cCap") - - cCapability := interpreter.NewUnmeteredPathCapabilityValue( - // NOTE: no borrow type - nil, - // Important: Capability must be for a different address, - // compared to where the capability is stored. - interpreter.AddressValue(addressB), - interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "c"), - ) - - storageMapForAddressA.WriteValue( - migrationRuntime.Interpreter, - cCapStorageMapKey, - cCapability, - ) - - // Store a fourth capability with storage path, and with a broken type - - dBorrowType := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.NewCompositeStaticTypeComputeTypeID( - nil, - common.NewAddressLocation(nil, contractAddress, "Test"), - "Test.R", - ), - ) - - dCapStorageMapKey := interpreter.StringStorageMapKey("dCap") - - dCapability := interpreter.NewUnmeteredPathCapabilityValue( - dBorrowType, - // Important: Capability must be for a different address, - // compared to where the capability is stored. - interpreter.AddressValue(addressB), - interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "d"), - ) - - storageMapForAddressA.WriteValue( - migrationRuntime.Interpreter, - dCapStorageMapKey, - dCapability, - ) - - // Commit - - err = storage.NondeterministicCommit(migrationRuntime.Interpreter, false) - require.NoError(t, err) - - // finalize the transaction - result, err := migrationRuntime.TransactionState.FinalizeMainTransaction() - require.NoError(t, err) - - // Merge the changes into the registers - - expectedAddresses := map[flow.Address]struct{}{ - flow.Address(addressA): {}, - } - - err = registers.ApplyChanges( - registersByAccount, - result.WriteSet, - expectedAddresses, - 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, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - reporter := rwf.reportWriters[capabilityValueMigrationReporterName] - require.NotNil(t, reporter) - require.Len(t, reporter.entries, 7) - - inferredBorrowType := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeString, - ) - - require.Equal( - t, - []any{ - capabilityMigrationEntry{ - AccountAddress: addressA, - AddressPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "c"), - }, - BorrowType: interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeString, - ), - CapabilityID: 4, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{ - Key: storageDomain, - Address: addressA, - }, - StorageMapKey: cCapStorageMapKey, - Migration: "CapabilityValueMigration", - }, - capabilityMigrationEntry{ - AccountAddress: addressA, - AddressPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "d"), - }, - BorrowType: dBorrowType, - CapabilityID: 5, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{ - Key: storageDomain, - Address: addressA, - }, - StorageMapKey: dCapStorageMapKey, - Migration: "CapabilityValueMigration", - }, - capabilityMigrationEntry{ - AccountAddress: addressA, - AddressPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "a"), - }, - BorrowType: borrowType, - CapabilityID: 3, - }, - cadenceValueMigrationEntry{ - StorageKey: interpreter.StorageKey{ - Key: storageDomain, - Address: addressA, - }, - StorageMapKey: aCapStorageMapKey, - Migration: "CapabilityValueMigration", - }, - capabilityMissingCapabilityIDEntry{ - AccountAddress: addressA, - AddressPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "b"), - }, - }, - }, - reporter.entries, - ) - - issueStorageCapConReporter := rwf.reportWriters[issueStorageCapConMigrationReporterName] - require.NotNil(t, issueStorageCapConReporter) - require.Len(t, issueStorageCapConReporter.entries, 6) - require.Equal( - t, - []any{ - storageCapConIssuedEntry{ - AccountAddress: addressB, - AddressPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "a"), - }, - BorrowType: borrowType, - CapabilityID: 3, - }, - storageCapConsMissingBorrowTypeEntry{ - TargetPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "b"), - }, - StoredPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "bCap"), - }, - }, - storageCapConsMissingBorrowTypeEntry{ - TargetPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "c"), - }, - StoredPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "cCap"), - }, - }, - storageCapConsInferredBorrowTypeEntry{ - TargetPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "c"), - }, - BorrowType: inferredBorrowType, - StoredPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "cCap"), - }, - }, - storageCapConIssuedEntry{ - AccountAddress: addressB, - AddressPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "c"), - }, - BorrowType: inferredBorrowType, - CapabilityID: 4, - }, - storageCapConIssuedEntry{ - AccountAddress: addressB, - AddressPath: interpreter.AddressPath{ - Address: addressB, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "d"), - }, - BorrowType: dBorrowType, - CapabilityID: 5, - }, - }, - issueStorageCapConReporter.entries, - ) - - // Check account A - - _, err = runScript( - chainID, - registersByAccount, - fmt.Sprintf( - //language=Cadence - ` - access(all) - fun main() { - let storage = getAuthAccount(%s).storage - let aCap = storage.copy(from: /storage/aCap)! - let bCap = storage.copy(from: /storage/bCap)! - let cCap = storage.copy(from: /storage/cCap)! - assert(aCap.id == 3) - assert(bCap.id == 0) - assert(cCap.id == 4) - } - `, - addressA.HexWithPrefix(), - ), - ) - require.NoError(t, err) - - // check the cap with the broken type - - _, err = runScript( - chainID, - registersByAccount, - fmt.Sprintf( - //language=Cadence - ` - access(all) - fun main() { - let storage = getAuthAccount(%s).storage - let dCap = storage.copy(from: /storage/dCap)! - } - `, - addressA.HexWithPrefix(), - ), - ) - require.Error(t, err) - require.ErrorAs(t, &runtime.ParsingCheckingError{}, &err) - - // Check account B - - _, err = runScript( - chainID, - registersByAccount, - fmt.Sprintf( - //language=Cadence - ` - access(all) - fun main() { - let capabilities = getAuthAccount(%s).capabilities.storage - let aCapCons = capabilities.getControllers(forPath: /storage/a) - assert(aCapCons.length == 1) - assert(aCapCons[0].capabilityID == 3) - } - `, - addressB.HexWithPrefix(), - ), - ) - require.NoError(t, err) - -} - -func TestStorageCapConIssuedEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := storageCapConIssuedEntry{ - AccountAddress: common.MustBytesToAddress([]byte{0x2}), - AddressPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainStorage, - Identifier: "test", - }, - }, - BorrowType: interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeInt, - ), - CapabilityID: 3, - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "storage-capcon-issued", - "account_address": "0x0000000000000002", - "address": "0x0000000000000001", - "path": "/storage/test", - "borrow_type": "&Int", - "capability_id": "3" - }`, - string(actual), - ) -} - -func TestStorageCapConsMissingBorrowTypeEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := storageCapConsMissingBorrowTypeEntry{ - TargetPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainStorage, - Identifier: "test", - }, - }, - StoredPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x2}), - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "storedCap"), - }, - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "storage-capcon-missing-borrow-type", - "account_address": "0x0000000000000002", - "address": "0x0000000000000001", - "target_path": "/storage/test", - "stored_path": "/storage/storedCap" - }`, - string(actual), - ) -} - -func TestStorageCapConsInferredBorrowTypeEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := storageCapConsInferredBorrowTypeEntry{ - TargetPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x1}), - Path: interpreter.PathValue{ - Domain: common.PathDomainStorage, - Identifier: "test", - }, - }, - StoredPath: interpreter.AddressPath{ - Address: common.MustBytesToAddress([]byte{0x2}), - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "storedCap"), - }, - BorrowType: interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeInt, - ), - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "storage-capcon-inferred-borrow-type", - "account_address": "0x0000000000000002", - "address": "0x0000000000000001", - "borrow_type":"&Int", - "target_path": "/storage/test", - "stored_path": "/storage/storedCap" - }`, - string(actual), - ) -} - -func TestTypeRequirementRemovalMigration(t *testing.T) { - t.Parallel() - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - const chainID = flow.Testnet - - payloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - storedAddress := common.Address(chainID.Chain().ServiceAddress()) - tiblesAddress := mustHexToAddress("e93c412c964bdf40") - - // Store a contract in `addressC`. - - migrationRuntime, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - storage := migrationRuntime.Storage - storageDomain := common.PathDomainStorage.Identifier() - - storageMap := storage.GetStorageMap( - storedAddress, - storageDomain, - true, - ) - - contractName := "TiblesProducer" - - // Store a value with the actual `TiblesProducer.Minter` type - storageMap.WriteValue( - migrationRuntime.Interpreter, - interpreter.StringStorageMapKey("a"), - interpreter.NewTypeValue( - nil, - interpreter.NewCompositeStaticTypeComputeTypeID( - nil, - common.AddressLocation{ - Name: contractName, - Address: tiblesAddress, - }, - "TiblesProducer.Minter", - ), - ), - ) - - // Store a value with a random `TiblesProducer.Minter` type (different address) - storageMap.WriteValue( - migrationRuntime.Interpreter, - interpreter.StringStorageMapKey("b"), - interpreter.NewTypeValue( - nil, - interpreter.NewCompositeStaticTypeComputeTypeID( - nil, - common.AddressLocation{ - Name: contractName, - Address: storedAddress, - }, - "TiblesProducer.Minter", - ), - ), - ) - - // Commit - - err = storage.NondeterministicCommit(migrationRuntime.Interpreter, false) - require.NoError(t, err) - - // finalize the transaction - result, err := migrationRuntime.TransactionState.FinalizeMainTransaction() - require.NoError(t, err) - - // Merge the changes into the registers - - expectedAddresses := map[flow.Address]struct{}{ - flow.Address(storedAddress): {}, - } - - err = registers.ApplyChanges( - registersByAccount, - result.WriteSet, - expectedAddresses, - logger, - ) - require.NoError(t, err) - - // Set contract code - - oldCode := ` - pub contract interface TiblesProducer { - - pub struct ContentLocation {} - pub struct interface IContentLocation {} - - pub resource interface IContent { - access(contract) let contentIdsToPaths: {String: TiblesProducer.ContentLocation} - pub fun getMetadata(contentId: String): {String: AnyStruct}? - } - - pub resource interface IProducer { - access(contract) let minters: @{String: Minter} - } - - pub resource Producer: IContent, IProducer { - access(contract) let minters: @{String: Minter} - } - - pub resource interface IMinter { - pub let id: String - pub var lastMintNumber: UInt32 - pub let contentCapability: Capability - pub fun mintNext() - } - - pub resource Minter: IMinter { - pub let id: String - pub var lastMintNumber: UInt32 - pub let contentCapability: Capability - pub fun mintNext() - } - } - ` - - err = registersByAccount.Set( - string(tiblesAddress[:]), - flow.ContractKey(contractName), - []byte(oldCode), - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{contractName}) - require.NoError(t, err) - - err = registersByAccount.Set( - string(tiblesAddress[:]), - flow.ContractNamesKey, - encodedContractNames, - ) - 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, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - // Check reporters - - reporter := rwf.reportWriters[typeRequirementExtractingReporterName] - require.NotNil(t, reporter) - require.Len(t, reporter.entries, 3) - - assert.Equal( - t, - []any{ - typeRequirementRemovalEntry{ - TypeRequirement{ - Address: tiblesAddress, - ContractName: contractName, - TypeName: "ContentLocation", - }, - }, - typeRequirementRemovalEntry{ - TypeRequirement{ - Address: tiblesAddress, - ContractName: contractName, - TypeName: "Producer", - }, - }, - typeRequirementRemovalEntry{ - TypeRequirement{ - Address: tiblesAddress, - ContractName: contractName, - TypeName: "Minter", - }, - }, - }, - reporter.entries, - ) - - // Check account - - _, err = runScript( - chainID, - registersByAccount, - fmt.Sprintf( - //language=Cadence - ` - access(all) - fun main() { - let storage = getAuthAccount(%s).storage - assert(storage.copy(from: /storage/a)!.identifier == "{A.%s.TiblesProducer.Minter}") - assert(storage.copy(from: /storage/b)!.identifier == "A.%s.TiblesProducer.Minter") - } - `, - storedAddress.HexWithPrefix(), - tiblesAddress.Hex(), - storedAddress.Hex(), - ), - ) - require.NoError(t, err) -} - -func runScript(chainID flow.ChainID, registersByAccount *registers.ByAccount, script string) (cadence.Value, error) { - options := computation.DefaultFVMOptions(chainID, false, false) - options = append(options, - fvm.WithContractDeploymentRestricted(false), - fvm.WithContractRemovalRestricted(false), - fvm.WithAuthorizationChecksEnabled(false), - fvm.WithSequenceNumberCheckAndIncrementEnabled(false), - fvm.WithTransactionFeesEnabled(false)) - ctx := fvm.NewContext(options...) - - storageSnapshot := registers.StorageSnapshot{ - Registers: registersByAccount, - } - - vm := fvm.NewVirtualMachine() - - _, res, err := vm.Run( - ctx, - fvm.Script([]byte(script)), - storageSnapshot, - ) - if err != nil { - return nil, fmt.Errorf("failed to run transaction: %w", err) - } - - if res.Err != nil { - return nil, fmt.Errorf("transaction failed: %w", res.Err) - } - - return res.Value, nil -} - -func TestStorageCapsMigrationDeterminism(t *testing.T) { - t.Parallel() - - const nWorker = 4 - - const chainID = flow.Emulator - - addressA := common.Address(chainID.Chain().ServiceAddress()) - - addressB, err := common.HexToAddress("0xe5a8b7f23e8b548f") - require.NoError(t, err) - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - // Store values. - - payloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - migrationRuntime, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - storage := migrationRuntime.Storage - storageDomain := common.PathDomainStorage.Identifier() - - // First, create a capability with storage path, issued for account A, - // and store it in account A. - - borrowType := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - interpreter.PrimitiveStaticTypeAnyStruct, - ) - - aCapability := interpreter.NewUnmeteredPathCapabilityValue( - borrowType, - // Important: Capability must be for address A. - interpreter.AddressValue(addressA), - interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "a"), - ) - - storageMapForAddressA := storage.GetStorageMap( - addressA, - storageDomain, - true, - ) - - aCapStorageMapKey := interpreter.StringStorageMapKey("aCap") - - storageMapForAddressA.WriteValue( - migrationRuntime.Interpreter, - aCapStorageMapKey, - aCapability, - ) - - // Then, create a capability with storage path, issued for account A, - // and store it in account B. - - bCapability := interpreter.NewUnmeteredPathCapabilityValue( - borrowType, - // Important: Capability must be for address A. - interpreter.AddressValue(addressA), - interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "b"), - ) - - storageMapForAddressB := storage.GetStorageMap( - addressB, - storageDomain, - true, - ) - - bCapStorageMapKey := interpreter.StringStorageMapKey("bCap") - - storageMapForAddressB.WriteValue( - migrationRuntime.Interpreter, - bCapStorageMapKey, - bCapability, - ) - - // Commit - - err = storage.NondeterministicCommit(migrationRuntime.Interpreter, false) - require.NoError(t, err) - - // finalize the transaction - result, err := migrationRuntime.TransactionState.FinalizeMainTransaction() - require.NoError(t, err) - - // Merge the changes into the registers - - expectedAddresses := map[flow.Address]struct{}{ - flow.Address(addressA): {}, - flow.Address(addressB): {}, - } - - err = registers.ApplyChanges( - registersByAccount, - result.WriteSet, - expectedAddresses, - 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, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - reporter := rwf.reportWriters[capabilityValueMigrationReporterName] - require.NotNil(t, reporter) - require.Len(t, reporter.entries, 4) - - issueStorageCapConReporter := rwf.reportWriters[issueStorageCapConMigrationReporterName] - require.NotNil(t, issueStorageCapConReporter) - require.Len(t, issueStorageCapConReporter.entries, 2) - require.Equal( - t, - []any{ - storageCapConIssuedEntry{ - AccountAddress: addressA, - AddressPath: interpreter.AddressPath{ - Address: addressA, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "a"), - }, - BorrowType: borrowType, - CapabilityID: 6, - }, - storageCapConIssuedEntry{ - AccountAddress: addressA, - AddressPath: interpreter.AddressPath{ - Address: addressA, - Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "b"), - }, - BorrowType: borrowType, - CapabilityID: 7, - }, - }, - issueStorageCapConReporter.entries, - ) -} diff --git a/cmd/util/ledger/migrations/change_contract_code_migration.go b/cmd/util/ledger/migrations/change_contract_code_migration.go deleted file mode 100644 index 6deefd278fd..00000000000 --- a/cmd/util/ledger/migrations/change_contract_code_migration.go +++ /dev/null @@ -1,309 +0,0 @@ -package migrations - -import ( - "fmt" - - "github.com/onflow/cadence/runtime/common" - coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" - nftStorefrontContracts "github.com/onflow/nft-storefront/lib/go/contracts" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - evm "github.com/onflow/flow-go/fvm/evm/stdlib" - "github.com/onflow/flow-go/fvm/systemcontracts" - "github.com/onflow/flow-go/model/flow" -) - -func NewSystemContractChange( - systemContract systemcontracts.SystemContract, - newContractCode []byte, -) StagedContract { - return StagedContract{ - Address: common.Address(systemContract.Address), - Contract: Contract{ - Name: systemContract.Name, - Code: newContractCode, - }, - } -} - -type EVMContractChange uint8 - -const ( - EVMContractChangeNone EVMContractChange = iota - // EVMContractChangeDeployFull deploys the full EVM contract - EVMContractChangeDeployFull - // EVMContractChangeUpdateFull updates the existing EVM contract to the latest, full EVM contract - EVMContractChangeUpdateFull - // EVMContractChangeDeployMinimalAndUpdateFull deploys the minimal EVM contract - // and updates it to the latest, full EVM contract - EVMContractChangeDeployMinimalAndUpdateFull -) - -type BurnerContractChange uint8 - -const ( - BurnerContractChangeNone BurnerContractChange = iota - BurnerContractChangeDeploy - BurnerContractChangeUpdate -) - -func BurnerAddressForChain(chainID flow.ChainID) flow.Address { - - systemContracts := systemcontracts.SystemContractsForChain(chainID) - serviceAccountAddress := systemContracts.FlowServiceAccount.Address - fungibleTokenAddress := systemContracts.FungibleToken.Address - - switch chainID { - case flow.Mainnet, flow.Testnet: - return fungibleTokenAddress - - case flow.Emulator, flow.Localnet: - return serviceAccountAddress - - default: - panic(fmt.Errorf("unsupported chain ID: %s", chainID)) - } -} - -func SystemContractChanges(chainID flow.ChainID, options SystemContractsMigrationOptions) []StagedContract { - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - env := systemContracts.AsTemplateEnv() - env.BurnerAddress = BurnerAddressForChain(chainID).Hex() - - switch chainID { - case flow.Mainnet: - env.StakingCollectionAddress = "0x8d0e87b65159ae63" - env.StakingProxyAddress = "0x62430cf28c26d095" - - case flow.Testnet: - env.StakingCollectionAddress = "0x95e019a17d0e23d7" - env.StakingProxyAddress = "0x7aad92e5a0715d21" - - case flow.Emulator, flow.Localnet: - env.StakingCollectionAddress = env.ServiceAccountAddress - env.StakingProxyAddress = env.ServiceAccountAddress - - default: - panic(fmt.Errorf("unsupported chain ID: %s", chainID)) - } - - env.LockedTokensAddress = env.StakingCollectionAddress - - contractChanges := []StagedContract{ - // epoch related contracts - NewSystemContractChange( - systemContracts.Epoch, - coreContracts.FlowEpoch( - env, - ), - ), - NewSystemContractChange( - systemContracts.IDTableStaking, - coreContracts.FlowIDTableStaking( - env, - ), - ), - NewSystemContractChange( - systemContracts.ClusterQC, - coreContracts.FlowQC(), - ), - NewSystemContractChange( - systemContracts.DKG, - coreContracts.FlowDKG(), - ), - - // service account related contracts - NewSystemContractChange( - systemContracts.FlowServiceAccount, - coreContracts.FlowServiceAccount( - env, - ), - ), - NewSystemContractChange( - systemContracts.NodeVersionBeacon, - coreContracts.NodeVersionBeacon(), - ), - NewSystemContractChange( - systemContracts.RandomBeaconHistory, - coreContracts.RandomBeaconHistory(), - ), - NewSystemContractChange( - systemContracts.FlowStorageFees, - coreContracts.FlowStorageFees( - env, - ), - ), - { - Address: common.Address(flow.HexToAddress(env.StakingCollectionAddress)), - Contract: Contract{ - Name: "FlowStakingCollection", - Code: coreContracts.FlowStakingCollection(env), - }, - }, - { - Address: common.Address(flow.HexToAddress(env.StakingProxyAddress)), - Contract: Contract{ - Name: "StakingProxy", - Code: coreContracts.FlowStakingProxy(), - }, - }, - { - Address: common.Address(flow.HexToAddress(env.LockedTokensAddress)), - Contract: Contract{ - Name: "LockedTokens", - Code: coreContracts.FlowLockedTokens(env), - }, - }, - - // token related contracts - NewSystemContractChange( - systemContracts.FlowFees, - coreContracts.FlowFees( - env, - ), - ), - NewSystemContractChange( - systemContracts.FlowToken, - coreContracts.FlowToken( - env, - ), - ), - NewSystemContractChange( - systemContracts.FungibleToken, - coreContracts.FungibleToken( - env, - ), - ), - { - Address: common.Address(flow.HexToAddress(env.FungibleTokenMetadataViewsAddress)), - Contract: Contract{ - Name: "FungibleTokenMetadataViews", - Code: coreContracts.FungibleTokenMetadataViews(env), - }, - }, - - // NFT related contracts - NewSystemContractChange( - systemContracts.NonFungibleToken, - coreContracts.NonFungibleToken( - env, - ), - ), - NewSystemContractChange( - systemContracts.MetadataViews, - coreContracts.MetadataViews( - env, - ), - ), - NewSystemContractChange( - systemContracts.ViewResolver, - coreContracts.ViewResolver(), - ), - } - - switch chainID { - case flow.Emulator, flow.Localnet: - // skip - - default: - contractChanges = append( - contractChanges, - StagedContract{ - Address: common.Address(flow.HexToAddress(env.FungibleTokenSwitchboardAddress)), - Contract: Contract{ - Name: "FungibleTokenSwitchboard", - Code: coreContracts.FungibleTokenSwitchboard(env), - }, - }, - ) - } - - // EVM contract - switch options.EVM { - case EVMContractChangeNone: - // NO-OP - - case EVMContractChangeDeployFull: - // handled in migration pipeline (NewCadence1ContractsMigrations) - - case EVMContractChangeUpdateFull, - EVMContractChangeDeployMinimalAndUpdateFull: - - contractChanges = append( - contractChanges, - NewSystemContractChange( - systemContracts.EVMContract, - evm.ContractCode( - systemContracts.NonFungibleToken.Address, - systemContracts.FungibleToken.Address, - systemContracts.FlowToken.Address, - ), - ), - ) - } - - // Burner contract - if options.Burner == BurnerContractChangeUpdate { - contractChanges = append( - contractChanges, - StagedContract{ - Address: common.Address(flow.HexToAddress(env.BurnerAddress)), - Contract: Contract{ - Name: "Burner", - Code: coreContracts.Burner(), - }, - }, - ) - } - - if chainID == flow.Testnet { - contractChanges = append( - contractChanges, - StagedContract{ - Address: common.Address(flow.HexToAddress("0x2d55b98eb200daef")), - Contract: Contract{ - Name: "NFTStorefrontV2", - Code: nftStorefrontContracts.NFTStorefrontV2( - systemContracts.FungibleToken.Address.Hex(), - systemContracts.NonFungibleToken.Address.Hex(), - ), - }, - }, - ) - } - - return contractChanges -} - -type SystemContractsMigrationOptions struct { - StagedContractsMigrationOptions - EVM EVMContractChange - Burner BurnerContractChange -} - -func NewSystemContractsMigration( - log zerolog.Logger, - rwf reporters.ReportWriterFactory, - locations map[common.AddressLocation]struct{}, - options SystemContractsMigrationOptions, -) ( - migration *StagedContractsMigration, -) { - migration = NewStagedContractsMigration( - "SystemContractsMigration", - "system-contracts-migration", - log, - rwf, - &LegacyTypeRequirements{}, // This is empty for system contracts - options.StagedContractsMigrationOptions, - ) - - for _, change := range SystemContractChanges(options.ChainID, options) { - migration.registerContractChange(change) - locations[change.AddressLocation()] = struct{}{} - } - - return migration -} diff --git a/cmd/util/ledger/migrations/change_contract_code_migration_test.go b/cmd/util/ledger/migrations/change_contract_code_migration_test.go deleted file mode 100644 index e9bf4c7d0bd..00000000000 --- a/cmd/util/ledger/migrations/change_contract_code_migration_test.go +++ /dev/null @@ -1,737 +0,0 @@ -package migrations - -import ( - "context" - "io" - "strings" - "testing" - - "github.com/onflow/cadence/runtime/common" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/model/flow" -) - -const contractA = ` -access(all) contract A { - access(all) fun foo() {} -}` - -const updatedContractA = ` -access(all) contract A { - access(all) fun bar() {} -}` - -const contractB = ` -access(all) contract B { - access(all) fun foo() {} -}` - -const updatedContractB = ` -access(all) contract B { - access(all) fun bar() {} -}` - -type errorLogWriter struct { - logs []string -} - -var _ io.Writer = &errorLogWriter{} - -const errorLogPrefix = "{\"level\":\"error\"" - -func (l *errorLogWriter) Write(bytes []byte) (int, error) { - logStr := string(bytes) - - // Ignore non-error logs - if !strings.HasPrefix(logStr, errorLogPrefix) { - return 0, nil - } - - l.logs = append(l.logs, logStr) - return len(bytes), nil -} - -func TestChangeContractCodeMigration(t *testing.T) { - t.Parallel() - - const chainID = flow.Emulator - addressGenerator := chainID.Chain().NewAddressGenerator() - - address1, err := addressGenerator.NextAddress() - require.NoError(t, err) - - address2, err := addressGenerator.NextAddress() - require.NoError(t, err) - - ctx := context.Background() - - t.Run("no contracts", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ) - - registersByAccount := registers.NewByAccount() - - err := migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - registersByAccount.AccountRegisters(string(address1[:])), - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, writer.logs) - }) - - t.Run("1 contract - dont migrate", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, - contractA, - contractCode(t, registersByAccount, owner1, "A"), - ) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, writer.logs) - }) - - t.Run("1 contract - migrate", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates([]StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(updatedContractA), - }, - }, - }) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, - updatedContractA, - contractCode(t, registersByAccount, owner1, "A"), - ) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, writer.logs) - }) - - t.Run("2 contracts - migrate 1", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates([]StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(updatedContractA), - }, - }, - }) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "B", - Code: []byte(contractB), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 2, accountRegisters1.Count()) - require.Equal(t, - updatedContractA, - contractCode(t, registersByAccount, owner1, "A"), - ) - require.Equal(t, - contractB, - contractCode(t, registersByAccount, owner1, "B"), - ) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, writer.logs) - }) - - t.Run("2 contracts - migrate 2", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates([]StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(updatedContractA), - }, - }, - { - Address: common.Address(address1), - Contract: Contract{ - Name: "B", - Code: []byte(updatedContractB), - }, - }, - }) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "B", - Code: []byte(contractB), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 2, accountRegisters1.Count()) - require.Equal(t, - updatedContractA, - contractCode(t, registersByAccount, owner1, "A"), - ) - require.Equal(t, - updatedContractB, - contractCode(t, registersByAccount, owner1, "B"), - ) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, writer.logs) - }) - - t.Run("2 contracts on different accounts - migrate 1", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates([]StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(updatedContractA), - }, - }, - }) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - StagedContract{ - Address: common.Address(address2), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - owner2 := string(address2[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - accountRegisters2 := registersByAccount.AccountRegisters(owner2) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - require.Equal(t, 2, registersByAccount.AccountCount()) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, 1, accountRegisters2.Count()) - require.Equal(t, - updatedContractA, - contractCode(t, registersByAccount, owner1, "A"), - ) - require.Equal(t, - contractA, - contractCode(t, registersByAccount, owner2, "A"), - ) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, writer.logs) - }) - - t.Run("not all contracts on one account migrated", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates([]StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(updatedContractA), - }, - }, - { - Address: common.Address(address1), - Contract: Contract{ - Name: "B", - Code: []byte(updatedContractB), - }, - }, - }) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - require.Len(t, writer.logs, 1) - assert.Contains(t, - writer.logs[0], - `missing old code`, - ) - }) - - t.Run("not all accounts migrated", func(t *testing.T) { - t.Parallel() - - writer := &errorLogWriter{} - log := zerolog.New(writer) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: flow.Emulator, - VerboseErrorOutput: true, - } - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates([]StagedContract{ - { - Address: common.Address(address2), - Contract: Contract{ - Name: "A", - Code: []byte(updatedContractA), - }, - }, - }) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(contractA), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - ctx, - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, writer.logs, 1) - assert.Contains(t, - writer.logs[0], - `"failed to find all contract registers that need to be changed"`, - ) - }) -} - -func TestSystemContractChanges(t *testing.T) { - t.Parallel() - - const chainID = flow.Mainnet - - changes := SystemContractChanges( - chainID, - SystemContractsMigrationOptions{ - StagedContractsMigrationOptions: StagedContractsMigrationOptions{ - ChainID: chainID, - }, - EVM: EVMContractChangeUpdateFull, - Burner: BurnerContractChangeDeploy, - }, - ) - - var changeLocations []common.AddressLocation - - for _, change := range changes { - location := change.AddressLocation() - changeLocations = append(changeLocations, location) - } - - address1 := common.Address{0x86, 0x24, 0xb5, 0x2f, 0x9d, 0xdc, 0xd0, 0x4a} - address2 := common.Address{0xe4, 0x67, 0xb9, 0xdd, 0x11, 0xfa, 0x00, 0xdf} - address3 := common.Address{0x8d, 0x0e, 0x87, 0xb6, 0x51, 0x59, 0xae, 0x63} - address4 := common.Address{0x62, 0x43, 0x0c, 0xf2, 0x8c, 0x26, 0xd0, 0x95} - address5 := common.Address{0xf9, 0x19, 0xee, 0x77, 0x44, 0x7b, 0x74, 0x97} - address6 := common.Address{0x16, 0x54, 0x65, 0x33, 0x99, 0x04, 0x0a, 0x61} - address7 := common.Address{0xf2, 0x33, 0xdc, 0xee, 0x88, 0xfe, 0x0a, 0xbe} - address8 := common.Address{0x1d, 0x7e, 0x57, 0xaa, 0x55, 0x81, 0x74, 0x48} - - assert.Equal(t, - []common.AddressLocation{ - { - Name: "FlowEpoch", - Address: address1, - }, - { - Name: "FlowIDTableStaking", - Address: address1, - }, - { - Name: "FlowClusterQC", - Address: address1, - }, - { - Name: "FlowDKG", - Address: address1, - }, - { - Name: "FlowServiceAccount", - Address: address2, - }, - { - Name: "NodeVersionBeacon", - Address: address2, - }, - { - Name: "RandomBeaconHistory", - Address: address2, - }, - { - Name: "FlowStorageFees", - Address: address2, - }, - { - Name: "FlowStakingCollection", - Address: address3, - }, - { - Name: "StakingProxy", - Address: address4, - }, - { - Name: "LockedTokens", - Address: address3, - }, - { - Name: "FlowFees", - Address: address5, - }, - { - Name: "FlowToken", - Address: address6, - }, - { - Name: "FungibleToken", - Address: address7, - }, - { - Name: "FungibleTokenMetadataViews", - Address: address7, - }, - { - Name: "NonFungibleToken", - Address: address8, - }, - { - Name: "MetadataViews", - Address: address8, - }, - { - Name: "ViewResolver", - Address: address8, - }, - { - Name: "FungibleTokenSwitchboard", - Address: address7, - }, - { - Name: "EVM", - Address: address2, - }, - }, - changeLocations, - ) -} diff --git a/cmd/util/ledger/migrations/contract_checking_migration.go b/cmd/util/ledger/migrations/contract_checking_migration.go deleted file mode 100644 index 91aa7bbb902..00000000000 --- a/cmd/util/ledger/migrations/contract_checking_migration.go +++ /dev/null @@ -1,259 +0,0 @@ -package migrations - -import ( - "bytes" - "encoding/json" - "fmt" - "sort" - "strings" - - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/pretty" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/model/flow" -) - -const contractCheckingReporterName = "contract-checking" -const contractCountEstimate = 1000 - -type AddressContract struct { - Location common.AddressLocation - Code []byte -} - -// NewContractCheckingMigration returns a migration that checks all contracts. -// It parses and checks all contract code and stores the programs in the provided map. -// Important locations is a set of locations that should always succeed to check. -func NewContractCheckingMigration( - log zerolog.Logger, - rwf reporters.ReportWriterFactory, - chainID flow.ChainID, - verboseErrorOutput bool, - importantLocations map[common.AddressLocation]struct{}, - programs map[common.Location]*interpreter.Program, -) RegistersMigration { - return func(registersByAccount *registers.ByAccount) error { - - reporter := rwf.ReportWriter(contractCheckingReporterName) - defer reporter.Close() - - mr, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - if err != nil { - return fmt.Errorf("failed to create interpreter migration runtime: %w", err) - } - - contracts, err := GatherContractsFromRegisters(registersByAccount, log) - if err != nil { - return err - } - - contractsForPrettyPrinting := make(map[common.Location][]byte, len(contracts)) - for _, contract := range contracts { - contractsForPrettyPrinting[contract.Location] = contract.Code - } - - // Check all contracts - - for _, contract := range contracts { - CheckContract( - contract, - log, - mr, - contractsForPrettyPrinting, - verboseErrorOutput, - reporter, - importantLocations, - programs, - ) - } - - return nil - } -} - -func GatherContractsFromRegisters(registersByAccount *registers.ByAccount, log zerolog.Logger) ([]AddressContract, error) { - log.Info().Msg("Gathering contracts ...") - - contracts := make([]AddressContract, 0, contractCountEstimate) - - err := registersByAccount.ForEachAccount(func(accountRegisters *registers.AccountRegisters) error { - owner := accountRegisters.Owner() - - encodedContractNames, err := accountRegisters.Get(owner, flow.ContractNamesKey) - if err != nil { - return err - } - - contractNames, err := environment.DecodeContractNames(encodedContractNames) - if err != nil { - return err - } - - for _, contractName := range contractNames { - - contractKey := flow.ContractKey(contractName) - - code, err := accountRegisters.Get(owner, contractKey) - if err != nil { - return err - } - - if len(bytes.TrimSpace(code)) == 0 { - continue - } - - address := common.Address([]byte(owner)) - location := common.AddressLocation{ - Address: address, - Name: contractName, - } - - contracts = append( - contracts, - AddressContract{ - Location: location, - Code: code, - }, - ) - } - - return nil - }) - if err != nil { - return nil, fmt.Errorf("failed to get contracts of accounts: %w", err) - } - - sort.Slice(contracts, func(i, j int) bool { - a := contracts[i] - b := contracts[j] - return a.Location.ID() < b.Location.ID() - }) - - log.Info().Msgf("Gathered all contracts (%d)", len(contracts)) - return contracts, nil -} - -func CheckContract( - contract AddressContract, - log zerolog.Logger, - mr *InterpreterMigrationRuntime, - contractsForPrettyPrinting map[common.Location][]byte, - verboseErrorOutput bool, - reporter reporters.ReportWriter, - importantLocations map[common.AddressLocation]struct{}, - programs map[common.Location]*interpreter.Program, -) { - location := contract.Location - code := contract.Code - - log.Info().Msgf("checking contract %s ...", location) - - // Check contract code - const getAndSetProgram = true - program, err := mr.ContractAdditionHandler.ParseAndCheckProgram(code, location, getAndSetProgram) - if err != nil { - - // Pretty print the error - var builder strings.Builder - errorPrinter := pretty.NewErrorPrettyPrinter(&builder, false) - - printErr := errorPrinter.PrettyPrintError(err, location, contractsForPrettyPrinting) - - var errorDetails string - if printErr == nil { - errorDetails = builder.String() - } else { - errorDetails = err.Error() - } - - if _, ok := importantLocations[location]; ok { - log.Error().Msgf( - "error checking important contract %s: %s", - location, - errorDetails, - ) - } else if verboseErrorOutput { - log.Error().Msgf( - "error checking contract %s: %s", - location, - errorDetails, - ) - } - - reporter.Write(contractCheckingFailure{ - AccountAddress: location.Address, - ContractName: location.Name, - Code: string(code), - Error: errorDetails, - }) - - return - } - - // Record the checked program for future use - programs[location] = program - - reporter.Write(contractCheckingSuccess{ - AccountAddress: location.Address, - ContractName: location.Name, - Code: string(code), - }) - - log.Info().Msgf("finished checking contract %s", location) -} - -type contractCheckingFailure struct { - AccountAddress common.Address - ContractName string - Code string - Error string -} - -var _ json.Marshaler = contractCheckingFailure{} - -func (e contractCheckingFailure) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"address"` - ContractName string `json:"name"` - Code string `json:"code"` - Error string `json:"error"` - }{ - Kind: "checking-failure", - AccountAddress: e.AccountAddress.HexWithPrefix(), - ContractName: e.ContractName, - Code: e.Code, - Error: e.Error, - }) -} - -type contractCheckingSuccess struct { - AccountAddress common.Address - ContractName string - Code string -} - -var _ json.Marshaler = contractCheckingSuccess{} - -func (e contractCheckingSuccess) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"address"` - ContractName string `json:"name"` - Code string `json:"code"` - }{ - Kind: "checking-success", - AccountAddress: e.AccountAddress.HexWithPrefix(), - ContractName: e.ContractName, - Code: e.Code, - }) -} diff --git a/cmd/util/ledger/migrations/contract_checking_migration_test.go b/cmd/util/ledger/migrations/contract_checking_migration_test.go deleted file mode 100644 index 4d286258227..00000000000 --- a/cmd/util/ledger/migrations/contract_checking_migration_test.go +++ /dev/null @@ -1,413 +0,0 @@ -package migrations - -import ( - "fmt" - "sort" - "testing" - - "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" - "github.com/onflow/flow-core-contracts/lib/go/templates" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/fvm/storage/snapshot" - "github.com/onflow/flow-go/fvm/systemcontracts" - "github.com/onflow/flow-go/ledger/common/convert" - "github.com/onflow/flow-go/model/flow" -) - -func oldExampleFungibleTokenCode(fungibleTokenAddress flow.Address) string { - return fmt.Sprintf( - ` - import FungibleToken from 0x%s - - pub contract ExampleFungibleToken: FungibleToken { - pub var totalSupply: UFix64 - - pub resource Vault { - pub var balance: UFix64 - } - } - `, - fungibleTokenAddress.Hex(), - ) -} - -func oldExampleNonFungibleTokenCode(fungibleTokenAddress flow.Address) string { - return fmt.Sprintf( - ` - import NonFungibleToken from 0x%s - - pub contract ExampleNFT: NonFungibleToken { - - pub var totalSupply: UInt64 - - pub resource NFT { - - pub let id: UInt64 - } - - pub resource Collection { - - pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} - } - } - `, - fungibleTokenAddress.Hex(), - ) -} - -func TestContractCheckingMigrationProgramRecovery(t *testing.T) { - - t.Parallel() - - registersByAccount := registers.NewByAccount() - - // Set up contracts - - const chainID = flow.Testnet - chain := chainID.Chain() - - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - contracts := map[flow.Address]map[string][]byte{} - - addContract := func(address flow.Address, name string, code []byte) { - addressContracts, ok := contracts[address] - if !ok { - addressContracts = map[string][]byte{} - contracts[address] = addressContracts - } - require.Empty(t, addressContracts[name]) - addressContracts[name] = code - } - - addSystemContract := func(systemContract systemcontracts.SystemContract, code []byte) { - addContract(systemContract.Address, systemContract.Name, code) - } - - env := templates.Environment{} - - addSystemContract( - systemContracts.ViewResolver, - coreContracts.ViewResolver(), - ) - env.ViewResolverAddress = systemContracts.ViewResolver.Address.Hex() - - addSystemContract( - systemContracts.Burner, - coreContracts.Burner(), - ) - env.BurnerAddress = systemContracts.Burner.Address.Hex() - - addSystemContract( - systemContracts.FungibleToken, - coreContracts.FungibleToken(env), - ) - addSystemContract( - systemContracts.NonFungibleToken, - coreContracts.NonFungibleToken(env), - ) - - const exampleFungibleTokenContractName = "ExampleFungibleToken" - const exampleNonFungibleTokenContractName = "ExampleNonFungibleToken" - - // Use an old version of the ExampleFungibleToken contract, - // and "deploy" it at some arbitrary, high (i.e. non-system) address - exampleAddress, err := chain.AddressAtIndex(1000) - require.NoError(t, err) - addContract( - exampleAddress, - exampleFungibleTokenContractName, - []byte(oldExampleFungibleTokenCode(systemContracts.FungibleToken.Address)), - ) - // Use an old version of the ExampleNonFungibleToken contract, - // and "deploy" it at some arbitrary, high (i.e. non-system) address - require.NoError(t, err) - addContract( - exampleAddress, - exampleNonFungibleTokenContractName, - []byte(oldExampleNonFungibleTokenCode(systemContracts.NonFungibleToken.Address)), - ) - - for address, addressContracts := range contracts { - - for contractName, code := range addressContracts { - - err := registersByAccount.Set( - string(address[:]), - flow.ContractKey(contractName), - code, - ) - require.NoError(t, err) - } - - contractNames := make([]string, 0, len(addressContracts)) - for contractName := range addressContracts { - contractNames = append(contractNames, contractName) - } - sort.Strings(contractNames) - - encodedContractNames, err := environment.EncodeContractNames(contractNames) - require.NoError(t, err) - - err = registersByAccount.Set( - string(address[:]), - flow.ContractNamesKey, - encodedContractNames, - ) - require.NoError(t, err) - } - - programs := map[common.Location]*interpreter.Program{} - - rwf := &testReportWriterFactory{} - - // Run contract checking migration - - log := zerolog.Nop() - checkingMigration := NewContractCheckingMigration( - log, - rwf, - chainID, - false, - nil, - programs, - ) - - err = checkingMigration(registersByAccount) - require.NoError(t, err) - - reporter := rwf.reportWriters[contractCheckingReporterName] - - assert.Equal(t, - []any{ - contractCheckingSuccess{ - AccountAddress: common.Address(systemContracts.NonFungibleToken.Address), - ContractName: systemcontracts.ContractNameNonFungibleToken, - Code: string(coreContracts.NonFungibleToken(env)), - }, - contractCheckingSuccess{ - AccountAddress: common.Address(systemContracts.ViewResolver.Address), - ContractName: systemcontracts.ContractNameViewResolver, - Code: string(coreContracts.ViewResolver()), - }, - contractCheckingSuccess{ - AccountAddress: common.Address(systemContracts.Burner.Address), - ContractName: systemcontracts.ContractNameBurner, - Code: string(coreContracts.Burner()), - }, - contractCheckingSuccess{ - AccountAddress: common.Address(systemContracts.FungibleToken.Address), - ContractName: systemcontracts.ContractNameFungibleToken, - Code: string(coreContracts.FungibleToken(env)), - }, - contractCheckingSuccess{ - AccountAddress: common.Address(exampleAddress), - ContractName: exampleFungibleTokenContractName, - Code: oldExampleFungibleTokenCode(systemContracts.FungibleToken.Address), - }, - contractCheckingSuccess{ - AccountAddress: common.Address(exampleAddress), - ContractName: exampleNonFungibleTokenContractName, - Code: oldExampleNonFungibleTokenCode(systemContracts.NonFungibleToken.Address), - }, - }, - reporter.entries, - ) - - // Check that the programs are recovered correctly after the migration. - - mr, err := NewInterpreterMigrationRuntime(registersByAccount, chainID, InterpreterMigrationRuntimeConfig{}) - require.NoError(t, err) - - // First, we need to create the example account - - err = mr.Accounts.Create(nil, exampleAddress) - require.NoError(t, err) - - expectedAddresses := map[flow.Address]struct{}{ - exampleAddress: {}, - } - - err = mr.Commit(expectedAddresses, log) - require.NoError(t, err) - - // Next, we need to manually store contract values in the example account, - // simulating the effect of the deploying the original contracts. - // - // We need to do so with a new runtime, - // because the previous runtime's transaction state is finalized. - - mr, err = NewInterpreterMigrationRuntime(registersByAccount, chainID, InterpreterMigrationRuntimeConfig{}) - require.NoError(t, err) - - contractsStorageMap := mr.Storage.GetStorageMap( - common.Address(exampleAddress), - runtime.StorageDomainContract, - true, - ) - - inter := mr.Interpreter - - exampleFungibleTokenContractValue := interpreter.NewCompositeValue( - inter, - interpreter.EmptyLocationRange, - common.NewAddressLocation( - nil, - common.Address(exampleAddress), - exampleFungibleTokenContractName, - ), - exampleFungibleTokenContractName, - common.CompositeKindContract, - []interpreter.CompositeField{ - { - Name: "totalSupply", - Value: interpreter.NewUnmeteredUFix64ValueWithInteger(42, interpreter.EmptyLocationRange), - }, - }, - common.Address(exampleAddress), - ) - - contractsStorageMap.SetValue( - inter, - interpreter.StringStorageMapKey(exampleFungibleTokenContractName), - exampleFungibleTokenContractValue, - ) - - exampleNonFungibleTokenContractValue := interpreter.NewCompositeValue( - inter, - interpreter.EmptyLocationRange, - common.NewAddressLocation( - nil, - common.Address(exampleAddress), - exampleNonFungibleTokenContractName, - ), - exampleNonFungibleTokenContractName, - common.CompositeKindContract, - []interpreter.CompositeField{ - { - Name: "totalSupply", - Value: interpreter.NewUnmeteredUInt64Value(42), - }, - }, - common.Address(exampleAddress), - ) - - contractsStorageMap.SetValue( - inter, - interpreter.StringStorageMapKey(exampleNonFungibleTokenContractName), - exampleNonFungibleTokenContractValue, - ) - - err = mr.Storage.NondeterministicCommit(inter, false) - require.NoError(t, err) - - err = mr.Commit(expectedAddresses, log) - require.NoError(t, err) - - // Setup complete, now we can run the test transactions - - type testCase struct { - name string - code string - check func(t *testing.T, err error) - } - - testCases := []testCase{ - { - name: exampleFungibleTokenContractName, - code: fmt.Sprintf( - ` - import ExampleFungibleToken from %s - - transaction { - execute { - assert(ExampleFungibleToken.totalSupply == 42.0) - destroy ExampleFungibleToken.createEmptyVault( - vaultType: Type<@ExampleFungibleToken.Vault>() - ) - } - } - `, - exampleAddress.HexWithPrefix(), - ), - check: func(t *testing.T, err error) { - require.Error(t, err) - require.ErrorContains(t, err, "Contract ExampleFungibleToken is no longer functional") - require.ErrorContains(t, err, "createEmptyVault is not available in recovered program.") - }, - }, - { - name: exampleNonFungibleTokenContractName, - code: fmt.Sprintf( - ` - import ExampleNonFungibleToken from %s - - transaction { - execute { - destroy ExampleNonFungibleToken.createEmptyCollection( - nftType: Type<@ExampleNonFungibleToken.NFT>() - ) - } - } - `, - exampleAddress.HexWithPrefix(), - ), - check: func(t *testing.T, err error) { - require.Error(t, err) - require.ErrorContains(t, err, "Contract ExampleNonFungibleToken is no longer functional") - require.ErrorContains(t, err, "createEmptyCollection is not available in recovered program.") - }, - }, - } - - storageSnapshot := snapshot.MapStorageSnapshot{} - - newPayloads := registersByAccount.DestructIntoPayloads(1) - - for _, newPayload := range newPayloads { - registerID, registerValue, err := convert.PayloadToRegister(newPayload) - require.NoError(t, err) - - storageSnapshot[registerID] = registerValue - } - - test := func(testCase testCase) { - - t.Run(testCase.name, func(t *testing.T) { - - txBody := flow.NewTransactionBody(). - SetScript([]byte(testCase.code)) - - vm := fvm.NewVirtualMachine() - - ctx := fvm.NewContext( - fvm.WithChain(chain), - fvm.WithAuthorizationChecksEnabled(false), - fvm.WithSequenceNumberCheckAndIncrementEnabled(false), - fvm.WithCadenceLogging(true), - ) - - _, output, err := vm.Run( - ctx, - fvm.Transaction(txBody, 0), - storageSnapshot, - ) - - require.NoError(t, err) - testCase.check(t, output.Err) - }) - } - - for _, testCase := range testCases { - test(testCase) - } -} diff --git a/cmd/util/ledger/migrations/contract_cleanup_migration.go b/cmd/util/ledger/migrations/contract_cleanup_migration.go deleted file mode 100644 index e60f1a2f83a..00000000000 --- a/cmd/util/ledger/migrations/contract_cleanup_migration.go +++ /dev/null @@ -1,298 +0,0 @@ -package migrations - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "sort" - - "github.com/rs/zerolog" - - "github.com/onflow/cadence/runtime/common" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/model/flow" -) - -// ContractCleanupMigration normalized account's contract names and removes empty contracts. -type ContractCleanupMigration struct { - log zerolog.Logger - reporter reporters.ReportWriter -} - -const contractCleanupReporterName = "contract-cleanup" - -var _ AccountBasedMigration = &ContractCleanupMigration{} - -func NewContractCleanupMigration(rwf reporters.ReportWriterFactory) *ContractCleanupMigration { - return &ContractCleanupMigration{ - reporter: rwf.ReportWriter(contractCleanupReporterName), - } -} - -func (d *ContractCleanupMigration) InitMigration( - log zerolog.Logger, - _ *registers.ByAccount, - _ int, -) error { - d.log = log. - With(). - Str("migration", "ContractCleanupMigration"). - Logger() - - return nil -} - -func (d *ContractCleanupMigration) MigrateAccount( - _ context.Context, - address common.Address, - accountRegisters *registers.AccountRegisters, -) error { - - // Get the set of all contract names for the account. - - oldContractNames, err := d.getContractNames(accountRegisters) - if err != nil { - return fmt.Errorf( - "failed to get contract names for %s: %w", - address.HexWithPrefix(), - err, - ) - } - - contractNameSet := make(map[string]struct{}) - for _, contractName := range oldContractNames { - contractNameSet[contractName] = struct{}{} - } - - contractNamesSorted := make([]string, 0, len(contractNameSet)) - for contractName := range contractNameSet { - contractNamesSorted = append(contractNamesSorted, contractName) - } - sort.Strings(contractNamesSorted) - - // Cleanup the code for each contract in the account. - // If the contract code is empty, the contract code register will be removed, - // and the contract name will be removed from the account's contract names. - - for _, contractName := range contractNamesSorted { - removed, err := d.cleanupContractCode( - address, - accountRegisters, - contractName, - ) - if err != nil { - return fmt.Errorf( - "failed to cleanup contract code for %s: %w", - address.HexWithPrefix(), - err, - ) - } - - if removed { - delete(contractNameSet, contractName) - } - } - - // Sort the contract names and set them back to the account. - - newContractNames := make([]string, 0, len(contractNameSet)) - for contractName := range contractNameSet { - newContractNames = append(newContractNames, contractName) - } - - sort.Strings(newContractNames) - - // NOTE: Always set the contract names back to the account, - // even if there are no contract names. - // This effectively clears the contract names register. - - err = d.setContractNames(accountRegisters, newContractNames) - if err != nil { - return fmt.Errorf( - "failed to set contract names for %s: %w", - address.HexWithPrefix(), - err, - ) - } - - if !stringSlicesEqual(newContractNames, oldContractNames) { - d.reporter.Write(contractNamesChanged{ - AccountAddress: address, - Old: oldContractNames, - New: newContractNames, - }) - } - - return nil -} - -func (d *ContractCleanupMigration) getContractNames( - accountRegisters *registers.AccountRegisters, -) ([]string, error) { - owner := accountRegisters.Owner() - - encodedContractNames, err := accountRegisters.Get(owner, flow.ContractNamesKey) - if err != nil { - return nil, fmt.Errorf( - "failed to get contract names: %w", - err, - ) - } - - if len(encodedContractNames) == 0 { - return nil, nil - } - - contractNames, err := environment.DecodeContractNames(encodedContractNames) - if err != nil { - return nil, fmt.Errorf( - "failed to decode contract names: %w", - err, - ) - } - - return contractNames, nil -} - -func (d *ContractCleanupMigration) setContractNames( - accountRegisters *registers.AccountRegisters, - contractNames []string, -) error { - owner := accountRegisters.Owner() - - var newEncodedContractNames []byte - var err error - - // Encode the new contract names, if there are any. - - if len(contractNames) > 0 { - newEncodedContractNames, err = environment.EncodeContractNames(contractNames) - if err != nil { - return fmt.Errorf( - "failed to encode contract names: %w", - err, - ) - } - } - - // NOTE: always set the contract names register, even if there are not contract names. - // This effectively clears the contract names register. - - err = accountRegisters.Set(owner, flow.ContractNamesKey, newEncodedContractNames) - if err != nil { - return fmt.Errorf( - "failed to set contract names: %w", - err, - ) - } - - return nil -} - -// cleanupContractCode removes the code for the contract if it is empty. -// Returns true if the contract code was removed. -func (d *ContractCleanupMigration) cleanupContractCode( - address common.Address, - accountRegisters *registers.AccountRegisters, - contractName string, -) (removed bool, err error) { - owner := accountRegisters.Owner() - - contractKey := flow.ContractKey(contractName) - - code, err := accountRegisters.Get(owner, contractKey) - if err != nil { - return false, fmt.Errorf( - "failed to get contract code for %s: %w", - contractName, - err, - ) - } - - // If the contract code is empty, remove the contract code register. - - if len(bytes.TrimSpace(code)) == 0 { - err = accountRegisters.Set(owner, contractKey, nil) - if err != nil { - return false, fmt.Errorf( - "failed to clear contract code for %s: %w", - contractName, - err, - ) - } - - d.reporter.Write(emptyContractRemoved{ - AccountAddress: address, - ContractName: contractName, - }) - - removed = true - } - - return removed, nil -} - -func (d *ContractCleanupMigration) Close() error { - d.reporter.Close() - - return nil -} - -type emptyContractRemoved struct { - AccountAddress common.Address - ContractName string -} - -var _ json.Marshaler = emptyContractRemoved{} - -func (e emptyContractRemoved) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"address"` - ContractName string `json:"name"` - }{ - Kind: "empty-contract-removed", - AccountAddress: e.AccountAddress.HexWithPrefix(), - ContractName: e.ContractName, - }) -} - -func stringSlicesEqual(a, b []string) bool { - if len(a) != len(b) { - return false - } - - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true -} - -type contractNamesChanged struct { - AccountAddress common.Address `json:"address"` - Old []string `json:"old"` - New []string `json:"new"` -} - -var _ json.Marshaler = contractNamesChanged{} - -func (e contractNamesChanged) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"address"` - Old []string `json:"old"` - New []string `json:"new"` - }{ - Kind: "contract-names-changed", - AccountAddress: e.AccountAddress.HexWithPrefix(), - Old: e.Old, - New: e.New, - }) -} diff --git a/cmd/util/ledger/migrations/contract_cleanup_migration_test.go b/cmd/util/ledger/migrations/contract_cleanup_migration_test.go deleted file mode 100644 index 36435c6ca8b..00000000000 --- a/cmd/util/ledger/migrations/contract_cleanup_migration_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package migrations - -import ( - "context" - "testing" - - "github.com/onflow/cadence/runtime/common" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/model/flow" -) - -func TestContractCleanupMigration1(t *testing.T) { - - t.Parallel() - - // Arrange - - address, err := common.HexToAddress("0x4184b8bdf78db9eb") - require.NoError(t, err) - - flowAddress := flow.ConvertAddress(address) - owner := flow.AddressToRegisterOwner(flowAddress) - - const contractNameEmpty = "Foo" - const contractNameNonEmpty = "Bar" - - registersByAccount := registers.NewByAccount() - - err = registersByAccount.Set( - owner, - flow.ContractKey(contractNameEmpty), - // Some whitespace for testing purposes - []byte(" \t \n "), - ) - require.NoError(t, err) - - err = registersByAccount.Set( - owner, - flow.ContractKey(contractNameNonEmpty), - []byte(" \n \t access(all) contract Bar {} \n \n"), - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{ - // Unsorted and duplicates for testing purposes - contractNameEmpty, - contractNameNonEmpty, - contractNameEmpty, - contractNameNonEmpty, - contractNameEmpty, - contractNameEmpty, - contractNameNonEmpty, - }) - require.NoError(t, err) - - err = registersByAccount.Set( - owner, - flow.ContractNamesKey, - encodedContractNames, - ) - require.NoError(t, err) - - // Act - - rwf := &testReportWriterFactory{} - - cleanupMigration := NewContractCleanupMigration(rwf) - - log := zerolog.Nop() - - err = cleanupMigration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - accountRegisters := registersByAccount.AccountRegisters(owner) - - err = cleanupMigration.MigrateAccount( - context.Background(), - address, - accountRegisters, - ) - require.NoError(t, err) - - err = cleanupMigration.Close() - require.NoError(t, err) - - // Assert - - encodedContractNames, err = registersByAccount.Get( - owner, - flow.ContractNamesKey, - ) - require.NoError(t, err) - - contractNames, err := environment.DecodeContractNames(encodedContractNames) - require.NoError(t, err) - assert.Equal(t, - []string{ - contractNameNonEmpty, - }, - contractNames, - ) - - contractEmpty, err := registersByAccount.Get( - owner, - flow.ContractKey(contractNameEmpty), - ) - require.NoError(t, err) - assert.Nil(t, contractEmpty) - - contractNonEmpty, err := registersByAccount.Get( - owner, - flow.ContractKey(contractNameNonEmpty), - ) - require.NoError(t, err) - assert.NotEmpty(t, contractNonEmpty) - - reporter := rwf.reportWriters[contractCleanupReporterName] - require.NotNil(t, reporter) - - assert.Equal(t, - []any{ - emptyContractRemoved{ - AccountAddress: address, - ContractName: contractNameEmpty, - }, - contractNamesChanged{ - AccountAddress: address, - Old: []string{ - contractNameEmpty, - contractNameNonEmpty, - contractNameEmpty, - contractNameNonEmpty, - contractNameEmpty, - contractNameEmpty, - contractNameNonEmpty, - }, - New: []string{ - contractNameNonEmpty, - }, - }, - }, - reporter.entries, - ) -} - -func TestContractCleanupMigration2(t *testing.T) { - - t.Parallel() - - // Arrange - - address, err := common.HexToAddress("0x4184b8bdf78db9eb") - require.NoError(t, err) - - flowAddress := flow.ConvertAddress(address) - owner := flow.AddressToRegisterOwner(flowAddress) - - const contractNameEmpty1 = "Foo" - const contractNameEmpty2 = "Bar" - - registersByAccount := registers.NewByAccount() - - err = registersByAccount.Set( - owner, - flow.ContractKey(contractNameEmpty1), - // Some whitespace for testing purposes - []byte(" \t \n "), - ) - require.NoError(t, err) - - err = registersByAccount.Set( - owner, - flow.ContractKey(contractNameEmpty2), - []byte("\n \t \n \t"), - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{ - // Unsorted and duplicates for testing purposes - contractNameEmpty1, - contractNameEmpty2, - contractNameEmpty1, - contractNameEmpty2, - contractNameEmpty1, - contractNameEmpty1, - contractNameEmpty2, - }) - require.NoError(t, err) - - err = registersByAccount.Set( - owner, - flow.ContractNamesKey, - encodedContractNames, - ) - require.NoError(t, err) - - // Act - - rwf := &testReportWriterFactory{} - - cleanupMigration := NewContractCleanupMigration(rwf) - - log := zerolog.Nop() - - err = cleanupMigration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - accountRegisters := registersByAccount.AccountRegisters(owner) - - err = cleanupMigration.MigrateAccount( - context.Background(), - address, - accountRegisters, - ) - require.NoError(t, err) - - err = cleanupMigration.Close() - require.NoError(t, err) - - // Assert - - encodedContractNames, err = registersByAccount.Get( - owner, - flow.ContractNamesKey, - ) - require.NoError(t, err) - assert.Nil(t, encodedContractNames) - - contractEmpty1, err := registersByAccount.Get( - owner, - flow.ContractKey(contractNameEmpty1), - ) - require.NoError(t, err) - assert.Nil(t, contractEmpty1) - - contractEmpty2, err := registersByAccount.Get( - owner, - flow.ContractKey(contractNameEmpty2), - ) - require.NoError(t, err) - assert.Nil(t, contractEmpty2) - - reporter := rwf.reportWriters[contractCleanupReporterName] - require.NotNil(t, reporter) - - // Order is alphabetical - assert.Equal(t, - []any{ - emptyContractRemoved{ - AccountAddress: address, - ContractName: contractNameEmpty2, - }, - emptyContractRemoved{ - AccountAddress: address, - ContractName: contractNameEmpty1, - }, - contractNamesChanged{ - AccountAddress: address, - Old: []string{ - contractNameEmpty1, - contractNameEmpty2, - contractNameEmpty1, - contractNameEmpty2, - contractNameEmpty1, - contractNameEmpty1, - contractNameEmpty2, - }, - New: []string{}, - }, - }, - reporter.entries, - ) -} diff --git a/cmd/util/ledger/migrations/deploy_migration.go b/cmd/util/ledger/migrations/deploy_migration.go index ed631279ca7..0fc2c4223e7 100644 --- a/cmd/util/ledger/migrations/deploy_migration.go +++ b/cmd/util/ledger/migrations/deploy_migration.go @@ -8,6 +8,11 @@ import ( "github.com/onflow/flow-go/model/flow" ) +type Contract struct { + Name string + Code []byte +} + func NewDeploymentMigration( chainID flow.ChainID, contract Contract, diff --git a/cmd/util/ledger/migrations/evm.go b/cmd/util/ledger/migrations/evm.go deleted file mode 100644 index 8ce7a49729c..00000000000 --- a/cmd/util/ledger/migrations/evm.go +++ /dev/null @@ -1,84 +0,0 @@ -package migrations - -import ( - "fmt" - - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/fvm/evm/stdlib" - "github.com/onflow/flow-go/fvm/systemcontracts" - "github.com/onflow/flow-go/model/flow" -) - -func NewEVMDeploymentMigration( - chainID flow.ChainID, - logger zerolog.Logger, - full bool, -) RegistersMigration { - - systemContracts := systemcontracts.SystemContractsForChain(chainID) - address := systemContracts.EVMContract.Address - - var code []byte - if full { - code = stdlib.ContractCode( - systemContracts.NonFungibleToken.Address, - systemContracts.FungibleToken.Address, - systemContracts.FlowToken.Address, - ) - } else { - code = []byte(stdlib.ContractMinimalCode) - } - - return NewDeploymentMigration( - chainID, - Contract{ - Name: systemContracts.EVMContract.Name, - Code: code, - }, - address, - map[flow.Address]struct{}{ - address: {}, - }, - logger, - ) -} - -// NewEVMSetupMigration returns a migration that sets up the EVM contract account. -// It performs the same operations as the EVM contract's initializer, calling the function EVM.setupHeartbeat, -// which in turn creates an EVM.Heartbeat resource, and writes it to the account's storage. -func NewEVMSetupMigration( - chainID flow.ChainID, - logger zerolog.Logger, -) RegistersMigration { - - systemContracts := systemcontracts.SystemContractsForChain(chainID) - evmContract := systemContracts.EVMContract - - tx := flow.NewTransactionBody(). - SetScript([]byte(fmt.Sprintf( - ` - import EVM from %s - - transaction { - prepare() { - EVM.setupHeartbeat() - } - } - `, - evmContract.Address.HexWithPrefix(), - ))) - - return NewTransactionBasedMigration( - tx, - chainID, - logger, - map[flow.Address]struct{}{ - // The function call writes to the EVM contract account - evmContract.Address: {}, - // The function call writes to the global account, - // as the function creates a resource, which gets initialized with a UUID - flow.Address{}: {}, - }, - ) -} diff --git a/cmd/util/ledger/migrations/filter_unreferenced_slabs_migration_test.go b/cmd/util/ledger/migrations/filter_unreferenced_slabs_migration_test.go index 0ed6f5f7752..523cca2e644 100644 --- a/cmd/util/ledger/migrations/filter_unreferenced_slabs_migration_test.go +++ b/cmd/util/ledger/migrations/filter_unreferenced_slabs_migration_test.go @@ -3,6 +3,8 @@ package migrations import ( "context" "encoding/binary" + "fmt" + "sync" "testing" "github.com/onflow/atree" @@ -13,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/cmd/util/ledger/util/registers" "github.com/onflow/flow-go/ledger" @@ -20,6 +23,42 @@ import ( "github.com/onflow/flow-go/model/flow" ) +type testReportWriterFactory struct { + lock sync.Mutex + reportWriters map[string]*testReportWriter +} + +func (f *testReportWriterFactory) ReportWriter(dataNamespace string) reporters.ReportWriter { + f.lock.Lock() + defer f.lock.Unlock() + + if f.reportWriters == nil { + f.reportWriters = make(map[string]*testReportWriter) + } + reportWriter := &testReportWriter{} + if _, ok := f.reportWriters[dataNamespace]; ok { + panic(fmt.Sprintf("report writer already exists for namespace %s", dataNamespace)) + } + f.reportWriters[dataNamespace] = reportWriter + return reportWriter +} + +type testReportWriter struct { + lock sync.Mutex + entries []any +} + +var _ reporters.ReportWriter = &testReportWriter{} + +func (r *testReportWriter) Write(entry any) { + r.lock.Lock() + defer r.lock.Unlock() + + r.entries = append(r.entries, entry) +} + +func (r *testReportWriter) Close() {} + func TestFilterUnreferencedSlabs(t *testing.T) { t.Parallel() diff --git a/cmd/util/ledger/migrations/fix_authorizations_migration.go b/cmd/util/ledger/migrations/fix_authorizations_migration.go deleted file mode 100644 index ff4ab7898af..00000000000 --- a/cmd/util/ledger/migrations/fix_authorizations_migration.go +++ /dev/null @@ -1,461 +0,0 @@ -package migrations - -import ( - "encoding/json" - "errors" - "fmt" - "io" - - "github.com/onflow/cadence/migrations" - "github.com/onflow/cadence/runtime/common" - cadenceErrors "github.com/onflow/cadence/runtime/errors" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/sema" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/fvm/environment" -) - -type AccountCapabilityID struct { - Address common.Address - CapabilityID uint64 -} - -// FixAuthorizationsMigration - -type FixAuthorizationsMigrationReporter interface { - MigratedCapability( - storageKey interpreter.StorageKey, - capabilityAddress common.Address, - capabilityID uint64, - ) - MigratedCapabilityController( - storageKey interpreter.StorageKey, - capabilityID uint64, - ) -} - -type FixAuthorizationsMigration struct { - Reporter FixAuthorizationsMigrationReporter - AuthorizationFixes AuthorizationFixes -} - -var _ migrations.ValueMigration = &FixAuthorizationsMigration{} - -func (*FixAuthorizationsMigration) Name() string { - return "FixAuthorizationsMigration" -} - -func (*FixAuthorizationsMigration) Domains() map[string]struct{} { - return nil -} - -func (m *FixAuthorizationsMigration) Migrate( - storageKey interpreter.StorageKey, - _ interpreter.StorageMapKey, - value interpreter.Value, - _ *interpreter.Interpreter, - _ migrations.ValueMigrationPosition, -) ( - interpreter.Value, - error, -) { - switch value := value.(type) { - case *interpreter.IDCapabilityValue: - capabilityAddress := common.Address(value.Address()) - capabilityID := uint64(value.ID) - - _, ok := m.AuthorizationFixes[AccountCapabilityID{ - Address: capabilityAddress, - CapabilityID: capabilityID, - }] - if !ok { - // This capability does not need to be fixed - return nil, nil - } - - oldBorrowType := value.BorrowType - if oldBorrowType == nil { - log.Warn().Msgf( - "missing borrow type for capability with target %s#%d", - capabilityAddress.HexWithPrefix(), - capabilityID, - ) - } - - oldBorrowReferenceType, ok := oldBorrowType.(*interpreter.ReferenceStaticType) - if !ok { - log.Warn().Msgf( - "invalid non-reference borrow type for capability with target %s#%d: %s", - capabilityAddress.HexWithPrefix(), - capabilityID, - oldBorrowType, - ) - return nil, nil - } - - newBorrowType := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - oldBorrowReferenceType.ReferencedType, - ) - newCapabilityValue := interpreter.NewUnmeteredCapabilityValue( - interpreter.UInt64Value(capabilityID), - interpreter.AddressValue(capabilityAddress), - newBorrowType, - ) - - m.Reporter.MigratedCapability( - storageKey, - capabilityAddress, - capabilityID, - ) - - return newCapabilityValue, nil - - case *interpreter.StorageCapabilityControllerValue: - // The capability controller's address is implicitly - // the address of the account in which it is stored - capabilityAddress := storageKey.Address - capabilityID := uint64(value.CapabilityID) - - _, ok := m.AuthorizationFixes[AccountCapabilityID{ - Address: capabilityAddress, - CapabilityID: capabilityID, - }] - if !ok { - // This capability controller does not need to be fixed - return nil, nil - } - - oldBorrowReferenceType := value.BorrowType - - newBorrowType := interpreter.NewReferenceStaticType( - nil, - interpreter.UnauthorizedAccess, - oldBorrowReferenceType.ReferencedType, - ) - newStorageCapabilityControllerValue := interpreter.NewUnmeteredStorageCapabilityControllerValue( - newBorrowType, - interpreter.UInt64Value(capabilityID), - value.TargetPath, - ) - - m.Reporter.MigratedCapabilityController( - storageKey, - capabilityID, - ) - - return newStorageCapabilityControllerValue, nil - } - - return nil, nil -} - -func (*FixAuthorizationsMigration) CanSkip(valueType interpreter.StaticType) bool { - return CanSkipFixAuthorizationsMigration(valueType) -} - -func CanSkipFixAuthorizationsMigration(valueType interpreter.StaticType) bool { - switch valueType := valueType.(type) { - case *interpreter.DictionaryStaticType: - return CanSkipFixAuthorizationsMigration(valueType.KeyType) && - CanSkipFixAuthorizationsMigration(valueType.ValueType) - - case interpreter.ArrayStaticType: - return CanSkipFixAuthorizationsMigration(valueType.ElementType()) - - case *interpreter.OptionalStaticType: - return CanSkipFixAuthorizationsMigration(valueType.Type) - - case *interpreter.CapabilityStaticType: - return false - - case interpreter.PrimitiveStaticType: - - switch valueType { - case interpreter.PrimitiveStaticTypeCapability, - interpreter.PrimitiveStaticTypeStorageCapabilityController: - return false - - case interpreter.PrimitiveStaticTypeBool, - interpreter.PrimitiveStaticTypeVoid, - interpreter.PrimitiveStaticTypeAddress, - interpreter.PrimitiveStaticTypeMetaType, - interpreter.PrimitiveStaticTypeBlock, - interpreter.PrimitiveStaticTypeString, - interpreter.PrimitiveStaticTypeCharacter: - - return true - } - - if !valueType.IsDeprecated() { //nolint:staticcheck - semaType := valueType.SemaType() - - if sema.IsSubType(semaType, sema.NumberType) || - sema.IsSubType(semaType, sema.PathType) { - - return true - } - } - } - - return false -} - -const fixAuthorizationsMigrationReporterName = "fix-authorizations-migration" - -func NewFixAuthorizationsMigration( - rwf reporters.ReportWriterFactory, - authorizationFixes AuthorizationFixes, - opts Options, -) *CadenceBaseMigration { - var diffReporter reporters.ReportWriter - if opts.DiffMigrations { - diffReporter = rwf.ReportWriter("fix-authorizations-migration-diff") - } - - reporter := rwf.ReportWriter(fixAuthorizationsMigrationReporterName) - - return &CadenceBaseMigration{ - name: "fix_authorizations_migration", - reporter: reporter, - diffReporter: diffReporter, - logVerboseDiff: opts.LogVerboseDiff, - verboseErrorOutput: opts.VerboseErrorOutput, - checkStorageHealthBeforeMigration: opts.CheckStorageHealthBeforeMigration, - valueMigrations: func( - _ *interpreter.Interpreter, - _ environment.Accounts, - _ *cadenceValueMigrationReporter, - ) []migrations.ValueMigration { - - return []migrations.ValueMigration{ - &FixAuthorizationsMigration{ - AuthorizationFixes: authorizationFixes, - Reporter: &fixAuthorizationsMigrationReporter{ - reportWriter: reporter, - verboseErrorOutput: opts.VerboseErrorOutput, - }, - }, - } - }, - chainID: opts.ChainID, - } -} - -type fixAuthorizationsMigrationReporter struct { - reportWriter reporters.ReportWriter - errorMessageHandler *errorMessageHandler - verboseErrorOutput bool -} - -var _ FixAuthorizationsMigrationReporter = &fixAuthorizationsMigrationReporter{} -var _ migrations.Reporter = &fixAuthorizationsMigrationReporter{} - -func (r *fixAuthorizationsMigrationReporter) Migrated( - storageKey interpreter.StorageKey, - storageMapKey interpreter.StorageMapKey, - migration string, -) { - r.reportWriter.Write(cadenceValueMigrationEntry{ - StorageKey: storageKey, - StorageMapKey: storageMapKey, - Migration: migration, - }) -} - -func (r *fixAuthorizationsMigrationReporter) Error(err error) { - - var migrationErr migrations.StorageMigrationError - - if !errors.As(err, &migrationErr) { - panic(cadenceErrors.NewUnreachableError()) - } - - message, showStack := r.errorMessageHandler.FormatError(migrationErr.Err) - - storageKey := migrationErr.StorageKey - storageMapKey := migrationErr.StorageMapKey - migration := migrationErr.Migration - - if showStack && len(migrationErr.Stack) > 0 { - message = fmt.Sprintf("%s\n%s", message, migrationErr.Stack) - } - - if r.verboseErrorOutput { - r.reportWriter.Write(cadenceValueMigrationFailureEntry{ - StorageKey: storageKey, - StorageMapKey: storageMapKey, - Migration: migration, - Message: message, - }) - } -} - -func (r *fixAuthorizationsMigrationReporter) DictionaryKeyConflict(accountAddressPath interpreter.AddressPath) { - r.reportWriter.Write(dictionaryKeyConflictEntry{ - AddressPath: accountAddressPath, - }) -} - -func (r *fixAuthorizationsMigrationReporter) MigratedCapabilityController( - storageKey interpreter.StorageKey, - capabilityID uint64, -) { - r.reportWriter.Write(capabilityControllerAuthorizationFixedEntry{ - StorageKey: storageKey, - CapabilityID: capabilityID, - }) -} - -func (r *fixAuthorizationsMigrationReporter) MigratedCapability( - storageKey interpreter.StorageKey, - capabilityAddress common.Address, - capabilityID uint64, -) { - r.reportWriter.Write(capabilityAuthorizationFixedEntry{ - StorageKey: storageKey, - CapabilityAddress: capabilityAddress, - CapabilityID: capabilityID, - }) -} - -// capabilityControllerAuthorizationFixedEntry -type capabilityControllerAuthorizationFixedEntry struct { - StorageKey interpreter.StorageKey - CapabilityID uint64 -} - -var _ json.Marshaler = capabilityControllerAuthorizationFixedEntry{} - -func (e capabilityControllerAuthorizationFixedEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - StorageDomain string `json:"domain"` - CapabilityID uint64 `json:"capability_id"` - }{ - Kind: "capability-controller-authorizations-fixed", - AccountAddress: e.StorageKey.Address.HexWithPrefix(), - StorageDomain: e.StorageKey.Key, - CapabilityID: e.CapabilityID, - }) -} - -// capabilityAuthorizationFixedEntry -type capabilityAuthorizationFixedEntry struct { - StorageKey interpreter.StorageKey - CapabilityAddress common.Address - CapabilityID uint64 -} - -var _ json.Marshaler = capabilityAuthorizationFixedEntry{} - -func (e capabilityAuthorizationFixedEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - StorageDomain string `json:"domain"` - CapabilityAddress string `json:"capability_address"` - CapabilityID uint64 `json:"capability_id"` - }{ - Kind: "capability-authorizations-fixed", - AccountAddress: e.StorageKey.Address.HexWithPrefix(), - StorageDomain: e.StorageKey.Key, - CapabilityAddress: e.CapabilityAddress.HexWithPrefix(), - CapabilityID: e.CapabilityID, - }) -} - -func NewFixAuthorizationsMigrations( - log zerolog.Logger, - rwf reporters.ReportWriterFactory, - authorizationFixes AuthorizationFixes, - opts Options, -) []NamedMigration { - - return []NamedMigration{ - { - Name: "fix-authorizations", - Migrate: NewAccountBasedMigration( - log, - opts.NWorker, - []AccountBasedMigration{ - NewFixAuthorizationsMigration( - rwf, - authorizationFixes, - opts, - ), - }, - ), - }, - } -} - -type AuthorizationFixes map[AccountCapabilityID]struct{} - -// ReadAuthorizationFixes reads a report of authorization fixes from the given reader. -// The report is expected to be a JSON array of objects with the following structure: -// -// [ -// {"capability_address":"0x1","capability_id":1} -// ] -func ReadAuthorizationFixes( - reader io.Reader, - filter map[common.Address]struct{}, -) (AuthorizationFixes, error) { - - fixes := AuthorizationFixes{} - - dec := json.NewDecoder(reader) - - token, err := dec.Token() - if err != nil { - return nil, fmt.Errorf("failed to read token: %w", err) - } - if token != json.Delim('[') { - return nil, fmt.Errorf("expected start of array, got %s", token) - } - - for dec.More() { - var entry struct { - CapabilityAddress string `json:"capability_address"` - CapabilityID uint64 `json:"capability_id"` - } - err := dec.Decode(&entry) - if err != nil { - return nil, fmt.Errorf("failed to decode entry: %w", err) - } - - address, err := common.HexToAddress(entry.CapabilityAddress) - if err != nil { - return nil, fmt.Errorf("failed to parse address: %w", err) - } - - if filter != nil { - if _, ok := filter[address]; !ok { - continue - } - } - - accountCapabilityID := AccountCapabilityID{ - Address: address, - CapabilityID: entry.CapabilityID, - } - - fixes[accountCapabilityID] = struct{}{} - } - - token, err = dec.Token() - if err != nil { - return nil, fmt.Errorf("failed to read token: %w", err) - } - if token != json.Delim(']') { - return nil, fmt.Errorf("expected end of array, got %s", token) - } - - return fixes, nil -} diff --git a/cmd/util/ledger/migrations/fix_authorizations_migration_test.go b/cmd/util/ledger/migrations/fix_authorizations_migration_test.go deleted file mode 100644 index 4ce19cff745..00000000000 --- a/cmd/util/ledger/migrations/fix_authorizations_migration_test.go +++ /dev/null @@ -1,336 +0,0 @@ -package migrations - -import ( - "fmt" - "strings" - "testing" - - "github.com/onflow/cadence" - jsoncdc "github.com/onflow/cadence/encoding/json" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/model/flow" -) - -func TestFixAuthorizationsMigration(t *testing.T) { - t.Parallel() - - // This test no longer works because publishing authorized capabilities is no longer allowed. - // The migration and test are kept for historical reasons. - - t.Skip() - - const chainID = flow.Emulator - chain := chainID.Chain() - - const nWorker = 2 - - address, err := chain.AddressAtIndex(1000) - require.NoError(t, err) - - require.Equal(t, "bf519681cdb888b1", address.Hex()) - - log := zerolog.New(zerolog.NewTestWriter(t)) - - bootstrapPayloads, err := newBootstrapPayloads(chainID) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(bootstrapPayloads) - require.NoError(t, err) - - mr := NewBasicMigrationRuntime(registersByAccount) - err = mr.Accounts.Create(nil, address) - require.NoError(t, err) - - expectedWriteAddresses := map[flow.Address]struct{}{ - address: {}, - } - - err = mr.Commit(expectedWriteAddresses, log) - require.NoError(t, err) - - const contractCode = ` - access(all) contract Test { - - access(all) entitlement E - - access(all) struct S {} - } - ` - - deployTX := flow.NewTransactionBody(). - SetScript([]byte(` - transaction(code: String) { - prepare(signer: auth(Contracts) &Account) { - signer.contracts.add(name: "Test", code: code.utf8) - } - } - `)). - AddAuthorizer(address). - AddArgument(jsoncdc.MustEncode(cadence.String(contractCode))) - - runDeployTx := NewTransactionBasedMigration( - deployTX, - chainID, - log, - expectedWriteAddresses, - ) - err = runDeployTx(registersByAccount) - require.NoError(t, err) - - setupTx := flow.NewTransactionBody(). - SetScript([]byte(fmt.Sprintf(` - import Test from %s - - transaction { - prepare(signer: auth(Storage, Capabilities) &Account) { - signer.storage.save(Test.S(), to: /storage/s) - - // Capability 1 was a public, unauthorized capability, which is now authorized. - // It should lose its entitlement - let cap1 = signer.capabilities.storage.issue(/storage/s) - assert(cap1.borrow() != nil) - signer.capabilities.publish(cap1, at: /public/s1) - - // Capability 2 was a public, unauthorized capability, which is now authorized. - // It is currently only stored, nested, in storage, and is not published. - // It should lose its entitlement - let cap2 = signer.capabilities.storage.issue(/storage/s) - assert(cap2.borrow() != nil) - signer.storage.save([cap2], to: /storage/caps2) - - // Capability 3 was a private, authorized capability. - // It is currently only stored, nested, in storage, and is not published. - // It should keep its entitlement - let cap3 = signer.capabilities.storage.issue(/storage/s) - assert(cap3.borrow() != nil) - signer.storage.save([cap3], to: /storage/caps3) - - // Capability 4 was a private, authorized capability. - // It is currently both stored, nested, in storage, and is published. - // It should keep its entitlement - let cap4 = signer.capabilities.storage.issue(/storage/s) - assert(cap4.borrow() != nil) - signer.storage.save([cap4], to: /storage/caps4) - signer.capabilities.publish(cap4, at: /public/s4) - - // Capability 5 was a public, unauthorized capability, which is still unauthorized. - // It is currently both stored, nested, in storage, and is published. - // There is no need to fix it. - let cap5 = signer.capabilities.storage.issue<&Test.S>(/storage/s) - assert(cap5.borrow() != nil) - signer.storage.save([cap5], to: /storage/caps5) - signer.capabilities.publish(cap5, at: /public/s5) - } - } - `, - address.HexWithPrefix(), - ))). - AddAuthorizer(address) - - runSetupTx := NewTransactionBasedMigration( - setupTx, - chainID, - log, - expectedWriteAddresses, - ) - - err = runSetupTx(registersByAccount) - require.NoError(t, err) - - rwf := &testReportWriterFactory{} - - options := Options{ - ChainID: chainID, - NWorker: nWorker, - } - - fixes := AuthorizationFixes{ - AccountCapabilityID{ - Address: common.Address(address), - CapabilityID: 1, - }: {}, - AccountCapabilityID{ - Address: common.Address(address), - CapabilityID: 2, - }: {}, - } - - migrations := NewFixAuthorizationsMigrations( - log, - rwf, - fixes, - options, - ) - - for _, namedMigration := range migrations { - err = namedMigration.Migrate(registersByAccount) - require.NoError(t, err) - } - - reporter := rwf.reportWriters[fixAuthorizationsMigrationReporterName] - require.NotNil(t, reporter) - - var entries []any - - for _, entry := range reporter.entries { - switch entry := entry.(type) { - case capabilityAuthorizationFixedEntry, - capabilityControllerAuthorizationFixedEntry: - - entries = append(entries, entry) - } - } - - require.ElementsMatch(t, - []any{ - capabilityControllerAuthorizationFixedEntry{ - StorageKey: interpreter.StorageKey{ - Key: "cap_con", - Address: common.Address(address), - }, - CapabilityID: 1, - }, - capabilityControllerAuthorizationFixedEntry{ - StorageKey: interpreter.StorageKey{ - Key: "cap_con", - Address: common.Address(address), - }, - CapabilityID: 2, - }, - capabilityAuthorizationFixedEntry{ - StorageKey: interpreter.StorageKey{ - Key: "public", - Address: common.Address(address), - }, - CapabilityAddress: common.Address(address), - CapabilityID: 1, - }, - capabilityAuthorizationFixedEntry{ - StorageKey: interpreter.StorageKey{ - Key: "storage", - Address: common.Address(address), - }, - CapabilityAddress: common.Address(address), - CapabilityID: 2, - }, - }, - entries, - ) - - // Check account - - _, err = runScript( - chainID, - registersByAccount, - fmt.Sprintf( - //language=Cadence - ` - import Test from %s - - access(all) - fun main() { - let account = getAuthAccount(%[1]s) - // NOTE: capability can NOT be borrowed with E anymore - assert(account.capabilities.borrow(/public/s1) == nil) - assert(account.capabilities.borrow<&Test.S>(/public/s1) != nil) - - let caps2 = account.storage.copy<[Capability]>(from: /storage/caps2)! - // NOTE: capability can NOT be borrowed with E anymore - assert(caps2[0].borrow() == nil) - assert(caps2[0].borrow<&Test.S>() != nil) - - let caps3 = account.storage.copy<[Capability]>(from: /storage/caps3)! - // NOTE: capability can still be borrowed with E - assert(caps3[0].borrow() != nil) - assert(caps3[0].borrow<&Test.S>() != nil) - - let caps4 = account.storage.copy<[Capability]>(from: /storage/caps4)! - // NOTE: capability can still be borrowed with E - assert(account.capabilities.borrow(/public/s4) != nil) - assert(account.capabilities.borrow<&Test.S>(/public/s4) != nil) - assert(caps4[0].borrow() != nil) - assert(caps4[0].borrow<&Test.S>() != nil) - } - `, - address.HexWithPrefix(), - ), - ) - require.NoError(t, err) -} - -func TestReadAuthorizationFixes(t *testing.T) { - t.Parallel() - - validContents := ` - [ - {"capability_address":"01","capability_id":4}, - {"capability_address":"02","capability_id":5}, - {"capability_address":"03","capability_id":6} - ] - ` - - t.Run("unfiltered", func(t *testing.T) { - - t.Parallel() - - reader := strings.NewReader(validContents) - - mapping, err := ReadAuthorizationFixes(reader, nil) - require.NoError(t, err) - - require.Equal(t, - AuthorizationFixes{ - { - Address: common.MustBytesToAddress([]byte{0x1}), - CapabilityID: 4, - }: {}, - { - Address: common.MustBytesToAddress([]byte{0x2}), - CapabilityID: 5, - }: {}, - { - Address: common.MustBytesToAddress([]byte{0x3}), - CapabilityID: 6, - }: {}, - }, - mapping, - ) - }) - - t.Run("filtered", func(t *testing.T) { - - t.Parallel() - - address1 := common.MustBytesToAddress([]byte{0x1}) - address3 := common.MustBytesToAddress([]byte{0x3}) - - addressFilter := map[common.Address]struct{}{ - address1: {}, - address3: {}, - } - - reader := strings.NewReader(validContents) - - mapping, err := ReadAuthorizationFixes(reader, addressFilter) - require.NoError(t, err) - - require.Equal(t, - AuthorizationFixes{ - { - Address: common.MustBytesToAddress([]byte{0x1}), - CapabilityID: 4, - }: {}, - { - Address: common.MustBytesToAddress([]byte{0x3}), - CapabilityID: 6, - }: {}, - }, - mapping, - ) - }) -} diff --git a/cmd/util/ledger/migrations/migration_matrics_collector_test.go b/cmd/util/ledger/migrations/migration_matrics_collector_test.go deleted file mode 100644 index 29afafb19ef..00000000000 --- a/cmd/util/ledger/migrations/migration_matrics_collector_test.go +++ /dev/null @@ -1,209 +0,0 @@ -package migrations - -import ( - "testing" - - "github.com/rs/zerolog" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/onflow/cadence/runtime/common" - - "github.com/onflow/flow-go/cmd/util/ledger/util" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/model/flow" -) - -func TestMigrationMetricsCollection(t *testing.T) { - - t.Parallel() - - t.Run("contract not staged", func(t *testing.T) { - - t.Parallel() - - // Get the old payloads - payloads, err := util.PayloadsFromEmulatorSnapshot(snapshotPath) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - const chainID = flow.Emulator - const evmContractChange = EVMContractChangeNone - const burnerContractChange = BurnerContractChangeDeploy - - migrations := NewCadence1Migrations( - logger, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - ReportMetrics: true, - - // Important: 'Test' contract is NOT staged intentionally. - // So values belongs to types from 'Test' contract should be - // identified as un-migrated values. - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - require.NoError(t, err) - - // Should have 1 error: - // - The `Test` contract checking error - logs := logWriter.logs - require.Len(t, logs, 1) - assert.Contains(t, logs[0], "`pub` is no longer a valid access keyword") - - reportWriter := rwf.reportWriters["metrics-collecting-migration"] - require.Len(t, reportWriter.entries, 1) - - entry := reportWriter.entries[0] - require.IsType(t, Metrics{}, entry) - - require.Equal( - t, - Metrics{ - TotalValues: 789, - TotalErrors: 6, - ErrorsPerContract: map[string]int{ - "A.01cf0e2f2f715450.Test": 6, - }, - ValuesPerContract: map[string]int{ - "A.01cf0e2f2f715450.Test": 6, - "A.0ae53cb6e3f42a79.FlowToken": 20, - "A.f8d6e0586b0a20c7.FlowClusterQC": 6, - "A.f8d6e0586b0a20c7.FlowDKG": 4, - "A.f8d6e0586b0a20c7.FlowEpoch": 1, - "A.f8d6e0586b0a20c7.FlowIDTableStaking": 5, - "A.f8d6e0586b0a20c7.LockedTokens": 3, - "A.f8d6e0586b0a20c7.NodeVersionBeacon": 1, - }, - }, - entry, - ) - }) - - t.Run("staged contract with errors", func(t *testing.T) { - - t.Parallel() - - address, err := common.HexToAddress(testAccountAddress) - require.NoError(t, err) - - // Get the old payloads - payloads, err := util.PayloadsFromEmulatorSnapshot(snapshotPath) - require.NoError(t, err) - - registersByAccount, err := registers.NewByAccountFromPayloads(payloads) - require.NoError(t, err) - - rwf := &testReportWriterFactory{} - - logWriter := &writer{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - const nWorker = 2 - - const chainID = flow.Emulator - const evmContractChange = EVMContractChangeNone - const burnerContractChange = BurnerContractChangeDeploy - - stagedContracts := []StagedContract{ - { - Contract: Contract{ - Name: "Test", - Code: []byte(`access(all) contract Test {}`), - }, - Address: address, - }, - } - - migrations := NewCadence1Migrations( - logger, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - VerboseErrorOutput: true, - ReportMetrics: true, - StagedContracts: stagedContracts, - }, - ) - - for _, migration := range migrations { - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - require.NoError(t, err) - - // Should have 2 errors: - // - Contract update failure error - // - The `Test` contract checking error - logs := logWriter.logs - require.Len(t, logs, 2) - assert.Contains(t, logs[0], `"migration":"StagedContractsMigration","account":"0x01cf0e2f2f715450","contract":"Test"`) - assert.Contains(t, logs[1], "`pub` is no longer a valid access keyword") - - reportWriter := rwf.reportWriters["metrics-collecting-migration"] - require.Len(t, reportWriter.entries, 1) - - entry := reportWriter.entries[0] - require.IsType(t, Metrics{}, entry) - - require.Equal( - t, - Metrics{ - TotalValues: 789, - TotalErrors: 6, - ErrorsPerContract: map[string]int{ - "A.01cf0e2f2f715450.Test": 6, - }, - ValuesPerContract: map[string]int{ - "A.01cf0e2f2f715450.Test": 6, - "A.0ae53cb6e3f42a79.FlowToken": 20, - "A.f8d6e0586b0a20c7.FlowClusterQC": 6, - "A.f8d6e0586b0a20c7.FlowDKG": 4, - "A.f8d6e0586b0a20c7.FlowEpoch": 1, - "A.f8d6e0586b0a20c7.FlowIDTableStaking": 5, - "A.f8d6e0586b0a20c7.LockedTokens": 3, - "A.f8d6e0586b0a20c7.NodeVersionBeacon": 1, - }, - }, - entry, - ) - }) -} diff --git a/cmd/util/ledger/migrations/migration_metrics_collector.go b/cmd/util/ledger/migrations/migration_metrics_collector.go deleted file mode 100644 index b6b62841203..00000000000 --- a/cmd/util/ledger/migrations/migration_metrics_collector.go +++ /dev/null @@ -1,444 +0,0 @@ -package migrations - -import ( - "context" - "fmt" - "sync" - - "github.com/rs/zerolog" - - "github.com/onflow/cadence/migrations" - "github.com/onflow/cadence/runtime/ast" - "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/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/model/flow" -) - -const metricsCollectingMigrationName = "metrics_collecting_migration" - -type MetricsCollectingMigration struct { - name string - chainID flow.ChainID - log zerolog.Logger - mutex sync.RWMutex - reporter reporters.ReportWriter - metricsCollector *MigrationMetricsCollector - migratedTypes map[common.TypeID]struct{} - migratedContracts map[common.Location]struct{} - programs map[common.Location]*interpreter.Program -} - -var _ migrations.ValueMigration = &MetricsCollectingMigration{} -var _ AccountBasedMigration = &MetricsCollectingMigration{} - -func NewMetricsCollectingMigration( - log zerolog.Logger, - chainID flow.ChainID, - rwf reporters.ReportWriterFactory, - programs map[common.Location]*interpreter.Program, -) *MetricsCollectingMigration { - - return &MetricsCollectingMigration{ - name: metricsCollectingMigrationName, - log: log, - reporter: rwf.ReportWriter("metrics-collecting-migration"), - metricsCollector: NewMigrationMetricsCollector(), - chainID: chainID, - programs: programs, - } -} - -func (*MetricsCollectingMigration) Name() string { - return "MetricsCollectingMigration" -} - -func (m *MetricsCollectingMigration) InitMigration( - _ zerolog.Logger, - _ *registers.ByAccount, - _ int, -) error { - m.migratedTypes = make(map[common.TypeID]struct{}) - m.migratedContracts = make(map[common.Location]struct{}) - - // If the program is available, that means the associated contracts is compatible with Cadence 1.0. - // i.e: the contract is either migrated to be compatible with 1.0 or existing contract already compatible. - for location, program := range m.programs { - var nestedDecls *ast.Members - - contract := program.Program.SoleContractDeclaration() - if contract != nil { - nestedDecls = contract.Members - - contractType := program.Elaboration.CompositeDeclarationType(contract) - m.migratedTypes[contractType.ID()] = struct{}{} - m.migratedContracts[contractType.Location] = struct{}{} - } else { - contractInterface := program.Program.SoleContractInterfaceDeclaration() - if contractInterface == nil { - declarations := program.Program.Declarations() - declarationKinds := make([]common.DeclarationKind, 0, len(declarations)) - for _, declaration := range declarations { - declarationKinds = append(declarationKinds, declaration.DeclarationKind()) - } - panic(errors.NewUnexpectedError( - "invalid program %s: expected a sole contract or contract interface, got %s", - location, - declarationKinds, - )) - } - nestedDecls = contractInterface.Members - - contractInterfaceType := program.Elaboration.InterfaceDeclarationType(contractInterface) - m.migratedTypes[contractInterfaceType.ID()] = struct{}{} - m.migratedContracts[contractInterfaceType.Location] = struct{}{} - } - - for _, compositeDecl := range nestedDecls.Composites() { - compositeType := program.Elaboration.CompositeDeclarationType(compositeDecl) - if compositeType == nil { - continue - } - m.migratedTypes[compositeType.ID()] = struct{}{} - } - - for _, interfaceDecl := range nestedDecls.Interfaces() { - interfaceType := program.Elaboration.InterfaceDeclarationType(interfaceDecl) - if interfaceType == nil { - continue - } - m.migratedTypes[interfaceType.ID()] = struct{}{} - } - - for _, attachmentDecl := range nestedDecls.Attachments() { - attachmentType := program.Elaboration.CompositeDeclarationType(attachmentDecl) - if attachmentType == nil { - continue - } - m.migratedTypes[attachmentType.ID()] = struct{}{} - } - - // Entitlements are not needed, since old values won't have them. - - // TODO: Anything else? e.g: Enum cases? - - } - - return nil -} - -func (m *MetricsCollectingMigration) MigrateAccount( - _ context.Context, - address common.Address, - accountRegisters *registers.AccountRegisters, -) error { - // Create all the runtime components we need for the migration - migrationRuntime, err := NewInterpreterMigrationRuntime( - accountRegisters, - m.chainID, - InterpreterMigrationRuntimeConfig{}, - ) - if err != nil { - return fmt.Errorf("failed to create interpreter migration runtime: %w", err) - } - - storage := migrationRuntime.Storage - - migration, err := migrations.NewStorageMigration( - migrationRuntime.Interpreter, - storage, - m.name, - address, - ) - if err != nil { - return fmt.Errorf("failed to create storage migration: %w", err) - } - - migration.Migrate( - migration.NewValueMigrationsPathMigrator( - NewStorageVisitingErrorReporter(m.log), - m, - ), - ) - - err = migration.Commit() - if err != nil { - return fmt.Errorf("failed to commit changes: %w", err) - } - - return nil -} - -func (m *MetricsCollectingMigration) Migrate( - _ interpreter.StorageKey, - _ interpreter.StorageMapKey, - value interpreter.Value, - _ *interpreter.Interpreter, - _ migrations.ValueMigrationPosition, -) ( - newValue interpreter.Value, - err error, -) { - - if m.metricsCollector != nil { - m.metricsCollector.RecordValue() - } - - var migrated bool - - switch value := value.(type) { - case interpreter.TypeValue: - // Type is optional. nil represents "unknown"/"invalid" type - ty := value.Type - if ty == nil { - return - } - - migrated = m.isTypeMigrated(ty) - - case *interpreter.IDCapabilityValue: - migrated = m.isTypeMigrated(value.BorrowType) - - case *interpreter.PathCapabilityValue: //nolint:staticcheck - // Type is optional - borrowType := value.BorrowType - if borrowType == nil { - return - } - migrated = m.isTypeMigrated(borrowType) - - case interpreter.PathLinkValue: //nolint:staticcheck - migrated = m.isTypeMigrated(value.Type) - - case *interpreter.AccountCapabilityControllerValue: - migrated = m.isTypeMigrated(value.BorrowType) - - case *interpreter.StorageCapabilityControllerValue: - migrated = m.isTypeMigrated(value.BorrowType) - - case *interpreter.ArrayValue: - migrated = m.isTypeMigrated(value.Type) - - case *interpreter.DictionaryValue: - migrated = m.isTypeMigrated(value.Type) - default: - migrated = true - } - - if !migrated && m.metricsCollector != nil { - m.metricsCollector.RecordError() - } - - return -} - -func (m *MetricsCollectingMigration) isTypeMigrated(staticType interpreter.StaticType) bool { - switch staticType := staticType.(type) { - case *interpreter.ConstantSizedStaticType: - return m.isTypeMigrated(staticType.Type) - - case *interpreter.VariableSizedStaticType: - return m.isTypeMigrated(staticType.Type) - - case *interpreter.DictionaryStaticType: - keyTypeMigrated := m.isTypeMigrated(staticType.KeyType) - if !keyTypeMigrated { - return false - } - return m.isTypeMigrated(staticType.ValueType) - - case *interpreter.CapabilityStaticType: - borrowType := staticType.BorrowType - if borrowType == nil { - return true - } - return m.isTypeMigrated(borrowType) - - case *interpreter.IntersectionStaticType: - for _, interfaceStaticType := range staticType.Types { - migrated := m.isTypeMigrated(interfaceStaticType) - if !migrated { - return false - } - } - return true - - case *interpreter.OptionalStaticType: - return m.isTypeMigrated(staticType.Type) - - case *interpreter.ReferenceStaticType: - return m.isTypeMigrated(staticType.ReferencedType) - - case interpreter.FunctionStaticType: - // Non-storable - return true - - case *interpreter.CompositeStaticType: - primitiveType := interpreter.PrimitiveStaticTypeFromTypeID(staticType.TypeID) - if primitiveType != interpreter.PrimitiveStaticTypeUnknown { - return true - } - return m.checkAndRecordIsTypeMigrated(staticType.TypeID, staticType.Location) - - case *interpreter.InterfaceStaticType: - return m.checkAndRecordIsTypeMigrated(staticType.TypeID, staticType.Location) - - case interpreter.PrimitiveStaticType: - return true - - default: - panic(errors.NewUnexpectedError("unexpected static type: %T", staticType)) - } -} - -func (m *MetricsCollectingMigration) checkAndRecordIsTypeMigrated(typeID sema.TypeID, location common.Location) bool { - // If a value related to a composite/interface type is found, - // then count this value, to measure the total number of values/objects. - m.metricsCollector.RecordValueForContract(location) - - _, ok := m.migratedTypes[typeID] - if !ok { - // If this type is not migrated/usable with cadence 1.0, - // then record this as an erroneous value. - m.metricsCollector.RecordErrorForContract(location) - - // If the type is not migrated, but the contract is migrated, then report an error. - // This is more likely to be an implementation error, where the typeID haven't got added to the list. - _, ok := m.migratedContracts[location] - if ok { - m.log.Error().Msgf( - "contract `%s` is migrated, but cannot find the migrated type: `%s`", - location, - typeID, - ) - } - } - - return ok -} - -func (m *MetricsCollectingMigration) Close() error { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.reporter.Write(m.metricsCollector.metrics()) - - // Close the report writer so it flushes to file. - m.reporter.Close() - return nil -} - -func (m *MetricsCollectingMigration) CanSkip(interpreter.StaticType) bool { - return false -} - -func (m *MetricsCollectingMigration) Domains() map[string]struct{} { - return nil -} - -type MigrationMetricsCollector struct { - mutex sync.RWMutex - TotalValues int - TotalErrors int - ValuesPerContract map[common.Location]int - ErrorsPerContract map[common.Location]int -} - -func NewMigrationMetricsCollector() *MigrationMetricsCollector { - return &MigrationMetricsCollector{ - ErrorsPerContract: make(map[common.Location]int), - ValuesPerContract: make(map[common.Location]int), - } -} - -func (c *MigrationMetricsCollector) RecordValue() { - c.mutex.Lock() - defer c.mutex.Unlock() - c.TotalValues++ -} - -func (c *MigrationMetricsCollector) RecordError() { - c.mutex.Lock() - defer c.mutex.Unlock() - c.TotalErrors++ -} - -func (c *MigrationMetricsCollector) RecordErrorForContract(location common.Location) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.ErrorsPerContract[location]++ -} - -func (c *MigrationMetricsCollector) RecordValueForContract(location common.Location) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.ValuesPerContract[location]++ -} - -func (c *MigrationMetricsCollector) metrics() Metrics { - c.mutex.RLock() - defer c.mutex.RUnlock() - - errorsPerContract := make(map[string]int, len(c.ErrorsPerContract)) - for location, count := range c.ErrorsPerContract { - errorsPerContract[location.ID()] = count - } - - valuesPerContract := make(map[string]int, len(c.ValuesPerContract)) - for location, count := range c.ValuesPerContract { - valuesPerContract[location.ID()] = count - } - - return Metrics{ - TotalValues: c.TotalValues, - TotalErrors: c.TotalErrors, - ErrorsPerContract: errorsPerContract, - ValuesPerContract: valuesPerContract, - } -} - -type Metrics struct { - // Total values in the storage - TotalValues int `json:"totalValues"` - - // Total values with errors (un-migrated values) - TotalErrors int `json:"TotalErrors"` - - // Values with errors (un-migrated) related to each contract - ErrorsPerContract map[string]int `json:"errorsPerContract"` - - // Total values related to each contract - ValuesPerContract map[string]int `json:"valuesPerContract"` -} - -type storageVisitingErrorReporter struct { - log zerolog.Logger -} - -func NewStorageVisitingErrorReporter(log zerolog.Logger) *storageVisitingErrorReporter { - return &storageVisitingErrorReporter{ - log: log, - } -} - -var _ migrations.Reporter = &storageVisitingErrorReporter{} - -func (p *storageVisitingErrorReporter) Migrated( - _ interpreter.StorageKey, - _ interpreter.StorageMapKey, - _ string, -) { - // Ignore -} - -func (p *storageVisitingErrorReporter) DictionaryKeyConflict(addressPath interpreter.AddressPath) { - p.log.Error().Msgf("dictionary key conflict for %s", addressPath) -} - -func (p *storageVisitingErrorReporter) Error(err error) { - p.log.Error().Msgf("%s", err.Error()) -} diff --git a/cmd/util/ledger/migrations/prune_migration.go b/cmd/util/ledger/migrations/prune_migration.go deleted file mode 100644 index a2670ba7734..00000000000 --- a/cmd/util/ledger/migrations/prune_migration.go +++ /dev/null @@ -1,113 +0,0 @@ -package migrations - -import ( - "fmt" - - "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/ledger" - "github.com/onflow/flow-go/model/flow" -) - -// PruneEmptyMigration removes all the payloads with empty value -// this prunes the trie for values that has been deleted -func PruneEmptyMigration(payload []ledger.Payload) ([]ledger.Payload, error) { - newPayload := make([]ledger.Payload, 0, len(payload)) - for _, p := range payload { - if len(p.Value()) > 0 { - newPayload = append(newPayload, p) - } - } - return newPayload, nil -} - -// NewCadence1PruneMigration prunes some values from the service account in the Testnet state -func NewCadence1PruneMigration( - chainID flow.ChainID, - log zerolog.Logger, - nWorkers int, -) RegistersMigration { - if chainID != flow.Testnet { - return nil - } - - serviceAccountAddress := common.Address(chainID.Chain().ServiceAddress()) - - migrate := func(storage *runtime.Storage, inter *interpreter.Interpreter) error { - - err := pruneRandomBeaconHistory(storage, inter, log, serviceAccountAddress) - if err != nil { - return err - } - - return nil - } - - return NewAccountStorageMigration( - serviceAccountAddress, - log, - chainID, - migrate, - ) -} - -func pruneRandomBeaconHistory( - storage *runtime.Storage, - inter *interpreter.Interpreter, - log zerolog.Logger, - serviceAccountAddress common.Address, -) error { - - log.Info().Msgf("pruning RandomBeaconHistory in service account %s", serviceAccountAddress) - - contracts := storage.GetStorageMap(serviceAccountAddress, runtime.StorageDomainContract, false) - if contracts == nil { - return fmt.Errorf("failed to get contracts storage map") - } - - randomBeaconHistory, ok := contracts.ReadValue( - nil, - interpreter.StringStorageMapKey("RandomBeaconHistory"), - ).(*interpreter.CompositeValue) - if !ok { - return fmt.Errorf("failed to read RandomBeaconHistory contract") - } - - randomSourceHistory, ok := randomBeaconHistory.GetField( - inter, - interpreter.EmptyLocationRange, - "randomSourceHistory", - ).(*interpreter.ArrayValue) - if !ok { - return fmt.Errorf("failed to read randomSourceHistory field") - } - - // Remove all but the last value from the randomSourceHistory - oldCount := randomSourceHistory.Count() - removalCount := oldCount - 1 - - for i := 0; i < removalCount; i++ { - randomSourceHistory.RemoveWithoutTransfer( - inter, - interpreter.EmptyLocationRange, - // NOTE: always remove the first element - 0, - ) - } - - // Check - if randomSourceHistory.Count() != 1 { - return fmt.Errorf("failed to prune randomSourceHistory") - } - - log.Info().Msgf( - "pruned %d entries in RandomBeaconHistory in service account %s", - removalCount, - serviceAccountAddress, - ) - - return nil -} diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go deleted file mode 100644 index 54725538726..00000000000 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ /dev/null @@ -1,724 +0,0 @@ -package migrations - -import ( - "context" - "encoding/csv" - "encoding/json" - "fmt" - "io" - "os" - "sort" - "strings" - "sync" - - "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/old_parser" - "github.com/onflow/cadence/runtime/pretty" - "github.com/onflow/cadence/runtime/sema" - "github.com/onflow/cadence/runtime/stdlib" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/ledger" - "github.com/onflow/flow-go/model/flow" - - coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" -) - -type StagedContractsMigration struct { - name string - chainID flow.ChainID - log zerolog.Logger - mutex sync.RWMutex - stagedContracts map[common.Address]map[string]Contract - contractsByLocation map[common.Location][]byte - enableUpdateValidation bool - userDefinedTypeChangeCheckFunc func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked bool, valid bool) - elaborations map[common.Location]*sema.Elaboration - contractAdditionHandler stdlib.AccountContractAdditionHandler - contractNamesProvider stdlib.AccountContractNamesProvider - reporter reporters.ReportWriter - verboseErrorOutput bool - legacyTypeRequirements *LegacyTypeRequirements -} - -type StagedContract struct { - Contract - Address common.Address -} - -func (s StagedContract) AddressLocation() common.AddressLocation { - return common.AddressLocation{ - Name: s.Name, - Address: s.Address, - } -} - -type Contract struct { - Name string - Code []byte -} - -var _ AccountBasedMigration = &StagedContractsMigration{} - -type StagedContractsMigrationOptions struct { - ChainID flow.ChainID - VerboseErrorOutput bool -} - -func NewStagedContractsMigration( - name string, - reporterName string, - log zerolog.Logger, - rwf reporters.ReportWriterFactory, - legacyTypeRequirements *LegacyTypeRequirements, - options StagedContractsMigrationOptions, -) *StagedContractsMigration { - return &StagedContractsMigration{ - name: name, - log: log, - chainID: options.ChainID, - legacyTypeRequirements: legacyTypeRequirements, - stagedContracts: map[common.Address]map[string]Contract{}, - contractsByLocation: map[common.Location][]byte{}, - reporter: rwf.ReportWriter(reporterName), - verboseErrorOutput: options.VerboseErrorOutput, - } -} - -func (m *StagedContractsMigration) WithContractUpdateValidation() *StagedContractsMigration { - m.enableUpdateValidation = true - return m -} - -// WithStagedContractUpdates prepares the contract updates as a map for easy lookup. -func (m *StagedContractsMigration) WithStagedContractUpdates(stagedContracts []StagedContract) *StagedContractsMigration { - m.registerContractUpdates(stagedContracts) - m.log.Info(). - Msgf("total of %d staged contracts are provided externally", len(m.contractsByLocation)) - - return m -} - -func (m *StagedContractsMigration) Close() error { - m.mutex.RLock() - defer m.mutex.RUnlock() - - // Close the report writer so it flushes to file. - m.reporter.Close() - - if len(m.stagedContracts) > 0 { - dict := zerolog.Dict() - for address, contracts := range m.stagedContracts { - arr := zerolog.Arr() - for name := range contracts { - arr = arr.Str(name) - } - dict = dict.Array( - address.HexWithPrefix(), - arr, - ) - } - m.log.Error(). - Dict("contracts", dict). - Msg("failed to find all contract registers that need to be changed") - } - - return nil -} - -func (m *StagedContractsMigration) InitMigration( - log zerolog.Logger, - registersByAccount *registers.ByAccount, - _ int, -) error { - m.log = log. - With(). - Str("migration", m.name). - Logger() - - err := m.collectAndRegisterStagedContracts(registersByAccount) - if err != nil { - return err - } - - // Manually register burner contract - burnerLocation := common.AddressLocation{ - Name: "Burner", - Address: common.Address(BurnerAddressForChain(m.chainID)), - } - m.contractsByLocation[burnerLocation] = coreContracts.Burner() - - // Initialize elaborations, ContractAdditionHandler and ContractNamesProvider. - // These needs to be initialized using **ALL** payloads, not just the payloads of the account. - - elaborations := map[common.Location]*sema.Elaboration{} - - config := InterpreterMigrationRuntimeConfig{ - GetCode: func(location common.AddressLocation) ([]byte, error) { - return m.contractsByLocation[location], nil - }, - GetOrLoadProgramListener: func(location runtime.Location, program *interpreter.Program, err error) { - if err == nil { - elaborations[location] = program.Elaboration - } - }, - } - - mr, err := NewInterpreterMigrationRuntime( - registersByAccount, - m.chainID, - config, - ) - if err != nil { - return err - } - - m.elaborations = elaborations - m.contractAdditionHandler = mr.ContractAdditionHandler - m.contractNamesProvider = mr.ContractNamesProvider - - // `legacyTypeRequirements` are populated by a previous migration. - // So it should only be used once the previous migrations are complete. - if m.enableUpdateValidation { - m.userDefinedTypeChangeCheckFunc = NewUserDefinedTypeChangeCheckerFunc(m.chainID, m.legacyTypeRequirements) - } - - return nil -} - -// collectAndRegisterStagedContracts scans through the registers and collects the contracts -// staged through the `MigrationContractStaging` contract. -func (m *StagedContractsMigration) collectAndRegisterStagedContracts( - registersByAccount *registers.ByAccount, -) error { - - // If the contracts are already passed as an input to the migration - // then no need to scan the storage. - if len(m.contractsByLocation) > 0 { - return nil - } - - var stagingAccount string - - switch m.chainID { - case flow.Testnet: - stagingAccount = "0x2ceae959ed1a7e7a" - case flow.Mainnet: - stagingAccount = "0x56100d46aa9b0212" - default: - // For other networks such as emulator etc. no need to scan for staged contracts. - m.log.Warn().Msgf("staged contracts are not collected for %s state", m.chainID) - return nil - } - - stagingAccountAddress := common.Address(flow.HexToAddress(stagingAccount)) - - stagingAccountRegisters := registersByAccount.AccountRegisters(string(stagingAccountAddress[:])) - - m.log.Info().Msgf( - "found %d registers in account %s", - stagingAccountRegisters.Count(), - stagingAccount, - ) - - mr, err := NewInterpreterMigrationRuntime( - stagingAccountRegisters, - m.chainID, - InterpreterMigrationRuntimeConfig{}, - ) - if err != nil { - return err - } - - inter := mr.Interpreter - locationRange := interpreter.EmptyLocationRange - - storageMap := mr.Storage.GetStorageMap( - stagingAccountAddress, - common.PathDomainStorage.Identifier(), - false, - ) - if storageMap == nil { - m.log.Error(). - Msgf("failed to get staged contracts from account %s", stagingAccount) - return nil - } - - iterator := storageMap.Iterator(inter) - - stagedContractCapsuleStaticType := interpreter.NewCompositeStaticTypeComputeTypeID( - nil, - common.AddressLocation{ - Name: "MigrationContractStaging", - Address: stagingAccountAddress, - }, - "MigrationContractStaging.Capsule", - ) - - stagedContracts := make([]StagedContract, 0) - - for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { - stringAtreeValue, ok := key.(interpreter.StringAtreeValue) - if !ok { - continue - } - - storagePath := string(stringAtreeValue) - - // Only consider paths that starts with "MigrationContractStagingCapsule_". - if !strings.HasPrefix(storagePath, "MigrationContractStagingCapsule_") { - continue - } - - staticType := value.StaticType(inter) - if !staticType.Equal(stagedContractCapsuleStaticType) { - // This shouldn't occur, but technically possible. - // e.g: accidentally storing other values under the same storage path pattern. - // So skip such values. We are not interested in those. - m.log.Debug(). - Msgf("found a value with an unexpected type `%s`", staticType) - continue - } - - stagedContract, err := m.getStagedContractFromValue(value, inter, locationRange) - if err != nil { - return err - } - - stagedContracts = append(stagedContracts, stagedContract) - } - - m.log.Info(). - Msgf("found %d staged contracts from payloads", len(stagedContracts)) - - m.registerContractUpdates(stagedContracts) - m.log.Info(). - Msgf("total of %d unique contracts are staged for all accounts", len(m.contractsByLocation)) - - return nil -} - -func (m *StagedContractsMigration) getStagedContractFromValue( - value interpreter.Value, - inter *interpreter.Interpreter, - locationRange interpreter.LocationRange, -) (StagedContract, error) { - - stagedContractCapsule, ok := value.(*interpreter.CompositeValue) - if !ok { - return StagedContract{}, - fmt.Errorf("unexpected value of type %T", value) - } - - // The stored value should take the form of: - // - // resource Capsule { - // let update: ContractUpdate - // } - // - // struct ContractUpdate { - // let address: Address - // let name: String - // var code: String - // var lastUpdated: UFix64 - // } - - updateField := stagedContractCapsule.GetField(inter, locationRange, "update") - contractUpdate, ok := updateField.(*interpreter.CompositeValue) - if !ok { - return StagedContract{}, - fmt.Errorf("unexpected value: expected `CompositeValue`, found `%T`", updateField) - } - - addressField := contractUpdate.GetField(inter, locationRange, "address") - address, ok := addressField.(interpreter.AddressValue) - if !ok { - return StagedContract{}, - fmt.Errorf("unexpected value: expected `AddressValue`, found `%T`", addressField) - } - - nameField := contractUpdate.GetField(inter, locationRange, "name") - name, ok := nameField.(*interpreter.StringValue) - if !ok { - return StagedContract{}, - fmt.Errorf("unexpected value: expected `StringValue`, found `%T`", nameField) - } - - codeField := contractUpdate.GetField(inter, locationRange, "code") - code, ok := codeField.(*interpreter.StringValue) - if !ok { - return StagedContract{}, - fmt.Errorf("unexpected value: expected `StringValue`, found `%T`", codeField) - } - - return StagedContract{ - Contract: Contract{ - Name: name.Str, - Code: []byte(code.Str), - }, - Address: common.Address(address), - }, nil -} - -// registerContractUpdates prepares the contract updates as a map for easy lookup. -func (m *StagedContractsMigration) registerContractUpdates(stagedContracts []StagedContract) { - for _, contractChange := range stagedContracts { - m.registerContractChange(contractChange) - } -} - -func (m *StagedContractsMigration) registerContractChange(change StagedContract) { - m.mutex.Lock() - defer m.mutex.Unlock() - - address := change.Address - - chain := m.chainID.Chain() - - if _, err := chain.IndexFromAddress(flow.Address(address)); err != nil { - m.log.Error().Msgf( - "invalid contract update: invalid address for chain %s: %s (%s)", - m.chainID, - address.HexWithPrefix(), - change.Name, - ) - } - - if _, ok := m.stagedContracts[address]; !ok { - m.stagedContracts[address] = map[string]Contract{} - } - - name := change.Name - - _, exist := m.stagedContracts[address][name] - if exist { - // Staged multiple updates for the same contract. - // Overwrite the previous update. - m.log.Warn().Msgf( - "existing staged update found for contract %s.%s. Previous update will be overwritten.", - address.HexWithPrefix(), - name, - ) - } - - m.stagedContracts[address][name] = change.Contract - - location := common.AddressLocation{ - Name: name, - Address: address, - } - m.contractsByLocation[location] = change.Code -} - -func (m *StagedContractsMigration) contractUpdatesForAccount( - address common.Address, -) (map[string]Contract, bool) { - m.mutex.Lock() - defer m.mutex.Unlock() - - contracts, ok := m.stagedContracts[address] - - // remove address from set of addresses - // to keep track of which addresses are left to change - delete(m.stagedContracts, address) - - return contracts, ok -} - -func (m *StagedContractsMigration) MigrateAccount( - _ context.Context, - address common.Address, - accountRegisters *registers.AccountRegisters, -) error { - - contractUpdates, ok := m.contractUpdatesForAccount(address) - if !ok { - // no contracts to change on this address - return nil - } - - var contractNames []string - for name := range contractUpdates { - contractNames = append(contractNames, name) - } - sort.Strings(contractNames) - - for _, name := range contractNames { - contract := contractUpdates[name] - - owner := string(address[:]) - key := flow.ContractKey(name) - - newCode := contract.Code - oldCode, err := accountRegisters.Get(owner, key) - if err != nil { - m.log.Err(err). - Str("account", address.HexWithPrefix()). - Str("contract", name). - Msg("failed to get old contract code") - continue - } - - if len(oldCode) == 0 { - m.log.Error(). - Str("address", address.HexWithPrefix()). - Str("contract", name). - Msg("missing old code for contract, skipping update") - continue - } - - if m.enableUpdateValidation { - err = m.checkContractUpdateValidity( - address, - name, - newCode, - oldCode, - ) - } - if err != nil { - var builder strings.Builder - errorPrinter := pretty.NewErrorPrettyPrinter(&builder, false) - - location := common.AddressLocation{ - Name: name, - Address: address, - } - printErr := errorPrinter.PrettyPrintError(err, location, m.contractsByLocation) - - var errorDetails string - if printErr == nil { - errorDetails = builder.String() - } else { - errorDetails = err.Error() - } - - if m.verboseErrorOutput { - m.log.Error(). - Str("account", address.HexWithPrefix()). - Str("contract", name). - Msgf( - "failed to update contract %s in account %s: %s", - name, - address.HexWithPrefix(), - errorDetails, - ) - } - - m.reporter.Write(contractUpdateFailureEntry{ - AccountAddress: address, - ContractName: name, - Error: errorDetails, - }) - - continue - } - - // change contract code - err = accountRegisters.Set(owner, key, newCode) - if err != nil { - m.log.Err(err). - Str("account", address.HexWithPrefix()). - Str("contract", name). - Msg("failed to set new contract code") - continue - } - - m.reporter.Write(contractUpdateEntry{ - AccountAddress: address, - ContractName: name, - }) - } - - return nil -} - -func (m *StagedContractsMigration) checkContractUpdateValidity( - address common.Address, - contractName string, - newCode []byte, - oldCode ledger.Value, -) (err error) { - // Parsing and checking of programs has to be done synchronously. - m.mutex.Lock() - defer m.mutex.Unlock() - - // Recover panics that might occur due to bugs while parsing, checking, - // or validating the contract update - defer func() { - // recover - if r := recover(); r != nil { - var ok bool - err, ok = r.(error) - if !ok { - err = fmt.Errorf("panic: %v", r) - } - - m.log.Err(err). - Stack(). - Str("account", address.HexWithPrefix()). - Str("contract", contractName). - Msg("failed to validate contract update") - } - }() - - location := common.AddressLocation{ - Name: contractName, - Address: address, - } - - // NOTE: do NOT use the program obtained from the host environment, as the current program. - // Always re-parse and re-check the new program. - // NOTE: *DO NOT* store the program – the new or updated program - // should not be effective during the execution - const getAndSetProgram = false - - newProgram, err := m.contractAdditionHandler.ParseAndCheckProgram(newCode, location, getAndSetProgram) - if err != nil { - return err - } - - oldProgram, err := old_parser.ParseProgram(nil, oldCode, old_parser.Config{}) - if err != nil { - return err - } - - validator := stdlib.NewCadenceV042ToV1ContractUpdateValidator( - location, - contractName, - m.contractNamesProvider, - oldProgram, - newProgram, - m.elaborations, - ) - - validator.WithUserDefinedTypeChangeChecker( - m.userDefinedTypeChangeCheckFunc, - ) - - return validator.Validate() -} - -func StagedContractsFromCSV(path string) ([]StagedContract, error) { - if path == "" { - return nil, nil - } - - file, err := os.Open(path) - if err != nil { - return nil, err - } - - defer file.Close() - - reader := csv.NewReader(file) - - // Expect 3 fields: address, name, code - reader.FieldsPerRecord = 3 - - var contracts []StagedContract - - for { - rec, err := reader.Read() - if err == io.EOF { - break - } - - if err != nil { - return nil, err - } - - addressHex := rec[0] - name := rec[1] - code := rec[2] - - address, err := common.HexToAddress(addressHex) - if err != nil { - return nil, err - } - - contracts = append(contracts, StagedContract{ - Contract: Contract{ - Name: name, - Code: []byte(code), - }, - Address: address, - }) - } - - return contracts, nil -} - -func NewUserDefinedTypeChangeCheckerFunc( - chainID flow.ChainID, - legacyTypeRequirements *LegacyTypeRequirements, -) func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked, valid bool) { - - typeChangeRules := map[common.TypeID]common.TypeID{} - - compositeTypeRules := NewCompositeTypeConversionRules(chainID, legacyTypeRequirements) - for typeID, newStaticType := range compositeTypeRules { - typeChangeRules[typeID] = newStaticType.ID() - } - - interfaceTypeRules := NewInterfaceTypeConversionRules(chainID) - for typeID, newStaticType := range interfaceTypeRules { - typeChangeRules[typeID] = newStaticType.ID() - } - - return func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked, valid bool) { - expectedNewTypeID, found := typeChangeRules[oldTypeID] - if found { - return true, expectedNewTypeID == newTypeID - } - return false, false - } -} - -// contractUpdateFailureEntry - -type contractUpdateEntry struct { - AccountAddress common.Address - ContractName string -} - -var _ json.Marshaler = contractUpdateEntry{} - -func (e contractUpdateEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - ContractName string `json:"contract_name"` - }{ - Kind: "contract-update-success", - AccountAddress: e.AccountAddress.HexWithPrefix(), - ContractName: e.ContractName, - }) -} - -// contractUpdateFailureEntry - -type contractUpdateFailureEntry struct { - AccountAddress common.Address - ContractName string - Error string -} - -var _ json.Marshaler = contractUpdateFailureEntry{} - -func (e contractUpdateFailureEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - ContractName string `json:"contract_name"` - Error string `json:"error"` - }{ - Kind: "contract-update-failure", - AccountAddress: e.AccountAddress.HexWithPrefix(), - ContractName: e.ContractName, - Error: e.Error, - }) -} diff --git a/cmd/util/ledger/migrations/staged_contracts_migration_test.go b/cmd/util/ledger/migrations/staged_contracts_migration_test.go deleted file mode 100644 index 972177e634f..00000000000 --- a/cmd/util/ledger/migrations/staged_contracts_migration_test.go +++ /dev/null @@ -1,2926 +0,0 @@ -package migrations - -import ( - "context" - "encoding/json" - "fmt" - "io" - "strings" - "testing" - - "github.com/onflow/cadence/runtime/interpreter" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/fvm/systemcontracts" - "github.com/onflow/flow-go/model/flow" - - "github.com/onflow/cadence/runtime/common" -) - -type logWriter struct { - logs []string - enableInfoLogs bool -} - -var _ io.Writer = &logWriter{} - -const infoLogPrefix = "{\"level\":\"info\"" - -func (l *logWriter) Write(bytes []byte) (int, error) { - logStr := string(bytes) - - if !l.enableInfoLogs && strings.HasPrefix(logStr, infoLogPrefix) { - return 0, nil - } - - l.logs = append(l.logs, logStr) - return len(bytes), nil -} - -func registersForStagedContracts(stagedContracts ...StagedContract) (*registers.ByAccount, error) { - registersByAccount := registers.NewByAccount() - - for _, stagedContract := range stagedContracts { - err := registersByAccount.Set( - string(stagedContract.Address[:]), - flow.ContractKey(stagedContract.Name), - stagedContract.Code, - ) - if err != nil { - return nil, err - } - } - - return registersByAccount, nil -} - -func TestStagedContractsMigration(t *testing.T) { - t.Parallel() - - const chainID = flow.Emulator - addressGenerator := chainID.Chain().NewAddressGenerator() - - address1, err := addressGenerator.NextAddress() - require.NoError(t, err) - - address2, err := addressGenerator.NextAddress() - require.NoError(t, err) - - t.Run("one contract", func(t *testing.T) { - t.Parallel() - - oldCode := "access(all) contract A {}" - newCode := "access(all) contract A { access(all) struct B {} }" - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, logWriter.logs) - - // Registers should have the new code - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, newCode, contractCode(t, registersByAccount, owner1, "A")) - }) - - t.Run("syntax error in new code", func(t *testing.T) { - t.Parallel() - - oldCode := "access(all) contract A {}" - newCode := "access(all) contract A { access(all) struct B () }" - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation() - migration.WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - require.Contains(t, logWriter.logs[0], `error: expected token '{'`) - - // Registers should still have the old code - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, oldCode, contractCode(t, registersByAccount, owner1, "A")) - }) - - t.Run("syntax error in old code", func(t *testing.T) { - t.Parallel() - - oldCode := "access(all) contract A {" - newCode := "access(all) contract A { access(all) struct B {} }" - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - require.Contains(t, logWriter.logs[0], `error: expected token '}'`) - - // Registers should still have the old code - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, oldCode, contractCode(t, registersByAccount, owner1, "A")) - }) - - t.Run("one fail, one success", func(t *testing.T) { - t.Parallel() - - oldCode1 := "access(all) contract A {}" - oldCode2 := "access(all) contract B {}" - - newCode1 := "access(all) contract A { access(all) struct C () }" // broken - newCode2 := "access(all) contract B { access(all) struct C {} }" // all good - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCode1), - }, - }, - { - Address: common.Address(address1), - Contract: Contract{ - Name: "B", - Code: []byte(newCode2), - }, - }, - } - - logWriter := &logWriter{ - enableInfoLogs: true, - } - - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - const reporterName = "test" - migration := NewStagedContractsMigration( - "test", - reporterName, - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode1), - }, - }, - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "B", - Code: []byte(oldCode2), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 2) - require.Contains(t, logWriter.logs[0], `{"level":"info","message":"total of 2 staged contracts are provided externally"}`) - require.Contains(t, logWriter.logs[1], `error: expected token '{'`) - - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 2, accountRegisters1.Count()) - // First register should still have the old code - require.Equal(t, oldCode1, contractCode(t, registersByAccount, owner1, "A")) - // Second register should have the updated code - require.Equal(t, newCode2, contractCode(t, registersByAccount, owner1, "B")) - - reportWriter := rwf.reportWriters[reporterName] - require.NotNil(t, reportWriter) - assert.ElementsMatch( - t, - []any{ - contractUpdateFailureEntry{ - AccountAddress: common.Address(address1), - ContractName: "A", - Error: "error: expected token '{'\n --> f8d6e0586b0a20c7.A:1:46\n |\n1 | access(all) contract A { access(all) struct C () }\n | ^\n", - }, - contractUpdateEntry{ - AccountAddress: common.Address(address1), - ContractName: "B", - }, - }, - reportWriter.entries, - ) - }) - - t.Run("different accounts", func(t *testing.T) { - t.Parallel() - - oldCode := "access(all) contract A {}" - newCode := "access(all) contract A { access(all) struct B {} }" - - stagedContracts := []StagedContract{ - { - Address: common.Address(address2), - Contract: Contract{ - Name: "A", - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - const reporterName = "test" - migration := NewStagedContractsMigration( - "test", - reporterName, - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - StagedContract{ - Address: common.Address(address2), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - // Run migration for account 1, - // There are no staged updates for contracts in account 1. - // So codes should not have been updated. - - owner1 := string(address1[:]) - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, oldCode, contractCode(t, registersByAccount, owner1, "A")) - - // Run migration for account 2 - // There is one staged update for contracts in account 2. - // So one register/contract-code should be updated, and the other should remain the same. - - owner2 := string(address2[:]) - accountRegisters2 := registersByAccount.AccountRegisters(owner2) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address2), - accountRegisters2, - ) - require.NoError(t, err) - require.Equal(t, 1, accountRegisters2.Count()) - require.Equal(t, newCode, contractCode(t, registersByAccount, owner2, "A")) - - err = migration.Close() - require.NoError(t, err) - - // No errors. - require.Empty(t, logWriter.logs) - - reportWriter := rwf.reportWriters[reporterName] - require.NotNil(t, reportWriter) - require.Len(t, reportWriter.entries, 1) - assert.Equal( - t, - contractUpdateEntry{ - AccountAddress: common.Address(address2), - ContractName: "A", - }, - reportWriter.entries[0], - ) - }) - - t.Run("multiple updates for same contract", func(t *testing.T) { - t.Parallel() - - oldCode := "access(all) contract A {}" - update1 := "access(all) contract A { access(all) struct B {} }" - update2 := "access(all) contract A { access(all) struct B {} access(all) struct C {} }" - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(update1), - }, - }, - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(update2), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - require.Contains( - t, - logWriter.logs[0], - `existing staged update found`, - ) - - require.Equal(t, 1, registersByAccount.AccountCount()) - require.Equal(t, 1, accountRegisters1.Count()) - require.Equal(t, update2, contractCode(t, registersByAccount, owner1, "A")) - }) - - t.Run("missing old contract", func(t *testing.T) { - t.Parallel() - - newCode := "access(all) contract A { access(all) struct B {} }" - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates(stagedContracts) - - // NOTE: no payloads - registersByAccount := registers.NewByAccount() - - err := migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - assert.Contains(t, - logWriter.logs[0], - "missing old code for contract", - ) - }) - - t.Run("staged contract in storage", func(t *testing.T) { - t.Parallel() - - oldCode := "access(all) contract A {}" - newCode := "access(all) contract A { access(all) struct B {} }" - - const chainID = flow.Testnet - addressGenerator := chainID.Chain().NewAddressGenerator() - accountAddress, err := addressGenerator.NextAddress() - require.NoError(t, err) - - stagingAccountAddress := common.Address(flow.HexToAddress("0x2ceae959ed1a7e7a")) - - registersByAccount := registers.NewByAccount() - - // Create account status register - accountStatus := environment.NewAccountStatus() - - err = registersByAccount.Set( - string(stagingAccountAddress[:]), - flow.AccountStatusKey, - accountStatus.ToBytes(), - ) - require.NoError(t, err) - - mr, err := NewInterpreterMigrationRuntime( - registersByAccount, - chainID, - InterpreterMigrationRuntimeConfig{}, - ) - require.NoError(t, err) - - // Create new storage map - domain := common.PathDomainStorage.Identifier() - storageMap := mr.Storage.GetStorageMap(stagingAccountAddress, domain, true) - - contractUpdateValue := interpreter.NewCompositeValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - common.AddressLocation{ - Address: stagingAccountAddress, - Name: "MigrationContractStaging", - }, - "MigrationContractStaging.ContractUpdate", - common.CompositeKindStructure, - []interpreter.CompositeField{ - { - Name: "address", - Value: interpreter.AddressValue(accountAddress), - }, - { - Name: "name", - Value: interpreter.NewUnmeteredStringValue("A"), - }, - { - Name: "code", - Value: interpreter.NewUnmeteredStringValue(newCode), - }, - }, - stagingAccountAddress, - ) - - capsuleValue := interpreter.NewCompositeValue( - mr.Interpreter, - interpreter.EmptyLocationRange, - common.AddressLocation{ - Address: stagingAccountAddress, - Name: "MigrationContractStaging", - }, - "MigrationContractStaging.Capsule", - common.CompositeKindResource, - []interpreter.CompositeField{ - { - Name: "update", - Value: contractUpdateValue, - }, - }, - stagingAccountAddress, - ) - - // Write the staged contract capsule value. - storageMap.WriteValue( - mr.Interpreter, - interpreter.StringStorageMapKey("MigrationContractStagingCapsule_some_random_suffix"), - capsuleValue, - ) - - // Write some random values as well. - storageMap.WriteValue( - mr.Interpreter, - interpreter.StringStorageMapKey("some_key"), - interpreter.NewUnmeteredStringValue("Also in the same account"), - ) - - storageMap.WriteValue( - mr.Interpreter, - interpreter.StringStorageMapKey("MigrationContractStagingCapsule_some_garbage_value"), - interpreter.NewUnmeteredStringValue("Also in the same storage path prefix"), - ) - - err = mr.Storage.NondeterministicCommit(mr.Interpreter, false) - require.NoError(t, err) - - result, err := mr.TransactionState.FinalizeMainTransaction() - require.NoError(t, err) - - err = registers.ApplyChanges( - registersByAccount, - result.WriteSet, - nil, - zerolog.Nop(), - ) - require.NoError(t, err) - - logWriter := &logWriter{ - enableInfoLogs: true, - } - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - // Important: Do not stage contracts externally. - // Should be scanned and collected from the storage. - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ) - - accountOwner := string(accountAddress[:]) - - err = registersByAccount.Set( - accountOwner, - flow.ContractKey("A"), - []byte(oldCode), - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - accountRegisters := registersByAccount.AccountRegisters(accountOwner) - - err = migration.MigrateAccount( - context.Background(), - common.Address(accountAddress), - accountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 4) - require.Contains(t, logWriter.logs[0], "found 4 registers in account 0x2ceae959ed1a7e7a") - require.Contains(t, logWriter.logs[1], "found a value with an unexpected type `String`") - require.Contains(t, logWriter.logs[2], "found 1 staged contracts from payloads") - require.Contains(t, logWriter.logs[3], "total of 1 unique contracts are staged for all accounts") - - require.Equal(t, 1, accountRegisters.Count()) - require.Equal(t, newCode, contractCode(t, registersByAccount, accountOwner, "A")) - }) -} - -func contractCode(t *testing.T, registersByAccount *registers.ByAccount, owner, contractName string) string { - code, err := registersByAccount.Get(owner, flow.ContractKey(contractName)) - require.NoError(t, err) - return string(code) -} - -func TestStagedContractsWithImports(t *testing.T) { - t.Parallel() - - const chainID = flow.Emulator - - addressGenerator := chainID.Chain().NewAddressGenerator() - - address1, err := addressGenerator.NextAddress() - require.NoError(t, err) - - address2, err := addressGenerator.NextAddress() - require.NoError(t, err) - - t.Run("valid import", func(t *testing.T) { - t.Parallel() - - oldCodeA := fmt.Sprintf(` - import B from %s - access(all) contract A {} - `, - address2.HexWithPrefix(), - ) - - oldCodeB := `access(all) contract B {}` - - newCodeA := fmt.Sprintf(` - import B from %s - access(all) contract A { - access(all) fun foo(a: B.C) {} - } - `, - address2.HexWithPrefix(), - ) - - newCodeB := ` - access(all) contract B { - access(all) struct C {} - } - ` - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - { - Address: common.Address(address2), - Contract: Contract{ - Name: "B", - Code: []byte(newCodeB), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: common.Address(address2), - Contract: Contract{ - Name: "B", - Code: []byte(oldCodeB), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - owner2 := string(address2[:]) - accountRegisters2 := registersByAccount.AccountRegisters(owner2) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address2), - accountRegisters2, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, logWriter.logs) - - require.Equal(t, 1, accountRegisters1.Count()) - assert.Equal(t, newCodeA, contractCode(t, registersByAccount, owner1, "A")) - - require.Equal(t, 1, accountRegisters2.Count()) - assert.Equal(t, newCodeB, contractCode(t, registersByAccount, owner2, "B")) - }) - - t.Run("broken import, no update staged", func(t *testing.T) { - t.Parallel() - - oldCodeA := fmt.Sprintf( - ` - import B from %s - access(all) contract A {} - `, - address2.HexWithPrefix(), - ) - - oldCodeB := `pub contract B {} // not compatible` - - newCodeA := fmt.Sprintf( - ` - import B from %s - access(all) contract A { - access(all) fun foo(a: B.C) {} - } - `, - address2.HexWithPrefix(), - ) - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: common.Address(address2), - Contract: Contract{ - Name: "B", - Code: []byte(oldCodeB), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - owner2 := string(address2[:]) - accountRegisters2 := registersByAccount.AccountRegisters(owner2) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address2), - accountRegisters2, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - require.Contains( - t, - logWriter.logs[0], - "`pub` is no longer a valid access keyword", - ) - - // Registers should be the old ones - require.Equal(t, 1, accountRegisters1.Count()) - assert.Equal(t, oldCodeA, contractCode(t, registersByAccount, owner1, "A")) - - require.Equal(t, 1, accountRegisters2.Count()) - assert.Equal(t, oldCodeB, contractCode(t, registersByAccount, owner2, "B")) - }) - - t.Run("broken import ", func(t *testing.T) { - t.Parallel() - - oldCodeA := fmt.Sprintf( - ` - import B from %s - access(all) contract A {} - `, - address2.HexWithPrefix(), - ) - - oldCodeB := `pub contract B {} // not compatible` - - newCodeA := fmt.Sprintf( - ` - import B from %s - access(all) contract A { - access(all) fun foo(a: B.C) {} - } - `, - address2.HexWithPrefix(), - ) - - newCodeB := `pub contract B {} // not compatible` - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - { - Address: common.Address(address2), - Contract: Contract{ - Name: "B", - Code: []byte(newCodeB), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: common.Address(address2), - Contract: Contract{ - Name: "B", - Code: []byte(oldCodeB), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - owner2 := string(address2[:]) - accountRegisters2 := registersByAccount.AccountRegisters(owner2) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address2), - accountRegisters2, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 2) - assert.Contains( - t, - logWriter.logs[0], - "cannot find type in this scope: `B`", - ) - assert.Contains( - t, - logWriter.logs[1], - "`pub` is no longer a valid access keyword", - ) - - // Registers should be the old ones - require.Equal(t, 1, accountRegisters1.Count()) - assert.Equal(t, oldCodeA, contractCode(t, registersByAccount, owner1, "A")) - - require.Equal(t, 1, accountRegisters2.Count()) - assert.Equal(t, oldCodeB, contractCode(t, registersByAccount, owner2, "B")) - }) - - t.Run("broken import in one, valid third contract", func(t *testing.T) { - t.Parallel() - - oldCodeA := fmt.Sprintf(` - import B from %s - access(all) contract A {} - `, - address2.HexWithPrefix(), - ) - - oldCodeB := `pub contract B {} // not compatible` - - oldCodeC := `pub contract C {}` - - newCodeA := fmt.Sprintf(` - import B from %s - access(all) contract A { - access(all) fun foo(a: B.X) {} - } - `, - address2.HexWithPrefix(), - ) - - newCodeC := `access(all) contract C {}` - - stagedContracts := []StagedContract{ - { - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - { - Address: common.Address(address1), - Contract: Contract{ - Name: "C", - Code: []byte(newCodeC), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: common.Address(address1), - Contract: Contract{ - Name: "C", - Code: []byte(oldCodeC), - }, - }, - StagedContract{ - Address: common.Address(address2), - Contract: Contract{ - Name: "B", - Code: []byte(oldCodeB), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner1 := string(address1[:]) - accountRegisters1 := registersByAccount.AccountRegisters(owner1) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address1), - accountRegisters1, - ) - require.NoError(t, err) - - owner2 := string(address2[:]) - accountRegisters2 := registersByAccount.AccountRegisters(owner2) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address2), - accountRegisters2, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - require.Contains( - t, - logWriter.logs[0], - "`pub` is no longer a valid access keyword", - ) - - // A and B should be the old ones. - // C should be updated. - // Type checking failures in unrelated contracts should not - // stop other contracts from being migrated. - require.Equal(t, 2, accountRegisters1.Count()) - require.Equal(t, oldCodeA, contractCode(t, registersByAccount, owner1, "A")) - require.Equal(t, newCodeC, contractCode(t, registersByAccount, owner1, "C")) - - require.Equal(t, 1, accountRegisters2.Count()) - require.Equal(t, oldCodeB, contractCode(t, registersByAccount, owner2, "B")) - }) -} - -func TestStagedContractsFromCSV(t *testing.T) { - - t.Parallel() - - t.Run("valid csv", func(t *testing.T) { - - t.Parallel() - - const path = "test-data/staged_contracts_migration/staged_contracts.csv" - - contracts, err := StagedContractsFromCSV(path) - require.NoError(t, err) - - require.Len(t, contracts, 4) - assert.Equal( - t, - contracts, - []StagedContract{ - { - Address: common.MustBytesToAddress([]byte{0x1}), - Contract: Contract{ - Name: "Foo", - Code: []byte("access(all) contract Foo{}"), - }, - }, - { - Address: common.MustBytesToAddress([]byte{0x1}), - Contract: Contract{ - Name: "Bar", - Code: []byte("access(all) contract Bar{}"), - }, - }, - { - Address: common.MustBytesToAddress([]byte{0x2}), - Contract: Contract{ - Name: "MultilineContract", - Code: []byte(` -import Foo from 0x01 - -access(all) -contract MultilineContract{ - init() { - var a = "hello" - } -} -`), - }, - }, - { - Address: common.MustBytesToAddress([]byte{0x2}), - Contract: Contract{ - Name: "Baz", - Code: []byte("import Foo from 0x01 access(all) contract Baz{}"), - }, - }, - }, - ) - }) - - t.Run("malformed csv", func(t *testing.T) { - - t.Parallel() - - const path = "test-data/staged_contracts_migration/staged_contracts_malformed.csv" - - contracts, err := StagedContractsFromCSV(path) - require.Error(t, err) - assert.Equal(t, "record on line 2: wrong number of fields", err.Error()) - require.Empty(t, contracts) - }) - - t.Run("too few fields", func(t *testing.T) { - - t.Parallel() - - const path = "test-data/staged_contracts_migration/too_few_fields.csv" - - contracts, err := StagedContractsFromCSV(path) - require.Error(t, err) - assert.Equal(t, "record on line 1: wrong number of fields", err.Error()) - require.Empty(t, contracts) - }) - - t.Run("empty path", func(t *testing.T) { - - t.Parallel() - - const emptyPath = "" - - contracts, err := StagedContractsFromCSV(emptyPath) - require.NoError(t, err) - require.Empty(t, contracts) - }) -} - -func TestStagedContractsWithUpdateValidator(t *testing.T) { - t.Parallel() - - const chainID = flow.Emulator - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - chain := chainID.Chain() - - // Prevent conflicts with system contracts - addressA, err := chain.AddressAtIndex(1_000_000) - require.NoError(t, err) - - addressB, err := chain.AddressAtIndex(2_000_000) - require.NoError(t, err) - - t.Run("FungibleToken.Vault", func(t *testing.T) { - t.Parallel() - - ftAddress := common.Address(systemContracts.FungibleToken.Address) - - oldCodeA := fmt.Sprintf( - ` - import FungibleToken from %s - - pub contract A { - pub var vault: @FungibleToken.Vault? - init() { - self.vault <- nil - } - } - `, - ftAddress.HexWithPrefix(), - ) - - newCodeA := fmt.Sprintf( - ` - import FungibleToken from %s - - access(all) contract A { - access(all) var vault: @{FungibleToken.Vault}? - init() { - self.vault <- nil - } - } - `, - ftAddress.HexWithPrefix(), - ) - - ftContract := ` - access(all) contract FungibleToken { - access(all) resource interface Vault {} - } - ` - - stagedContracts := []StagedContract{ - { - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - { - Address: ftAddress, - Contract: Contract{ - Name: "FungibleToken", - Code: []byte(ftContract), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: ftAddress, - Contract: Contract{ - Name: "FungibleToken", - Code: []byte(ftContract), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - ownerA := string(addressA[:]) - accountRegistersA := registersByAccount.AccountRegisters(ownerA) - - err = migration.MigrateAccount( - context.Background(), - common.Address(addressA), - accountRegistersA, - ) - require.NoError(t, err) - - ftOwner := string(ftAddress[:]) - ftAccountRegisters := registersByAccount.AccountRegisters(ftOwner) - - err = migration.MigrateAccount( - context.Background(), - ftAddress, - ftAccountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, logWriter.logs) - - require.Equal(t, 1, accountRegistersA.Count()) - assert.Equal(t, newCodeA, contractCode(t, registersByAccount, ownerA, "A")) - - require.Equal(t, 1, ftAccountRegisters.Count()) - assert.Equal(t, ftContract, contractCode(t, registersByAccount, ftOwner, "FungibleToken")) - }) - - t.Run("other type", func(t *testing.T) { - t.Parallel() - - otherAddress, err := common.HexToAddress("0x2") - require.NoError(t, err) - - oldCodeA := fmt.Sprintf( - ` - import FungibleToken from %s - - pub contract A { - pub var vault: @FungibleToken.Vault? - init() { - self.vault <- nil - } - } - `, - otherAddress.HexWithPrefix(), // Importing from some other address - ) - - newCodeA := fmt.Sprintf( - ` - import FungibleToken from %s - - access(all) contract A { - access(all) var vault: @{FungibleToken.Vault}? - init() { - self.vault <- nil - } - } - `, - otherAddress.HexWithPrefix(), // Importing from some other address - ) - - ftContract := ` - access(all) contract FungibleToken { - access(all) resource interface Vault {} - } - ` - - stagedContracts := []StagedContract{ - { - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: otherAddress, - Contract: Contract{ - Name: "FungibleToken", - Code: []byte(ftContract), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - ownerA := string(addressA[:]) - accountRegistersA := registersByAccount.AccountRegisters(ownerA) - - err = migration.MigrateAccount( - context.Background(), - common.Address(addressA), - accountRegistersA, - ) - require.NoError(t, err) - - otherOwner := string(otherAddress[:]) - otherAccountRegisters := registersByAccount.AccountRegisters(otherOwner) - - err = migration.MigrateAccount( - context.Background(), - otherAddress, - otherAccountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - assert.Contains(t, - logWriter.logs[0], - "incompatible type annotations. expected `FungibleToken.Vault`, found `{FungibleToken.Vault}`", - ) - - require.Equal(t, 1, accountRegistersA.Count()) - assert.Equal(t, oldCodeA, contractCode(t, registersByAccount, ownerA, "A")) - - require.Equal(t, 1, otherAccountRegisters.Count()) - assert.Equal(t, ftContract, contractCode(t, registersByAccount, otherOwner, "FungibleToken")) - }) - - t.Run("import from other account", func(t *testing.T) { - t.Parallel() - - oldCodeA := fmt.Sprintf( - ` - import B from %s - - pub contract A {} - `, - addressB.HexWithPrefix(), - ) - - newCodeA := fmt.Sprintf( - ` - import B from %s - - access(all) contract A {} - `, - addressB.HexWithPrefix(), - ) - - codeB := ` - access(all) contract B {} - ` - - stagedContracts := []StagedContract{ - { - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: common.Address(addressB), - Contract: Contract{ - Name: "B", - Code: []byte(codeB), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - ownerA := string(addressA[:]) - accountRegistersA := registersByAccount.AccountRegisters(ownerA) - - err = migration.MigrateAccount( - context.Background(), - common.Address(addressA), - accountRegistersA, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, logWriter.logs) - - require.Equal(t, 1, accountRegistersA.Count()) - assert.Equal(t, newCodeA, contractCode(t, registersByAccount, ownerA, "A")) - }) -} - -func TestStagedContractConformanceChanges(t *testing.T) { - t.Parallel() - - const chainID = flow.Emulator - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - // Prevent conflict with system contracts - address, err := chainID.Chain().AddressAtIndex(1_000_000) - require.NoError(t, err) - - type testCase struct { - oldContract, oldInterface, newContract, newInterface string - } - - test := func(oldContract, oldInterface, newContract, newInterface string) { - - name := fmt.Sprintf( - "%s.%s to %s.%s", - oldContract, - oldInterface, - newContract, - newInterface, - ) - - t.Run(name, func(t *testing.T) { - - t.Parallel() - - metadataViewsAddress := common.Address(systemContracts.MetadataViews.Address) - viewResolverAddress := common.Address(systemContracts.ViewResolver.Address) - - oldCode := fmt.Sprintf( - ` - import %[2]s from %[1]s - - pub contract C { - pub resource A: %[2]s.%[3]s {} - } - `, - metadataViewsAddress.HexWithPrefix(), - oldContract, - oldInterface, - ) - - newCode := fmt.Sprintf( - ` - import %[2]s from %[1]s - - access(all) contract C { - access(all) resource A: %[2]s.%[3]s {} - } - `, - viewResolverAddress.HexWithPrefix(), - newContract, - newInterface, - ) - - newImportedContract := fmt.Sprintf( - ` - access(all) contract %s { - access(all) resource interface %s {} - } - `, - newContract, - newInterface, - ) - - stagedContracts := []StagedContract{ - { - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - StagedContract{ - Address: viewResolverAddress, - Contract: Contract{ - Name: newContract, - Code: []byte(newImportedContract), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner := string(address[:]) - accountRegisters := registersByAccount.AccountRegisters(owner) - require.Equal(t, 1, accountRegisters.Count()) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address), - accountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, logWriter.logs) - - require.Equal(t, 1, accountRegisters.Count()) - assert.Equal(t, newCode, contractCode(t, registersByAccount, owner, "A")) - }) - } - - testCases := []testCase{ - { - oldContract: "MetadataViews", - oldInterface: "Resolver", - newContract: "ViewResolver", - newInterface: "Resolver", - }, - { - oldContract: "MetadataViews", - oldInterface: "ResolverCollection", - newContract: "ViewResolver", - newInterface: "ResolverCollection", - }, - { - oldContract: "NonFungibleToken", - oldInterface: "INFT", - newContract: "NonFungibleToken", - newInterface: "NFT", - }, - } - - for _, testCase := range testCases { - test( - testCase.oldContract, - testCase.oldInterface, - testCase.newContract, - testCase.newInterface, - ) - } - - t.Run("MetadataViews.Resolver to ArbitraryContract.Resolver unsupported", func(t *testing.T) { - - // `MetadataViews.Resolver` shouldn't be able to replace with any arbitrary `Resolver` interface! - - t.Parallel() - - metadataViewsAddress := common.Address(systemContracts.MetadataViews.Address) - viewResolverAddress := common.Address(systemContracts.ViewResolver.Address) - - oldCode := fmt.Sprintf( - ` - import MetadataViews from %s - - pub contract C { - pub resource A: MetadataViews.Resolver {} - } - `, - metadataViewsAddress.HexWithPrefix(), - ) - - newCode := fmt.Sprintf( - ` - import ArbitraryContract from %s - - access(all) contract C { - access(all) resource A: ArbitraryContract.Resolver {} - } - `, - viewResolverAddress.HexWithPrefix(), - ) - - arbitraryContract := ` - access(all) contract ArbitraryContract { - access(all) resource interface Resolver {} - } - ` - - stagedContracts := []StagedContract{ - { - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(oldCode), - }, - }, - StagedContract{ - Address: viewResolverAddress, - Contract: Contract{ - Name: "ArbitraryContract", - Code: []byte(arbitraryContract), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner := string(address[:]) - accountRegisters := registersByAccount.AccountRegisters(owner) - require.Equal(t, 1, accountRegisters.Count()) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address), - accountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - require.Contains(t, logWriter.logs[0], "conformances do not match in `A`") - - require.Equal(t, 1, accountRegisters.Count()) - assert.Equal(t, oldCode, contractCode(t, registersByAccount, owner, "A")) - }) -} - -func TestConcurrentContractUpdate(t *testing.T) { - - t.Parallel() - - const chainID = flow.Emulator - addressGenerator := chainID.Chain().NewAddressGenerator() - - addressA, err := addressGenerator.NextAddress() - require.NoError(t, err) - - addressB, err := addressGenerator.NextAddress() - require.NoError(t, err) - - addressImport, err := addressGenerator.NextAddress() - require.NoError(t, err) - - oldCodeA := fmt.Sprintf( - ` - import Foo from %[1]s - import Bar from %[1]s - import Baz from %[1]s - - pub contract A {} - `, - addressImport.HexWithPrefix(), - ) - - newCodeA := fmt.Sprintf( - ` - import Foo from %[1]s - import Baz from %[1]s - import Bar from %[1]s - - access(all) contract A { - access(all) struct AA {} - } - `, - addressImport.HexWithPrefix(), - ) - - oldCodeB := fmt.Sprintf( - ` - import Foo from %[1]s - import Bar from %[1]s - import Baz from %[1]s - - pub contract B {} - `, - addressImport.HexWithPrefix(), - ) - - newCodeB := fmt.Sprintf( - ` - import Foo from %[1]s - import Baz from %[1]s - import Bar from %[1]s - - access(all) contract B { - access(all) struct BB {} - } - `, - addressImport.HexWithPrefix(), - ) - - codeFoo := `access(all) contract Foo {}` - codeBar := `access(all) contract Bar {}` - codeBaz := `access(all) contract Baz {}` - - stagedContracts := []StagedContract{ - { - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - { - Address: common.Address(addressB), - Contract: Contract{ - Name: "B", - Code: []byte(newCodeB), - }, - }, - } - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(addressA), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: common.Address(addressB), - Contract: Contract{ - Name: "B", - Code: []byte(oldCodeB), - }, - }, - StagedContract{ - Address: common.Address(addressImport), - Contract: Contract{ - Name: "Foo", - Code: []byte(codeFoo), - }, - }, - StagedContract{ - Address: common.Address(addressImport), - Contract: Contract{ - Name: "Bar", - Code: []byte(codeBar), - }, - }, - StagedContract{ - Address: common.Address(addressImport), - Contract: Contract{ - Name: "Baz", - Code: []byte(codeBaz), - }, - }, - ) - require.NoError(t, err) - - rwf := &testReportWriterFactory{} - - logWriter := &logWriter{} - logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - - // NOTE: Run with multiple workers (>2) - const nWorker = 2 - - const evmContractChange = EVMContractChangeNone - const burnerContractChange = BurnerContractChangeNone - - migrations := NewCadence1Migrations( - logger, - t.TempDir(), - rwf, - Options{ - NWorker: nWorker, - ChainID: chainID, - EVMContractChange: evmContractChange, - BurnerContractChange: burnerContractChange, - StagedContracts: stagedContracts, - VerboseErrorOutput: true, - }, - ) - - for _, migration := range migrations { - // only run the staged contracts migration - if migration.Name != stagedContractUpdateMigrationName { - continue - } - - err = migration.Migrate(registersByAccount) - require.NoError( - t, - err, - "migration `%s` failed, logs: %v", - migration.Name, - logWriter.logs, - ) - } - - // No errors. - require.Empty(t, logWriter.logs) - - require.NoError(t, err) - require.Equal(t, 5, registersByAccount.Count()) -} - -func TestStagedContractsUpdateValidationErrors(t *testing.T) { - t.Parallel() - - const chainID = flow.Emulator - systemContracts := systemcontracts.SystemContractsForChain(chainID) - - // Prevent conflict with system contracts - address, err := chainID.Chain().AddressAtIndex(1_000_000) - require.NoError(t, err) - - t.Run("field mismatch", func(t *testing.T) { - t.Parallel() - - oldCodeA := ` - access(all) contract Test { - access(all) var a: Int - init() { - self.a = 0 - } - } - ` - - newCodeA := ` - access(all) contract Test { - access(all) var a: String - init() { - self.a = "hello" - } - } - ` - - stagedContracts := []StagedContract{ - { - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner := string(address[:]) - accountRegisters := registersByAccount.AccountRegisters(owner) - require.Equal(t, 1, accountRegisters.Count()) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address), - accountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - - var jsonObject map[string]any - - err = json.Unmarshal([]byte(logWriter.logs[0]), &jsonObject) - require.NoError(t, err) - - assert.Equal( - t, - "failed to update contract A in account 0x9bc576e17b370b16: error: mismatching field `a` in `Test`\n"+ - " --> 9bc576e17b370b16.A:3:33\n"+ - " |\n"+ - "3 | access(all) var a: String\n"+ - " | ^^^^^^ incompatible type annotations. expected `Int`, found `String`\n", - jsonObject["message"], - ) - }) - - t.Run("field mismatch with entitlements", func(t *testing.T) { - t.Parallel() - - nftAddress := common.Address(systemContracts.NonFungibleToken.Address) - - oldCodeA := fmt.Sprintf( - ` - import NonFungibleToken from %s - - access(all) contract Test { - access(all) var a: Capability<&{NonFungibleToken.Provider}>? - init() { - self.a = nil - } - } - `, - nftAddress.HexWithPrefix(), - ) - - newCodeA := fmt.Sprintf( - ` - import NonFungibleToken from %s - - access(all) contract Test { - access(all) var a: Capability? - init() { - self.a = nil - } - } - `, - nftAddress.HexWithPrefix(), - ) - - nftContract := ` - access(all) contract NonFungibleToken { - - access(all) entitlement E1 - access(all) entitlement E2 - - access(all) resource interface Provider { - access(E1) fun foo() - access(E2) fun bar() - } - } - ` - - stagedContracts := []StagedContract{ - { - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(newCodeA), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - &LegacyTypeRequirements{}, - options, - ). - WithContractUpdateValidation(). - WithStagedContractUpdates(stagedContracts) - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(address), - Contract: Contract{ - Name: "A", - Code: []byte(oldCodeA), - }, - }, - StagedContract{ - Address: nftAddress, - Contract: Contract{ - Name: "NonFungibleToken", - Code: []byte(nftContract), - }, - }, - ) - require.NoError(t, err) - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner := string(address[:]) - accountRegisters := registersByAccount.AccountRegisters(owner) - require.Equal(t, 1, accountRegisters.Count()) - - err = migration.MigrateAccount( - context.Background(), - common.Address(address), - accountRegisters, - ) - require.NoError(t, err) - - require.Len(t, logWriter.logs, 1) - - var jsonObject map[string]any - err = json.Unmarshal([]byte(logWriter.logs[0]), &jsonObject) - require.NoError(t, err) - - assert.Equal( - t, - "failed to update contract A in account 0x9bc576e17b370b16: error: mismatching field `a` in `Test`\n"+ - " --> 9bc576e17b370b16.A:5:37\n"+ - " |\n"+ - "5 | access(all) var a: Capability?\n"+ - " | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mismatching authorization:"+ - " the entitlements migration would only grant this value `NonFungibleToken.E1, NonFungibleToken.E2`, but the annotation present is `NonFungibleToken.E1`\n", - jsonObject["message"], - ) - }) -} - -func TestContractUpdateEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := contractUpdateEntry{ - AccountAddress: common.MustBytesToAddress([]byte{0x1}), - ContractName: "Test", - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "contract-update-success", - "account_address": "0x0000000000000001", - "contract_name": "Test" - }`, - string(actual), - ) -} - -func TestContractUpdateFailureEntry_MarshalJSON(t *testing.T) { - - t.Parallel() - - e := contractUpdateFailureEntry{ - AccountAddress: common.MustBytesToAddress([]byte{0x1}), - ContractName: "Test", - Error: "unknown", - } - - actual, err := e.MarshalJSON() - require.NoError(t, err) - - require.JSONEq(t, - //language=JSON - `{ - "kind": "contract-update-failure", - "account_address": "0x0000000000000001", - "contract_name": "Test", - "error": "unknown" - }`, - string(actual), - ) -} - -func TestTypeRequirementRemoval(t *testing.T) { - t.Parallel() - - const chainID = flow.Testnet - - t.Run("TiblesProducer contract", func(t *testing.T) { - t.Parallel() - - tiblesAddress := mustHexToAddress("e93c412c964bdf40") - - oldCode := ` - pub contract interface TiblesProducer { - - pub struct ContentLocation {} - pub struct interface IContentLocation {} - - pub resource interface IContent { - access(contract) let contentIdsToPaths: {String: TiblesProducer.ContentLocation} - pub fun getMetadata(contentId: String): {String: AnyStruct}? - } - - pub resource interface IProducer { - access(contract) let minters: @{String: Minter} - } - - pub resource Producer: IContent, IProducer { - access(contract) let minters: @{String: Minter} - } - - pub resource interface IMinter { - pub let id: String - pub var lastMintNumber: UInt32 - pub let contentCapability: Capability - pub fun mintNext() - } - - pub resource Minter: IMinter { - pub let id: String - pub var lastMintNumber: UInt32 - pub let contentCapability: Capability - pub fun mintNext() - } - } - ` - - newCode := ` - access(all) contract interface TiblesProducer { - - access(all) struct interface ContentLocation {} - access(all) struct interface IContentLocation {} - - access(all) resource interface IContent { - access(contract) let contentIdsToPaths: {String: {TiblesProducer.ContentLocation}} - access(all) fun getMetadata(contentId: String): {String: AnyStruct}? - } - - access(all) resource interface IProducer { - access(contract) let minters: @{String: {Minter}} - } - - access(all) resource interface Producer: IContent, IProducer { - access(contract) let minters: @{String: {Minter}} - } - - access(all) resource interface IMinter { - access(all) let id: String - access(all) var lastMintNumber: UInt32 - access(all) let contentCapability: Capability - access(all) fun mintNext() - } - - access(all) resource interface Minter: IMinter { - access(all) let id: String - access(all) var lastMintNumber: UInt32 - access(all) let contentCapability: Capability - access(all) fun mintNext() - } - } - ` - - contractName := "TiblesProducer" - - stagedContracts := []StagedContract{ - { - Address: tiblesAddress, - Contract: Contract{ - Name: contractName, - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: tiblesAddress, - Contract: Contract{ - Name: contractName, - Code: []byte(oldCode), - }, - }, - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{contractName}) - require.NoError(t, err) - - err = registersByAccount.Set( - string(tiblesAddress[:]), - flow.ContractNamesKey, - encodedContractNames, - ) - require.NoError(t, err) - - // Run type-requirement extractor - - legacyTypeRequirements := &LegacyTypeRequirements{} - - cadenceTypeRequirementsExtractor := NewTypeRequirementsExtractingMigration( - log, - rwf, - nil, - legacyTypeRequirements, - ) - err = cadenceTypeRequirementsExtractor(registersByAccount) - require.NoError(t, err) - - require.Equal( - t, - []TypeRequirement{ - { - Address: tiblesAddress, - ContractName: contractName, - TypeName: "ContentLocation", - }, - { - Address: tiblesAddress, - ContractName: contractName, - TypeName: "Producer", - }, - { - Address: tiblesAddress, - ContractName: contractName, - TypeName: "Minter", - }, - }, - legacyTypeRequirements.typeRequirements, - ) - - // Run staged contract migration - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - legacyTypeRequirements, - options, - ). - WithStagedContractUpdates(stagedContracts). - WithContractUpdateValidation() - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner := string(tiblesAddress[:]) - accountRegisters := registersByAccount.AccountRegisters(owner) - - err = migration.MigrateAccount( - context.Background(), - tiblesAddress, - accountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, logWriter.logs) - - require.Equal(t, 2, accountRegisters.Count()) - assert.Equal(t, newCode, contractCode(t, registersByAccount, owner, contractName)) - }) - - t.Run("random contract", func(t *testing.T) { - t.Parallel() - - addressGenerator := chainID.Chain().NewAddressGenerator() - randomAddress, err := addressGenerator.NextAddress() - require.NoError(t, err) - - oldCode := ` - pub contract interface Foo { - - pub struct ContentLocation {} - pub struct interface IContentLocation {} - - pub resource interface IContent { - access(contract) let contentIdsToPaths: {String: Foo.ContentLocation} - pub fun getMetadata(contentId: String): {String: AnyStruct}? - } - - pub resource interface IProducer { - access(contract) let minters: @{String: Minter} - } - - pub resource Producer: IContent, IProducer { - access(contract) let minters: @{String: Minter} - } - - pub resource interface IMinter { - pub let id: String - pub var lastMintNumber: UInt32 - pub let contentCapability: Capability - pub fun mintNext() - } - - pub resource Minter: IMinter { - pub let id: String - pub var lastMintNumber: UInt32 - pub let contentCapability: Capability - pub fun mintNext() - } - } - ` - - newCode := ` - access(all) contract interface Foo { - - access(all) struct interface ContentLocation {} - access(all) struct interface IContentLocation {} - - access(all) resource interface IContent { - access(contract) let contentIdsToPaths: {String: {Foo.ContentLocation}} - access(all) fun getMetadata(contentId: String): {String: AnyStruct}? - } - - access(all) resource interface IProducer { - access(contract) let minters: @{String: {Minter}} - } - - access(all) resource interface Producer: IContent, IProducer { - access(contract) let minters: @{String: {Minter}} - } - - access(all) resource interface IMinter { - access(all) let id: String - access(all) var lastMintNumber: UInt32 - access(all) let contentCapability: Capability - access(all) fun mintNext() - } - - access(all) resource interface Minter: IMinter { - access(all) let id: String - access(all) var lastMintNumber: UInt32 - access(all) let contentCapability: Capability - access(all) fun mintNext() - } - } - ` - - fooContractName := "Foo" - - stagedContracts := []StagedContract{ - { - Address: common.Address(randomAddress), - Contract: Contract{ - Name: fooContractName, - Code: []byte(newCode), - }, - }, - } - - logWriter := &logWriter{} - log := zerolog.New(logWriter) - - rwf := &testReportWriterFactory{} - - options := StagedContractsMigrationOptions{ - ChainID: chainID, - VerboseErrorOutput: true, - } - - registersByAccount, err := registersForStagedContracts( - StagedContract{ - Address: common.Address(randomAddress), - Contract: Contract{ - Name: fooContractName, - Code: []byte(oldCode), - }, - }, - ) - require.NoError(t, err) - - encodedContractNames, err := environment.EncodeContractNames([]string{fooContractName}) - require.NoError(t, err) - - err = registersByAccount.Set( - string(randomAddress[:]), - flow.ContractNamesKey, - encodedContractNames, - ) - require.NoError(t, err) - - // Run type-requirement extractor - - legacyTypeRequirements := &LegacyTypeRequirements{} - - cadenceTypeRequirementsExtractor := NewTypeRequirementsExtractingMigration( - log, - rwf, - nil, - legacyTypeRequirements, - ) - err = cadenceTypeRequirementsExtractor(registersByAccount) - require.NoError(t, err) - - require.Equal( - t, - []TypeRequirement{ - { - Address: common.Address(randomAddress), - ContractName: fooContractName, - TypeName: "ContentLocation", - }, - { - Address: common.Address(randomAddress), - ContractName: fooContractName, - TypeName: "Producer", - }, - { - Address: common.Address(randomAddress), - ContractName: fooContractName, - TypeName: "Minter", - }, - }, - legacyTypeRequirements.typeRequirements, - ) - - // Run staged contract migration - - migration := NewStagedContractsMigration( - "test", - "test", - log, - rwf, - legacyTypeRequirements, - options, - ). - WithStagedContractUpdates(stagedContracts). - WithContractUpdateValidation() - - err = migration.InitMigration(log, registersByAccount, 1) - require.NoError(t, err) - - owner := string(randomAddress[:]) - accountRegisters := registersByAccount.AccountRegisters(owner) - - err = migration.MigrateAccount( - context.Background(), - common.Address(randomAddress), - accountRegisters, - ) - require.NoError(t, err) - - err = migration.Close() - require.NoError(t, err) - - require.Empty(t, logWriter.logs) - - require.Equal(t, 2, accountRegisters.Count()) - assert.Equal(t, newCode, contractCode(t, registersByAccount, owner, fooContractName)) - }) -} diff --git a/cmd/util/ledger/migrations/static_type_migration.go b/cmd/util/ledger/migrations/static_type_migration.go deleted file mode 100644 index c34164cd755..00000000000 --- a/cmd/util/ledger/migrations/static_type_migration.go +++ /dev/null @@ -1,38 +0,0 @@ -package migrations - -import ( - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" -) - -type StaticTypeMigrationRules map[common.TypeID]interpreter.StaticType - -// NewStaticTypeMigration returns a type converter function. -// Accepts a `rulesGetter` which return -// This is because this constructor is called at the time of constructing the migrations (e.g: Cadence value migration), -// but the rules can only be finalized after running previous TypeRequirementsExtractingMigration migration. -// i.e: the LegacyTypeRequirements list used by NewCompositeTypeConversionRules is lazily populated. -// So we need to delay the construction of the rules, until after the execution of previous migration. -func NewStaticTypeMigration[T interpreter.StaticType]( - rulesGetter func() StaticTypeMigrationRules, -) func(staticType T) interpreter.StaticType { - - var rules StaticTypeMigrationRules - - return func(original T) interpreter.StaticType { - // Initialize only once - if rules == nil { - rules = rulesGetter() - } - - // Returning `nil` form the callback indicates the type wasn't converted. - if rules == nil { - return nil - } - - if replacement, ok := rules[original.ID()]; ok { - return replacement - } - return nil - } -} diff --git a/cmd/util/ledger/migrations/type_requirements_extractor.go b/cmd/util/ledger/migrations/type_requirements_extractor.go deleted file mode 100644 index 67de5cb52c9..00000000000 --- a/cmd/util/ledger/migrations/type_requirements_extractor.go +++ /dev/null @@ -1,135 +0,0 @@ -package migrations - -import ( - "encoding/json" - - "github.com/rs/zerolog" - - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/old_parser" - - "github.com/onflow/flow-go/cmd/util/ledger/reporters" - "github.com/onflow/flow-go/cmd/util/ledger/util/registers" -) - -const typeRequirementExtractingReporterName = "type-requirements-extracting" - -type LegacyTypeRequirements struct { - typeRequirements []TypeRequirement -} - -type TypeRequirement struct { - Address common.Address - ContractName string - TypeName string -} - -func NewTypeRequirementsExtractingMigration( - log zerolog.Logger, - rwf reporters.ReportWriterFactory, - importantLocations map[common.AddressLocation]struct{}, - legacyTypeRequirements *LegacyTypeRequirements, -) RegistersMigration { - return func(registersByAccount *registers.ByAccount) error { - - reporter := rwf.ReportWriter(typeRequirementExtractingReporterName) - defer reporter.Close() - - // Gather all contracts - - contracts, err := GatherContractsFromRegisters(registersByAccount, log) - if err != nil { - return err - } - - // Extract type requirements from all contracts - - for _, contract := range contracts { - if _, isSystemContract := importantLocations[contract.Location]; isSystemContract { - // System contracts have their own type-changing rules. - // So do not add them here. - continue - } - - ExtractTypeRequirements( - contract, - log, - reporter, - legacyTypeRequirements, - ) - } - - return nil - } -} - -func ExtractTypeRequirements( - contract AddressContract, - log zerolog.Logger, - reporter reporters.ReportWriter, - legacyTypeRequirements *LegacyTypeRequirements, -) { - - // must be parsed with the old parser. - program, err := old_parser.ParseProgram( - nil, - contract.Code, - old_parser.Config{}, - ) - - if err != nil { - // If the old contract cannot be parsed, then ignore - return - } - - contractInterface := program.SoleContractInterfaceDeclaration() - if contractInterface == nil { - // Type requirements can only reside inside contract interfaces. - // Ignore all other programs. - return - } - - for _, composites := range contractInterface.DeclarationMembers().Composites() { - typeRequirement := TypeRequirement{ - Address: contract.Location.Address, - ContractName: contractInterface.Identifier.Identifier, - TypeName: composites.Identifier.Identifier, - } - - legacyTypeRequirements.typeRequirements = append(legacyTypeRequirements.typeRequirements, typeRequirement) - - reporter.Write(typeRequirementRemovalEntry{ - TypeRequirement: typeRequirement, - }) - } - - log.Info().Msgf("Collected %d type-requirements", len(legacyTypeRequirements.typeRequirements)) -} - -// cadenceValueMigrationFailureEntry - -type typeRequirementRemovalEntry struct { - TypeRequirement -} - -var _ valueMigrationReportEntry = typeRequirementRemovalEntry{} - -func (e typeRequirementRemovalEntry) accountAddress() common.Address { - return e.Address -} - -var _ json.Marshaler = typeRequirementRemovalEntry{} - -func (e typeRequirementRemovalEntry) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Kind string `json:"kind"` - AccountAddress string `json:"account_address"` - ContractName string `json:"contract_name"` - TypeName string `json:"type_name"` - }{ - Kind: "cadence-type-requirement-remove", - AccountAddress: e.Address.HexWithPrefix(), - ContractName: e.ContractName, - TypeName: e.TypeName, - }) -} diff --git a/cmd/util/ledger/migrations/utils.go b/cmd/util/ledger/migrations/utils.go index d719a2fbff8..5132f6d69df 100644 --- a/cmd/util/ledger/migrations/utils.go +++ b/cmd/util/ledger/migrations/utils.go @@ -10,6 +10,11 @@ import ( type RegistersMigration func(registersByAccount *registers.ByAccount) error +type NamedMigration struct { + Name string + Migrate RegistersMigration +} + var AllStorageMapDomains = []string{ common.PathDomainStorage.Identifier(), common.PathDomainPrivate.Identifier(), diff --git a/go.mod b/go.mod index dbed42d9182..a9a225cb97b 100644 --- a/go.mod +++ b/go.mod @@ -98,7 +98,6 @@ require ( github.com/coreos/go-semver v0.3.0 github.com/docker/go-units v0.5.0 github.com/dustin/go-humanize v1.0.1 - github.com/glebarez/go-sqlite v1.22.0 github.com/go-playground/validator/v10 v10.14.1 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/websocket v1.5.0 @@ -108,7 +107,6 @@ require ( github.com/ipfs/boxo v0.17.1-0.20240131173518-89bceff34bf1 github.com/mitchellh/mapstructure v1.5.0 github.com/onflow/go-ethereum v1.14.7 - github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 github.com/onflow/wal v1.0.2 github.com/slok/go-http-metrics v0.10.0 github.com/sony/gobreaker v0.5.0 @@ -278,7 +276,6 @@ require ( github.com/quic-go/quic-go v0.40.1 // indirect github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect @@ -321,10 +318,6 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect - modernc.org/libc v1.37.6 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.28.0 // indirect nhooyr.io/websocket v1.8.7 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 8f4f3a4f317..1f5a9c84d23 100644 --- a/go.sum +++ b/go.sum @@ -1408,8 +1408,6 @@ github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmC github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= -github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= -github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= @@ -2198,8 +2196,6 @@ github.com/onflow/flow/protobuf/go/flow v0.4.7 h1:iP6DFx4wZ3ETORsyeqzHu7neFT3d1C github.com/onflow/flow/protobuf/go/flow v0.4.7/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-ethereum v1.14.7 h1:gg3awYqI02e3AypRdpJKEvNTJ6kz/OhAqRti0h54Wlc= github.com/onflow/go-ethereum v1.14.7/go.mod h1:zV14QLrXyYu5ucvcwHUA0r6UaqveqbXaehAVQJlSW+I= -github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 h1:sxyWLqGm/p4EKT6DUlQESDG1ZNMN9GjPCm1gTq7NGfc= -github.com/onflow/nft-storefront/lib/go/contracts v1.0.0/go.mod h1:kMeq9zUwCrgrSojEbTUTTJpZ4WwacVm2pA7LVFr+glk= github.com/onflow/sdks v0.5.1-0.20230912225508-b35402f12bba/go.mod h1:F0dj0EyHC55kknLkeD10js4mo14yTdMotnWMslPirrU= github.com/onflow/sdks v0.6.0-preview.1 h1:mb/cUezuqWEP1gFZNAgUI4boBltudv4nlfxke1KBp9k= github.com/onflow/sdks v0.6.0-preview.1/go.mod h1:F0dj0EyHC55kknLkeD10js4mo14yTdMotnWMslPirrU= @@ -2344,7 +2340,6 @@ github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtB github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -3691,27 +3686,19 @@ modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= -modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= -modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=