From 4fb3a50fa73c24f83f018e765edc4c123f6b5707 Mon Sep 17 00:00:00 2001 From: Alexander Peters Date: Mon, 9 Nov 2020 09:16:41 +0100 Subject: [PATCH] Support self calling contract on instantiation (#300) * Support self calling contract on instantiation * Review feedback * Review feedback --- app/app.go | 4 +- x/wasm/genesis_test.go | 4 +- x/wasm/handler.go | 14 +- x/wasm/internal/keeper/genesis.go | 4 +- x/wasm/internal/keeper/genesis_test.go | 4 +- x/wasm/internal/keeper/keeper.go | 16 +- x/wasm/internal/keeper/keeper_test.go | 15 ++ x/wasm/internal/keeper/legacy_querier.go | 12 +- x/wasm/internal/keeper/querier.go | 14 +- x/wasm/internal/keeper/recurse_test.go | 4 +- x/wasm/internal/keeper/staking_test.go | 2 +- x/wasm/internal/keeper/test_common.go | 187 +++++++++++++++++------ x/wasm/internal/types/wasmer_engine.go | 102 +++++++++++++ x/wasm/module.go | 4 +- x/wasm/module_test.go | 2 +- 15 files changed, 299 insertions(+), 89 deletions(-) create mode 100644 x/wasm/internal/types/wasmer_engine.go diff --git a/app/app.go b/app/app.go index fcc1636b0e..61f351df46 100644 --- a/app/app.go +++ b/app/app.go @@ -422,7 +422,7 @@ func NewWasmApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b distr.NewAppModule(appCodec, app.distrKeeper, app.accountKeeper, app.bankKeeper, app.stakingKeeper), staking.NewAppModule(appCodec, app.stakingKeeper, app.accountKeeper, app.bankKeeper), upgrade.NewAppModule(app.upgradeKeeper), - wasm.NewAppModule(app.wasmKeeper), + wasm.NewAppModule(&app.wasmKeeper), evidence.NewAppModule(app.evidenceKeeper), ibc.NewAppModule(app.ibcKeeper), params.NewAppModule(app.paramsKeeper), @@ -472,7 +472,7 @@ func NewWasmApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b distr.NewAppModule(appCodec, app.distrKeeper, app.accountKeeper, app.bankKeeper, app.stakingKeeper), slashing.NewAppModule(appCodec, app.slashingKeeper, app.accountKeeper, app.bankKeeper, app.stakingKeeper), params.NewAppModule(app.paramsKeeper), - wasm.NewAppModule(app.wasmKeeper), + wasm.NewAppModule(&app.wasmKeeper), evidence.NewAppModule(app.evidenceKeeper), ibc.NewAppModule(app.ibcKeeper), transferModule, diff --git a/x/wasm/genesis_test.go b/x/wasm/genesis_test.go index 8bd4cc5133..d14ac52ea4 100644 --- a/x/wasm/genesis_test.go +++ b/x/wasm/genesis_test.go @@ -118,14 +118,14 @@ func TestInitGenesis(t *testing.T) { }) // export into genstate - genState := ExportGenesis(data.ctx, data.keeper) + genState := ExportGenesis(data.ctx, &data.keeper) // create new app to import genstate into newData := setupTest(t) q2 := newData.module.LegacyQuerierHandler(nil) // initialize new app with genstate - InitGenesis(newData.ctx, newData.keeper, *genState) + InitGenesis(newData.ctx, &newData.keeper, *genState) // run same checks again on newdata, to make sure it was reinitialized correctly assertCodeList(t, q2, newData.ctx, 1) diff --git a/x/wasm/handler.go b/x/wasm/handler.go index f04769119f..5bb823695f 100644 --- a/x/wasm/handler.go +++ b/x/wasm/handler.go @@ -10,7 +10,7 @@ import ( ) // NewHandler returns a handler for "bank" type messages. -func NewHandler(k Keeper) sdk.Handler { +func NewHandler(k *Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) @@ -47,7 +47,7 @@ func filteredMessageEvents(manager *sdk.EventManager) []abci.Event { return res } -func handleStoreCode(ctx sdk.Context, k Keeper, msg *MsgStoreCode) (*sdk.Result, error) { +func handleStoreCode(ctx sdk.Context, k *Keeper, msg *MsgStoreCode) (*sdk.Result, error) { err := msg.ValidateBasic() if err != nil { return nil, err @@ -73,7 +73,7 @@ func handleStoreCode(ctx sdk.Context, k Keeper, msg *MsgStoreCode) (*sdk.Result, }, nil } -func handleInstantiate(ctx sdk.Context, k Keeper, msg *MsgInstantiateContract) (*sdk.Result, error) { +func handleInstantiate(ctx sdk.Context, k *Keeper, msg *MsgInstantiateContract) (*sdk.Result, error) { contractAddr, err := k.Instantiate(ctx, msg.CodeID, msg.Sender, msg.Admin, msg.InitMsg, msg.Label, msg.InitFunds) if err != nil { return nil, err @@ -95,7 +95,7 @@ func handleInstantiate(ctx sdk.Context, k Keeper, msg *MsgInstantiateContract) ( }, nil } -func handleExecute(ctx sdk.Context, k Keeper, msg *MsgExecuteContract) (*sdk.Result, error) { +func handleExecute(ctx sdk.Context, k *Keeper, msg *MsgExecuteContract) (*sdk.Result, error) { res, err := k.Execute(ctx, msg.Contract, msg.Sender, msg.Msg, msg.SentFunds) if err != nil { return nil, err @@ -115,7 +115,7 @@ func handleExecute(ctx sdk.Context, k Keeper, msg *MsgExecuteContract) (*sdk.Res return res, nil } -func handleMigration(ctx sdk.Context, k Keeper, msg *MsgMigrateContract) (*sdk.Result, error) { +func handleMigration(ctx sdk.Context, k *Keeper, msg *MsgMigrateContract) (*sdk.Result, error) { res, err := k.Migrate(ctx, msg.Contract, msg.Sender, msg.CodeID, msg.MigrateMsg) if err != nil { return nil, err @@ -133,7 +133,7 @@ func handleMigration(ctx sdk.Context, k Keeper, msg *MsgMigrateContract) (*sdk.R return res, nil } -func handleUpdateContractAdmin(ctx sdk.Context, k Keeper, msg *MsgUpdateAdmin) (*sdk.Result, error) { +func handleUpdateContractAdmin(ctx sdk.Context, k *Keeper, msg *MsgUpdateAdmin) (*sdk.Result, error) { if err := k.UpdateContractAdmin(ctx, msg.Contract, msg.Sender, msg.NewAdmin); err != nil { return nil, err } @@ -149,7 +149,7 @@ func handleUpdateContractAdmin(ctx sdk.Context, k Keeper, msg *MsgUpdateAdmin) ( }, nil } -func handleClearContractAdmin(ctx sdk.Context, k Keeper, msg *MsgClearAdmin) (*sdk.Result, error) { +func handleClearContractAdmin(ctx sdk.Context, k *Keeper, msg *MsgClearAdmin) (*sdk.Result, error) { if err := k.ClearContractAdmin(ctx, msg.Contract, msg.Sender); err != nil { return nil, err } diff --git a/x/wasm/internal/keeper/genesis.go b/x/wasm/internal/keeper/genesis.go index 021af53809..22ba9cbde1 100644 --- a/x/wasm/internal/keeper/genesis.go +++ b/x/wasm/internal/keeper/genesis.go @@ -11,7 +11,7 @@ import ( // InitGenesis sets supply information for genesis. // // CONTRACT: all types of accounts must have been already initialized/created -func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error { +func InitGenesis(ctx sdk.Context, keeper *Keeper, data types.GenesisState) error { var maxCodeID uint64 for i, code := range data.Codes { err := keeper.importCode(ctx, code.CodeID, code.CodeInfo, code.CodeBytes) @@ -52,7 +52,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error } // ExportGenesis returns a GenesisState for a given context and keeper. -func ExportGenesis(ctx sdk.Context, keeper Keeper) *types.GenesisState { +func ExportGenesis(ctx sdk.Context, keeper *Keeper) *types.GenesisState { var genState types.GenesisState genState.Params = keeper.GetParams(ctx) diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index b5cbd8f66b..76cc097407 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -473,7 +473,7 @@ func TestImportContractWithCodeHistoryReset(t *testing.T) { assert.Equal(t, expHistory, keeper.GetContractHistory(ctx, contractAddr).CodeHistoryEntries) } -func setupKeeper(t *testing.T) (Keeper, sdk.Context, []sdk.StoreKey, func()) { +func setupKeeper(t *testing.T) (*Keeper, sdk.Context, []sdk.StoreKey, func()) { t.Helper() tempDir, err := ioutil.TempDir("", "wasm") require.NoError(t, err) @@ -504,5 +504,5 @@ func setupKeeper(t *testing.T) (Keeper, sdk.Context, []sdk.StoreKey, func()) { srcKeeper := NewKeeper(encodingConfig.Marshaler, keyWasm, pk.Subspace(wasmTypes.DefaultParamspace), authkeeper.AccountKeeper{}, nil, stakingkeeper.Keeper{}, distributionkeeper.Keeper{}, nil, tempDir, wasmConfig, "", nil, nil) srcKeeper.setParams(ctx, wasmTypes.DefaultParams()) - return srcKeeper, ctx, []sdk.StoreKey{keyWasm, keyParams}, cleanup + return &srcKeeper, ctx, []sdk.StoreKey{keyWasm, keyParams}, cleanup } diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 6748a8f0cf..d01b5f9cb3 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -47,7 +47,7 @@ type Keeper struct { accountKeeper authkeeper.AccountKeeper bankKeeper bankkeeper.Keeper - wasmer wasm.Wasmer + wasmer types.WasmerEngine queryPlugins QueryPlugins messenger MessageHandler // queryGasLimit is the max wasm gas that can be spent on executing a query with a contract @@ -86,7 +86,7 @@ func NewKeeper( keeper := Keeper{ storeKey: storeKey, cdc: cdc, - wasmer: *wasmer, + wasmer: wasmer, accountKeeper: accountKeeper, bankKeeper: bankKeeper, messenger: NewMessageHandler(router, customEncoders), @@ -254,16 +254,18 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A events := types.ParseEvents(res.Attributes, contractAddress) ctx.EventManager().EmitEvents(events) + // persist instance first + createdAt := types.NewAbsoluteTxPosition(ctx) + instance := types.NewContractInfo(codeID, creator, admin, label, createdAt) + store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(&instance)) + k.appendToContractHistory(ctx, contractAddress, instance.InitialHistory(initMsg)) + + // then dispatch so that contract could be called back err = k.dispatchMessages(ctx, contractAddress, res.Messages) if err != nil { return nil, err } - // persist instance - createdAt := types.NewAbsoluteTxPosition(ctx) - instance := types.NewContractInfo(codeID, creator, admin, label, createdAt) - store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(&instance)) - k.appendToContractHistory(ctx, contractAddress, instance.InitialHistory(initMsg)) return contractAddress, nil } diff --git a/x/wasm/internal/keeper/keeper_test.go b/x/wasm/internal/keeper/keeper_test.go index e7b727955c..bbb79e54a0 100644 --- a/x/wasm/internal/keeper/keeper_test.go +++ b/x/wasm/internal/keeper/keeper_test.go @@ -432,6 +432,21 @@ func TestInstantiateWithNonExistingCodeID(t *testing.T) { require.Nil(t, addr) } +func TestInstantiateWithCallbackToContract(t *testing.T) { + ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil) + var ( + executeCalled bool + err error + ) + wasmerMock := selfCallingInstMockWasmer(&executeCalled) + + keepers.WasmKeeper.wasmer = wasmerMock + example := StoreHackatomExampleContract(t, ctx, keepers) + _, err = keepers.WasmKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, nil, nil, "test", nil) + require.NoError(t, err) + assert.True(t, executeCalled) +} + func TestExecute(t *testing.T) { ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil) accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper diff --git a/x/wasm/internal/keeper/legacy_querier.go b/x/wasm/internal/keeper/legacy_querier.go index 906681111d..7b4ffcb041 100644 --- a/x/wasm/internal/keeper/legacy_querier.go +++ b/x/wasm/internal/keeper/legacy_querier.go @@ -27,7 +27,7 @@ const ( ) // NewLegacyQuerier creates a new querier -func NewLegacyQuerier(keeper Keeper) sdk.Querier { +func NewLegacyQuerier(keeper *Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { var ( rsp interface{} @@ -39,13 +39,13 @@ func NewLegacyQuerier(keeper Keeper) sdk.Querier { if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) } - rsp, err = queryContractInfo(ctx, addr, keeper) + rsp, err = queryContractInfo(ctx, addr, *keeper) case QueryListContractByCode: codeID, err := strconv.ParseUint(path[1], 10, 64) if err != nil { return nil, sdkerrors.Wrapf(types.ErrInvalid, "code id: %s", err.Error()) } - rsp, err = queryContractListByCode(ctx, codeID, keeper) + rsp, err = queryContractListByCode(ctx, codeID, *keeper) case QueryGetContractState: if len(path) < 3 { return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") @@ -58,13 +58,13 @@ func NewLegacyQuerier(keeper Keeper) sdk.Querier { } rsp, err = queryCode(ctx, codeID, keeper) case QueryListCode: - rsp, err = queryCodeList(ctx, keeper) + rsp, err = queryCodeList(ctx, *keeper) case QueryContractHistory: contractAddr, err := sdk.AccAddressFromBech32(path[1]) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) } - rsp, err = queryContractHistory(ctx, contractAddr, keeper) + rsp, err = queryContractHistory(ctx, contractAddr, *keeper) default: return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") } @@ -82,7 +82,7 @@ func NewLegacyQuerier(keeper Keeper) sdk.Querier { } } -func queryContractState(ctx sdk.Context, bech, queryMethod string, data []byte, keeper Keeper) (json.RawMessage, error) { +func queryContractState(ctx sdk.Context, bech, queryMethod string, data []byte, keeper *Keeper) (json.RawMessage, error) { contractAddr, err := sdk.AccAddressFromBech32(bech) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, bech) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 14570e7b0e..ed6db7343f 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -11,11 +11,11 @@ import ( ) type grpcQuerier struct { - keeper Keeper + keeper *Keeper } // todo: this needs proper tests and doc -func NewQuerier(keeper Keeper) grpcQuerier { +func NewQuerier(keeper *Keeper) grpcQuerier { return grpcQuerier{keeper: keeper} } @@ -23,7 +23,7 @@ func (q grpcQuerier) ContractInfo(c context.Context, req *types.QueryContractInf if err := sdk.VerifyAddressFormat(req.Address); err != nil { return nil, err } - rsp, err := queryContractInfo(sdk.UnwrapSDKContext(c), req.Address, q.keeper) + rsp, err := queryContractInfo(sdk.UnwrapSDKContext(c), req.Address, *q.keeper) switch { case err != nil: return nil, err @@ -40,7 +40,7 @@ func (q grpcQuerier) ContractHistory(c context.Context, req *types.QueryContract if err := sdk.VerifyAddressFormat(req.Address); err != nil { return nil, err } - rsp, err := queryContractHistory(sdk.UnwrapSDKContext(c), req.Address, q.keeper) + rsp, err := queryContractHistory(sdk.UnwrapSDKContext(c), req.Address, *q.keeper) switch { case err != nil: return nil, err @@ -56,7 +56,7 @@ func (q grpcQuerier) ContractsByCode(c context.Context, req *types.QueryContract if req.CodeId == 0 { return nil, sdkerrors.Wrap(types.ErrInvalid, "code id") } - rsp, err := queryContractListByCode(sdk.UnwrapSDKContext(c), req.CodeId, q.keeper) + rsp, err := queryContractListByCode(sdk.UnwrapSDKContext(c), req.CodeId, *q.keeper) switch { case err != nil: return nil, err @@ -134,7 +134,7 @@ func (q grpcQuerier) Code(c context.Context, req *types.QueryCodeRequest) (*type } func (q grpcQuerier) Codes(c context.Context, _ *empty.Empty) (*types.QueryCodesResponse, error) { - rsp, err := queryCodeList(sdk.UnwrapSDKContext(c), q.keeper) + rsp, err := queryCodeList(sdk.UnwrapSDKContext(c), *q.keeper) switch { case err != nil: return nil, err @@ -182,7 +182,7 @@ func queryContractListByCode(ctx sdk.Context, codeID uint64, keeper Keeper) ([]t return contracts, nil } -func queryCode(ctx sdk.Context, codeID uint64, keeper Keeper) (*types.QueryCodeResponse, error) { +func queryCode(ctx sdk.Context, codeID uint64, keeper *Keeper) (*types.QueryCodeResponse, error) { if codeID == 0 { return nil, nil } diff --git a/x/wasm/internal/keeper/recurse_test.go b/x/wasm/internal/keeper/recurse_test.go index c532688797..3abba76a09 100644 --- a/x/wasm/internal/keeper/recurse_test.go +++ b/x/wasm/internal/keeper/recurse_test.go @@ -36,7 +36,7 @@ type recurseResponse struct { // number os wasm queries called from a contract var totalWasmQueryCounter int -func initRecurseContract(t *testing.T) (contract sdk.AccAddress, creator sdk.AccAddress, ctx sdk.Context, keeper Keeper) { +func initRecurseContract(t *testing.T) (contract sdk.AccAddress, creator sdk.AccAddress, ctx sdk.Context, keeper *Keeper) { // we do one basic setup before all test cases (which are read-only and don't change state) var realWasmQuerier func(ctx sdk.Context, request *wasmTypes.WasmQuery) ([]byte, error) countingQuerier := &QueryPlugins{ @@ -48,7 +48,7 @@ func initRecurseContract(t *testing.T) (contract sdk.AccAddress, creator sdk.Acc ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, countingQuerier) keeper = keepers.WasmKeeper - realWasmQuerier = WasmQuerier(&keeper) + realWasmQuerier = WasmQuerier(keeper) exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers) return exampleContract.Contract, exampleContract.CreatorAddr, ctx, keeper diff --git a/x/wasm/internal/keeper/staking_test.go b/x/wasm/internal/keeper/staking_test.go index fb297f6df5..a0ccf57e50 100644 --- a/x/wasm/internal/keeper/staking_test.go +++ b/x/wasm/internal/keeper/staking_test.go @@ -213,7 +213,7 @@ func initializeStaking(t *testing.T) initInfo { ctx: ctx, accKeeper: accKeeper, stakingKeeper: stakingKeeper, - wasmKeeper: keeper, + wasmKeeper: *keeper, distKeeper: k.DistKeeper, bankKeeper: bankKeeper, } diff --git a/x/wasm/internal/keeper/test_common.go b/x/wasm/internal/keeper/test_common.go index 062a2162e1..478ae02848 100644 --- a/x/wasm/internal/keeper/test_common.go +++ b/x/wasm/internal/keeper/test_common.go @@ -1,6 +1,7 @@ package keeper import ( + "bytes" "encoding/binary" "encoding/json" "fmt" @@ -9,11 +10,12 @@ import ( "testing" "time" - wasmTypes "github.com/CosmWasm/wasmd/x/wasm/internal/types" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/internal/types" + "github.com/CosmWasm/go-cosmwasm" + wasmTypes "github.com/CosmWasm/go-cosmwasm/types" + "github.com/CosmWasm/wasmd/x/wasm/internal/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" params2 "github.com/cosmos/cosmos-sdk/simapp/params" "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/store" @@ -87,7 +89,7 @@ func MakeTestCodec() codec.Marshaler { } func MakeEncodingConfig() params2.EncodingConfig { amino := codec.NewLegacyAmino() - interfaceRegistry := types.NewInterfaceRegistry() + interfaceRegistry := codectypes.NewInterfaceRegistry() marshaler := codec.NewProtoCodec(interfaceRegistry) txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) @@ -115,10 +117,10 @@ var TestingStakeParams = stakingtypes.Params{ type TestKeepers struct { AccountKeeper authkeeper.AccountKeeper StakingKeeper stakingkeeper.Keeper - WasmKeeper Keeper DistKeeper distributionkeeper.Keeper BankKeeper bankkeeper.Keeper GovKeeper govkeeper.Keeper + WasmKeeper *Keeper } // encoders can be nil to accept the defaults, or set it to override some of the message handlers (like default) @@ -127,7 +129,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, supportedFeatures string, enc require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(tempDir) }) - keyWasm := sdk.NewKVStoreKey(wasmTypes.StoreKey) + keyWasm := sdk.NewKVStoreKey(types.StoreKey) keyAcc := sdk.NewKVStoreKey(authtypes.StoreKey) keyBank := sdk.NewKVStoreKey(banktypes.StoreKey) keyStaking := sdk.NewKVStoreKey(stakingtypes.StoreKey) @@ -229,12 +231,12 @@ func CreateTestInput(t *testing.T, isCheckTx bool, supportedFeatures string, enc router.AddRoute(sdk.NewRoute(distributiontypes.RouterKey, dh)) // Load default wasm config - wasmConfig := wasmTypes.DefaultWasmConfig() + wasmConfig := types.DefaultWasmConfig() keeper := NewKeeper( appCodec, keyWasm, - paramsKeeper.Subspace(wasmtypes.DefaultParamspace), + paramsKeeper.Subspace(types.DefaultParamspace), authKeeper, bankKeeper, stakingKeeper, @@ -246,15 +248,15 @@ func CreateTestInput(t *testing.T, isCheckTx bool, supportedFeatures string, enc encoders, queriers, ) - keeper.setParams(ctx, wasmtypes.DefaultParams()) + keeper.setParams(ctx, types.DefaultParams()) // add wasm handler so we can loop-back (contracts calling contracts) - router.AddRoute(sdk.NewRoute(wasmTypes.RouterKey, TestHandler(keeper))) + router.AddRoute(sdk.NewRoute(types.RouterKey, TestHandler(&keeper))) govRouter := govtypes.NewRouter(). AddRoute(govtypes.RouterKey, govtypes.ProposalHandler). AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(paramsKeeper)). AddRoute(distributiontypes.RouterKey, distribution.NewCommunityPoolSpendProposalHandler(distKeeper)). - AddRoute(wasmtypes.RouterKey, NewWasmProposalHandler(keeper, wasmtypes.EnableAllProposals)) + AddRoute(types.RouterKey, NewWasmProposalHandler(keeper, types.EnableAllProposals)) govKeeper := govkeeper.NewKeeper( appCodec, keyGov, paramsKeeper.Subspace(govtypes.ModuleName).WithKeyTable(govtypes.ParamKeyTable()), authKeeper, bankKeeper, stakingKeeper, govRouter, @@ -269,7 +271,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, supportedFeatures string, enc AccountKeeper: authKeeper, StakingKeeper: stakingKeeper, DistKeeper: distKeeper, - WasmKeeper: keeper, + WasmKeeper: &keeper, BankKeeper: bankKeeper, GovKeeper: govKeeper, } @@ -277,15 +279,15 @@ func CreateTestInput(t *testing.T, isCheckTx bool, supportedFeatures string, enc } // TestHandler returns a wasm handler for tests (to avoid circular imports) -func TestHandler(k Keeper) sdk.Handler { +func TestHandler(k *Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) switch msg := msg.(type) { - case *wasmtypes.MsgInstantiateContract: + case *types.MsgInstantiateContract: return handleInstantiate(ctx, k, msg) - case *wasmtypes.MsgExecuteContract: + case *types.MsgExecuteContract: return handleExecute(ctx, k, msg) default: @@ -295,7 +297,7 @@ func TestHandler(k Keeper) sdk.Handler { } } -func handleInstantiate(ctx sdk.Context, k Keeper, msg *wasmtypes.MsgInstantiateContract) (*sdk.Result, error) { +func handleInstantiate(ctx sdk.Context, k *Keeper, msg *types.MsgInstantiateContract) (*sdk.Result, error) { contractAddr, err := k.Instantiate(ctx, msg.CodeID, msg.Sender, msg.Admin, msg.InitMsg, msg.Label, msg.InitFunds) if err != nil { return nil, err @@ -307,7 +309,7 @@ func handleInstantiate(ctx sdk.Context, k Keeper, msg *wasmtypes.MsgInstantiateC }, nil } -func handleExecute(ctx sdk.Context, k Keeper, msg *wasmtypes.MsgExecuteContract) (*sdk.Result, error) { +func handleExecute(ctx sdk.Context, k *Keeper, msg *types.MsgExecuteContract) (*sdk.Result, error) { res, err := k.Execute(ctx, msg.Contract, msg.Sender, msg.Msg, msg.SentFunds) if err != nil { return nil, err @@ -323,23 +325,40 @@ func AnyAccAddress(_ *testing.T) sdk.AccAddress { } type HackatomExampleContract struct { - InitialAmount sdk.Coins + InitialAmount sdk.Coins + Creator crypto.PrivKey + CreatorAddr sdk.AccAddress + CodeID uint64 +} + +func StoreHackatomExampleContract(t *testing.T, ctx sdk.Context, keepers TestKeepers) HackatomExampleContract { + anyAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000)) + creator, _, creatorAddr := keyPubAddr() + fundAccounts(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, creatorAddr, anyAmount) + + wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm") + require.NoError(t, err) + + codeID, err := keepers.WasmKeeper.Create(ctx, creatorAddr, wasmCode, "", "", nil) + require.NoError(t, err) + return HackatomExampleContract{anyAmount, creator, creatorAddr, codeID} +} + +type HackatomExampleInstance struct { + HackatomExampleContract Contract sdk.AccAddress - Creator crypto.PrivKey - CreatorAddr sdk.AccAddress Verifier crypto.PrivKey VerifierAddr sdk.AccAddress Beneficiary crypto.PrivKey BeneficiaryAddr sdk.AccAddress - CodeID uint64 } // InstantiateHackatomExampleContract load and instantiate the "./testdata/hackatom.wasm" contract -func InstantiateHackatomExampleContract(t *testing.T, ctx sdk.Context, keepers TestKeepers) HackatomExampleContract { - anyAmount, creator, creatorAddr, codeID := StoreHackatomExampleContract(t, ctx, keepers) +func InstantiateHackatomExampleContract(t *testing.T, ctx sdk.Context, keepers TestKeepers) HackatomExampleInstance { + contract := StoreHackatomExampleContract(t, ctx, keepers) verifier, _, verifierAddr := keyPubAddr() - fundAccounts(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, verifierAddr, anyAmount) + fundAccounts(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, verifierAddr, contract.InitialAmount) beneficiary, _, beneficiaryAddr := keyPubAddr() initMsgBz := HackatomExampleInitMsg{ @@ -347,34 +366,18 @@ func InstantiateHackatomExampleContract(t *testing.T, ctx sdk.Context, keepers T Beneficiary: beneficiaryAddr, }.GetBytes(t) initialAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 100)) - contractAddr, err := keepers.WasmKeeper.Instantiate(ctx, codeID, creatorAddr, nil, initMsgBz, "demo contract to query", initialAmount) + contractAddr, err := keepers.WasmKeeper.Instantiate(ctx, contract.CodeID, contract.CreatorAddr, nil, initMsgBz, "demo contract to query", initialAmount) require.NoError(t, err) - return HackatomExampleContract{ - InitialAmount: initialAmount, - Contract: contractAddr, - Creator: creator, - CreatorAddr: creatorAddr, - Verifier: verifier, - VerifierAddr: verifierAddr, - Beneficiary: beneficiary, - BeneficiaryAddr: beneficiaryAddr, - CodeID: codeID, + return HackatomExampleInstance{ + HackatomExampleContract: contract, + Contract: contractAddr, + Verifier: verifier, + VerifierAddr: verifierAddr, + Beneficiary: beneficiary, + BeneficiaryAddr: beneficiaryAddr, } } -func StoreHackatomExampleContract(t *testing.T, ctx sdk.Context, keepers TestKeepers) (sdk.Coins, crypto.PrivKey, sdk.AccAddress, uint64) { - anyAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000)) - creator, _, creatorAddr := keyPubAddr() - fundAccounts(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, creatorAddr, anyAmount) - - wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - codeID, err := keepers.WasmKeeper.Create(ctx, creatorAddr, wasmCode, "", "", nil) - require.NoError(t, err) - return anyAmount, creator, creatorAddr, codeID -} - type HackatomExampleInitMsg struct { Verifier sdk.AccAddress `json:"verifier"` Beneficiary sdk.AccAddress `json:"beneficiary"` @@ -412,3 +415,91 @@ func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { addr := sdk.AccAddress(pub.Address()) return key, pub, addr } + +var _ types.WasmerEngine = &MockWasmer{} + +// MockWasmer implements types.WasmerEngine for testing purpose. One or multiple messages can be stubbed. +// Without a stub function a panic is thrown. +type MockWasmer struct { + CreateFn func(code cosmwasm.WasmCode) (cosmwasm.CodeID, error) + InstantiateFn func(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, initMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.InitResponse, uint64, error) + ExecuteFn func(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, executeMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.HandleResponse, uint64, error) + QueryFn func(code cosmwasm.CodeID, env wasmTypes.Env, queryMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) ([]byte, uint64, error) + MigrateFn func(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, migrateMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.MigrateResponse, uint64, error) + GetCodeFn func(code cosmwasm.CodeID) (cosmwasm.WasmCode, error) + CleanupFn func() +} + +func (m *MockWasmer) Create(code cosmwasm.WasmCode) (cosmwasm.CodeID, error) { + if m.CreateFn == nil { + panic("not supposed to be called!") + } + return m.CreateFn(code) +} + +func (m *MockWasmer) Instantiate(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, initMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.InitResponse, uint64, error) { + if m.InstantiateFn == nil { + panic("not supposed to be called!") + } + + return m.InstantiateFn(code, env, info, initMsg, store, goapi, querier, gasMeter, gasLimit) +} + +func (m *MockWasmer) Execute(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, executeMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.HandleResponse, uint64, error) { + if m.ExecuteFn == nil { + panic("not supposed to be called!") + } + return m.ExecuteFn(code, env, info, executeMsg, store, goapi, querier, gasMeter, gasLimit) +} + +func (m *MockWasmer) Query(code cosmwasm.CodeID, env wasmTypes.Env, queryMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) ([]byte, uint64, error) { + if m.QueryFn == nil { + panic("not supposed to be called!") + } + return m.QueryFn(code, env, queryMsg, store, goapi, querier, gasMeter, gasLimit) +} + +func (m *MockWasmer) Migrate(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, migrateMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.MigrateResponse, uint64, error) { + if m.MigrateFn == nil { + panic("not supposed to be called!") + } + return m.MigrateFn(code, env, info, migrateMsg, store, goapi, querier, gasMeter, gasLimit) +} + +func (m *MockWasmer) GetCode(code cosmwasm.CodeID) (cosmwasm.WasmCode, error) { + if m.GetCodeFn == nil { + panic("not supposed to be called!") + } + return m.GetCodeFn(code) +} + +func (m *MockWasmer) Cleanup() { + if m.CleanupFn == nil { + panic("not supposed to be called!") + } + m.CleanupFn() +} + +var alwaysPanicMockWasmer = &MockWasmer{} + +// selfCallingInstMockWasmer prepares a Wasmer mock that calls itself on instantiation. +func selfCallingInstMockWasmer(executeCalled *bool) *MockWasmer { + return &MockWasmer{ + + CreateFn: func(code cosmwasm.WasmCode) (cosmwasm.CodeID, error) { + anyCodeID := bytes.Repeat([]byte{0x1}, 32) + return anyCodeID, nil + }, + InstantiateFn: func(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, initMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.InitResponse, uint64, error) { + return &wasmTypes.InitResponse{ + Messages: []wasmTypes.CosmosMsg{ + {Wasm: &wasmTypes.WasmMsg{Execute: &wasmTypes.ExecuteMsg{ContractAddr: env.Contract.Address, Msg: []byte(`{}`)}}}, + }, + }, 1, nil + }, + ExecuteFn: func(code cosmwasm.CodeID, env wasmTypes.Env, info wasmTypes.MessageInfo, executeMsg []byte, store cosmwasm.KVStore, goapi cosmwasm.GoAPI, querier cosmwasm.Querier, gasMeter cosmwasm.GasMeter, gasLimit uint64) (*wasmTypes.HandleResponse, uint64, error) { + *executeCalled = true + return &wasmTypes.HandleResponse{}, 1, nil + }, + } +} diff --git a/x/wasm/internal/types/wasmer_engine.go b/x/wasm/internal/types/wasmer_engine.go new file mode 100644 index 0000000000..8794b639d6 --- /dev/null +++ b/x/wasm/internal/types/wasmer_engine.go @@ -0,0 +1,102 @@ +package types + +import ( + "github.com/CosmWasm/go-cosmwasm" + "github.com/CosmWasm/go-cosmwasm/types" +) + +// WasmerEngine defines the WASM contract runtime engine. +type WasmerEngine interface { + + // Create will compile the wasm code, and store the resulting pre-compile + // as well as the original code. Both can be referenced later via CodeID + // This must be done one time for given code, after which it can be + // instatitated many times, and each instance called many times. + // + // For example, the code for all ERC-20 contracts should be the same. + // This function stores the code for that contract only once, but it can + // be instantiated with custom inputs in the future. + Create(code cosmwasm.WasmCode) (cosmwasm.CodeID, error) + + // Instantiate will create a new contract based on the given codeID. + // We can set the initMsg (contract "genesis") here, and it then receives + // an account and address and can be invoked (Execute) many times. + // + // Storage should be set with a PrefixedKVStore that this code can safely access. + // + // Under the hood, we may recompile the wasm, use a cached native compile, or even use a cached instance + // for performance. + Instantiate( + code cosmwasm.CodeID, + env types.Env, + info types.MessageInfo, + initMsg []byte, + store cosmwasm.KVStore, + goapi cosmwasm.GoAPI, + querier cosmwasm.Querier, + gasMeter cosmwasm.GasMeter, + gasLimit uint64, + ) (*types.InitResponse, uint64, error) + + // Execute calls a given contract. Since the only difference between contracts with the same CodeID is the + // data in their local storage, and their address in the outside world, we need no ContractID here. + // (That is a detail for the external, sdk-facing, side). + // + // The caller is responsible for passing the correct `store` (which must have been initialized exactly once), + // and setting the env with relevent info on this instance (address, balance, etc) + Execute( + code cosmwasm.CodeID, + env types.Env, + info types.MessageInfo, + executeMsg []byte, + store cosmwasm.KVStore, + goapi cosmwasm.GoAPI, + querier cosmwasm.Querier, + gasMeter cosmwasm.GasMeter, + gasLimit uint64, + ) (*types.HandleResponse, uint64, error) + + // Query allows a client to execute a contract-specific query. If the result is not empty, it should be + // valid json-encoded data to return to the client. + // The meaning of path and data can be determined by the code. Path is the suffix of the abci.QueryRequest.Path + Query( + code cosmwasm.CodeID, + env types.Env, + queryMsg []byte, + store cosmwasm.KVStore, + goapi cosmwasm.GoAPI, + querier cosmwasm.Querier, + gasMeter cosmwasm.GasMeter, + gasLimit uint64, + ) ([]byte, uint64, error) + + // Migrate will migrate an existing contract to a new code binary. + // This takes storage of the data from the original contract and the CodeID of the new contract that should + // replace it. This allows it to run a migration step if needed, or return an error if unable to migrate + // the given data. + // + // MigrateMsg has some data on how to perform the migration. + Migrate( + code cosmwasm.CodeID, + env types.Env, + info types.MessageInfo, + migrateMsg []byte, + store cosmwasm.KVStore, + goapi cosmwasm.GoAPI, + querier cosmwasm.Querier, + gasMeter cosmwasm.GasMeter, + gasLimit uint64, + ) (*types.MigrateResponse, uint64, error) + + // GetCode will load the original wasm code for the given code id. + // This will only succeed if that code id was previously returned from + // a call to Create. + // + // This can be used so that the (short) code id (hash) is stored in the iavl tree + // and the larger binary blobs (wasm and pre-compiles) are all managed by the + // rust library + GetCode(code cosmwasm.CodeID) (cosmwasm.WasmCode, error) + + // Cleanup should be called when no longer using this to free resources on the rust-side + Cleanup() +} diff --git a/x/wasm/module.go b/x/wasm/module.go index 13ee84e57f..f0da3a7d01 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -85,11 +85,11 @@ func (b AppModuleBasic) RegisterInterfaces(registry cdctypes.InterfaceRegistry) // AppModule implements an application module for the wasm module. type AppModule struct { AppModuleBasic - keeper Keeper + keeper *Keeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper) AppModule { +func NewAppModule(keeper *Keeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index afff454bd6..d01805f479 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -36,7 +36,7 @@ func setupTest(t *testing.T) testData { module: NewAppModule(keeper), ctx: ctx, acctKeeper: acctKeeper, - keeper: keeper, + keeper: *keeper, bankKeeper: bankKeeper, } return data