1189 lines
44 KiB
Go
1189 lines
44 KiB
Go
package keeper
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
wasmvm "github.com/CosmWasm/wasmvm"
|
|
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
"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"
|
|
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
"github.com/cerc-io/laconicd/x/wasm/ioutils"
|
|
"github.com/cerc-io/laconicd/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
|
|
|
|
type contextKey int
|
|
|
|
const (
|
|
// private type creates an interface key for Context that cannot be accessed by any other package
|
|
contextKeyQueryStackSize contextKey = iota
|
|
)
|
|
|
|
// 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 sdk.AccAddress, 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 authtypes.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 Wasmer with it's own data directory.
|
|
type Keeper struct {
|
|
storeKey sdk.StoreKey
|
|
cdc codec.Codec
|
|
accountKeeper types.AccountKeeper
|
|
bank CoinTransferrer
|
|
portKeeper types.PortKeeper
|
|
capabilityKeeper types.CapabilityKeeper
|
|
wasmVM types.WasmerEngine
|
|
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
|
|
paramSpace paramtypes.Subspace
|
|
gasRegister GasRegister
|
|
maxQueryStackSize uint32
|
|
acceptedAccountTypes map[reflect.Type]struct{}
|
|
accountPruner AccountPruner
|
|
}
|
|
|
|
func (k Keeper) getUploadAccessConfig(ctx sdk.Context) types.AccessConfig {
|
|
var a types.AccessConfig
|
|
k.paramSpace.Get(ctx, types.ParamStoreKeyUploadAccess, &a)
|
|
return a
|
|
}
|
|
|
|
func (k Keeper) getInstantiateAccessConfig(ctx sdk.Context) types.AccessType {
|
|
var a types.AccessType
|
|
k.paramSpace.Get(ctx, types.ParamStoreKeyInstantiateAccess, &a)
|
|
return a
|
|
}
|
|
|
|
// GetParams returns the total set of wasm parameters.
|
|
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
|
|
var params types.Params
|
|
k.paramSpace.GetParamSet(ctx, ¶ms)
|
|
return params
|
|
}
|
|
|
|
func (k Keeper) SetParams(ctx sdk.Context, ps types.Params) {
|
|
k.paramSpace.SetParamSet(ctx, &ps)
|
|
}
|
|
|
|
func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, checksum []byte, err error) {
|
|
if creator == nil {
|
|
return 0, checksum, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot be nil")
|
|
}
|
|
|
|
// figure out proper instantiate access
|
|
defaultAccessConfig := k.getInstantiateAccessConfig(ctx).With(creator)
|
|
if instantiateAccess == nil {
|
|
instantiateAccess = &defaultAccessConfig
|
|
}
|
|
chainConfigs := ChainAccessConfigs{
|
|
Instantiate: defaultAccessConfig,
|
|
Upload: k.getUploadAccessConfig(ctx),
|
|
}
|
|
|
|
if !authZ.CanCreateCode(chainConfigs, creator, *instantiateAccess) {
|
|
return 0, checksum, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not create code")
|
|
}
|
|
|
|
if ioutils.IsGzip(wasmCode) {
|
|
ctx.GasMeter().ConsumeGas(k.gasRegister.UncompressCosts(len(wasmCode)), "Uncompress gzip bytecode")
|
|
wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize))
|
|
if err != nil {
|
|
return 0, checksum, sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
}
|
|
|
|
ctx.GasMeter().ConsumeGas(k.gasRegister.CompileCosts(len(wasmCode)), "Compiling wasm bytecode")
|
|
checksum, err = k.wasmVM.Create(wasmCode)
|
|
if err != nil {
|
|
return 0, checksum, sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
report, err := k.wasmVM.AnalyzeCode(checksum)
|
|
if err != nil {
|
|
return 0, checksum, sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
codeID = k.autoIncrementID(ctx, types.KeyLastCodeID)
|
|
k.Logger(ctx).Debug("storing new contract", "capabilities", report.RequiredCapabilities, "code_id", codeID)
|
|
codeInfo := types.NewCodeInfo(checksum, creator, *instantiateAccess)
|
|
k.storeCodeInfo(ctx, 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(report.RequiredCapabilities, ",") {
|
|
evt.AppendAttributes(sdk.NewAttribute(types.AttributeKeyRequiredCapability, strings.TrimSpace(f)))
|
|
}
|
|
ctx.EventManager().EmitEvent(evt)
|
|
|
|
return codeID, checksum, nil
|
|
}
|
|
|
|
func (k Keeper) storeCodeInfo(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
// 0x01 | codeID (uint64) -> ContractInfo
|
|
store.Set(types.GetCodeKey(codeID), k.cdc.MustMarshal(&codeInfo))
|
|
}
|
|
|
|
func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error {
|
|
if ioutils.IsGzip(wasmCode) {
|
|
var err error
|
|
wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize))
|
|
if err != nil {
|
|
return sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
}
|
|
newCodeHash, err := k.wasmVM.Create(wasmCode)
|
|
if err != nil {
|
|
return sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
if !bytes.Equal(codeInfo.CodeHash, newCodeHash) {
|
|
return sdkerrors.Wrap(types.ErrInvalid, "code hashes not same")
|
|
}
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
key := types.GetCodeKey(codeID)
|
|
if store.Has(key) {
|
|
return sdkerrors.Wrapf(types.ErrDuplicate, "duplicate code: %d", codeID)
|
|
}
|
|
// 0x01 | codeID (uint64) -> ContractInfo
|
|
store.Set(key, k.cdc.MustMarshal(&codeInfo))
|
|
return nil
|
|
}
|
|
|
|
func (k Keeper) instantiate(
|
|
ctx sdk.Context,
|
|
codeID uint64,
|
|
creator, admin sdk.AccAddress,
|
|
initMsg []byte,
|
|
label string,
|
|
deposit sdk.Coins,
|
|
addressGenerator AddressGenerator,
|
|
authPolicy AuthorizationPolicy,
|
|
) (sdk.AccAddress, []byte, error) {
|
|
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "instantiate")
|
|
|
|
if creator == nil {
|
|
return nil, nil, types.ErrEmpty.Wrap("creator")
|
|
}
|
|
instanceCosts := k.gasRegister.NewContractInstanceCosts(k.IsPinnedCode(ctx, codeID), len(initMsg))
|
|
ctx.GasMeter().ConsumeGas(instanceCosts, "Loading CosmWasm module: instantiate")
|
|
|
|
// get contact info
|
|
codeInfo := k.GetCodeInfo(ctx, codeID)
|
|
if codeInfo == nil {
|
|
return nil, nil, sdkerrors.Wrap(types.ErrNotFound, "code")
|
|
}
|
|
if !authPolicy.CanInstantiateContract(codeInfo.InstantiateConfig, creator) {
|
|
return nil, nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not instantiate")
|
|
}
|
|
|
|
contractAddress := addressGenerator(ctx, codeID, codeInfo.CodeHash)
|
|
if k.HasContractInfo(ctx, contractAddress) {
|
|
return nil, nil, types.ErrDuplicate.Wrap("instance with this code id, sender and label exists: try a different label")
|
|
}
|
|
|
|
// 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(ctx, 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(ctx).Info("instantiate contract with existing account", "address", contractAddress.String())
|
|
} else {
|
|
// consider an account in the wasmd namespace spam and overwrite it.
|
|
k.Logger(ctx).Info("pruning existing account for contract instantiation", "address", contractAddress.String())
|
|
contractAccount := k.accountKeeper.NewAccountWithAddress(ctx, contractAddress)
|
|
k.accountKeeper.SetAccount(ctx, contractAccount)
|
|
// also handle balance to not open cases where these accounts are abused and become liquid
|
|
switch handled, err := k.accountPruner.CleanupExistingAccount(ctx, existingAcct); {
|
|
case err != nil:
|
|
return nil, nil, sdkerrors.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(ctx, contractAddress)
|
|
k.accountKeeper.SetAccount(ctx, contractAccount)
|
|
}
|
|
// deposit initial contract funds
|
|
if !deposit.IsZero() {
|
|
if err := k.bank.TransferCoins(ctx, creator, contractAddress, deposit); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
// prepare params for contract instantiate call
|
|
env := types.NewEnv(ctx, contractAddress)
|
|
info := types.NewInfo(creator, deposit)
|
|
|
|
// create prefixed data store
|
|
// 0x03 | BuildContractAddressClassic (sdk.AccAddress)
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
|
|
// prepare querier
|
|
querier := k.newQueryHandler(ctx, contractAddress)
|
|
|
|
// instantiate wasm contract
|
|
gas := k.runtimeGasForContract(ctx)
|
|
res, gasUsed, err := k.wasmVM.Instantiate(codeInfo.CodeHash, env, info, initMsg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization)
|
|
k.consumeRuntimeGas(ctx, gasUsed)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(types.ErrInstantiateFailed, err.Error())
|
|
}
|
|
|
|
// persist instance first
|
|
createdAt := types.NewAbsoluteTxPosition(ctx)
|
|
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, sdkerrors.Wrap(types.ErrInstantiateFailed, err.Error())
|
|
}
|
|
if report.HasIBCEntryPoints {
|
|
// register IBC port
|
|
ibcPort, err := k.ensureIbcPort(ctx, contractAddress)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
contractInfo.IBCPortID = ibcPort
|
|
}
|
|
|
|
// store contract before dispatch so that contract could be called back
|
|
historyEntry := contractInfo.InitialHistory(initMsg)
|
|
k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
|
|
k.addToContractCreatorSecondaryIndex(ctx, creator, historyEntry.Updated, contractAddress)
|
|
k.appendToContractHistory(ctx, contractAddress, historyEntry)
|
|
k.storeContractInfo(ctx, contractAddress, &contractInfo)
|
|
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
types.EventTypeInstantiate,
|
|
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
|
|
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)),
|
|
))
|
|
|
|
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "dispatch")
|
|
}
|
|
|
|
return contractAddress, data, nil
|
|
}
|
|
|
|
// Execute executes the contract instance
|
|
func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) {
|
|
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "execute")
|
|
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
executeCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(msg))
|
|
ctx.GasMeter().ConsumeGas(executeCosts, "Loading CosmWasm module: execute")
|
|
|
|
// add more funds
|
|
if !coins.IsZero() {
|
|
if err := k.bank.TransferCoins(ctx, caller, contractAddress, coins); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
env := types.NewEnv(ctx, contractAddress)
|
|
info := types.NewInfo(caller, coins)
|
|
|
|
// prepare querier
|
|
querier := k.newQueryHandler(ctx, contractAddress)
|
|
gas := k.runtimeGasForContract(ctx)
|
|
res, gasUsed, execErr := k.wasmVM.Execute(codeInfo.CodeHash, env, info, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization)
|
|
k.consumeRuntimeGas(ctx, gasUsed)
|
|
if execErr != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error())
|
|
}
|
|
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
types.EventTypeExecute,
|
|
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
|
|
))
|
|
|
|
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "dispatch")
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error) {
|
|
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "migrate")
|
|
migrateSetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, newCodeID), len(msg))
|
|
ctx.GasMeter().ConsumeGas(migrateSetupCosts, "Loading CosmWasm module: migrate")
|
|
|
|
contractInfo := k.GetContractInfo(ctx, contractAddress)
|
|
if contractInfo == nil {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
|
|
}
|
|
if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not migrate")
|
|
}
|
|
|
|
newCodeInfo := k.GetCodeInfo(ctx, newCodeID)
|
|
if newCodeInfo == nil {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown code")
|
|
}
|
|
|
|
if !authZ.CanInstantiateContract(newCodeInfo.InstantiateConfig, caller) {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "to use new code")
|
|
}
|
|
|
|
// check for IBC flag
|
|
switch report, err := k.wasmVM.AnalyzeCode(newCodeInfo.CodeHash); {
|
|
case err != nil:
|
|
return nil, sdkerrors.Wrap(types.ErrMigrationFailed, err.Error())
|
|
case !report.HasIBCEntryPoints && contractInfo.IBCPortID != "":
|
|
// prevent update to non ibc contract
|
|
return nil, sdkerrors.Wrap(types.ErrMigrationFailed, "requires ibc callbacks")
|
|
case report.HasIBCEntryPoints && contractInfo.IBCPortID == "":
|
|
// add ibc port
|
|
ibcPort, err := k.ensureIbcPort(ctx, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contractInfo.IBCPortID = ibcPort
|
|
}
|
|
|
|
env := types.NewEnv(ctx, contractAddress)
|
|
|
|
// prepare querier
|
|
querier := k.newQueryHandler(ctx, contractAddress)
|
|
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
gas := k.runtimeGasForContract(ctx)
|
|
res, gasUsed, err := k.wasmVM.Migrate(newCodeInfo.CodeHash, env, msg, &prefixStore, cosmwasmAPI, &querier, k.gasMeter(ctx), gas, costJSONDeserialization)
|
|
k.consumeRuntimeGas(ctx, gasUsed)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrMigrationFailed, err.Error())
|
|
}
|
|
// delete old secondary index entry
|
|
k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.getLastContractHistoryEntry(ctx, contractAddress))
|
|
// persist migration updates
|
|
historyEntry := contractInfo.AddMigration(ctx, newCodeID, msg)
|
|
k.appendToContractHistory(ctx, contractAddress, historyEntry)
|
|
k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
|
|
k.storeContractInfo(ctx, contractAddress, contractInfo)
|
|
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
types.EventTypeMigrate,
|
|
sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(newCodeID, 10)),
|
|
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
|
|
))
|
|
|
|
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "dispatch")
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// Sudo allows priviledged 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)
|
|
func (k Keeper) Sudo(ctx sdk.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
|
|
}
|
|
|
|
sudoSetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(msg))
|
|
ctx.GasMeter().ConsumeGas(sudoSetupCosts, "Loading CosmWasm module: sudo")
|
|
|
|
env := types.NewEnv(ctx, contractAddress)
|
|
|
|
// prepare querier
|
|
querier := k.newQueryHandler(ctx, contractAddress)
|
|
gas := k.runtimeGasForContract(ctx)
|
|
res, gasUsed, execErr := k.wasmVM.Sudo(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization)
|
|
k.consumeRuntimeGas(ctx, gasUsed)
|
|
if execErr != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error())
|
|
}
|
|
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
types.EventTypeSudo,
|
|
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
|
|
))
|
|
|
|
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events)
|
|
if err != nil {
|
|
return nil, sdkerrors.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
|
|
}
|
|
|
|
// always consider this pinned
|
|
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)
|
|
gas := k.runtimeGasForContract(ctx)
|
|
|
|
res, gasUsed, execErr := k.wasmVM.Reply(codeInfo.CodeHash, env, reply, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization)
|
|
k.consumeRuntimeGas(ctx, gasUsed)
|
|
if execErr != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error())
|
|
}
|
|
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
types.EventTypeReply,
|
|
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
|
|
))
|
|
|
|
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "dispatch")
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// addToContractCodeSecondaryIndex adds element to the index for contracts-by-codeid queries
|
|
func (k Keeper) addToContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry), []byte{})
|
|
}
|
|
|
|
// removeFromContractCodeSecondaryIndex removes element to the index for contracts-by-codeid queries
|
|
func (k Keeper) removeFromContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) {
|
|
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry))
|
|
}
|
|
|
|
// addToContractCreatorSecondaryIndex adds element to the index for contracts-by-creator queries
|
|
func (k Keeper) addToContractCreatorSecondaryIndex(ctx sdk.Context, creatorAddress sdk.AccAddress, position *types.AbsoluteTxPosition, contractAddress sdk.AccAddress) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
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 sdk.Context, creator sdk.AccAddress, cb func(address sdk.AccAddress) bool) {
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), 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 sdk.Context, codeID uint64, cb func(address sdk.AccAddress) bool) {
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), 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 sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) error {
|
|
contractInfo := k.GetContractInfo(ctx, contractAddress)
|
|
if contractInfo == nil {
|
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
|
|
}
|
|
if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) {
|
|
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract")
|
|
}
|
|
newAdminStr := newAdmin.String()
|
|
contractInfo.Admin = newAdminStr
|
|
k.storeContractInfo(ctx, contractAddress, contractInfo)
|
|
ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
types.EventTypeUpdateContractAdmin,
|
|
sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),
|
|
sdk.NewAttribute(types.AttributeKeyNewAdmin, newAdminStr),
|
|
))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (k Keeper) appendToContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress, newEntries ...types.ContractCodeHistoryEntry) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
// find last element position
|
|
var pos uint64
|
|
prefixStore := prefix.NewStore(store, types.GetContractCodeHistoryElementPrefix(contractAddr))
|
|
iter := prefixStore.ReverseIterator(nil, nil)
|
|
defer iter.Close()
|
|
|
|
if iter.Valid() {
|
|
pos = sdk.BigEndianToUint64(iter.Key())
|
|
}
|
|
// then store with incrementing position
|
|
for _, e := range newEntries {
|
|
pos++
|
|
key := types.GetContractCodeHistoryElementKey(contractAddr, pos)
|
|
store.Set(key, k.cdc.MustMarshal(&e)) //nolint:gosec
|
|
}
|
|
}
|
|
|
|
func (k Keeper) GetContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress) []types.ContractCodeHistoryEntry {
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr))
|
|
r := make([]types.ContractCodeHistoryEntry, 0)
|
|
iter := prefixStore.Iterator(nil, nil)
|
|
defer iter.Close()
|
|
|
|
for ; iter.Valid(); iter.Next() {
|
|
var e types.ContractCodeHistoryEntry
|
|
k.cdc.MustUnmarshal(iter.Value(), &e)
|
|
r = append(r, e)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// getLastContractHistoryEntry returns the last element from history. To be used internally only as it panics when none exists
|
|
func (k Keeper) getLastContractHistoryEntry(ctx sdk.Context, contractAddr sdk.AccAddress) types.ContractCodeHistoryEntry {
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr))
|
|
iter := prefixStore.ReverseIterator(nil, nil)
|
|
defer iter.Close()
|
|
|
|
var r types.ContractCodeHistoryEntry
|
|
if !iter.Valid() {
|
|
// all contracts have a history
|
|
panic(fmt.Sprintf("no history for %s", contractAddr.String()))
|
|
}
|
|
k.cdc.MustUnmarshal(iter.Value(), &r)
|
|
return r
|
|
}
|
|
|
|
// QuerySmart queries the smart contract itself.
|
|
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
|
|
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "query-smart")
|
|
|
|
// checks and increase query stack size
|
|
ctx, err := checkAndIncreaseQueryStackSize(ctx, k.maxQueryStackSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
smartQuerySetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(req))
|
|
ctx.GasMeter().ConsumeGas(smartQuerySetupCosts, "Loading CosmWasm module: query")
|
|
|
|
// prepare querier
|
|
querier := k.newQueryHandler(ctx, contractAddr)
|
|
|
|
env := types.NewEnv(ctx, contractAddr)
|
|
queryResult, gasUsed, qErr := k.wasmVM.Query(codeInfo.CodeHash, env, req, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), k.runtimeGasForContract(ctx), costJSONDeserialization)
|
|
k.consumeRuntimeGas(ctx, gasUsed)
|
|
if qErr != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrQueryFailed, qErr.Error())
|
|
}
|
|
return queryResult, nil
|
|
}
|
|
|
|
func checkAndIncreaseQueryStackSize(ctx sdk.Context, maxQueryStackSize uint32) (sdk.Context, error) {
|
|
var queryStackSize uint32
|
|
|
|
// read current value
|
|
if size := ctx.Context().Value(contextKeyQueryStackSize); size != nil {
|
|
queryStackSize = size.(uint32)
|
|
} else {
|
|
queryStackSize = 0
|
|
}
|
|
|
|
// increase
|
|
queryStackSize++
|
|
|
|
// did we go too far?
|
|
if queryStackSize > maxQueryStackSize {
|
|
return ctx, types.ErrExceedMaxQueryStackSize
|
|
}
|
|
|
|
// set updated stack size
|
|
ctx = ctx.WithContext(context.WithValue(ctx.Context(), contextKeyQueryStackSize, queryStackSize))
|
|
|
|
return ctx, nil
|
|
}
|
|
|
|
// QueryRaw returns the contract's state for give key. Returns `nil` when key is `nil`.
|
|
func (k Keeper) QueryRaw(ctx sdk.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(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
return prefixStore.Get(key)
|
|
}
|
|
|
|
func (k Keeper) contractInstance(ctx sdk.Context, contractAddress sdk.AccAddress) (types.ContractInfo, types.CodeInfo, prefix.Store, error) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
contractBz := store.Get(types.GetContractAddressKey(contractAddress))
|
|
if contractBz == nil {
|
|
return types.ContractInfo{}, types.CodeInfo{}, prefix.Store{}, sdkerrors.Wrap(types.ErrNotFound, "contract")
|
|
}
|
|
var contractInfo types.ContractInfo
|
|
k.cdc.MustUnmarshal(contractBz, &contractInfo)
|
|
|
|
codeInfoBz := store.Get(types.GetCodeKey(contractInfo.CodeID))
|
|
if codeInfoBz == nil {
|
|
return contractInfo, types.CodeInfo{}, prefix.Store{}, sdkerrors.Wrap(types.ErrNotFound, "code info")
|
|
}
|
|
var codeInfo types.CodeInfo
|
|
k.cdc.MustUnmarshal(codeInfoBz, &codeInfo)
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
return contractInfo, codeInfo, prefixStore, nil
|
|
}
|
|
|
|
func (k Keeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo {
|
|
store := ctx.KVStore(k.storeKey)
|
|
var contract types.ContractInfo
|
|
contractBz := store.Get(types.GetContractAddressKey(contractAddress))
|
|
if contractBz == nil {
|
|
return nil
|
|
}
|
|
k.cdc.MustUnmarshal(contractBz, &contract)
|
|
return &contract
|
|
}
|
|
|
|
func (k Keeper) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool {
|
|
store := ctx.KVStore(k.storeKey)
|
|
return store.Has(types.GetContractAddressKey(contractAddress))
|
|
}
|
|
|
|
// storeContractInfo persists the ContractInfo. No secondary index updated here.
|
|
func (k Keeper) storeContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress, contract *types.ContractInfo) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshal(contract))
|
|
}
|
|
|
|
func (k Keeper) IterateContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) {
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), 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 sdk.Context, contractAddress sdk.AccAddress, cb func(key, value []byte) bool) {
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), 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 sdk.Context, contractAddress sdk.AccAddress, models []types.Model) error {
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
for _, model := range models {
|
|
if model.Value == nil {
|
|
model.Value = []byte{}
|
|
}
|
|
if prefixStore.Has(model.Key) {
|
|
return sdkerrors.Wrapf(types.ErrDuplicate, "duplicate key: %x", model.Key)
|
|
}
|
|
prefixStore.Set(model.Key, model.Value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (k Keeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo {
|
|
store := ctx.KVStore(k.storeKey)
|
|
var codeInfo types.CodeInfo
|
|
codeInfoBz := store.Get(types.GetCodeKey(codeID))
|
|
if codeInfoBz == nil {
|
|
return nil
|
|
}
|
|
k.cdc.MustUnmarshal(codeInfoBz, &codeInfo)
|
|
return &codeInfo
|
|
}
|
|
|
|
func (k Keeper) containsCodeInfo(ctx sdk.Context, codeID uint64) bool {
|
|
store := ctx.KVStore(k.storeKey)
|
|
return store.Has(types.GetCodeKey(codeID))
|
|
}
|
|
|
|
func (k Keeper) IterateCodeInfos(ctx sdk.Context, cb func(uint64, types.CodeInfo) bool) {
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), 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 sdk.Context, codeID uint64) ([]byte, error) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
var codeInfo types.CodeInfo
|
|
codeInfoBz := store.Get(types.GetCodeKey(codeID))
|
|
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 sdk.Context, codeID uint64) error {
|
|
codeInfo := k.GetCodeInfo(ctx, codeID)
|
|
if codeInfo == nil {
|
|
return sdkerrors.Wrap(types.ErrNotFound, "code info")
|
|
}
|
|
|
|
if err := k.wasmVM.Pin(codeInfo.CodeHash); err != nil {
|
|
return sdkerrors.Wrap(types.ErrPinContractFailed, err.Error())
|
|
}
|
|
store := ctx.KVStore(k.storeKey)
|
|
// store 1 byte to not run into `nil` debugging issues
|
|
store.Set(types.GetPinnedCodeIndexPrefix(codeID), []byte{1})
|
|
|
|
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 sdk.Context, codeID uint64) error {
|
|
codeInfo := k.GetCodeInfo(ctx, codeID)
|
|
if codeInfo == nil {
|
|
return sdkerrors.Wrap(types.ErrNotFound, "code info")
|
|
}
|
|
if err := k.wasmVM.Unpin(codeInfo.CodeHash); err != nil {
|
|
return sdkerrors.Wrap(types.ErrUnpinContractFailed, err.Error())
|
|
}
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
store.Delete(types.GetPinnedCodeIndexPrefix(codeID))
|
|
|
|
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 sdk.Context, codeID uint64) bool {
|
|
store := ctx.KVStore(k.storeKey)
|
|
return store.Has(types.GetPinnedCodeIndexPrefix(codeID))
|
|
}
|
|
|
|
// InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned
|
|
func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error {
|
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PinnedCodeIndexPrefix)
|
|
iter := store.Iterator(nil, nil)
|
|
defer iter.Close()
|
|
|
|
for ; iter.Valid(); iter.Next() {
|
|
codeInfo := k.GetCodeInfo(ctx, types.ParsePinnedCodeIndex(iter.Key()))
|
|
if codeInfo == nil {
|
|
return sdkerrors.Wrap(types.ErrNotFound, "code info")
|
|
}
|
|
if err := k.wasmVM.Pin(codeInfo.CodeHash); err != nil {
|
|
return sdkerrors.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 sdk.Context, contractAddr sdk.AccAddress, ext types.ContractInfoExtension) error {
|
|
info := k.GetContractInfo(ctx, contractAddr)
|
|
if info == nil {
|
|
return sdkerrors.Wrap(types.ErrNotFound, "contract info")
|
|
}
|
|
if err := info.SetExtension(ext); err != nil {
|
|
return err
|
|
}
|
|
k.storeContractInfo(ctx, contractAddr, info)
|
|
return nil
|
|
}
|
|
|
|
// setAccessConfig updates the access config of a code id.
|
|
func (k Keeper) setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, authz AuthorizationPolicy) error {
|
|
info := k.GetCodeInfo(ctx, codeID)
|
|
if info == nil {
|
|
return sdkerrors.Wrap(types.ErrNotFound, "code info")
|
|
}
|
|
isSubset := newConfig.Permission.IsSubset(k.getInstantiateAccessConfig(ctx))
|
|
if !authz.CanModifyCodeAccessConfig(sdk.MustAccAddressFromBech32(info.Creator), caller, isSubset) {
|
|
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not modify code access config")
|
|
}
|
|
|
|
info.InstantiateConfig = newConfig
|
|
k.storeCodeInfo(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())
|
|
}
|
|
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.Events,
|
|
) ([]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() == 0 { // infinite gas meter with limit=0 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(sdk.ErrorOutOfGas{Descriptor: "Wasmer function execution"})
|
|
}
|
|
}
|
|
|
|
func (k Keeper) autoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(lastIDKey)
|
|
id := uint64(1)
|
|
if bz != nil {
|
|
id = binary.BigEndian.Uint64(bz)
|
|
}
|
|
bz = sdk.Uint64ToBigEndian(id + 1)
|
|
store.Set(lastIDKey, bz)
|
|
return id
|
|
}
|
|
|
|
// PeekAutoIncrementID reads the current value without incrementing it.
|
|
func (k Keeper) PeekAutoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(lastIDKey)
|
|
id := uint64(1)
|
|
if bz != nil {
|
|
id = binary.BigEndian.Uint64(bz)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func (k Keeper) importAutoIncrementID(ctx sdk.Context, lastIDKey []byte, val uint64) error {
|
|
store := ctx.KVStore(k.storeKey)
|
|
if store.Has(lastIDKey) {
|
|
return sdkerrors.Wrapf(types.ErrDuplicate, "autoincrement id: %s", string(lastIDKey))
|
|
}
|
|
bz := sdk.Uint64ToBigEndian(val)
|
|
store.Set(lastIDKey, bz)
|
|
return nil
|
|
}
|
|
|
|
func (k Keeper) importContract(ctx sdk.Context, contractAddr sdk.AccAddress, c *types.ContractInfo, state []types.Model, entries []types.ContractCodeHistoryEntry) error {
|
|
if !k.containsCodeInfo(ctx, c.CodeID) {
|
|
return sdkerrors.Wrapf(types.ErrNotFound, "code id: %d", c.CodeID)
|
|
}
|
|
if k.HasContractInfo(ctx, contractAddr) {
|
|
return sdkerrors.Wrapf(types.ErrDuplicate, "contract: %s", contractAddr)
|
|
}
|
|
|
|
creatorAddress, err := sdk.AccAddressFromBech32(c.Creator)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
k.appendToContractHistory(ctx, contractAddr, entries...)
|
|
k.storeContractInfo(ctx, contractAddr, c)
|
|
k.addToContractCodeSecondaryIndex(ctx, contractAddr, entries[len(entries)-1])
|
|
k.addToContractCreatorSecondaryIndex(ctx, creatorAddress, entries[0].Updated, contractAddr)
|
|
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 sdk.GasMeter
|
|
GasRegister GasRegister
|
|
}
|
|
|
|
func NewMultipliedGasMeter(originalMeter sdk.GasMeter, gr GasRegister) MultipliedGasMeter {
|
|
return MultipliedGasMeter{originalMeter: originalMeter, GasRegister: gr}
|
|
}
|
|
|
|
var _ wasmvm.GasMeter = MultipliedGasMeter{}
|
|
|
|
func (m MultipliedGasMeter) GasConsumed() sdk.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 { //nolint:revive
|
|
return NewGrpcQuerier(k.cdc, k.storeKey, k, k.queryGasLimit)
|
|
}
|
|
|
|
// QueryGasLimit returns the gas limit for smart queries.
|
|
func (k Keeper) QueryGasLimit() sdk.Gas {
|
|
return k.queryGasLimit
|
|
}
|
|
|
|
// BankCoinTransferrer replicates the cosmos-sdk behaviour 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 sdk.AccAddress, 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 sdkerrors.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 authtypes.AccountI) (handled bool, err error) {
|
|
v, ok := existingAcc.(vestingexported.VestingAccount)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
ctx = ctx.WithGasMeter(sdk.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, sdkerrors.Wrap(err, "prune account balance")
|
|
}
|
|
if err := b.bank.BurnCoins(ctx, types.ModuleName, coinsToBurn); err != nil {
|
|
return false, sdkerrors.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, sdkerrors.Wrap(err, "submessages")
|
|
case rsp != nil:
|
|
result = rsp
|
|
}
|
|
return result, nil
|
|
}
|