From 8282aa89352e7074540c8e9e3a3e35931d904526 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Thu, 22 Feb 2024 15:18:10 -0800 Subject: [PATCH] Add `flow migrate state` command to migrate emulator state to Cadence 1.0 --- internal/migrate/state.go | 64 ++++++++++++++------- internal/migrate/state_test.go | 101 +++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 internal/migrate/state_test.go diff --git a/internal/migrate/state.go b/internal/migrate/state.go index 2de02a5cf..8cdcfbb19 100644 --- a/internal/migrate/state.go +++ b/internal/migrate/state.go @@ -26,8 +26,10 @@ import ( "github.com/onflow/flow-emulator/storage/migration" emulatorMigrate "github.com/onflow/flow-emulator/storage/migration" "github.com/onflow/flow-emulator/storage/sqlite" + "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go/cmd/util/ledger/migrations" "github.com/onflow/flowkit/v2" + "github.com/onflow/flowkit/v2/config" "github.com/onflow/flowkit/v2/output" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -37,7 +39,7 @@ import ( var stateFlags struct { Contracts []string `default:"" flag:"contracts" info:"contract names to migrate"` - DBPath string `default:"./flowdb" flag:"db-path" info:"path to the database file"` + DBPath string `default:"./flowdb" flag:"db-path" info:"path to the sqlite database file"` } var stateCommand = &command.Command{ @@ -58,22 +60,56 @@ func migrateState( flow flowkit.Services, state *flowkit.State, ) (command.Result, error) { - contracts := make([]migrations.StagedContract, len(stateFlags.Contracts)) - contractNames := stateFlags.Contracts + if globalFlags.Network != "emulator" { + return nil, fmt.Errorf("state migration is only supported for the emulator network") + } + + contracts, err := resolveStagedContracts(state, stateFlags.Contracts) + if err != nil { + return nil, fmt.Errorf("failed to resolve staged contracts: %w", err) + } + + store, err := sqlite.New(stateFlags.DBPath) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + + rwf := &migration.NOOPReportWriterFactory{} + logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger() + err = emulatorMigrate.MigrateCadence1(store, contracts, rwf, logger) + if err != nil { + return nil, fmt.Errorf("failed to migrate database: %w", err) + } + + return nil, nil +} + +func resolveStagedContracts(state *flowkit.State, contractNames []string) ([]migrations.StagedContract, error) { + contracts := make([]migrations.StagedContract, len(contractNames)) + for i, contractName := range contractNames { + // First try to get contract address from aliases contract, err := state.Contracts().ByName(contractName) if err != nil { return nil, fmt.Errorf("failed to get contract by name: %w", err) } + var address flow.Address + if contract.Aliases.ByNetwork(config.EmulatorNetwork.Name) != nil { + address = contract.Aliases.ByNetwork(config.EmulatorNetwork.Name).Address + } + code, err := state.ReadFile(contract.Location) if err != nil { return nil, fmt.Errorf("failed to read contract file: %w", err) } - address, err := getAddressByContractName(state, contractName, flow.Network()) - if err != nil { - return nil, fmt.Errorf("failed to get address by contract name: %w", err) + // If contract is not aliased, try to get address by deployment account + if address == flow.EmptyAddress { + address, err = getAddressByContractName(state, contractName, config.EmulatorNetwork) + if err != nil { + return nil, fmt.Errorf("failed to get address by contract name: %w", err) + } } contracts[i] = migrations.StagedContract{ @@ -85,19 +121,5 @@ func migrateState( } } - store, err := sqlite.New(stateFlags.DBPath) - - if err != nil { - return nil, fmt.Errorf("failed to open database: %w", err) - } - - rwf := &migration.NOOPReportWriterFactory{} - logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger() - - err = emulatorMigrate.MigrateCadence1(store, contracts, rwf, logger) - if err != nil { - return nil, fmt.Errorf("failed to migrate database: %w", err) - } - - return nil, nil + return contracts, nil } diff --git a/internal/migrate/state_test.go b/internal/migrate/state_test.go new file mode 100644 index 000000000..1377aef19 --- /dev/null +++ b/internal/migrate/state_test.go @@ -0,0 +1,101 @@ +/* + * Flow CLI + * + * Copyright 2019 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package migrate + +import ( + "testing" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flowkit/v2/config" + "github.com/onflow/flowkit/v2/tests" + "github.com/stretchr/testify/assert" + + "github.com/onflow/flow-cli/internal/util" +) + +func Test_MigrateState(t *testing.T) { + _, state, _ := util.TestMocks(t) + + testContractAliased := tests.ContractSimple + testContractDeployed := tests.ContractA + + t.Run("resolves staged contracts by name", func(t *testing.T) { + // Add an aliased contract to state + state.Contracts().AddOrUpdate( + config.Contract{ + Name: testContractAliased.Name, + Location: testContractAliased.Filename, + Aliases: config.Aliases{ + { + Network: "emulator", + Address: flow.HexToAddress("0x1"), + }, + }, + }, + ) + + state.Contracts().AddOrUpdate( + config.Contract{ + Name: testContractDeployed.Name, + Location: testContractDeployed.Filename, + }, + ) + + // Add deployment to state + state.Deployments().AddOrUpdate( + config.Deployment{ + Network: "emulator", + Account: "emulator-account", + Contracts: []config.ContractDeployment{ + { + Name: testContractDeployed.Name, + }, + }, + }, + ) + + account, err := state.EmulatorServiceAccount() + assert.NoError(t, err) + + contracts, err := resolveStagedContracts( + state, + []string{testContractAliased.Name, testContractDeployed.Name}, + ) + assert.NoError(t, err) + + assert.Equal(t, []migrations.StagedContract{ + { + Contract: migrations.Contract{ + Name: testContractAliased.Name, + Code: testContractAliased.Source, + }, + Address: common.Address(flow.HexToAddress("0x1")), + }, + { + Contract: migrations.Contract{ + Name: testContractDeployed.Name, + Code: testContractDeployed.Source, + }, + Address: common.Address(account.Address), + }, + }, contracts) + }) +}