Skip to content

Commit

Permalink
feat: Add conditional migrate calling (#1966)
Browse files Browse the repository at this point in the history
  • Loading branch information
kulikthebird authored Nov 4, 2024
1 parent 88cba83 commit 60ed81c
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 12 deletions.
12 changes: 10 additions & 2 deletions x/wasm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,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
}
Expand Down Expand Up @@ -560,6 +560,8 @@ func (k Keeper) callMigrateEntrypoint(
newChecksum wasmvmtypes.Checksum,
msg []byte,
newCodeID uint64,
senderAddress sdk.AccAddress,
oldMigrateVersion *uint64,
) (*wasmvmtypes.Response, error) {
sdkCtx, discount := k.checkDiscountEligibility(sdkCtx, newChecksum, k.IsPinnedCode(sdkCtx, newCodeID))
setupCost := k.gasRegister.SetupContractCost(discount, len(msg))
Expand All @@ -573,7 +575,13 @@ 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(),
OldMigrateVersion: oldMigrateVersion,
}
res, gasUsed, err := k.wasmVM.MigrateWithInfo(newChecksum, env, msg, migrateInfo, vmStore, cosmwasmAPI, &querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization)

k.consumeRuntimeGas(sdkCtx, gasUsed)
if err != nil {
return nil, errorsmod.Wrap(types.ErrVMError, err.Error())
Expand Down
23 changes: 13 additions & 10 deletions x/wasm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var hackatomWasm []byte

var AvailableCapabilities = []string{
"iterator", "staking", "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3",
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1",
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1", "cosmwasm_2_2",
}

func TestNewKeeper(t *testing.T) {
Expand Down Expand Up @@ -1372,14 +1372,14 @@ func TestMigrate(t *testing.T) {
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"all good with migration to older migrate version": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom420.CodeID,
toCodeID: hackatom42.CodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
"contract returns error when downgrading version": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom420.CodeID,
toCodeID: hackatom42.CodeID,
migrateMsg: migMsgBz,
expErr: types.ErrMigrationFailed,
},
}

Expand Down Expand Up @@ -1599,7 +1599,10 @@ 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
}}
},
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) {
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
Expand Down
23 changes: 23 additions & 0 deletions x/wasm/keeper/submsg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,29 @@ func TestMigrateGovSubMsgAuthzPropagated(t *testing.T) {

var instanceLevel int
// mock wasvm to return new migrate msgs with the response
mockWasmVM.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) {
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
}
mockWasmVM.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) {
if instanceLevel == 1 {
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 0, nil
Expand Down
Binary file modified x/wasm/keeper/testdata/hackatom_42.wasm
Binary file not shown.
Binary file modified x/wasm/keeper/testdata/hackatom_420.wasm
Binary file not shown.
8 changes: 8 additions & 0 deletions x/wasm/keeper/wasmtesting/mock_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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!")
Expand Down
22 changes: 22 additions & 0 deletions x/wasm/types/wasmer_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,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.
//
Expand Down

0 comments on commit 60ed81c

Please sign in to comment.