mirror of https://github.com/certusone/wasmd.git
780 lines
28 KiB
Go
780 lines
28 KiB
Go
package keeper
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
|
|
wasmvm "github.com/CosmWasm/wasmvm"
|
|
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
|
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
|
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
|
|
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
|
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
)
|
|
|
|
// GasMultiplier is how many cosmwasm gas points = 1 sdk gas point
|
|
// SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164
|
|
// A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io
|
|
// Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read)
|
|
//
|
|
// Please not that all gas prices returned to the wasmer engine should have this multiplied
|
|
const GasMultiplier uint64 = 100
|
|
|
|
// MaxGas for a contract is 10 billion wasmer gas (enforced in rust to prevent overflow)
|
|
// The limit for v0.9.3 is defined here: https://github.com/CosmWasm/cosmwasm/blob/v0.9.3/packages/vm/src/backends/singlepass.rs#L15-L23
|
|
// (this will be increased in future releases)
|
|
const MaxGas = 10_000_000_000
|
|
|
|
// InstanceCost is how much SDK gas we charge each time we load a WASM instance.
|
|
// Creating a new instance is costly, and this helps put a recursion limit to contracts calling contracts.
|
|
const InstanceCost uint64 = 40_000
|
|
|
|
// CompileCost is how much SDK gas we charge *per byte* for compiling WASM code.
|
|
const CompileCost uint64 = 2
|
|
|
|
// 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)
|
|
}
|
|
|
|
type messenger interface {
|
|
Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error
|
|
}
|
|
|
|
// Keeper will have a reference to Wasmer with it's own data directory.
|
|
type Keeper struct {
|
|
storeKey sdk.StoreKey
|
|
cdc codec.Marshaler
|
|
accountKeeper authkeeper.AccountKeeper
|
|
bankKeeper bankkeeper.Keeper
|
|
ChannelKeeper types.ChannelKeeper
|
|
portKeeper types.PortKeeper
|
|
capabilityKeeper types.CapabilityKeeper
|
|
|
|
wasmer types.WasmerEngine
|
|
queryPlugins QueryPlugins
|
|
messenger messenger
|
|
// queryGasLimit is the max wasmvm gas that can be spent on executing a query with a contract
|
|
queryGasLimit uint64
|
|
authZPolicy AuthorizationPolicy
|
|
paramSpace paramtypes.Subspace
|
|
}
|
|
|
|
// NewKeeper creates a new contract Keeper instance
|
|
// If customEncoders is non-nil, we can use this to override some of the message handler, especially custom
|
|
func NewKeeper(
|
|
cdc codec.Marshaler,
|
|
storeKey sdk.StoreKey,
|
|
paramSpace paramtypes.Subspace,
|
|
accountKeeper authkeeper.AccountKeeper,
|
|
bankKeeper bankkeeper.Keeper,
|
|
stakingKeeper stakingkeeper.Keeper,
|
|
distKeeper distributionkeeper.Keeper,
|
|
channelKeeper types.ChannelKeeper,
|
|
portKeeper types.PortKeeper,
|
|
capabilityKeeper types.CapabilityKeeper,
|
|
router sdk.Router,
|
|
homeDir string,
|
|
wasmConfig types.WasmConfig,
|
|
supportedFeatures string,
|
|
customEncoders *MessageEncoders,
|
|
customPlugins *QueryPlugins,
|
|
opts ...Option,
|
|
) Keeper {
|
|
wasmer, err := wasmvm.NewVM(filepath.Join(homeDir, "wasm"), supportedFeatures, contractMemoryLimit, wasmConfig.ContractDebugMode, wasmConfig.MemoryCacheSize)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// set KeyTable if it has not already been set
|
|
if !paramSpace.HasKeyTable() {
|
|
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
|
|
}
|
|
|
|
messageEncoders := DefaultEncoders(channelKeeper, capabilityKeeper).Merge(customEncoders)
|
|
keeper := Keeper{
|
|
storeKey: storeKey,
|
|
cdc: cdc,
|
|
wasmer: wasmer,
|
|
accountKeeper: accountKeeper,
|
|
bankKeeper: bankKeeper,
|
|
ChannelKeeper: channelKeeper,
|
|
portKeeper: portKeeper,
|
|
capabilityKeeper: capabilityKeeper,
|
|
messenger: NewDefaultMessageHandler(router, channelKeeper, capabilityKeeper, &messageEncoders),
|
|
queryGasLimit: wasmConfig.SmartQueryGasLimit,
|
|
authZPolicy: DefaultAuthorizationPolicy{},
|
|
paramSpace: paramSpace,
|
|
}
|
|
keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, &keeper).Merge(customPlugins)
|
|
for _, o := range opts {
|
|
o.apply(&keeper)
|
|
}
|
|
return keeper
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (k Keeper) GetMaxWasmCodeSize(ctx sdk.Context) uint64 {
|
|
var a uint64
|
|
k.paramSpace.Get(ctx, types.ParamStoreKeyMaxWasmCodeSize, &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)
|
|
}
|
|
|
|
// Create uploads and compiles a WASM contract, returning a short identifier for the contract
|
|
func (k Keeper) Create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, source string, builder string, instantiateAccess *types.AccessConfig) (codeID uint64, err error) {
|
|
return k.create(ctx, creator, wasmCode, source, builder, instantiateAccess, k.authZPolicy)
|
|
}
|
|
|
|
func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, source string, builder string, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, err error) {
|
|
if !authZ.CanCreateCode(k.getUploadAccessConfig(ctx), creator) {
|
|
return 0, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not create code")
|
|
}
|
|
wasmCode, err = uncompress(wasmCode, k.GetMaxWasmCodeSize(ctx))
|
|
if err != nil {
|
|
return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
ctx.GasMeter().ConsumeGas(CompileCost*uint64(len(wasmCode)), "Compiling WASM Bytecode")
|
|
|
|
codeHash, err := k.wasmer.Create(wasmCode)
|
|
if err != nil {
|
|
return 0, sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
codeID = k.autoIncrementID(ctx, types.KeyLastCodeID)
|
|
if instantiateAccess == nil {
|
|
defaultAccessConfig := k.getInstantiateAccessConfig(ctx).With(creator)
|
|
instantiateAccess = &defaultAccessConfig
|
|
}
|
|
codeInfo := types.NewCodeInfo(codeHash, creator, source, builder, *instantiateAccess)
|
|
k.storeCodeInfo(ctx, codeID, codeInfo)
|
|
return codeID, 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.MustMarshalBinaryBare(&codeInfo))
|
|
}
|
|
|
|
func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error {
|
|
wasmCode, err := uncompress(wasmCode, k.GetMaxWasmCodeSize(ctx))
|
|
if err != nil {
|
|
return sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
|
|
}
|
|
newCodeHash, err := k.wasmer.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.MustMarshalBinaryBare(&codeInfo))
|
|
return nil
|
|
}
|
|
|
|
// Instantiate creates an instance of a WASM contract
|
|
func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, error) {
|
|
return k.instantiate(ctx, codeID, creator, admin, initMsg, label, deposit, k.authZPolicy)
|
|
}
|
|
|
|
func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.AccAddress, error) {
|
|
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: init")
|
|
|
|
// create contract address
|
|
contractAddress := k.generateContractAddress(ctx, codeID)
|
|
existingAcct := k.accountKeeper.GetAccount(ctx, contractAddress)
|
|
if existingAcct != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrAccountExists, existingAcct.GetAddress().String())
|
|
}
|
|
|
|
// deposit initial contract funds
|
|
if !deposit.IsZero() {
|
|
if k.bankKeeper.BlockedAddr(creator) {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "blocked address can not be used")
|
|
}
|
|
sdkerr := k.bankKeeper.SendCoins(ctx, creator, contractAddress, deposit)
|
|
if sdkerr != nil {
|
|
return nil, sdkerr
|
|
}
|
|
} else {
|
|
// create an empty account (so we don't have issues later)
|
|
// TODO: can we remove this?
|
|
contractAccount := k.accountKeeper.NewAccountWithAddress(ctx, contractAddress)
|
|
k.accountKeeper.SetAccount(ctx, contractAccount)
|
|
}
|
|
|
|
// get contact info
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(types.GetCodeKey(codeID))
|
|
if bz == nil {
|
|
return nil, sdkerrors.Wrap(types.ErrNotFound, "code")
|
|
}
|
|
var codeInfo types.CodeInfo
|
|
k.cdc.MustUnmarshalBinaryBare(bz, &codeInfo)
|
|
|
|
if !authZ.CanInstantiateContract(codeInfo.InstantiateConfig, creator) {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not instantiate")
|
|
}
|
|
|
|
// prepare params for contract instantiate call
|
|
env := types.NewEnv(ctx, contractAddress)
|
|
info := types.NewInfo(creator, deposit)
|
|
|
|
// create prefixed data store
|
|
// 0x03 | contractAddress (sdk.AccAddress)
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
|
|
// prepare querier
|
|
querier := QueryHandler{
|
|
Ctx: ctx,
|
|
Plugins: k.queryPlugins,
|
|
}
|
|
|
|
// instantiate wasm contract
|
|
gas := gasForContract(ctx)
|
|
res, gasUsed, err := k.wasmer.Instantiate(codeInfo.CodeHash, env, info, initMsg, prefixStore, cosmwasmAPI, querier, gasMeter(ctx), gas)
|
|
consumeGas(ctx, gasUsed)
|
|
if err != nil {
|
|
return contractAddress, sdkerrors.Wrap(types.ErrInstantiateFailed, err.Error())
|
|
}
|
|
|
|
// emit all events from this contract itself
|
|
events := types.ParseEvents(res.Attributes, contractAddress)
|
|
ctx.EventManager().EmitEvents(events)
|
|
|
|
// persist instance first
|
|
createdAt := types.NewAbsoluteTxPosition(ctx)
|
|
contractInfo := types.NewContractInfo(codeID, creator, admin, label, createdAt)
|
|
|
|
// check for IBC flag
|
|
report, err := k.wasmer.AnalyzeCode(codeInfo.CodeHash)
|
|
if err != nil {
|
|
return contractAddress, sdkerrors.Wrap(types.ErrInstantiateFailed, err.Error())
|
|
}
|
|
if report.HasIBCEntryPoints {
|
|
// register IBC port
|
|
ibcPort, err := k.ensureIbcPort(ctx, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contractInfo.IBCPortID = ibcPort
|
|
}
|
|
|
|
k.storeContractInfo(ctx, contractAddress, &contractInfo)
|
|
k.appendToContractHistory(ctx, contractAddress, contractInfo.InitialHistory(initMsg))
|
|
|
|
// then dispatch so that contract could be called back
|
|
err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return contractAddress, nil
|
|
}
|
|
|
|
// Execute executes the contract instance
|
|
func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) (*sdk.Result, error) {
|
|
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: execute")
|
|
|
|
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// add more funds
|
|
if !coins.IsZero() {
|
|
if k.bankKeeper.BlockedAddr(caller) {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "blocked address can not be used")
|
|
}
|
|
|
|
sdkerr := k.bankKeeper.SendCoins(ctx, caller, contractAddress, coins)
|
|
if sdkerr != nil {
|
|
return nil, sdkerr
|
|
}
|
|
}
|
|
|
|
env := types.NewEnv(ctx, contractAddress)
|
|
info := types.NewInfo(caller, coins)
|
|
|
|
// prepare querier
|
|
querier := QueryHandler{
|
|
Ctx: ctx,
|
|
Plugins: k.queryPlugins,
|
|
}
|
|
gas := gasForContract(ctx)
|
|
res, gasUsed, execErr := k.wasmer.Execute(codeInfo.CodeHash, env, info, msg, prefixStore, cosmwasmAPI, querier, gasMeter(ctx), gas)
|
|
consumeGas(ctx, gasUsed)
|
|
if execErr != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error())
|
|
}
|
|
|
|
// emit all events from this contract itself
|
|
events := types.ParseEvents(res.Attributes, contractAddress)
|
|
ctx.EventManager().EmitEvents(events)
|
|
|
|
err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &sdk.Result{
|
|
Data: res.Data,
|
|
}, nil
|
|
}
|
|
|
|
// Migrate allows to upgrade a contract to a new code with data migration.
|
|
func (k Keeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte) (*sdk.Result, error) {
|
|
return k.migrate(ctx, contractAddress, caller, newCodeID, msg, k.authZPolicy)
|
|
}
|
|
|
|
func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) (*sdk.Result, error) {
|
|
ctx.GasMeter().ConsumeGas(InstanceCost, "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")
|
|
}
|
|
|
|
// check for IBC flag
|
|
switch report, err := k.wasmer.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 := QueryHandler{
|
|
Ctx: ctx,
|
|
Plugins: k.queryPlugins,
|
|
}
|
|
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
gas := gasForContract(ctx)
|
|
res, gasUsed, err := k.wasmer.Migrate(newCodeInfo.CodeHash, env, msg, &prefixStore, cosmwasmAPI, &querier, gasMeter(ctx), gas)
|
|
consumeGas(ctx, gasUsed)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrMigrationFailed, err.Error())
|
|
}
|
|
|
|
// emit all events from this contract migration itself
|
|
events := types.ParseEvents(res.Attributes, contractAddress)
|
|
ctx.EventManager().EmitEvents(events)
|
|
|
|
// delete old secondary index entry
|
|
k.deleteContractSecondIndex(ctx, contractAddress, contractInfo)
|
|
// persist migration updates
|
|
historyEntry := contractInfo.AddMigration(ctx, newCodeID, msg)
|
|
k.appendToContractHistory(ctx, contractAddress, historyEntry)
|
|
k.storeContractInfo(ctx, contractAddress, contractInfo)
|
|
|
|
// then dispatch
|
|
if err := k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages); err != nil {
|
|
return nil, sdkerrors.Wrap(err, "dispatch")
|
|
}
|
|
|
|
return &sdk.Result{
|
|
Data: res.Data,
|
|
}, nil
|
|
}
|
|
|
|
func (k Keeper) deleteContractSecondIndex(ctx sdk.Context, contractAddress sdk.AccAddress, contractInfo *types.ContractInfo) {
|
|
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contractInfo))
|
|
}
|
|
|
|
// UpdateContractAdmin sets the admin value on the ContractInfo. It must be a valid address (use ClearContractAdmin to remove it)
|
|
func (k Keeper) UpdateContractAdmin(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newAdmin sdk.AccAddress) error {
|
|
return k.setContractAdmin(ctx, contractAddress, caller, newAdmin, k.authZPolicy)
|
|
}
|
|
|
|
// ClearContractAdmin sets the admin value on the ContractInfo to nil, to disable further migrations/ updates.
|
|
func (k Keeper) ClearContractAdmin(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress) error {
|
|
return k.setContractAdmin(ctx, contractAddress, caller, nil, k.authZPolicy)
|
|
}
|
|
|
|
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")
|
|
}
|
|
contractInfo.Admin = newAdmin.String()
|
|
k.storeContractInfo(ctx, contractAddress, contractInfo)
|
|
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))
|
|
if iter := prefixStore.ReverseIterator(nil, nil); iter.Valid() {
|
|
pos = sdk.BigEndianToUint64(iter.Value())
|
|
}
|
|
// then store with incrementing position
|
|
for _, e := range newEntries {
|
|
pos++
|
|
key := types.GetContractCodeHistoryElementKey(contractAddr, pos)
|
|
store.Set(key, k.cdc.MustMarshalBinaryBare(&e))
|
|
}
|
|
}
|
|
|
|
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)
|
|
for ; iter.Valid(); iter.Next() {
|
|
var e types.ContractCodeHistoryEntry
|
|
k.cdc.MustUnmarshalBinaryBare(iter.Value(), &e)
|
|
r = append(r, e)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// QuerySmart queries the smart contract itself.
|
|
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
|
|
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: query")
|
|
|
|
_, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// prepare querier
|
|
querier := QueryHandler{
|
|
Ctx: ctx,
|
|
Plugins: k.queryPlugins,
|
|
}
|
|
|
|
env := types.NewEnv(ctx, contractAddr)
|
|
queryResult, gasUsed, qErr := k.wasmer.Query(codeInfo.CodeHash, env, req, prefixStore, cosmwasmAPI, querier, gasMeter(ctx), gasForContract(ctx))
|
|
consumeGas(ctx, gasUsed)
|
|
if qErr != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrQueryFailed, qErr.Error())
|
|
}
|
|
return queryResult, 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 {
|
|
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.MustUnmarshalBinaryBare(contractBz, &contractInfo)
|
|
|
|
contractInfoBz := store.Get(types.GetCodeKey(contractInfo.CodeID))
|
|
if contractInfoBz == nil {
|
|
return contractInfo, types.CodeInfo{}, prefix.Store{}, sdkerrors.Wrap(types.ErrNotFound, "contract info")
|
|
}
|
|
var codeInfo types.CodeInfo
|
|
k.cdc.MustUnmarshalBinaryBare(contractInfoBz, &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.MustUnmarshalBinaryBare(contractBz, &contract)
|
|
return &contract
|
|
}
|
|
|
|
func (k Keeper) containsContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool {
|
|
store := ctx.KVStore(k.storeKey)
|
|
return store.Has(types.GetContractAddressKey(contractAddress))
|
|
}
|
|
|
|
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.MustMarshalBinaryBare(contract))
|
|
store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contract), []byte{})
|
|
}
|
|
|
|
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)
|
|
for ; iter.Valid(); iter.Next() {
|
|
var contract types.ContractInfo
|
|
k.cdc.MustUnmarshalBinaryBare(iter.Value(), &contract)
|
|
// cb returns true to stop early
|
|
if cb(iter.Key(), contract) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (k Keeper) GetContractState(ctx sdk.Context, contractAddress sdk.AccAddress) sdk.Iterator {
|
|
prefixStoreKey := types.GetContractStorePrefix(contractAddress)
|
|
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
|
|
return prefixStore.Iterator(nil, nil)
|
|
}
|
|
|
|
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.MustUnmarshalBinaryBare(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)
|
|
for ; iter.Valid(); iter.Next() {
|
|
var c types.CodeInfo
|
|
k.cdc.MustUnmarshalBinaryBare(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.MustUnmarshalBinaryBare(codeInfoBz, &codeInfo)
|
|
return k.wasmer.GetCode(codeInfo.CodeHash)
|
|
}
|
|
|
|
func (k Keeper) dispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error {
|
|
for _, msg := range msgs {
|
|
if err := k.messenger.Dispatch(ctx, contractAddr, ibcPort, msg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func gasForContract(ctx sdk.Context) uint64 {
|
|
meter := ctx.GasMeter()
|
|
if meter.IsOutOfGas() {
|
|
return 0
|
|
}
|
|
remaining := (meter.Limit() - meter.GasConsumedToLimit()) * GasMultiplier
|
|
if remaining > MaxGas {
|
|
return MaxGas
|
|
}
|
|
return remaining
|
|
}
|
|
|
|
func consumeGas(ctx sdk.Context, gas uint64) {
|
|
consumed := gas / GasMultiplier
|
|
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"})
|
|
}
|
|
}
|
|
|
|
// generates a contract address from codeID + instanceID
|
|
func (k Keeper) generateContractAddress(ctx sdk.Context, codeID uint64) sdk.AccAddress {
|
|
instanceID := k.autoIncrementID(ctx, types.KeyLastInstanceID)
|
|
return contractAddress(codeID, instanceID)
|
|
}
|
|
|
|
// contractAddress builds an sdk account address for a contract.
|
|
// Intentionally kept private as this is module internal logic.
|
|
func contractAddress(codeID, instanceID uint64) sdk.AccAddress {
|
|
// NOTE: It is possible to get a duplicate address if either codeID or instanceID
|
|
// overflow 32 bits. This is highly improbable, but something that could be refactored.
|
|
contractID := codeID<<32 + instanceID
|
|
return addrFromUint64(contractID)
|
|
}
|
|
|
|
// GetNextCodeID reads the next sequence id used for storing wasm code.
|
|
// Read only operation.
|
|
func (k Keeper) GetNextCodeID(ctx sdk.Context) uint64 {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(types.KeyLastCodeID)
|
|
id := uint64(1)
|
|
if bz != nil {
|
|
id = binary.BigEndian.Uint64(bz)
|
|
}
|
|
return id
|
|
}
|
|
|
|
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) error {
|
|
if !k.containsCodeInfo(ctx, c.CodeID) {
|
|
return sdkerrors.Wrapf(types.ErrNotFound, "code id: %d", c.CodeID)
|
|
}
|
|
if k.containsContractInfo(ctx, contractAddr) {
|
|
return sdkerrors.Wrapf(types.ErrDuplicate, "contract: %s", contractAddr)
|
|
}
|
|
|
|
historyEntry := c.ResetFromGenesis(ctx)
|
|
k.appendToContractHistory(ctx, contractAddr, historyEntry)
|
|
k.storeContractInfo(ctx, contractAddr, c)
|
|
return k.importContractState(ctx, contractAddr, state)
|
|
}
|
|
|
|
func addrFromUint64(id uint64) sdk.AccAddress {
|
|
addr := make([]byte, 20)
|
|
addr[0] = 'C'
|
|
binary.PutUvarint(addr[1:], id)
|
|
return sdk.AccAddress(crypto.AddressHash(addr))
|
|
}
|
|
|
|
// MultipliedGasMeter wraps the GasMeter from context and multiplies all reads by out defined multiplier
|
|
type MultipiedGasMeter struct {
|
|
originalMeter sdk.GasMeter
|
|
}
|
|
|
|
var _ wasmvm.GasMeter = MultipiedGasMeter{}
|
|
|
|
func (m MultipiedGasMeter) GasConsumed() sdk.Gas {
|
|
return m.originalMeter.GasConsumed() * GasMultiplier
|
|
}
|
|
|
|
func gasMeter(ctx sdk.Context) MultipiedGasMeter {
|
|
return MultipiedGasMeter{
|
|
originalMeter: ctx.GasMeter(),
|
|
}
|
|
}
|
|
|
|
// Logger returns a module-specific logger.
|
|
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
|
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
|
|
}
|