diff --git a/cmd/util/cmd/execution-state-extract/cmd.go b/cmd/util/cmd/execution-state-extract/cmd.go index c526dc3664a..09d6407a499 100644 --- a/cmd/util/cmd/execution-state-extract/cmd.go +++ b/cmd/util/cmd/execution-state-extract/cmd.go @@ -42,6 +42,7 @@ var ( flagInputPayloadFileName string flagOutputPayloadFileName string flagOutputPayloadByAddresses string + flagMaxAccountSize uint64 ) var Cmd = &cobra.Command{ @@ -132,6 +133,9 @@ func init() { "", "extract payloads of addresses (comma separated hex-encoded addresses) to file specified by output-payload-filename", ) + + Cmd.Flags().Uint64Var(&flagMaxAccountSize, "max-account-size", 0, + "max account size") } func run(*cobra.Command, []string) { @@ -346,6 +350,7 @@ func run(*cobra.Command, []string) { exportedAddresses, flagSortPayloads, flagPrune, + flagMaxAccountSize, ) } else { err = extractExecutionState( @@ -365,6 +370,7 @@ func run(*cobra.Command, []string) { exportedAddresses, flagSortPayloads, flagPrune, + flagMaxAccountSize, ) } 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 f5c14ec7eaa..1b2d700e236 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -15,7 +15,6 @@ import ( "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/convert" "github.com/onflow/flow-go/ledger/common/hash" "github.com/onflow/flow-go/ledger/common/pathfinder" "github.com/onflow/flow-go/ledger/complete" @@ -48,6 +47,7 @@ func extractExecutionState( exportPayloadsByAddresses []common.Address, sortPayloads bool, prune bool, + maxAccountSize uint64, ) error { log.Info().Msg("init WAL") @@ -119,6 +119,7 @@ func extractExecutionState( burnerContractChange, stagedContracts, prune, + maxAccountSize, ) newState := ledger.State(targetHash) @@ -220,20 +221,6 @@ func writeStatusFile(fileName string, e error) error { return err } -func ByteCountIEC(b int64) string { - const unit = 1024 - if b < unit { - return fmt.Sprintf("%d B", b) - } - div, exp := int64(unit), 0 - for n := b / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %ciB", - float64(b)/float64(div), "KMGTPE"[exp]) -} - func extractExecutionStateFromPayloads( log zerolog.Logger, dir string, @@ -251,6 +238,7 @@ func extractExecutionStateFromPayloads( exportPayloadsByAddresses []common.Address, sortPayloads bool, prune bool, + maxAccountSize uint64, ) error { inputPayloadsFromPartialState, payloads, err := util.ReadPayloadFile(log, inputPayloadFile) @@ -260,36 +248,6 @@ func extractExecutionStateFromPayloads( log.Info().Msgf("read %d payloads", len(payloads)) - if log.Debug().Enabled() { - - type accountInfo struct { - count int - size uint64 - } - payloadCountByAddress := make(map[string]accountInfo) - - for _, payload := range payloads { - registerID, payloadValue, err := convert.PayloadToRegister(payload) - if err != nil { - return fmt.Errorf("cannot convert payload to register: %w", err) - } - owner := registerID.Owner - accountInfo := payloadCountByAddress[owner] - accountInfo.count++ - accountInfo.size += uint64(len(payloadValue)) - payloadCountByAddress[owner] = accountInfo - } - - for address, info := range payloadCountByAddress { - log.Debug().Msgf( - "address %x has %d payloads and a total size of %s", - address, - info.count, - ByteCountIEC(int64(info.size)), - ) - } - } - migrations := newMigrations( log, dir, @@ -302,6 +260,7 @@ func extractExecutionStateFromPayloads( burnerContractChange, stagedContracts, prune, + maxAccountSize, ) payloads, err = migratePayloads(log, payloads, migrations) @@ -393,7 +352,7 @@ func migratePayloads(logger zerolog.Logger, payloads []*ledger.Payload, migratio // migrate payloads for i, migrate := range migrations { - logger.Info().Msgf("migration %d/%d is underway", i, len(migrations)) + logger.Info().Msgf("migration %d/%d is underway", i+1, len(migrations)) start := time.Now() payloads, err = migrate(payloads) @@ -458,6 +417,7 @@ func newMigrations( burnerContractChange migrators.BurnerContractChange, stagedContracts []migrators.StagedContract, prune bool, + maxAccountSize uint64, ) []ledger.Migration { if !runMigrations { return nil @@ -478,6 +438,7 @@ func newMigrations( burnerContractChange, stagedContracts, prune, + maxAccountSize, ) migrations := make([]ledger.Migration, 0, len(namedMigrations)) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go index 82ba3bac242..477849d31d9 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go @@ -83,6 +83,7 @@ func TestExtractExecutionState(t *testing.T) { nil, false, false, + 0, ) require.Error(t, err) }) diff --git a/cmd/util/ledger/migrations/account_based_migration.go b/cmd/util/ledger/migrations/account_based_migration.go index f7f95d4391a..c183154d37a 100644 --- a/cmd/util/ledger/migrations/account_based_migration.go +++ b/cmd/util/ledger/migrations/account_based_migration.go @@ -127,7 +127,7 @@ func withMigrations( Type("migration", migrator). Msg("closing migration") if cerr := migrator.Close(); cerr != nil { - log.Error().Err(cerr).Msg("error closing migration") + log.Err(cerr).Msg("error closing migration") if err == nil { // only set the error if it's not already set // so that we don't overwrite the original error @@ -315,27 +315,7 @@ func MigrateGroupConcurrently( return migrated, nil } -var knownProblematicAccounts = map[common.Address]string{ - // Testnet accounts with broken contracts - mustHexToAddress("434a1f199a7ae3ba"): "Broken contract FanTopPermission", - mustHexToAddress("454c9991c2b8d947"): "Broken contract Test", - mustHexToAddress("48602d8056ff9d93"): "Broken contract FanTopPermission", - mustHexToAddress("5d63c34d7f05e5a4"): "Broken contract FanTopPermission", - mustHexToAddress("5e3448b3cffb97f2"): "Broken contract FanTopPermission", - mustHexToAddress("7d8c7e050c694eaa"): "Broken contract Test", - mustHexToAddress("ba53f16ede01972d"): "Broken contract FanTopPermission", - mustHexToAddress("c843c1f5a4805c3a"): "Broken contract FanTopPermission", - mustHexToAddress("48d3be92e6e4a973"): "Broken contract FanTopPermission", - // Mainnet account -} - -func mustHexToAddress(hex string) common.Address { - address, err := common.HexToAddress(hex) - if err != nil { - panic(err) - } - return address -} +var knownProblematicAccounts = map[common.Address]string{} type jobMigrateAccountGroup struct { Address common.Address diff --git a/cmd/util/ledger/migrations/account_size_filter_migration.go b/cmd/util/ledger/migrations/account_size_filter_migration.go new file mode 100644 index 00000000000..9982283681c --- /dev/null +++ b/cmd/util/ledger/migrations/account_size_filter_migration.go @@ -0,0 +1,99 @@ +package migrations + +import ( + "fmt" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" +) + +func NewAccountSizeFilterMigration( + maxAccountSize uint64, + exceptions map[string]struct{}, + log zerolog.Logger, +) ledger.Migration { + + if maxAccountSize == 0 { + return nil + } + + return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { + + type accountInfo struct { + count int + size uint64 + } + payloadCountByAddress := make(map[string]accountInfo) + + for _, payload := range payloads { + registerID, payloadValue, err := convert.PayloadToRegister(payload) + if err != nil { + return nil, fmt.Errorf("cannot convert payload to register: %w", err) + } + + owner := registerID.Owner + accountInfo := payloadCountByAddress[owner] + accountInfo.count++ + accountInfo.size += uint64(len(payloadValue)) + payloadCountByAddress[owner] = accountInfo + } + + if log.Debug().Enabled() { + for address, info := range payloadCountByAddress { + log.Debug().Msgf( + "address %x has %d payloads and a total size of %s", + address, + info.count, + ByteCountIEC(int64(info.size)), + ) + + if _, ok := exceptions[address]; !ok && info.size > maxAccountSize { + log.Warn().Msgf( + "dropping payloads of account %x. size of payloads %s exceeds max size %s", + address, + ByteCountIEC(int64(info.size)), + ByteCountIEC(int64(maxAccountSize)), + ) + } + } + } + + filteredPayloads := make([]*ledger.Payload, 0, int(0.8*float32(len(payloads)))) + + for _, payload := range payloads { + registerID, _, err := convert.PayloadToRegister(payload) + if err != nil { + return nil, fmt.Errorf("cannot convert payload to register: %w", err) + } + + owner := registerID.Owner + + if _, ok := exceptions[owner]; !ok { + info := payloadCountByAddress[owner] + if info.size > maxAccountSize { + continue + } + } + + filteredPayloads = append(filteredPayloads, payload) + } + + return filteredPayloads, nil + } +} + +func ByteCountIEC(b int64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %ciB", + float64(b)/float64(div), "KMGTPE"[exp]) +} diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index b0874c6a71b..dc6029021c9 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -2,6 +2,7 @@ package migrations import ( _ "embed" + "fmt" "github.com/onflow/cadence/migrations/capcons" "github.com/onflow/cadence/migrations/statictypes" @@ -18,10 +19,15 @@ import ( func NewInterfaceTypeConversionRules(chainID flow.ChainID) StaticTypeMigrationRules { systemContracts := systemcontracts.SystemContractsForChain(chainID) - oldFungibleTokenResolverType, newFungibleTokenResolverType := fungibleTokenResolverRule(systemContracts) + oldFungibleTokenResolverType, newFungibleTokenResolverType := + newFungibleTokenMetadataViewsToViewResolverRule(systemContracts, "Resolver") + + oldFungibleTokenResolverCollectionType, newFungibleTokenResolverCollectionType := + newFungibleTokenMetadataViewsToViewResolverRule(systemContracts, "ResolverCollection") return StaticTypeMigrationRules{ - oldFungibleTokenResolverType.ID(): newFungibleTokenResolverType, + oldFungibleTokenResolverType.ID(): newFungibleTokenResolverType, + oldFungibleTokenResolverCollectionType.ID(): newFungibleTokenResolverCollectionType, } } @@ -119,8 +125,9 @@ func fungibleTokenVaultRule( return oldType, newType } -func fungibleTokenResolverRule( +func newFungibleTokenMetadataViewsToViewResolverRule( systemContracts *systemcontracts.SystemContracts, + typeName string, ) ( *interpreter.InterfaceStaticType, *interpreter.InterfaceStaticType, @@ -138,8 +145,8 @@ func fungibleTokenResolverRule( Name: newContract.Name, } - oldQualifiedIdentifier := oldContract.Name + ".Resolver" - newQualifiedIdentifier := newContract.Name + ".Resolver" + oldQualifiedIdentifier := fmt.Sprintf("%s.%s", oldContract.Name, typeName) + newQualifiedIdentifier := fmt.Sprintf("%s.%s", newContract.Name, typeName) oldType := &interpreter.InterfaceStaticType{ Location: oldLocation, @@ -176,12 +183,29 @@ func NewCadence1ValueMigrations( 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. + + contracts := make(map[common.AddressLocation][]byte, 1000) + + migrations = []NamedMigration{ + { + Name: "contracts", + Migrate: NewContractsExtractionMigration(contracts, log), + }, + } + for _, accountBasedMigration := range []*CadenceBaseMigrator{ NewCadence1ValueMigrator( rwf, diffMigrations, logVerboseDiff, errorMessageHandler, + contracts, NewCadence1CompositeStaticTypeConverter(chainID), NewCadence1InterfaceStaticTypeConverter(chainID), ), @@ -190,6 +214,7 @@ func NewCadence1ValueMigrations( diffMigrations, logVerboseDiff, errorMessageHandler, + contracts, capabilityMapping, ), NewCadence1CapabilityValueMigrator( @@ -197,6 +222,7 @@ func NewCadence1ValueMigrations( diffMigrations, logVerboseDiff, errorMessageHandler, + contracts, capabilityMapping, ), } { @@ -288,10 +314,29 @@ func NewCadence1Migrations( burnerContractChange BurnerContractChange, stagedContracts []StagedContract, prune bool, + maxAccountSize uint64, ) []NamedMigration { var migrations []NamedMigration + if maxAccountSize > 0 { + + maxSizeExceptions := map[string]struct{}{} + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + for _, systemContract := range systemContracts.All() { + maxSizeExceptions[string(systemContract.Address.Bytes())] = struct{}{} + } + + migrations = append( + migrations, + NamedMigration{ + Name: "account-size-filter-migration", + Migrate: NewAccountSizeFilterMigration(maxAccountSize, maxSizeExceptions, log), + }, + ) + } + if prune { migration := NewCadence1PruneMigration(chainID, log) if migration != nil { diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index e5c9a76f212..8e9a7c0da0a 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -41,6 +41,7 @@ type CadenceBaseMigrator struct { ) []migrations.ValueMigration runtimeInterfaceConfig util.RuntimeInterfaceConfig errorMessageHandler *errorMessageHandler + contracts map[common.AddressLocation][]byte } var _ AccountBasedMigration = (*CadenceBaseMigrator)(nil) @@ -64,21 +65,13 @@ func (m *CadenceBaseMigrator) InitMigration( ) error { m.log = log.With().Str("migration", m.name).Logger() - // The MigrateAccount function is only given the payloads for the account to be migrated. - // However, the migration needs to be able to get the code for contracts of any account. - - contracts, err := getContractMap(allPayloads) - if err != nil { - return err - } - m.runtimeInterfaceConfig.GetContractCodeFunc = func(location runtime.Location) ([]byte, error) { addressLocation, ok := location.(common.AddressLocation) if !ok { return nil, nil } - contract, ok := contracts[addressLocation] + contract, ok := m.contracts[addressLocation] if !ok { return nil, fmt.Errorf("failed to get contract code for location %s", location) } @@ -89,36 +82,6 @@ func (m *CadenceBaseMigrator) InitMigration( return nil } -func getContractMap(allPayloads []*ledger.Payload) (map[common.AddressLocation][]byte, error) { - contracts := make(map[common.AddressLocation][]byte) - - for _, payload := range allPayloads { - registerID, registerValue, err := convert.PayloadToRegister(payload) - if err != nil { - return nil, fmt.Errorf("failed to convert payload to register: %w", err) - } - - contractName := flow.RegisterIDContractName(registerID) - if contractName == "" { - continue - } - - address, err := common.BytesToAddress([]byte(registerID.Owner)) - if err != nil { - return nil, fmt.Errorf("failed to convert register owner to address: %w", err) - } - - addressLocation := common.AddressLocation{ - Address: address, - Name: contractName, - } - - contracts[addressLocation] = registerValue - } - - return contracts, nil -} - func (m *CadenceBaseMigrator) MigrateAccount( _ context.Context, address common.Address, @@ -216,7 +179,7 @@ func checkPayloadsOwnership(payloads []*ledger.Payload, address common.Address, func checkPayloadOwnership(payload *ledger.Payload, address common.Address, log zerolog.Logger) { registerID, _, err := convert.PayloadToRegister(payload) if err != nil { - log.Error().Err(err).Msg("failed to convert payload to register") + log.Err(err).Msg("failed to convert payload to register") return } @@ -225,7 +188,7 @@ func checkPayloadOwnership(payload *ledger.Payload, address common.Address, log if len(owner) > 0 { payloadAddress, err := common.BytesToAddress([]byte(owner)) if err != nil { - log.Error().Err(err).Msgf("failed to convert register owner to address: %x", owner) + log.Err(err).Msgf("failed to convert register owner to address: %x", owner) return } @@ -246,6 +209,7 @@ func NewCadence1ValueMigrator( diffMigrations bool, logVerboseDiff bool, errorMessageHandler *errorMessageHandler, + contracts map[common.AddressLocation][]byte, compositeTypeConverter statictypes.CompositeTypeConverterFunc, interfaceTypeConverter statictypes.InterfaceTypeConverterFunc, ) *CadenceBaseMigrator { @@ -274,6 +238,7 @@ func NewCadence1ValueMigrator( } }, errorMessageHandler: errorMessageHandler, + contracts: contracts, } } @@ -285,6 +250,7 @@ func NewCadence1LinkValueMigrator( diffMigrations bool, logVerboseDiff bool, errorMessageHandler *errorMessageHandler, + contracts map[common.AddressLocation][]byte, capabilityMapping *capcons.CapabilityMapping, ) *CadenceBaseMigrator { var diffReporter reporters.ReportWriter @@ -316,6 +282,7 @@ func NewCadence1LinkValueMigrator( } }, errorMessageHandler: errorMessageHandler, + contracts: contracts, } } @@ -328,6 +295,7 @@ func NewCadence1CapabilityValueMigrator( diffMigrations bool, logVerboseDiff bool, errorMessageHandler *errorMessageHandler, + contracts map[common.AddressLocation][]byte, capabilityMapping *capcons.CapabilityMapping, ) *CadenceBaseMigrator { var diffReporter reporters.ReportWriter @@ -353,6 +321,7 @@ func NewCadence1CapabilityValueMigrator( } }, errorMessageHandler: errorMessageHandler, + contracts: contracts, } } diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 832efb65577..fb1d6ed6e2b 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -20,6 +20,7 @@ import ( "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/model/flow" ) @@ -87,6 +88,7 @@ func TestCadenceValuesMigration(t *testing.T) { burnerContractChange, stagedContracts, false, + 0, ) for _, migration := range migrations { @@ -110,9 +112,6 @@ func TestCadenceValuesMigration(t *testing.T) { require.Empty(t, logWriter.logs) } -// TODO: -//func TestCadenceValuesMigrationWithSwappedOrder(t *testing.T) { - var flowTokenAddress = func() common.Address { address, _ := common.HexToAddress("0ae53cb6e3f42a79") return address @@ -727,6 +726,7 @@ func TestBootstrappedStateMigration(t *testing.T) { burnerContractChange, nil, false, + 0, ) for _, migration := range migrations { @@ -759,11 +759,6 @@ func TestProgramParsingError(t *testing.T) { testAddress := common.Address(chain.ServiceAddress()) - // 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) @@ -843,6 +838,11 @@ func TestProgramParsingError(t *testing.T) { // Migrate + // TODO: EVM contract is not deployed in snapshot yet, so can't update it + const evmContractChange = EVMContractChangeNone + + const burnerContractChange = BurnerContractChangeUpdate + migrations := NewCadence1Migrations( logger, rwf, @@ -854,6 +854,7 @@ func TestProgramParsingError(t *testing.T) { burnerContractChange, nil, false, + 0, ) for _, migration := range migrations { @@ -882,3 +883,425 @@ func TestProgramParsingError(t *testing.T) { assert.Contains(t, entry.Message, "`pub` is no longer a valid access keyword") assert.NotContains(t, entry.Message, "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) + + runtime, err := NewMigratorRuntime( + testAddress, + payloads, + util.RuntimeInterfaceConfig{}, + ) + 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( + 0, + interpreter.AddressValue(testAddress), + staticType, + ) + + storageMap.WriteValue( + runtime.Interpreter, + storageMapKey, + capabilityValue, + ) + + err = storage.Commit(runtime.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := runtime.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + // Merge the changes to the original payloads. + + expectedAddresses := map[flow.Address]struct{}{ + flow.Address(testAddress): {}, + } + + payloads, err = MergeRegisterChanges( + runtime.Snapshot.Payloads, + result.WriteSet, + expectedAddresses, + nil, + logger, + ) + require.NoError(t, err) + + // Migrate + + // TODO: EVM contract is not deployed in snapshot yet, so can't update it + const evmContractChange = EVMContractChangeNone + + const burnerContractChange = BurnerContractChangeUpdate + + migrations := NewCadence1Migrations( + logger, + rwf, + nWorker, + chainID, + false, + false, + evmContractChange, + burnerContractChange, + nil, + false, + 0, + ) + + for _, migration := range migrations { + payloads, err = migration.Migrate(payloads) + require.NoError( + t, + err, + "migration `%s` failed, logs: %v", + migration.Name, + logWriter.logs, + ) + } + + // Check error logs + require.Len(t, logWriter.logs, 0) + + // Get result + + mr, err := NewMigratorRuntime( + testAddress, + payloads, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + storageMap = mr.Storage.GetStorageMap( + testAddress, + storageDomain, + false, + ) + require.NotNil(t, storageMap) + + resultValue := storageMap.ReadValue(nil, storageMapKey) + require.NotNil(t, resultValue) + require.IsType(t, &interpreter.IDCapabilityValue{}, resultValue) + + resultCap := resultValue.(*interpreter.IDCapabilityValue) + return resultCap.BorrowType + } + + t.Run("&FungibleToken.Vault => auth(Withdraw) &{FungibleToken.Vault}", func(t *testing.T) { + t.Parallel() + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + + const fungibleTokenContractName = "FungibleToken" + fungibleTokenContractLocation := common.NewAddressLocation( + nil, + common.Address(systemContracts.FungibleToken.Address), + fungibleTokenContractName, + ) + + const fungibleTokenVaultTypeQualifiedIdentifier = fungibleTokenContractName + ".Vault" + + input := interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewCompositeStaticType( + nil, + fungibleTokenContractLocation, + fungibleTokenVaultTypeQualifiedIdentifier, + fungibleTokenContractLocation.TypeID(nil, fungibleTokenVaultTypeQualifiedIdentifier), + ), + ) + + const fungibleTokenWithdrawTypeQualifiedIdentifier = fungibleTokenContractName + ".Withdraw" + expected := interpreter.NewReferenceStaticType( + nil, + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { + return []common.TypeID{ + fungibleTokenContractLocation.TypeID(nil, fungibleTokenWithdrawTypeQualifiedIdentifier), + } + }, + 1, + sema.Conjunction, + ), + interpreter.NewIntersectionStaticType( + nil, + []*interpreter.InterfaceStaticType{ + 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("&{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) + }) + +} diff --git a/cmd/util/ledger/migrations/contracts.go b/cmd/util/ledger/migrations/contracts.go new file mode 100644 index 00000000000..adf823ec040 --- /dev/null +++ b/cmd/util/ledger/migrations/contracts.go @@ -0,0 +1,53 @@ +package migrations + +import ( + "fmt" + + "github.com/onflow/cadence/runtime/common" + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +// NewContractsExtractionMigration returns a migration that extracts the code for contracts from the payloads. +// The given contracts map must be allocated, and gets populated with the code for the contracts found in the payloads. +func NewContractsExtractionMigration( + contracts map[common.AddressLocation][]byte, + log zerolog.Logger, +) ledger.Migration { + return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { + + log.Info().Msg("extracting contracts from payloads ...") + + for _, payload := range payloads { + registerID, registerValue, err := convert.PayloadToRegister(payload) + if err != nil { + return nil, fmt.Errorf("failed to convert payload to register: %w", err) + } + + contractName := flow.RegisterIDContractName(registerID) + if contractName == "" { + continue + } + + address, err := common.BytesToAddress([]byte(registerID.Owner)) + if err != nil { + return nil, fmt.Errorf("failed to convert register owner to address: %w", err) + } + + addressLocation := common.AddressLocation{ + Address: address, + Name: contractName, + } + + contracts[addressLocation] = registerValue + } + + log.Info().Msgf("extracted %d contracts from payloads", len(contracts)) + + // Return the payloads as-is + return payloads, nil + } +} diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index fe3e5f9ad98..d06111ccdb3 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -244,7 +244,7 @@ func (m *StagedContractsMigration) MigrateAccount( } if err != nil { - m.log.Error().Err(err). + m.log.Err(err). Msgf( "failed to update contract %s in account %s", name, diff --git a/ledger/complete/ledger.go b/ledger/complete/ledger.go index 4ddd327f308..7007574fda3 100644 --- a/ledger/complete/ledger.go +++ b/ledger/complete/ledger.go @@ -377,7 +377,7 @@ func (l *Ledger) MigrateAt( // migrate payloads for i, migrate := range migrations { - l.logger.Info().Msgf("migration %d/%d is underway", i, len(migrations)) + l.logger.Info().Msgf("migration %d/%d is underway", i+1, len(migrations)) start := time.Now() payloads, err = migrate(payloads) @@ -426,9 +426,9 @@ func (l *Ledger) MigrateAt( } } - statecommitment := ledger.State(newTrie.RootHash()) + stateCommitment := ledger.State(newTrie.RootHash()) - l.logger.Info().Msgf("successfully built new trie. NEW ROOT STATECOMMIEMENT: %v", statecommitment.String()) + l.logger.Info().Msgf("successfully built new trie. NEW ROOT STATECOMMIEMENT: %v", stateCommitment.String()) return newTrie, nil }