wasmd/x/wasm/keeper/keeper.go

1551 lines
56 KiB
Go

package keeper
import (
"bytes"
"context"
"encoding/binary"
"encoding/hex"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"time"
wasmvm "github.com/CosmWasm/wasmvm/v3"
wasmvmtypes "github.com/CosmWasm/wasmvm/v3/types"
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
ibcapi "github.com/cosmos/ibc-go/v10/modules/core/api"
"cosmossdk.io/collections"
corestoretypes "cosmossdk.io/core/store"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store/prefix"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestingexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
"github.com/CosmWasm/wasmd/x/wasm/ioutils"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
// contractMemoryLimit is the memory limit of each contract execution (in MiB)
// constant value so all nodes run with the same limit.
const contractMemoryLimit = 32
// Option is an extension point to instantiate keeper with non default values
type Option interface {
apply(*Keeper)
}
// WasmVMQueryHandler is an extension point for custom query handler implementations
type WasmVMQueryHandler interface {
// HandleQuery executes the requested query
HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error)
}
type CoinTransferrer interface {
// TransferCoins sends the coin amounts from the source to the destination with rules applied.
TransferCoins(ctx sdk.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error
}
// AccountPruner handles the balances and data cleanup for accounts that are pruned on contract instantiate.
// This is an extension point to attach custom logic
type AccountPruner interface {
// CleanupExistingAccount handles the cleanup process for balances and data of the given account. The persisted account
// type is already reset to base account at this stage.
// The method returns true when the account address can be reused. Unsupported account types are rejected by returning false
CleanupExistingAccount(ctx sdk.Context, existingAccount sdk.AccountI) (handled bool, err error)
}
// WasmVMResponseHandler is an extension point to handles the response data returned by a contract call.
type WasmVMResponseHandler interface {
// Handle processes the data returned by a contract invocation.
Handle(
ctx sdk.Context,
contractAddr sdk.AccAddress,
ibcPort string,
messages []wasmvmtypes.SubMsg,
origRspData []byte,
) ([]byte, error)
}
// list of account types that are accepted for wasm contracts. Chains importing wasmd
// can overwrite this list with the WithAcceptedAccountTypesOnContractInstantiation option.
var defaultAcceptedAccountTypes = map[reflect.Type]struct{}{
reflect.TypeOf(&authtypes.BaseAccount{}): {},
}
// Keeper will have a reference to Wasm Engine with it's own data directory.
type Keeper struct {
// The (unexposed) keys used to access the stores from the Context.
storeService corestoretypes.KVStoreService
cdc codec.Codec
accountKeeper types.AccountKeeper
bank CoinTransferrer
wasmVM types.WasmEngine
wasmVMQueryHandler WasmVMQueryHandler
wasmVMResponseHandler WasmVMResponseHandler
messenger Messenger
// queryGasLimit is the max wasmvm gas that can be spent on executing a query with a contract
queryGasLimit uint64
gasRegister types.GasRegister
maxQueryStackSize uint32
maxCallDepth uint32
acceptedAccountTypes map[reflect.Type]struct{}
accountPruner AccountPruner
params collections.Item[types.Params]
// propagate gov authZ to sub-messages
propagateGovAuthorization map[types.AuthorizationPolicyAction]struct{}
// the address capable of executing a MsgUpdateParams message. Typically, this
// should be the x/gov module account.
authority string
// wasmLimits contains the limits sent to wasmvm on init
wasmLimits wasmvmtypes.WasmLimits
ibcRouterV2 *ibcapi.Router
}
func (k Keeper) GetIBCRouterV2() *ibcapi.Router {
return k.ibcRouterV2
}
func (k Keeper) getUploadAccessConfig(ctx context.Context) types.AccessConfig {
return k.GetParams(ctx).CodeUploadAccess
}
func (k Keeper) getInstantiateAccessConfig(ctx context.Context) types.AccessType {
return k.GetParams(ctx).InstantiateDefaultPermission
}
func (k Keeper) GetWasmLimits() wasmvmtypes.WasmLimits {
return k.wasmLimits
}
// GetParams returns the total set of wasm parameters.
func (k Keeper) GetParams(ctx context.Context) types.Params {
p, err := k.params.Get(ctx)
if err != nil {
panic(err)
}
return p
}
// SetParams sets all wasm parameters.
func (k Keeper) SetParams(ctx context.Context, ps types.Params) error {
return k.params.Set(ctx, ps)
}
// GetAuthority returns the x/wasm module's authority.
func (k Keeper) GetAuthority() string {
return k.authority
}
// GetGasRegister returns the x/wasm module's gas register.
func (k Keeper) GetGasRegister() types.GasRegister {
return k.gasRegister
}
func (k Keeper) create(ctx context.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ types.AuthorizationPolicy) (codeID uint64, checksum []byte, err error) {
if creator == nil {
return 0, checksum, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "cannot be nil")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
// figure out proper instantiate access
defaultAccessConfig := k.getInstantiateAccessConfig(sdkCtx).With(creator)
if instantiateAccess == nil {
instantiateAccess = &defaultAccessConfig
}
chainConfigs := types.ChainAccessConfigs{
Instantiate: defaultAccessConfig,
Upload: k.getUploadAccessConfig(sdkCtx),
}
if !authZ.CanCreateCode(chainConfigs, creator, *instantiateAccess) {
return 0, checksum, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not create code")
}
if ioutils.IsGzip(wasmCode) {
sdkCtx.GasMeter().ConsumeGas(k.gasRegister.UncompressCosts(len(wasmCode)), "Uncompress gzip bytecode")
wasmCode, err = ioutils.Uncompress(wasmCode, int64(types.MaxWasmSize))
if err != nil {
return 0, checksum, types.ErrCreateFailed.Wrap(errorsmod.Wrap(err, "uncompress wasm archive").Error())
}
}
gasLeft := k.runtimeGasForContract(sdkCtx)
var gasUsed uint64
isSimulation := sdkCtx.ExecMode() == sdk.ExecModeSimulate
if isSimulation {
// only simulate storing the code, no files are written
checksum, gasUsed, err = k.wasmVM.SimulateStoreCode(wasmCode, gasLeft)
} else {
checksum, gasUsed, err = k.wasmVM.StoreCode(wasmCode, gasLeft)
}
k.consumeRuntimeGas(sdkCtx, gasUsed)
if err != nil {
return 0, checksum, errorsmod.Wrap(types.ErrCreateFailed, err.Error())
}
// simulation gets default value for capabilities
var requiredCapabilities string
if !isSimulation {
report, err := k.wasmVM.AnalyzeCode(checksum)
if err != nil {
return 0, checksum, errorsmod.Wrap(types.ErrCreateFailed, err.Error())
}
requiredCapabilities = report.RequiredCapabilities
}
codeID = k.mustAutoIncrementID(sdkCtx, types.KeySequenceCodeID)
k.Logger(sdkCtx).Debug("storing new contract", "capabilities", requiredCapabilities, "code_id", codeID)
codeInfo := types.NewCodeInfo(checksum, creator, *instantiateAccess)
k.mustStoreCodeInfo(sdkCtx, codeID, codeInfo)
evt := sdk.NewEvent(
types.EventTypeStoreCode,
sdk.NewAttribute(types.AttributeKeyChecksum, hex.EncodeToString(checksum)),
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), // last element to be compatible with scripts
)
for _, f := range strings.Split(requiredCapabilities, ",") {
evt.AppendAttributes(sdk.NewAttribute(types.AttributeKeyRequiredCapability, strings.TrimSpace(f)))
}
sdkCtx.EventManager().EmitEvent(evt)
return codeID, checksum, nil
}
func (k Keeper) mustStoreCodeInfo(ctx context.Context, codeID uint64, codeInfo types.CodeInfo) {
store := k.storeService.OpenKVStore(ctx)
// 0x01 | codeID (uint64) -> ContractInfo
err := store.Set(types.GetCodeKey(codeID), k.cdc.MustMarshal(&codeInfo))
if err != nil {
panic(err)
}
}
func (k Keeper) importCode(ctx context.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error {
if ioutils.IsGzip(wasmCode) {
var err error
wasmCode, err = ioutils.Uncompress(wasmCode, math.MaxInt64)
if err != nil {
return types.ErrCreateFailed.Wrap(errorsmod.Wrap(err, "uncompress wasm archive").Error())
}
}
newCodeHash, err := k.wasmVM.StoreCodeUnchecked(wasmCode)
if err != nil {
return errorsmod.Wrap(types.ErrCreateFailed, err.Error())
}
if !bytes.Equal(codeInfo.CodeHash, newCodeHash) {
return errorsmod.Wrap(types.ErrInvalid, "code hashes not same")
}
store := k.storeService.OpenKVStore(ctx)
key := types.GetCodeKey(codeID)
ok, err := store.Has(key)
if err != nil {
return errorsmod.Wrap(err, "has code-id key")
}
if ok {
return errorsmod.Wrapf(types.ErrDuplicate, "duplicate code: %d", codeID)
}
// 0x01 | codeID (uint64) -> ContractInfo
return store.Set(key, k.cdc.MustMarshal(&codeInfo))
}
func (k Keeper) instantiate(
ctx context.Context,
codeID uint64,
creator, admin sdk.AccAddress,
initMsg []byte,
label string,
deposit sdk.Coins,
addressGenerator AddressGenerator,
authPolicy types.AuthorizationPolicy,
) (sdk.AccAddress, []byte, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "instantiate")
if creator == nil {
return nil, nil, types.ErrEmpty.Wrap("creator")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
codeInfo := k.GetCodeInfo(ctx, codeID)
if codeInfo == nil {
return nil, nil, types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID)
}
sdkCtx, discount := k.checkDiscountEligibility(sdkCtx, codeInfo.CodeHash, k.IsPinnedCode(sdkCtx, codeID))
setupCost := k.gasRegister.SetupContractCost(discount, len(initMsg))
sdkCtx.GasMeter().ConsumeGas(setupCost, "Loading CosmWasm module: instantiate")
if !authPolicy.CanInstantiateContract(codeInfo.InstantiateConfig, creator) {
return nil, nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not instantiate")
}
contractAddress := addressGenerator(ctx, codeID, codeInfo.CodeHash)
if k.HasContractInfo(ctx, contractAddress) {
// This case must only happen for instantiate2 because instantiate is based on a counter in state.
// So we create an instantiate2 specific error message here even though technically this function
// is used for both cases.
return nil, nil, types.ErrDuplicate.Wrap("contract address already exists, try a different combination of creator, checksum and salt")
}
// check account
// every cosmos module can define custom account types when needed. The cosmos-sdk comes with extension points
// to support this and a set of base and vesting account types that we integrated in our default lists.
// But not all account types of other modules are known or may make sense for contracts, therefore we kept this
// decision logic also very flexible and extendable. We provide new options to overwrite the default settings via WithAcceptedAccountTypesOnContractInstantiation and
// WithPruneAccountTypesOnContractInstantiation as constructor arguments
existingAcct := k.accountKeeper.GetAccount(sdkCtx, contractAddress)
if existingAcct != nil {
if existingAcct.GetSequence() != 0 || existingAcct.GetPubKey() != nil {
return nil, nil, types.ErrAccountExists.Wrap("address is claimed by external account")
}
if _, accept := k.acceptedAccountTypes[reflect.TypeOf(existingAcct)]; accept {
// keep account and balance as it is
k.Logger(sdkCtx).Info("instantiate contract with existing account", "address", contractAddress.String())
} else {
// consider an account in the wasmd namespace spam and overwrite it.
k.Logger(sdkCtx).Info("pruning existing account for contract instantiation", "address", contractAddress.String())
contractAccount := k.accountKeeper.NewAccountWithAddress(sdkCtx, contractAddress)
k.accountKeeper.SetAccount(sdkCtx, contractAccount)
// also handle balance to not open cases where these accounts are abused and become liquid
switch handled, err := k.accountPruner.CleanupExistingAccount(sdkCtx, existingAcct); {
case err != nil:
return nil, nil, errorsmod.Wrap(err, "prune balance")
case !handled:
return nil, nil, types.ErrAccountExists.Wrap("address is claimed by external account")
}
}
} else {
// create an empty account (so we don't have issues later)
contractAccount := k.accountKeeper.NewAccountWithAddress(sdkCtx, contractAddress)
k.accountKeeper.SetAccount(sdkCtx, contractAccount)
}
// deposit initial contract funds
if !deposit.IsZero() {
if err := k.bank.TransferCoins(sdkCtx, creator, contractAddress, deposit); err != nil {
return nil, nil, err
}
}
// prepare params for contract instantiate call
env := types.NewEnv(sdkCtx, contractAddress)
info := types.NewInfo(creator, deposit)
// create prefixed data store
// 0x03 | BuildContractAddressClassic (sdk.AccAddress)
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
vmStore := types.NewStoreAdapter(prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(sdkCtx)), prefixStoreKey))
// prepare querier
querier := k.newQueryHandler(sdkCtx, contractAddress)
// instantiate wasm contract
gasLeft := k.runtimeGasForContract(sdkCtx)
res, gasUsed, err := k.wasmVM.Instantiate(codeInfo.CodeHash, env, info, initMsg, vmStore, cosmwasmAPI, querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization)
k.consumeRuntimeGas(sdkCtx, gasUsed)
if err != nil {
return nil, nil, errorsmod.Wrap(types.ErrVMError, err.Error())
}
if res == nil {
// If this gets executed, that's a bug in wasmvm
return nil, nil, errorsmod.Wrap(types.ErrVMError, "internal wasmvm error")
}
if res.Err != "" {
return nil, nil, types.MarkErrorDeterministic(errorsmod.Wrap(types.ErrInstantiateFailed, res.Err))
}
// persist instance first
createdAt := types.NewAbsoluteTxPosition(sdkCtx)
contractInfo := types.NewContractInfo(codeID, creator, admin, label, createdAt)
// check for IBC flag
report, err := k.wasmVM.AnalyzeCode(codeInfo.CodeHash)
if err != nil {
return nil, nil, errorsmod.Wrap(types.ErrVMError, err.Error())
}
if report.HasIBCEntryPoints {
// register IBC port
ibcPort := PortIDForContract(contractAddress)
contractInfo.IBCPortID = ibcPort
}
ibc2Port := PortIDForContractV2(contractAddress)
contractInfo.IBC2PortID = ibc2Port
// TODO: Remove AddRoute in https://github.com/CosmWasm/wasmd/issues/2144
if !k.ibcRouterV2.HasRoute(ibc2Port) {
k.ibcRouterV2.AddRoute(ibc2Port, NewIBC2Handler(k))
}
// store contract before dispatch so that contract could be called back
historyEntry := contractInfo.InitialHistory(initMsg)
err = k.addToContractCodeSecondaryIndex(sdkCtx, contractAddress, historyEntry)
if err != nil {
return nil, nil, err
}
err = k.addToContractCreatorSecondaryIndex(sdkCtx, creator, historyEntry.Updated, contractAddress)
if err != nil {
return nil, nil, err
}
err = k.appendToContractHistory(sdkCtx, contractAddress, historyEntry)
if err != nil {
return nil, nil, err
}
k.mustStoreContractInfo(sdkCtx, contractAddress, &contractInfo)
sdkCtx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeInstantiate,
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)),
))
sdkCtx = types.WithSubMsgAuthzPolicy(sdkCtx, authPolicy.SubMessageAuthorizationPolicy(types.AuthZActionInstantiate))
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, nil, errorsmod.Wrap(err, "dispatch")
}
return contractAddress, data, nil
}
// Execute executes the contract instance
func (k Keeper) execute(ctx context.Context, contractAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "execute")
sdkCtx := sdk.UnwrapSDKContext(ctx)
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
if err != nil {
return nil, err
}
sdkCtx, discount := k.checkDiscountEligibility(sdkCtx, codeInfo.CodeHash, k.IsPinnedCode(ctx, contractInfo.CodeID))
setupCost := k.gasRegister.SetupContractCost(discount, len(msg))
sdkCtx.GasMeter().ConsumeGas(setupCost, "Loading CosmWasm module: execute")
// add more funds
if !coins.IsZero() {
if err := k.bank.TransferCoins(sdkCtx, caller, contractAddress, coins); err != nil {
return nil, err
}
}
env := types.NewEnv(sdkCtx, contractAddress)
info := types.NewInfo(caller, coins)
// prepare querier
querier := k.newQueryHandler(sdkCtx, contractAddress)
gasLeft := k.runtimeGasForContract(sdkCtx)
res, gasUsed, execErr := k.wasmVM.Execute(codeInfo.CodeHash, env, info, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization)
k.consumeRuntimeGas(sdkCtx, gasUsed)
if execErr != nil {
return nil, errorsmod.Wrap(types.ErrVMError, execErr.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.ErrExecuteFailed, res.Err))
}
sdkCtx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeExecute,
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
))
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, err
}
return data, nil
}
func (k Keeper) migrate(
ctx context.Context,
contractAddress sdk.AccAddress,
caller sdk.AccAddress,
newCodeID uint64,
msg []byte,
authZ types.AuthorizationPolicy,
) ([]byte, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "migrate")
sdkCtx := sdk.UnwrapSDKContext(ctx)
contractInfo := k.GetContractInfo(ctx, contractAddress)
if contractInfo == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
}
if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not migrate")
}
newCodeInfo := k.GetCodeInfo(ctx, newCodeID)
if newCodeInfo == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown code")
}
if !authZ.CanInstantiateContract(newCodeInfo.InstantiateConfig, caller) {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "to use new code")
}
// check for IBC flag
report, err := k.wasmVM.AnalyzeCode(newCodeInfo.CodeHash)
switch {
case err != nil:
return nil, errorsmod.Wrap(types.ErrVMError, err.Error())
case !report.HasIBCEntryPoints && contractInfo.IBCPortID != "":
// prevent update to non ibc contract
return nil, errorsmod.Wrap(types.ErrMigrationFailed, "requires ibc callbacks")
case report.HasIBCEntryPoints && contractInfo.IBCPortID == "":
// add ibc port
ibcPort := PortIDForContract(contractAddress)
contractInfo.IBCPortID = ibcPort
}
ibc2Port := PortIDForContractV2(contractAddress)
contractInfo.IBC2PortID = ibc2Port
var response *wasmvmtypes.Response
// 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())
}
// 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, caller, oldReport.ContractMigrateVersion)
if err != nil {
return nil, err
}
}
// delete old secondary index entry
err = k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.mustGetLastContractHistoryEntry(sdkCtx, contractAddress))
if err != nil {
return nil, err
}
// persist migration updates
historyEntry := contractInfo.AddMigration(sdkCtx, newCodeID, msg)
err = k.appendToContractHistory(ctx, contractAddress, historyEntry)
if err != nil {
return nil, err
}
err = k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
if err != nil {
return nil, err
}
k.mustStoreContractInfo(ctx, contractAddress, contractInfo)
sdkCtx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeMigrate,
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(newCodeID, 10)),
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
))
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,
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))
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)
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())
}
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)
//
// Sub-messages returned from the sudo call to the contract are executed with the default authorization policy. This can be
// customized though by passing a new policy with the context. See types.WithSubMsgAuthzPolicy.
// The policy will be read in msgServer.selectAuthorizationPolicy and used for sub-message executions.
// This is an extension point for some very advanced scenarios only. Use with care!
func (k Keeper) Sudo(ctx context.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "sudo")
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
if err != nil {
return nil, err
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx, discount := k.checkDiscountEligibility(sdkCtx, codeInfo.CodeHash, k.IsPinnedCode(ctx, contractInfo.CodeID))
setupCost := k.gasRegister.SetupContractCost(discount, len(msg))
sdkCtx.GasMeter().ConsumeGas(setupCost, "Loading CosmWasm module: sudo")
env := types.NewEnv(sdkCtx, contractAddress)
// prepare querier
querier := k.newQueryHandler(sdkCtx, contractAddress)
gasLeft := k.runtimeGasForContract(sdkCtx)
res, gasUsed, execErr := k.wasmVM.Sudo(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(sdkCtx), gasLeft, costJSONDeserialization)
k.consumeRuntimeGas(sdkCtx, gasUsed)
if execErr != nil {
return nil, errorsmod.Wrap(types.ErrVMError, execErr.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.ErrExecuteFailed, res.Err))
}
sdkCtx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeSudo,
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
))
// sudo submessages are executed with the default authorization policy
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")
}
return data, nil
}
// reply is only called from keeper internal functions (dispatchSubmessages) after processing the submessage
func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) {
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
if err != nil {
return nil, err
}
replyCosts := k.gasRegister.ReplyCosts(true, reply)
ctx.GasMeter().ConsumeGas(replyCosts, "Loading CosmWasm module: reply")
env := types.NewEnv(ctx, contractAddress)
// prepare querier
querier := k.newQueryHandler(ctx, contractAddress)
gasLeft := k.runtimeGasForContract(ctx)
res, gasUsed, execErr := k.wasmVM.Reply(codeInfo.CodeHash, env, reply, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gasLeft, costJSONDeserialization)
k.consumeRuntimeGas(ctx, gasUsed)
if execErr != nil {
return nil, errorsmod.Wrap(types.ErrVMError, execErr.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.ErrExecuteFailed, res.Err))
}
ctx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeReply,
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
))
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Data, res.Ok.Events)
if err != nil {
return nil, errorsmod.Wrap(err, "dispatch")
}
return data, nil
}
// addToContractCodeSecondaryIndex adds element to the index for contracts-by-codeid queries
func (k Keeper) addToContractCodeSecondaryIndex(ctx context.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) error {
store := k.storeService.OpenKVStore(ctx)
return store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry), []byte{})
}
// removeFromContractCodeSecondaryIndex removes element to the index for contracts-by-codeid queries
func (k Keeper) removeFromContractCodeSecondaryIndex(ctx context.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) error {
return k.storeService.OpenKVStore(ctx).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry))
}
// addToContractCreatorSecondaryIndex adds element to the index for contracts-by-creator queries
func (k Keeper) addToContractCreatorSecondaryIndex(ctx context.Context, creatorAddress sdk.AccAddress, position *types.AbsoluteTxPosition, contractAddress sdk.AccAddress) error {
store := k.storeService.OpenKVStore(ctx)
return store.Set(types.GetContractByCreatorSecondaryIndexKey(creatorAddress, position.Bytes(), contractAddress), []byte{})
}
// IterateContractsByCreator iterates over all contracts with given creator address in order of creation time asc.
func (k Keeper) IterateContractsByCreator(ctx context.Context, creator sdk.AccAddress, cb func(address sdk.AccAddress) bool) {
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.GetContractsByCreatorPrefix(creator))
for iter := prefixStore.Iterator(nil, nil); iter.Valid(); iter.Next() {
key := iter.Key()
if cb(key[types.AbsoluteTxPositionLen:]) {
return
}
}
}
// IterateContractsByCode iterates over all contracts with given codeID ASC on code update time.
func (k Keeper) IterateContractsByCode(ctx context.Context, codeID uint64, cb func(address sdk.AccAddress) bool) {
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.GetContractByCodeIDSecondaryIndexPrefix(codeID))
iter := prefixStore.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
key := iter.Key()
if cb(key[types.AbsoluteTxPositionLen:]) {
return
}
}
}
func (k Keeper) setContractAdmin(ctx context.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ types.AuthorizationPolicy) error {
sdkCtx := sdk.UnwrapSDKContext(ctx)
contractInfo := k.GetContractInfo(sdkCtx, contractAddress)
if contractInfo == nil {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
}
if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract")
}
newAdminStr := newAdmin.String()
contractInfo.Admin = newAdminStr
k.mustStoreContractInfo(sdkCtx, contractAddress, contractInfo)
sdkCtx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeUpdateContractAdmin,
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
sdk.NewAttribute(types.AttributeKeyNewAdmin, newAdminStr),
))
return nil
}
func (k Keeper) setContractLabel(ctx context.Context, contractAddress, caller sdk.AccAddress, newLabel string, authZ types.AuthorizationPolicy) error {
sdkCtx := sdk.UnwrapSDKContext(ctx)
contractInfo := k.GetContractInfo(sdkCtx, contractAddress)
if contractInfo == nil {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
}
if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract")
}
contractInfo.Label = newLabel
k.mustStoreContractInfo(sdkCtx, contractAddress, contractInfo)
sdkCtx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeUpdateContractLabel,
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
sdk.NewAttribute(types.AttributeKeyNewLabel, newLabel),
))
return nil
}
func (k Keeper) appendToContractHistory(ctx context.Context, contractAddr sdk.AccAddress, newEntries ...types.ContractCodeHistoryEntry) error {
store := k.storeService.OpenKVStore(ctx)
// find last element position
var pos uint64
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(store), types.GetContractCodeHistoryElementPrefix(contractAddr))
iter := prefixStore.ReverseIterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
if len(iter.Key()) == 8 { // add extra safety in a mixed contract length environment
pos = sdk.BigEndianToUint64(iter.Key())
break
}
}
// then store with incrementing position
for _, e := range newEntries {
pos++
key := types.GetContractCodeHistoryElementKey(contractAddr, pos)
if err := store.Set(key, k.cdc.MustMarshal(&e)); err != nil {
return err
}
}
return nil
}
func (k Keeper) GetContractHistory(ctx context.Context, contractAddr sdk.AccAddress) []types.ContractCodeHistoryEntry {
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.GetContractCodeHistoryElementPrefix(contractAddr))
r := make([]types.ContractCodeHistoryEntry, 0)
iter := prefixStore.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
if len(iter.Key()) != 8 { // add extra safety in a mixed contract length environment
continue
}
var e types.ContractCodeHistoryEntry
k.cdc.MustUnmarshal(iter.Value(), &e)
r = append(r, e)
}
return r
}
// mustGetLastContractHistoryEntry returns the last element from history. To be used internally only as it panics when none exists
func (k Keeper) mustGetLastContractHistoryEntry(ctx context.Context, contractAddr sdk.AccAddress) types.ContractCodeHistoryEntry {
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.GetContractCodeHistoryElementPrefix(contractAddr))
iter := prefixStore.ReverseIterator(nil, nil)
defer iter.Close()
var r types.ContractCodeHistoryEntry
for ; iter.Valid(); iter.Next() {
if len(iter.Key()) == 8 { // add extra safety in a mixed contract length environment
k.cdc.MustUnmarshal(iter.Value(), &r)
return r
}
}
// all contracts have a history
panic(fmt.Sprintf("no history for %s", contractAddr.String()))
}
// QuerySmart queries the smart contract itself.
func (k Keeper) QuerySmart(ctx context.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "query-smart")
// checks and increase query stack size
sdkCtx, err := checkAndIncreaseQueryStackSize(sdk.UnwrapSDKContext(ctx), k.maxQueryStackSize)
if err != nil {
return nil, err
}
contractInfo, codeInfo, prefixStore, err := k.contractInstance(sdkCtx, contractAddr)
if err != nil {
return nil, err
}
sdkCtx, discount := k.checkDiscountEligibility(sdkCtx, codeInfo.CodeHash, k.IsPinnedCode(ctx, contractInfo.CodeID))
setupCost := k.gasRegister.SetupContractCost(discount, len(req))
sdkCtx.GasMeter().ConsumeGas(setupCost, "Loading CosmWasm module: query")
// prepare querier
querier := k.newQueryHandler(sdkCtx, contractAddr)
env := types.NewEnv(sdkCtx, contractAddr)
queryResult, gasUsed, qErr := k.wasmVM.Query(codeInfo.CodeHash, env, req, prefixStore, cosmwasmAPI, querier, k.gasMeter(sdkCtx), k.runtimeGasForContract(sdkCtx), costJSONDeserialization)
k.consumeRuntimeGas(sdkCtx, gasUsed)
if qErr != nil {
return nil, errorsmod.Wrap(types.ErrVMError, qErr.Error())
}
if queryResult.Err != "" {
return nil, types.MarkErrorDeterministic(errorsmod.Wrap(types.ErrQueryFailed, queryResult.Err))
}
return queryResult.Ok, nil
}
func checkAndIncreaseQueryStackSize(ctx context.Context, maxQueryStackSize uint32) (sdk.Context, error) {
var queryStackSize uint32 = 0
if size, ok := types.QueryStackSize(ctx); ok {
queryStackSize = size
}
// increase
queryStackSize++
// did we go too far?
if queryStackSize > maxQueryStackSize {
return sdk.Context{}, types.ErrExceedMaxQueryStackSize
}
// set updated stack size
return types.WithQueryStackSize(sdk.UnwrapSDKContext(ctx), queryStackSize), nil
}
func checkAndIncreaseCallDepth(ctx context.Context, maxCallDepth uint32) (sdk.Context, error) {
var callDepth uint32 = 0
if size, ok := types.CallDepth(ctx); ok {
callDepth = size
}
// increase
callDepth++
// did we go too far?
if callDepth > maxCallDepth {
return sdk.Context{}, types.ErrExceedMaxCallDepth
}
// set updated stack size
return types.WithCallDepth(sdk.UnwrapSDKContext(ctx), callDepth), nil
}
// QueryRaw returns the contract's state for give key. Returns `nil` when key is `nil`.
func (k Keeper) QueryRaw(ctx context.Context, contractAddress sdk.AccAddress, key []byte) []byte {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "query-raw")
if key == nil {
return nil
}
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), prefixStoreKey)
return prefixStore.Get(key)
}
// internal helper function
func (k Keeper) contractInstance(ctx context.Context, contractAddress sdk.AccAddress) (types.ContractInfo, types.CodeInfo, wasmvm.KVStore, error) {
store := k.storeService.OpenKVStore(ctx)
contractBz, err := store.Get(types.GetContractAddressKey(contractAddress))
if err != nil {
return types.ContractInfo{}, types.CodeInfo{}, nil, err
}
if contractBz == nil {
return types.ContractInfo{}, types.CodeInfo{}, nil, types.ErrNoSuchContractFn(contractAddress.String()).
Wrapf("address %s", contractAddress.String())
}
var contractInfo types.ContractInfo
k.cdc.MustUnmarshal(contractBz, &contractInfo)
codeInfoBz, err := store.Get(types.GetCodeKey(contractInfo.CodeID))
if err != nil {
return types.ContractInfo{}, types.CodeInfo{}, nil, err
}
if codeInfoBz == nil {
return contractInfo, types.CodeInfo{}, nil, types.ErrNoSuchCodeFn(contractInfo.CodeID).
Wrapf("code id %d", contractInfo.CodeID)
}
var codeInfo types.CodeInfo
k.cdc.MustUnmarshal(codeInfoBz, &codeInfo)
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), prefixStoreKey)
return contractInfo, codeInfo, types.NewStoreAdapter(prefixStore), nil
}
func (k Keeper) LoadAsyncAckPacket(ctx context.Context, portID, channelID string, sequence uint64) (channeltypes.Packet, error) {
prefixStore, key := k.getAsyncAckStoreAndKey(ctx, portID, channelID, sequence)
packetBz := prefixStore.Get(key)
if len(packetBz) == 0 {
return channeltypes.Packet{}, types.ErrNotFound.Wrap("packet")
}
var packet channeltypes.Packet
// unmarshal packet
if err := k.cdc.Unmarshal(packetBz, &packet); err != nil {
return channeltypes.Packet{}, err
}
return packet, nil
}
func (k Keeper) StoreAsyncAckPacket(ctx context.Context, packet channeltypes.Packet) error {
prefixStore, key := k.getAsyncAckStoreAndKey(ctx, packet.DestinationPort, packet.DestinationChannel, packet.Sequence)
packetBz, err := k.cdc.Marshal(&packet)
if err != nil {
return err
}
prefixStore.Set(key, packetBz)
return nil
}
func (k Keeper) DeleteAsyncAckPacket(ctx context.Context, portID, channelID string, sequence uint64) {
prefixStore, key := k.getAsyncAckStoreAndKey(ctx, portID, channelID, sequence)
prefixStore.Delete(key)
}
func (k Keeper) getAsyncAckStoreAndKey(ctx context.Context, portID, channelID string, sequence uint64) (prefix.Store, []byte) {
// packets are stored under the destination port
prefixStoreKey := types.GetAsyncAckStorePrefix(portID)
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), prefixStoreKey)
key := types.GetAsyncPacketKey(channelID, sequence)
return prefixStore, key
}
func (k Keeper) GetContractInfo(ctx context.Context, contractAddress sdk.AccAddress) *types.ContractInfo {
store := k.storeService.OpenKVStore(ctx)
var contract types.ContractInfo
contractBz, err := store.Get(types.GetContractAddressKey(contractAddress))
if err != nil {
panic(err)
}
if contractBz == nil {
return nil
}
k.cdc.MustUnmarshal(contractBz, &contract)
return &contract
}
func (k Keeper) HasContractInfo(ctx context.Context, contractAddress sdk.AccAddress) bool {
store := k.storeService.OpenKVStore(ctx)
ok, err := store.Has(types.GetContractAddressKey(contractAddress))
if err != nil {
panic(err)
}
return ok
}
// mustStoreContractInfo persists the ContractInfo. No secondary index updated here.
func (k Keeper) mustStoreContractInfo(ctx context.Context, contractAddress sdk.AccAddress, contract *types.ContractInfo) {
store := k.storeService.OpenKVStore(ctx)
err := store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshal(contract))
if err != nil {
panic(err)
}
}
func (k Keeper) IterateContractInfo(ctx context.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) {
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.ContractKeyPrefix)
iter := prefixStore.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var contract types.ContractInfo
k.cdc.MustUnmarshal(iter.Value(), &contract)
// cb returns true to stop early
if cb(iter.Key(), contract) {
break
}
}
}
// IterateContractState iterates through all elements of the key value store for the given contract address and passes
// them to the provided callback function. The callback method can return true to abort early.
func (k Keeper) IterateContractState(ctx context.Context, contractAddress sdk.AccAddress, cb func(key, value []byte) bool) {
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), prefixStoreKey)
iter := prefixStore.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
if cb(iter.Key(), iter.Value()) {
break
}
}
}
func (k Keeper) importContractState(ctx context.Context, contractAddress sdk.AccAddress, models []types.Model) error {
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), prefixStoreKey)
for _, model := range models {
if model.Value == nil {
model.Value = []byte{}
}
if prefixStore.Has(model.Key) {
return errorsmod.Wrapf(types.ErrDuplicate, "duplicate key: %x", model.Key)
}
prefixStore.Set(model.Key, model.Value)
}
return nil
}
func (k Keeper) GetCodeInfo(ctx context.Context, codeID uint64) *types.CodeInfo {
store := k.storeService.OpenKVStore(ctx)
var codeInfo types.CodeInfo
codeInfoBz, err := store.Get(types.GetCodeKey(codeID))
if err != nil {
panic(err)
}
if codeInfoBz == nil {
return nil
}
k.cdc.MustUnmarshal(codeInfoBz, &codeInfo)
return &codeInfo
}
func (k Keeper) containsCodeInfo(ctx context.Context, codeID uint64) bool {
store := k.storeService.OpenKVStore(ctx)
ok, err := store.Has(types.GetCodeKey(codeID))
if err != nil {
panic(err)
}
return ok
}
func (k Keeper) IterateCodeInfos(ctx context.Context, cb func(uint64, types.CodeInfo) bool) {
prefixStore := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.CodeKeyPrefix)
iter := prefixStore.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var c types.CodeInfo
k.cdc.MustUnmarshal(iter.Value(), &c)
// cb returns true to stop early
if cb(binary.BigEndian.Uint64(iter.Key()), c) {
return
}
}
}
func (k Keeper) GetByteCode(ctx context.Context, codeID uint64) ([]byte, error) {
store := k.storeService.OpenKVStore(ctx)
var codeInfo types.CodeInfo
codeInfoBz, err := store.Get(types.GetCodeKey(codeID))
if err != nil {
return nil, err
}
if codeInfoBz == nil {
return nil, nil
}
k.cdc.MustUnmarshal(codeInfoBz, &codeInfo)
return k.wasmVM.GetCode(codeInfo.CodeHash)
}
// PinCode pins the wasm contract in wasmvm cache
func (k Keeper) pinCode(ctx context.Context, codeID uint64) error {
codeInfo := k.GetCodeInfo(ctx, codeID)
if codeInfo == nil {
return types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID)
}
if err := k.wasmVM.Pin(codeInfo.CodeHash); err != nil {
return errorsmod.Wrap(types.ErrPinContractFailed, err.Error())
}
store := k.storeService.OpenKVStore(ctx)
// store 1 byte to not run into `nil` debugging issues
err := store.Set(types.GetPinnedCodeIndexPrefix(codeID), []byte{1})
if err != nil {
return err
}
sdk.UnwrapSDKContext(ctx).EventManager().EmitEvent(sdk.NewEvent(
types.EventTypePinCode,
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)),
))
return nil
}
// UnpinCode removes the wasm contract from wasmvm cache
func (k Keeper) unpinCode(ctx context.Context, codeID uint64) error {
codeInfo := k.GetCodeInfo(ctx, codeID)
if codeInfo == nil {
return types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID)
}
if err := k.wasmVM.Unpin(codeInfo.CodeHash); err != nil {
return errorsmod.Wrap(types.ErrUnpinContractFailed, err.Error())
}
store := k.storeService.OpenKVStore(ctx)
err := store.Delete(types.GetPinnedCodeIndexPrefix(codeID))
if err != nil {
return err
}
sdk.UnwrapSDKContext(ctx).EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeUnpinCode,
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)),
))
return nil
}
// IsPinnedCode returns true when codeID is pinned in wasmvm cache
func (k Keeper) IsPinnedCode(ctx context.Context, codeID uint64) bool {
store := k.storeService.OpenKVStore(ctx)
ok, err := store.Has(types.GetPinnedCodeIndexPrefix(codeID))
if err != nil {
panic(err)
}
return ok
}
func (k Keeper) checkDiscountEligibility(ctx sdk.Context, checksum []byte, isPinned bool) (sdk.Context, bool) {
if isPinned {
return ctx, true
}
txContracts, ok := types.TxContractsFromContext(ctx)
if !ok || txContracts.GetContracts() == nil {
return ctx, false
} else if txContracts.Exists(checksum) {
return ctx, true
}
txContracts.AddContract(checksum)
return types.WithTxContracts(ctx, txContracts), false
}
// InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned
func (k Keeper) InitializePinnedCodes(ctx context.Context) error {
store := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.PinnedCodeIndexPrefix)
iter := store.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
codeID := types.ParsePinnedCodeIndex(iter.Key())
codeInfo := k.GetCodeInfo(ctx, codeID)
if codeInfo == nil {
return types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID)
}
if err := k.wasmVM.Pin(codeInfo.CodeHash); err != nil {
return errorsmod.Wrap(types.ErrPinContractFailed, err.Error())
}
}
return nil
}
// setContractInfoExtension updates the extension point data that is stored with the contract info
func (k Keeper) setContractInfoExtension(ctx context.Context, contractAddr sdk.AccAddress, ext types.ContractInfoExtension) error {
info := k.GetContractInfo(ctx, contractAddr)
if info == nil {
return types.ErrNoSuchContractFn(contractAddr.String()).
Wrapf("address %s", contractAddr.String())
}
if err := info.SetExtension(ext); err != nil {
return err
}
k.mustStoreContractInfo(ctx, contractAddr, info)
return nil
}
// setAccessConfig updates the access config of a code id.
func (k Keeper) setAccessConfig(ctx context.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, authz types.AuthorizationPolicy) error {
info := k.GetCodeInfo(ctx, codeID)
if info == nil {
return types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID)
}
isSubset := newConfig.Permission.IsSubset(k.getInstantiateAccessConfig(ctx))
if !authz.CanModifyCodeAccessConfig(sdk.MustAccAddressFromBech32(info.Creator), caller, isSubset) {
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify code access config")
}
info.InstantiateConfig = newConfig
k.mustStoreCodeInfo(ctx, codeID, *info)
evt := sdk.NewEvent(
types.EventTypeUpdateCodeAccessConfig,
sdk.NewAttribute(types.AttributeKeyCodePermission, newConfig.Permission.String()),
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)),
)
if addrs := newConfig.AllAuthorizedAddresses(); len(addrs) != 0 {
attr := sdk.NewAttribute(types.AttributeKeyAuthorizedAddresses, strings.Join(addrs, ","))
evt.Attributes = append(evt.Attributes, attr.ToKVPair())
}
sdk.UnwrapSDKContext(ctx).EventManager().EmitEvent(evt)
return nil
}
// handleContractResponse processes the contract response data by emitting events and sending sub-/messages.
func (k *Keeper) handleContractResponse(
ctx sdk.Context,
contractAddr sdk.AccAddress,
ibcPort string,
msgs []wasmvmtypes.SubMsg,
attrs []wasmvmtypes.EventAttribute,
data []byte,
evts wasmvmtypes.Array[wasmvmtypes.Event],
) ([]byte, error) {
attributeGasCost := k.gasRegister.EventCosts(attrs, evts)
ctx.GasMeter().ConsumeGas(attributeGasCost, "Custom contract event attributes")
// emit all events from this contract itself
if len(attrs) != 0 {
wasmEvents, err := newWasmModuleEvent(attrs, contractAddr)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(wasmEvents)
}
if len(evts) > 0 {
customEvents, err := newCustomEvents(evts, contractAddr)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(customEvents)
}
return k.wasmVMResponseHandler.Handle(ctx, contractAddr, ibcPort, msgs, data)
}
func (k Keeper) runtimeGasForContract(ctx sdk.Context) uint64 {
meter := ctx.GasMeter()
if meter.IsOutOfGas() {
return 0
}
if meter.Limit() == math.MaxUint64 { // infinite gas meter and not out of gas
return math.MaxUint64
}
return k.gasRegister.ToWasmVMGas(meter.Limit() - meter.GasConsumedToLimit())
}
func (k Keeper) consumeRuntimeGas(ctx sdk.Context, gas uint64) {
consumed := k.gasRegister.FromWasmVMGas(gas)
ctx.GasMeter().ConsumeGas(consumed, "wasm contract")
// throw OutOfGas error if we ran out (got exactly to zero due to better limit enforcing)
if ctx.GasMeter().IsOutOfGas() {
panic(storetypes.ErrorOutOfGas{Descriptor: "Wasm engine function execution"})
}
}
func (k Keeper) mustAutoIncrementID(ctx context.Context, sequenceKey []byte) uint64 {
store := k.storeService.OpenKVStore(ctx)
bz, err := store.Get(sequenceKey)
if err != nil {
panic(err)
}
id := uint64(1)
if bz != nil {
id = binary.BigEndian.Uint64(bz)
}
bz = sdk.Uint64ToBigEndian(id + 1)
err = store.Set(sequenceKey, bz)
if err != nil {
panic(err)
}
return id
}
// PeekAutoIncrementID reads the current value without incrementing it.
func (k Keeper) PeekAutoIncrementID(ctx context.Context, sequenceKey []byte) (uint64, error) {
store := k.storeService.OpenKVStore(ctx)
bz, err := store.Get(sequenceKey)
if err != nil {
return 0, errorsmod.Wrap(err, "sequence key")
}
id := uint64(1)
if bz != nil {
id = binary.BigEndian.Uint64(bz)
}
return id, nil
}
func (k Keeper) importAutoIncrementID(ctx context.Context, sequenceKey []byte, val uint64) error {
store := k.storeService.OpenKVStore(ctx)
ok, err := store.Has(sequenceKey)
if err != nil {
return errorsmod.Wrap(err, "sequence key")
}
if ok {
return errorsmod.Wrapf(types.ErrDuplicate, "autoincrement id: %s", string(sequenceKey))
}
bz := sdk.Uint64ToBigEndian(val)
return store.Set(sequenceKey, bz)
}
func (k Keeper) importContract(ctx context.Context, contractAddr sdk.AccAddress, c *types.ContractInfo, state []types.Model, historyEntries []types.ContractCodeHistoryEntry) error {
if !k.containsCodeInfo(ctx, c.CodeID) {
return types.ErrNoSuchCodeFn(c.CodeID).Wrapf("code id %d", c.CodeID)
}
if k.HasContractInfo(ctx, contractAddr) {
return errorsmod.Wrapf(types.ErrDuplicate, "contract: %s", contractAddr)
}
if len(historyEntries) == 0 {
return types.ErrEmpty.Wrap("contract history")
}
creatorAddress, err := sdk.AccAddressFromBech32(c.Creator)
if err != nil {
return err
}
err = k.appendToContractHistory(ctx, contractAddr, historyEntries...)
if err != nil {
return err
}
k.mustStoreContractInfo(ctx, contractAddr, c)
err = k.addToContractCodeSecondaryIndex(ctx, contractAddr, historyEntries[len(historyEntries)-1])
if err != nil {
return err
}
err = k.addToContractCreatorSecondaryIndex(ctx, creatorAddress, historyEntries[0].Updated, contractAddr)
if err != nil {
return err
}
return k.importContractState(ctx, contractAddr, state)
}
func (k Keeper) newQueryHandler(ctx sdk.Context, contractAddress sdk.AccAddress) QueryHandler {
return NewQueryHandler(ctx, k.wasmVMQueryHandler, contractAddress, k.gasRegister)
}
// MultipliedGasMeter wraps the GasMeter from context and multiplies all reads by out defined multiplier
type MultipliedGasMeter struct {
originalMeter storetypes.GasMeter
GasRegister types.GasRegister
}
func NewMultipliedGasMeter(originalMeter storetypes.GasMeter, gr types.GasRegister) MultipliedGasMeter {
return MultipliedGasMeter{originalMeter: originalMeter, GasRegister: gr}
}
var _ wasmvm.GasMeter = MultipliedGasMeter{}
func (m MultipliedGasMeter) GasConsumed() storetypes.Gas {
return m.GasRegister.ToWasmVMGas(m.originalMeter.GasConsumed())
}
func (k Keeper) gasMeter(ctx sdk.Context) MultipliedGasMeter {
return NewMultipliedGasMeter(ctx.GasMeter(), k.gasRegister)
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return moduleLogger(ctx)
}
func moduleLogger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// Querier creates a new grpc querier instance
func Querier(k *Keeper) *GrpcQuerier {
return NewGrpcQuerier(k.cdc, k.storeService, k, k.queryGasLimit)
}
// QueryGasLimit returns the gas limit for smart queries.
func (k Keeper) QueryGasLimit() storetypes.Gas {
return k.queryGasLimit
}
// BankCoinTransferrer replicates the cosmos-sdk behavior as in
// https://github.com/cosmos/cosmos-sdk/blob/v0.41.4/x/bank/keeper/msg_server.go#L26
type BankCoinTransferrer struct {
keeper types.BankKeeper
}
func NewBankCoinTransferrer(keeper types.BankKeeper) BankCoinTransferrer {
return BankCoinTransferrer{
keeper: keeper,
}
}
// TransferCoins transfers coins from source to destination account when coin send was enabled for them and the recipient
// is not in the blocked address list.
func (c BankCoinTransferrer) TransferCoins(parentCtx sdk.Context, fromAddr, toAddr sdk.AccAddress, amount sdk.Coins) error {
em := sdk.NewEventManager()
ctx := parentCtx.WithEventManager(em)
if err := c.keeper.IsSendEnabledCoins(ctx, amount...); err != nil {
return err
}
if c.keeper.BlockedAddr(toAddr) {
return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", toAddr.String())
}
sdkerr := c.keeper.SendCoins(ctx, fromAddr, toAddr, amount)
if sdkerr != nil {
return sdkerr
}
for _, e := range em.Events() {
if e.Type == sdk.EventTypeMessage { // skip messages as we talk to the keeper directly
continue
}
parentCtx.EventManager().EmitEvent(e)
}
return nil
}
var _ AccountPruner = VestingCoinBurner{}
// VestingCoinBurner default implementation for AccountPruner to burn the coins
type VestingCoinBurner struct {
bank types.BankKeeper
}
// NewVestingCoinBurner constructor
func NewVestingCoinBurner(bank types.BankKeeper) VestingCoinBurner {
if bank == nil {
panic("bank keeper must not be nil")
}
return VestingCoinBurner{bank: bank}
}
// CleanupExistingAccount accepts only vesting account types to burns all their original vesting coin balances.
// Other account types will be rejected and returned as unhandled.
func (b VestingCoinBurner) CleanupExistingAccount(ctx sdk.Context, existingAcc sdk.AccountI) (handled bool, err error) {
v, ok := existingAcc.(vestingexported.VestingAccount)
if !ok {
return false, nil
}
ctx = ctx.WithGasMeter(storetypes.NewInfiniteGasMeter())
coinsToBurn := sdk.NewCoins()
for _, orig := range v.GetOriginalVesting() { // focus on the coin denoms that were setup originally; getAllBalances has some issues
coinsToBurn = append(coinsToBurn, b.bank.GetBalance(ctx, existingAcc.GetAddress(), orig.Denom))
}
if err := b.bank.SendCoinsFromAccountToModule(ctx, existingAcc.GetAddress(), types.ModuleName, coinsToBurn); err != nil {
return false, errorsmod.Wrap(err, "prune account balance")
}
if err := b.bank.BurnCoins(ctx, types.ModuleName, coinsToBurn); err != nil {
return false, errorsmod.Wrap(err, "burn account balance")
}
return true, nil
}
type msgDispatcher interface {
DispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error)
}
// DefaultWasmVMContractResponseHandler default implementation that first dispatches submessage then normal messages.
// The Submessage execution may include an success/failure response handling by the contract that can overwrite the
// original
type DefaultWasmVMContractResponseHandler struct {
md msgDispatcher
}
func NewDefaultWasmVMContractResponseHandler(md msgDispatcher) *DefaultWasmVMContractResponseHandler {
return &DefaultWasmVMContractResponseHandler{md: md}
}
// Handle processes the data returned by a contract invocation.
func (h DefaultWasmVMContractResponseHandler) Handle(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, messages []wasmvmtypes.SubMsg, origRspData []byte) ([]byte, error) {
result := origRspData
switch rsp, err := h.md.DispatchSubmessages(ctx, contractAddr, ibcPort, messages); {
case err != nil:
return nil, err
case rsp != nil:
result = rsp
}
return result, nil
}