Skip to content

Commit

Permalink
Merge pull request #1924 from CosmWasm/co/migrate-version
Browse files Browse the repository at this point in the history
Migrate Version checks
  • Loading branch information
chipshort authored Jul 4, 2024
2 parents 9aaeff8 + 1a7e2e1 commit 74b5871
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 22 deletions.
89 changes: 67 additions & 22 deletions x/wasm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,6 @@ func (k Keeper) migrate(
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "migrate")

sdkCtx := sdk.UnwrapSDKContext(ctx)
setupCost := k.gasRegister.SetupContractCost(k.IsPinnedCode(ctx, newCodeID), len(msg))
sdkCtx.GasMeter().ConsumeGas(setupCost, "Loading CosmWasm module: migrate")

contractInfo := k.GetContractInfo(ctx, contractAddress)
if contractInfo == nil {
Expand All @@ -468,7 +466,8 @@ func (k Keeper) migrate(
}

// check for IBC flag
switch report, err := k.wasmVM.AnalyzeCode(newCodeInfo.CodeHash); {
report, err := k.wasmVM.AnalyzeCode(newCodeInfo.CodeHash)
switch {
case err != nil:
return nil, errorsmod.Wrap(types.ErrVMError, err.Error())
case !report.HasIBCEntryPoints && contractInfo.IBCPortID != "":
Expand All @@ -483,26 +482,25 @@ func (k Keeper) migrate(
contractInfo.IBCPortID = ibcPort
}

env := types.NewEnv(sdkCtx, contractAddress)
var response *wasmvmtypes.Response

// prepare querier
querier := k.newQueryHandler(sdkCtx, contractAddress)

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(newCodeInfo.CodeHash, env, msg, vmStore, cosmwasmAPI, &querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization)
k.consumeRuntimeGas(sdkCtx, gasUsed)
// check for migrate version
oldCodeInfo := k.GetCodeInfo(ctx, contractInfo.CodeID)
oldReport, err := k.wasmVM.AnalyzeCode(oldCodeInfo.CodeHash)
if err != nil {
return nil, errorsmod.Wrap(types.ErrVMError, err.Error())
}
if res == nil {
// If this gets executed, that's a bug in wasmvm
return nil, errorsmod.Wrap(types.ErrVMError, "internal wasmvm error")
}
if res.Err != "" {
return nil, types.MarkErrorDeterministic(errorsmod.Wrap(types.ErrMigrationFailed, res.Err))

// call migrate entrypoint, except if both migrate versions are set and the same value
if report.ContractMigrateVersion == nil ||
oldReport.ContractMigrateVersion == nil ||
*report.ContractMigrateVersion != *oldReport.ContractMigrateVersion {
response, err = k.callMigrateEntrypoint(sdkCtx, contractAddress, wasmvmtypes.Checksum(newCodeInfo.CodeHash), msg, newCodeID)
if err != nil {
return nil, err
}
}

// delete old secondary index entry
err = k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.mustGetLastContractHistoryEntry(sdkCtx, contractAddress))
if err != nil {
Expand All @@ -526,15 +524,62 @@ func (k Keeper) migrate(
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
))

sdkCtx = types.WithSubMsgAuthzPolicy(sdkCtx, authZ.SubMessageAuthorizationPolicy(types.AuthZActionMigrateContract))
data, err := k.handleContractResponse(sdkCtx, contractAddress, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Data, res.Ok.Events)
if err != nil {
return nil, errorsmod.Wrap(err, "dispatch")
var data []byte

// if migrate entry point was called
if response != nil {
sdkCtx = types.WithSubMsgAuthzPolicy(sdkCtx, authZ.SubMessageAuthorizationPolicy(types.AuthZActionMigrateContract))
data, err = k.handleContractResponse(
sdkCtx,
contractAddress,
contractInfo.IBCPortID,
response.Messages,
response.Attributes,
response.Data,
response.Events,
)
if err != nil {
return nil, errorsmod.Wrap(err, "dispatch")
}
return data, nil
}

return data, nil
}

func (k Keeper) callMigrateEntrypoint(
sdkCtx sdk.Context,
contractAddress sdk.AccAddress,
newChecksum wasmvmtypes.Checksum,
msg []byte,
newCodeID uint64,
) (*wasmvmtypes.Response, error) {
setupCost := k.gasRegister.SetupContractCost(k.IsPinnedCode(sdkCtx, newCodeID), len(msg))
sdkCtx.GasMeter().ConsumeGas(setupCost, "Loading CosmWasm module: migrate")

env := types.NewEnv(sdkCtx, contractAddress)

// prepare querier
querier := k.newQueryHandler(sdkCtx, contractAddress)

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)
k.consumeRuntimeGas(sdkCtx, gasUsed)
if err != nil {
return nil, errorsmod.Wrap(types.ErrVMError, err.Error())
}
if res == nil {
// If this gets executed, that's a bug in wasmvm
return nil, errorsmod.Wrap(types.ErrVMError, "internal wasmvm error")
}
if res.Err != "" {
return nil, types.MarkErrorDeterministic(errorsmod.Wrap(types.ErrMigrationFailed, res.Err))
}
return res.Ok, nil
}

// Sudo allows privileged access to a contract. This can never be called by an external tx, but only by
// another native Go module directly, or on-chain governance (if sudo proposals are enabled). Thus, the keeper doesn't
// place any access controls on it, that is the responsibility or the app developer (who passes the wasm.Keeper in app.go)
Expand Down
49 changes: 49 additions & 0 deletions x/wasm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,10 @@ func TestMigrate(t *testing.T) {
require.NoError(t, keeper.SetAccessConfig(parentCtx, restrictedCodeExample.CodeID, restrictedCodeExample.CreatorAddr, types.AllowNobody))
require.NotEqual(t, originalCodeID, restrictedCodeExample.CodeID)

// store hackatom contracts with "migrate_version" attributes
hackatom42 := StoreExampleContract(t, parentCtx, keepers, "./testdata/hackatom_42.wasm")
hackatom420 := StoreExampleContract(t, parentCtx, keepers, "./testdata/hackatom_420.wasm")

anyAddr := RandomAccountAddress(t)
newVerifierAddr := RandomAccountAddress(t)
initMsgBz := HackatomExampleInitMsg{
Expand Down Expand Up @@ -1351,6 +1355,51 @@ func TestMigrate(t *testing.T) {
migrateMsg: migMsgBz,
expErr: types.ErrMigrationFailed,
},
"all good with migrate versions": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom42.CodeID,
toCodeID: hackatom420.CodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"all good with no migrate version to migrate version contract": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: hackatom42.CodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"all good with same migrate version": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom42.CodeID,
toCodeID: hackatom42.CodeID,
migrateMsg: migMsgBz,
expVerifier: fred, // not updated
},
"all good with migrate version contract to no migrate version contract": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom42.CodeID,
toCodeID: originalCodeID,
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,
},
}

blockHeight := parentCtx.BlockHeight()
Expand Down
Binary file added x/wasm/keeper/testdata/hackatom_42.wasm
Binary file not shown.
Binary file added x/wasm/keeper/testdata/hackatom_420.wasm
Binary file not shown.

0 comments on commit 74b5871

Please sign in to comment.