Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Version checks #1924

Merged
merged 7 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
}

// 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 @@
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 @@
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 {
chipshort marked this conversation as resolved.
Show resolved Hide resolved
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")

Check warning on line 575 in x/wasm/keeper/keeper.go

View check run for this annotation

Codecov / codecov/patch

x/wasm/keeper/keeper.go#L575

Added line #L575 was not covered by tests
}
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.