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..0a7202f5a 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -495,7 +495,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 +553,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 +567,30 @@ 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, + } + var res *wasmvmtypes.ContractResult + var gasUsed uint64 + var err error + if OldStateVersion == nil { + res, gasUsed, err = k.wasmVM.Migrate(newChecksum, env, msg, vmStore, cosmwasmAPI, &querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization) + if err != nil { + if strings.Contains(err.Error(), "The called function args arity does not match") { + res, gasUsed, err = k.wasmVM.MigrateWithInfo(newChecksum, env, msg, migrateInfo, vmStore, cosmwasmAPI, &querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization) + } + } + } else { + res, gasUsed, err = k.wasmVM.MigrateWithInfo(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 does not match") { + 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..860317010 100644 --- a/x/wasm/keeper/keeper_test.go +++ b/x/wasm/keeper/keeper_test.go @@ -1356,6 +1356,15 @@ func TestMigrate(t *testing.T) { migrateMsg: migMsgBz, expErr: types.ErrMigrationFailed, }, + "fail when contract expect previous version present": { + admin: fred, + caller: fred, + initMsg: initMsgBz, + fromCodeID: originalCodeID, + toCodeID: hackatom420.CodeID, + migrateMsg: migMsgBz, + expErr: types.ErrMigrationFailed, + }, "all good with migrate versions": { admin: creator, caller: creator, diff --git a/x/wasm/keeper/testdata/hackatom_420.wasm b/x/wasm/keeper/testdata/hackatom_420.wasm index 6baf02d83..ea6c64183 100644 Binary files a/x/wasm/keeper/testdata/hackatom_420.wasm and b/x/wasm/keeper/testdata/hackatom_420.wasm differ diff --git a/x/wasm/keeper/wasmtesting/mock_engine.go b/x/wasm/keeper/wasmtesting/mock_engine.go index 14a3976d1..ca42c534a 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) + MigrateWithInfoFn 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) MigrateWithInfo(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.MigrateWithInfoFn(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..a21228000 100644 --- a/x/wasm/types/wasmer_engine.go +++ b/x/wasm/types/wasmer_engine.go @@ -105,6 +105,28 @@ type WasmEngine interface { deserCost wasmvmtypes.UFraction, ) (*wasmvmtypes.ContractResult, uint64, error) + // MigrateWithInfo 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. + // + // MigrateWithInfo takes one more argument - `migateInfo`. It consist of an additional data + // related to the on-chain current contract's state version. + MigrateWithInfo( + 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. //