diff --git a/go.mod b/go.mod index 8b4cb9054..d373d4d5d 100644 --- a/go.mod +++ b/go.mod @@ -222,6 +222,9 @@ replace ( // pin version! 126854af5e6d has issues with the store so that queries fail github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + + // TODO tkulik: UNDO + github.com/CosmWasm/wasmvm/v2 => /home/tkulik/Workspace/wasmvm ) retract ( diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 40d9cc176..c4897d23e 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -485,6 +485,7 @@ func (k Keeper) migrate( var response *wasmvmtypes.Response // check for migrate version + // TODO tkulik: Here we call AnalyzeCode: oldCodeInfo := k.GetCodeInfo(ctx, contractInfo.CodeID) oldReport, err := k.wasmVM.AnalyzeCode(oldCodeInfo.CodeHash) if err != nil { @@ -495,7 +496,7 @@ func (k Keeper) migrate( if report.ContractMigrateVersion == nil || oldReport.ContractMigrateVersion == nil || *report.ContractMigrateVersion != *oldReport.ContractMigrateVersion { - response, err = k.callMigrateEntrypoint(sdkCtx, contractAddress, wasmvmtypes.Checksum(newCodeInfo.CodeHash), msg, newCodeID) + response, err = k.callMigrateEntrypoint(sdkCtx, contractAddress, wasmvmtypes.Checksum(newCodeInfo.CodeHash), msg, newCodeID, caller, oldReport.ContractMigrateVersion) if err != nil { return nil, err } @@ -553,6 +554,8 @@ func (k Keeper) callMigrateEntrypoint( newChecksum wasmvmtypes.Checksum, msg []byte, newCodeID uint64, + senderAddress sdk.AccAddress, + OldStateVersion *uint64, ) (*wasmvmtypes.Response, error) { setupCost := k.gasRegister.SetupContractCost(k.IsPinnedCode(sdkCtx, newCodeID), len(msg)) sdkCtx.GasMeter().ConsumeGas(setupCost, "Loading CosmWasm module: migrate") @@ -565,7 +568,18 @@ func (k Keeper) callMigrateEntrypoint( prefixStoreKey := types.GetContractStorePrefix(contractAddress) vmStore := types.NewStoreAdapter(prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(sdkCtx)), prefixStoreKey)) gasLeft := k.runtimeGasForContract(sdkCtx) - res, gasUsed, err := k.wasmVM.Migrate(newChecksum, env, msg, vmStore, cosmwasmAPI, &querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization) + + migrateInfo := wasmvmtypes.MigrateInfo{ + Sender: senderAddress.String(), + OldStateVersion: OldStateVersion, + } + res, gasUsed, err := k.wasmVM.Migrate2(newChecksum, env, msg, migrateInfo, vmStore, cosmwasmAPI, &querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization) + if err != nil { + if strings.Contains(err.Error(), "The called function args arity mismatch, expected 2 args") { + res, gasUsed, err = k.wasmVM.Migrate(newChecksum, env, msg, vmStore, cosmwasmAPI, &querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization) + } + } + k.consumeRuntimeGas(sdkCtx, gasUsed) if err != nil { return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) diff --git a/x/wasm/keeper/keeper_test.go b/x/wasm/keeper/keeper_test.go index a8f069bf7..22d6e5628 100644 --- a/x/wasm/keeper/keeper_test.go +++ b/x/wasm/keeper/keeper_test.go @@ -1615,11 +1615,15 @@ func TestIterateContractsByCode(t *testing.T) { } } +// TODO tkulik: Update this testcase's mock func TestIterateContractsByCodeWithMigration(t *testing.T) { // mock migration so that it does not fail when migrate example1 to example2.codeID mockWasmVM := wasmtesting.MockWasmEngine{MigrateFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 1, nil - }} + }, + Migrate2Fn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, migrateInfo wasmvmtypes.MigrateInfo, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 1, nil + }} wasmtesting.MakeInstantiable(&mockWasmVM) ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithWasmEngine(&mockWasmVM)) k, c := keepers.WasmKeeper, keepers.ContractKeeper @@ -1627,6 +1631,7 @@ func TestIterateContractsByCodeWithMigration(t *testing.T) { ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) example2 := InstantiateIBCReflectContract(t, ctx, keepers) ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + // return _, err := c.Migrate(ctx, example1.Contract, example1.CreatorAddr, example2.CodeID, []byte("{}")) require.NoError(t, err) diff --git a/x/wasm/keeper/submsg_test.go b/x/wasm/keeper/submsg_test.go index b5e4a879a..ef6f6a212 100644 --- a/x/wasm/keeper/submsg_test.go +++ b/x/wasm/keeper/submsg_test.go @@ -641,6 +641,7 @@ func TestInstantiateGovSubMsgAuthzPropagated(t *testing.T) { } } +// TODO tkulik: Update this testcase's mock func TestMigrateGovSubMsgAuthzPropagated(t *testing.T) { mockWasmVM := &wasmtesting.MockWasmEngine{} wasmtesting.MakeInstantiable(mockWasmVM) @@ -675,6 +676,29 @@ func TestMigrateGovSubMsgAuthzPropagated(t *testing.T) { }, }, 0, nil } + mockWasmVM.Migrate2Fn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, migrateInfo wasmvmtypes.MigrateInfo, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + if instanceLevel == 1 { + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 0, nil + } + instanceLevel++ + submsgPayload := fmt.Sprintf(`{"sub":%d}`, instanceLevel) + return &wasmvmtypes.ContractResult{ + Ok: &wasmvmtypes.Response{ + Messages: []wasmvmtypes.SubMsg{ + { + ReplyOn: wasmvmtypes.ReplyNever, + Msg: wasmvmtypes.CosmosMsg{ + Wasm: &wasmvmtypes.WasmMsg{Migrate: &wasmvmtypes.MigrateMsg{ + ContractAddr: example1.Contract.String(), + NewCodeID: example2.CodeID, + Msg: []byte(submsgPayload), + }}, + }, + }, + }, + }, + }, 0, nil + } specs := map[string]struct { policy types.AuthorizationPolicy diff --git a/x/wasm/keeper/wasmtesting/mock_engine.go b/x/wasm/keeper/wasmtesting/mock_engine.go index 14a3976d1..f398e8675 100644 --- a/x/wasm/keeper/wasmtesting/mock_engine.go +++ b/x/wasm/keeper/wasmtesting/mock_engine.go @@ -29,6 +29,7 @@ type MockWasmEngine struct { ExecuteFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) QueryFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) MigrateFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) + Migrate2Fn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, migrateInfo wasmvmtypes.MigrateInfo, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) SudoFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) ReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) GetCodeFn func(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) @@ -152,6 +153,13 @@ func (m *MockWasmEngine) Migrate(codeID wasmvm.Checksum, env wasmvmtypes.Env, mi return m.MigrateFn(codeID, env, migrateMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) } +func (m *MockWasmEngine) Migrate2(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, migrateInfo wasmvmtypes.MigrateInfo, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + if m.MigrateFn == nil { + panic("not supposed to be called!") + } + return m.Migrate2Fn(codeID, env, migrateMsg, migrateInfo, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + func (m *MockWasmEngine) Sudo(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { if m.SudoFn == nil { panic("not supposed to be called!") diff --git a/x/wasm/types/wasmer_engine.go b/x/wasm/types/wasmer_engine.go index 31114e67c..0f5b59fb5 100644 --- a/x/wasm/types/wasmer_engine.go +++ b/x/wasm/types/wasmer_engine.go @@ -105,6 +105,26 @@ type WasmEngine interface { deserCost wasmvmtypes.UFraction, ) (*wasmvmtypes.ContractResult, uint64, error) + // TODO tkulik: Update the description + // Migrate will migrate an existing contract to a new code binary. + // This takes storage of the data from the original contract and the CodeID of the new contract that should + // replace it. This allows it to run a migration step if needed, or return an error if unable to migrate + // the given data. + // + // MigrateMsg has some data on how to perform the migration. + Migrate2( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + migrateMsg []byte, + migrateInfo wasmvmtypes.MigrateInfo, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) + // Sudo runs an existing contract in read/write mode (like Execute), but is never exposed to external callers // (either transactions or government proposals), but can only be called by other native Go modules directly. //