Submsg and replies (#441)
* Add dispatchSubmessages and reply to Keeper * Update all mock types * Dispatch submessages in all entry points * Rename mask -> reflect in all tests (that was cosmwasm 0.8...) * Basic submessage dispatch test; * Simplify messanger interface again * Start table tests * Added table tests * Debuging handling out of gas and panics * Properly handle gas limits and out of gas panics * Test parsing return values from WasmMsg::Instantiate * PR feedback * Add test to trigger 0 len data panic * Safely handle 0 sdk msg submsg responses * Charge gas on reply
This commit is contained in:
@@ -320,50 +320,49 @@ func convertWasmIBCTimeoutTimestampToCosmosTimestamp(timestamp *uint64) uint64 {
|
||||
return *timestamp
|
||||
}
|
||||
|
||||
func (h DefaultMessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
for _, msg := range msgs {
|
||||
sdkMsgs, err := h.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sdkMsg := range sdkMsgs {
|
||||
if err := h.handleSdkMessage(ctx, contractAddr, sdkMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
func (h DefaultMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
sdkMsgs, err := h.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil
|
||||
for _, sdkMsg := range sdkMsgs {
|
||||
res, err := h.handleSdkMessage(ctx, contractAddr, sdkMsg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// append data
|
||||
data = append(data, res.Data)
|
||||
// append events
|
||||
sdkEvents := make([]sdk.Event, len(res.Events))
|
||||
for i := range res.Events {
|
||||
sdkEvents[i] = sdk.Event(res.Events[i])
|
||||
}
|
||||
events = append(events, sdkEvents...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h DefaultMessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) error {
|
||||
func (h DefaultMessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) {
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
// make sure this account can send it
|
||||
for _, acct := range msg.GetSigners() {
|
||||
if !acct.Equals(contractAddr) {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission")
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission")
|
||||
}
|
||||
}
|
||||
|
||||
// find the handler and execute it
|
||||
handler := h.router.Route(ctx, msg.Route())
|
||||
if handler == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, msg.Route())
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, msg.Route())
|
||||
}
|
||||
res, err := handler(ctx, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make(sdk.Events, len(res.Events))
|
||||
for i := range res.Events {
|
||||
events[i] = sdk.Event(res.Events[i])
|
||||
}
|
||||
// redispatch all events, (type sdk.EventTypeMessage will be filtered out in the handler)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
return nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func convertWasmCoinsToSdkCoins(coins []wasmvmtypes.Coin) (sdk.Coins, error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
|
||||
@@ -52,7 +53,7 @@ type Option interface {
|
||||
}
|
||||
|
||||
type messenger interface {
|
||||
Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error
|
||||
DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error)
|
||||
}
|
||||
|
||||
// Keeper will have a reference to Wasmer with it's own data directory.
|
||||
@@ -220,7 +221,7 @@ func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A
|
||||
|
||||
func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.AccAddress, []byte, error) {
|
||||
if !k.IsPinnedCode(ctx, codeID) {
|
||||
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: init")
|
||||
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: instantiate")
|
||||
}
|
||||
|
||||
// create contract address
|
||||
@@ -301,13 +302,14 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A
|
||||
contractInfo.IBCPortID = ibcPort
|
||||
}
|
||||
|
||||
// store contract before dispatch so that contract could be called back
|
||||
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)
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return contractAddress, res.Data, nil
|
||||
@@ -352,9 +354,10 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
|
||||
events := types.ParseEvents(res.Attributes, contractAddress)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages)
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return &sdk.Result{
|
||||
@@ -426,8 +429,9 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
|
||||
k.appendToContractHistory(ctx, contractAddress, historyEntry)
|
||||
k.storeContractInfo(ctx, contractAddress, contractInfo)
|
||||
|
||||
// then dispatch
|
||||
if err := k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages); err != nil {
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
@@ -464,7 +468,50 @@ func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte
|
||||
events := types.ParseEvents(res.Attributes, contractAddress)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages)
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return &sdk.Result{
|
||||
Data: res.Data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// reply is only called from keeper internal functions (dispatchSubmessages) after processing the submessage
|
||||
// it
|
||||
func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// current thought is to charge gas like a fresh run, we can revisit whether to give it a discount later
|
||||
if !k.IsPinnedCode(ctx, contractInfo.CodeID) {
|
||||
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: reply")
|
||||
}
|
||||
|
||||
env := types.NewEnv(ctx, contractAddress)
|
||||
|
||||
// prepare querier
|
||||
querier := QueryHandler{
|
||||
Ctx: ctx,
|
||||
Plugins: k.queryPlugins,
|
||||
}
|
||||
gas := gasForContract(ctx)
|
||||
res, gasUsed, execErr := k.wasmer.Reply(codeInfo.CodeHash, env, reply, 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)
|
||||
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
@@ -678,15 +725,6 @@ func (k Keeper) GetByteCode(ctx sdk.Context, codeID uint64) ([]byte, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// PinCode pins the wasm contract in wasmvm cache
|
||||
func (k Keeper) PinCode(ctx sdk.Context, codeID uint64) error {
|
||||
codeInfo := k.GetCodeInfo(ctx, codeID)
|
||||
@@ -740,6 +778,139 @@ func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) dispatchAll(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, subMsgs []wasmvmtypes.SubMsg, msgs []wasmvmtypes.CosmosMsg) error {
|
||||
// first dispatch all submessages (and the replies).
|
||||
err := k.dispatchSubmessages(ctx, contractAddr, ibcPort, subMsgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// then dispatch all the normal messages
|
||||
return k.dispatchMessages(ctx, contractAddr, ibcPort, msgs)
|
||||
}
|
||||
|
||||
func (k Keeper) dispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error {
|
||||
for _, msg := range msgs {
|
||||
events, _, err := k.messenger.DispatchMsg(ctx, contractAddr, ibcPort, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// redispatch all events, (type sdk.EventTypeMessage will be filtered out in the handler)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) dispatchMsgWithGasLimit(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msg wasmvmtypes.CosmosMsg, gasLimit uint64) (events []sdk.Event, data [][]byte, err error) {
|
||||
limitedMeter := sdk.NewGasMeter(gasLimit)
|
||||
subCtx := ctx.WithGasMeter(limitedMeter)
|
||||
|
||||
// catch out of gas panic and just charge the entire gas limit
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// if it's not an OutOfGas error, raise it again
|
||||
if _, ok := r.(sdk.ErrorOutOfGas); !ok {
|
||||
// log it to get the original stack trace somewhere (as panic(r) keeps message but stacktrace to here
|
||||
k.Logger(ctx).Info("SubMsg rethrowing panic: %#v", r)
|
||||
panic(r)
|
||||
}
|
||||
ctx.GasMeter().ConsumeGas(gasLimit, "Sub-Message OutOfGas panic")
|
||||
err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "SubMsg hit gas limit")
|
||||
}
|
||||
}()
|
||||
events, data, err = k.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg)
|
||||
|
||||
// make sure we charge the parent what was spent
|
||||
spent := subCtx.GasMeter().GasConsumed()
|
||||
ctx.GasMeter().ConsumeGas(spent, "From limited Sub-Message")
|
||||
|
||||
return events, data, err
|
||||
}
|
||||
|
||||
// dispatchSubmessages builds a sandbox to execute these messages and returns the execution result to the contract
|
||||
// that dispatched them, both on success as well as failure
|
||||
func (k Keeper) dispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) error {
|
||||
for _, msg := range msgs {
|
||||
// first, we build a sub-context which we can use inside the submessages
|
||||
subCtx, commit := ctx.CacheContext()
|
||||
|
||||
// check how much gas left locally, optionally wrap the gas meter
|
||||
gasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumed()
|
||||
limitGas := msg.GasLimit != nil && (*msg.GasLimit < gasRemaining)
|
||||
|
||||
var err error
|
||||
var events []sdk.Event
|
||||
var data [][]byte
|
||||
if limitGas {
|
||||
events, data, err = k.dispatchMsgWithGasLimit(subCtx, contractAddr, ibcPort, msg.Msg, *msg.GasLimit)
|
||||
} else {
|
||||
events, data, err = k.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg.Msg)
|
||||
}
|
||||
|
||||
// if it succeeds, commit state changes from submessage, and pass on events to Event Manager
|
||||
if err == nil {
|
||||
commit()
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
}
|
||||
// on failure, revert state from sandbox, and ignore events (just skip doing the above)
|
||||
|
||||
var result wasmvmtypes.SubcallResult
|
||||
if err == nil {
|
||||
// just take the first one for now if there are multiple sub-sdk messages
|
||||
// and safely return nothing if no data
|
||||
var responseData []byte
|
||||
if len(data) > 0 {
|
||||
responseData = data[0]
|
||||
}
|
||||
result = wasmvmtypes.SubcallResult{
|
||||
Ok: &wasmvmtypes.SubcallResponse{
|
||||
Events: sdkEventsToWasmVmEvents(events),
|
||||
Data: responseData,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
result = wasmvmtypes.SubcallResult{
|
||||
Err: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// now handle the reply, we use the parent context, and abort on error
|
||||
reply := wasmvmtypes.Reply{
|
||||
ID: msg.ID,
|
||||
Result: result,
|
||||
}
|
||||
|
||||
// we can ignore any result returned as there is nothing to do with the data
|
||||
// and the events are already in the ctx.EventManager()
|
||||
_, err = k.reply(ctx, contractAddr, reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sdkEventsToWasmVmEvents(events []sdk.Event) []wasmvmtypes.Event {
|
||||
res := make([]wasmvmtypes.Event, len(events))
|
||||
for i, ev := range events {
|
||||
res[i] = wasmvmtypes.Event{
|
||||
Type: ev.Type,
|
||||
Attributes: sdkAttributesToWasmVmAttributes(ev.Attributes),
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func sdkAttributesToWasmVmAttributes(attrs []abci.EventAttribute) []wasmvmtypes.EventAttribute {
|
||||
res := make([]wasmvmtypes.EventAttribute, len(attrs))
|
||||
for i, attr := range attrs {
|
||||
res[i] = wasmvmtypes.EventAttribute{
|
||||
Key: string(attr.Key),
|
||||
Value: string(attr.Value),
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func gasForContract(ctx sdk.Context) uint64 {
|
||||
meter := ctx.GasMeter()
|
||||
if meter.IsOutOfGas() {
|
||||
|
||||
@@ -20,12 +20,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// MaskInitMsg is {}
|
||||
// ReflectInitMsg is {}
|
||||
|
||||
// MaskHandleMsg is used to encode handle messages
|
||||
type MaskHandleMsg struct {
|
||||
Reflect *reflectPayload `json:"reflect_msg,omitempty"`
|
||||
Change *ownerPayload `json:"change_owner,omitempty"`
|
||||
// ReflectHandleMsg is used to encode handle messages
|
||||
type ReflectHandleMsg struct {
|
||||
Reflect *reflectPayload `json:"reflect_msg,omitempty"`
|
||||
ReflectSubCall *reflectSubPayload `json:"reflect_sub_call,omitempty"`
|
||||
Change *ownerPayload `json:"change_owner,omitempty"`
|
||||
}
|
||||
|
||||
type ownerPayload struct {
|
||||
@@ -36,11 +37,16 @@ type reflectPayload struct {
|
||||
Msgs []wasmvmtypes.CosmosMsg `json:"msgs"`
|
||||
}
|
||||
|
||||
// MaskQueryMsg is used to encode query messages
|
||||
type MaskQueryMsg struct {
|
||||
Owner *struct{} `json:"owner,omitempty"`
|
||||
Capitalized *Text `json:"capitalized,omitempty"`
|
||||
Chain *ChainQuery `json:"chain,omitempty"`
|
||||
type reflectSubPayload struct {
|
||||
Msgs []wasmvmtypes.SubMsg `json:"msgs"`
|
||||
}
|
||||
|
||||
// ReflectQueryMsg is used to encode query messages
|
||||
type ReflectQueryMsg struct {
|
||||
Owner *struct{} `json:"owner,omitempty"`
|
||||
Capitalized *Text `json:"capitalized,omitempty"`
|
||||
Chain *ChainQuery `json:"chain,omitempty"`
|
||||
SubCallResult *SubCall `json:"sub_call_result,omitempty"`
|
||||
}
|
||||
|
||||
type ChainQuery struct {
|
||||
@@ -51,6 +57,10 @@ type Text struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type SubCall struct {
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
|
||||
type OwnerResponse struct {
|
||||
Owner string `json:"owner,omitempty"`
|
||||
}
|
||||
@@ -59,7 +69,7 @@ type ChainResponse struct {
|
||||
Data []byte `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func buildMaskQuery(t *testing.T, query *MaskQueryMsg) []byte {
|
||||
func buildReflectQuery(t *testing.T, query *ReflectQueryMsg) []byte {
|
||||
bz, err := json.Marshal(query)
|
||||
require.NoError(t, err)
|
||||
return bz
|
||||
@@ -70,23 +80,23 @@ func mustParse(t *testing.T, data []byte, res interface{}) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
const MaskFeatures = "staking,mask,stargate"
|
||||
const ReflectFeatures = "staking,mask,stargate"
|
||||
|
||||
func TestMaskReflectContractSend(t *testing.T) {
|
||||
func TestReflectContractSend(t *testing.T) {
|
||||
cdc := MakeTestCodec(t)
|
||||
ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), nil)
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), nil)
|
||||
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
|
||||
|
||||
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
|
||||
_, _, bob := keyPubAddr()
|
||||
|
||||
// upload mask code
|
||||
maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
// upload reflect code
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil)
|
||||
reflectID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), maskID)
|
||||
require.Equal(t, uint64(1), reflectID)
|
||||
|
||||
// upload hackatom escrow code
|
||||
escrowCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
|
||||
@@ -96,14 +106,14 @@ func TestMaskReflectContractSend(t *testing.T) {
|
||||
require.Equal(t, uint64(2), escrowID)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
maskAddr, _, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart)
|
||||
reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, maskAddr)
|
||||
require.NotEmpty(t, reflectAddr)
|
||||
|
||||
// now we set contract as verifier of an escrow
|
||||
initMsg := HackatomExampleInitMsg{
|
||||
Verifier: maskAddr,
|
||||
Verifier: reflectAddr,
|
||||
Beneficiary: bob,
|
||||
}
|
||||
initMsgBz, err := json.Marshal(initMsg)
|
||||
@@ -115,13 +125,13 @@ func TestMaskReflectContractSend(t *testing.T) {
|
||||
|
||||
// let's make sure all balances make sense
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // 100k - 40k - 25k
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, maskAddr, maskStart)
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, reflectStart)
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, escrowStart)
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, bob, nil)
|
||||
|
||||
// now for the trick.... we reflect a message through the mask to call the escrow
|
||||
// now for the trick.... we reflect a message through the reflect to call the escrow
|
||||
// we also send an additional 14k tokens there.
|
||||
// this should reduce the mask balance by 14k (to 26k)
|
||||
// this should reduce the reflect balance by 14k (to 26k)
|
||||
// this 14k is added to the escrow, then the entire balance is sent to bob (total: 39k)
|
||||
approveMsg := []byte(`{"release":{}}`)
|
||||
msgs := []wasmvmtypes.CosmosMsg{{
|
||||
@@ -136,27 +146,27 @@ func TestMaskReflectContractSend(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}}
|
||||
reflectSend := MaskHandleMsg{
|
||||
reflectSend := ReflectHandleMsg{
|
||||
Reflect: &reflectPayload{
|
||||
Msgs: msgs,
|
||||
},
|
||||
}
|
||||
reflectSendBz, err := json.Marshal(reflectSend)
|
||||
require.NoError(t, err)
|
||||
_, err = keeper.Execute(ctx, maskAddr, creator, reflectSendBz, nil)
|
||||
_, err = keeper.Execute(ctx, reflectAddr, creator, reflectSendBz, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// did this work???
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // same as before
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, maskAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 26000))) // 40k - 14k (from send)
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, sdk.Coins{}) // emptied reserved
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, bob, sdk.NewCoins(sdk.NewInt64Coin("denom", 39000))) // all escrow of 25k + 14k
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // same as before
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 26000))) // 40k - 14k (from send)
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, sdk.Coins{}) // emptied reserved
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, bob, sdk.NewCoins(sdk.NewInt64Coin("denom", 39000))) // all escrow of 25k + 14k
|
||||
|
||||
}
|
||||
|
||||
func TestMaskReflectCustomMsg(t *testing.T) {
|
||||
func TestReflectCustomMsg(t *testing.T) {
|
||||
cdc := MakeTestCodec(t)
|
||||
ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), maskPlugins())
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), reflectPlugins())
|
||||
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
|
||||
|
||||
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
||||
@@ -165,20 +175,20 @@ func TestMaskReflectCustomMsg(t *testing.T) {
|
||||
_, _, fred := keyPubAddr()
|
||||
|
||||
// upload code
|
||||
maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
codeID, err := keeper.Create(ctx, creator, maskCode, "", "", nil)
|
||||
codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), codeID)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart)
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, contractAddr)
|
||||
|
||||
// set owner to bob
|
||||
transfer := MaskHandleMsg{
|
||||
transfer := ReflectHandleMsg{
|
||||
Change: &ownerPayload{
|
||||
Owner: bob,
|
||||
},
|
||||
@@ -205,7 +215,7 @@ func TestMaskReflectCustomMsg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}}
|
||||
reflectSend := MaskHandleMsg{
|
||||
reflectSend := ReflectHandleMsg{
|
||||
Reflect: &reflectPayload{
|
||||
Msgs: msgs,
|
||||
},
|
||||
@@ -227,9 +237,9 @@ func TestMaskReflectCustomMsg(t *testing.T) {
|
||||
ToAddress: fred.String(),
|
||||
Amount: sdk.NewCoins(sdk.NewInt64Coin("denom", 23000)),
|
||||
}
|
||||
opaque, err := toMaskRawMsg(cdc, sdkSendMsg)
|
||||
opaque, err := toReflectRawMsg(cdc, sdkSendMsg)
|
||||
require.NoError(t, err)
|
||||
reflectOpaque := MaskHandleMsg{
|
||||
reflectOpaque := ReflectHandleMsg{
|
||||
Reflect: &reflectPayload{
|
||||
Msgs: []wasmvmtypes.CosmosMsg{opaque},
|
||||
},
|
||||
@@ -249,27 +259,27 @@ func TestMaskReflectCustomMsg(t *testing.T) {
|
||||
|
||||
func TestMaskReflectCustomQuery(t *testing.T) {
|
||||
cdc := MakeTestCodec(t)
|
||||
ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), maskPlugins())
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), reflectPlugins())
|
||||
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
|
||||
|
||||
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
|
||||
|
||||
// upload code
|
||||
maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
codeID, err := keeper.Create(ctx, creator, maskCode, "", "", nil)
|
||||
codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), codeID)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart)
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, contractAddr)
|
||||
|
||||
// let's perform a normal query of state
|
||||
ownerQuery := MaskQueryMsg{
|
||||
ownerQuery := ReflectQueryMsg{
|
||||
Owner: &struct{}{},
|
||||
}
|
||||
ownerQueryBz, err := json.Marshal(ownerQuery)
|
||||
@@ -282,7 +292,7 @@ func TestMaskReflectCustomQuery(t *testing.T) {
|
||||
assert.Equal(t, res.Owner, creator.String())
|
||||
|
||||
// and now making use of the custom querier callbacks
|
||||
customQuery := MaskQueryMsg{
|
||||
customQuery := ReflectQueryMsg{
|
||||
Capitalized: &Text{
|
||||
Text: "all Caps noW",
|
||||
},
|
||||
@@ -299,7 +309,7 @@ func TestMaskReflectCustomQuery(t *testing.T) {
|
||||
|
||||
func TestReflectStargateQuery(t *testing.T) {
|
||||
cdc := MakeTestCodec(t)
|
||||
ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), maskPlugins())
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), reflectPlugins())
|
||||
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
|
||||
|
||||
funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000))
|
||||
@@ -308,14 +318,14 @@ func TestReflectStargateQuery(t *testing.T) {
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, funds)
|
||||
|
||||
// upload code
|
||||
maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
codeID, err := keeper.Create(ctx, creator, maskCode, "", "", nil)
|
||||
codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), codeID)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart)
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, contractAddr)
|
||||
|
||||
@@ -327,7 +337,7 @@ func TestReflectStargateQuery(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
simpleQueryBz, err := json.Marshal(MaskQueryMsg{
|
||||
simpleQueryBz, err := json.Marshal(ReflectQueryMsg{
|
||||
Chain: &ChainQuery{Request: &bankQuery},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -352,7 +362,7 @@ func TestReflectStargateQuery(t *testing.T) {
|
||||
Data: protoQueryBin,
|
||||
},
|
||||
}
|
||||
protoQueryBz, err := json.Marshal(MaskQueryMsg{
|
||||
protoQueryBz, err := json.Marshal(ReflectQueryMsg{
|
||||
Chain: &ChainQuery{Request: &protoRequest},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -370,33 +380,33 @@ func TestReflectStargateQuery(t *testing.T) {
|
||||
assert.Equal(t, expectedBalance, protoResult.Balances)
|
||||
}
|
||||
|
||||
type maskState struct {
|
||||
type reflectState struct {
|
||||
Owner []byte `json:"owner"`
|
||||
}
|
||||
|
||||
func TestMaskReflectWasmQueries(t *testing.T) {
|
||||
ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(MakeTestCodec(t)), nil)
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(MakeTestCodec(t)), nil)
|
||||
accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper
|
||||
|
||||
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, keepers.BankKeeper, deposit)
|
||||
|
||||
// upload mask code
|
||||
maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
// upload reflect code
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil)
|
||||
reflectID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), maskID)
|
||||
require.Equal(t, uint64(1), reflectID)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
maskAddr, _, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart)
|
||||
reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, maskAddr)
|
||||
require.NotEmpty(t, reflectAddr)
|
||||
|
||||
// for control, let's make some queries directly on the mask
|
||||
ownerQuery := buildMaskQuery(t, &MaskQueryMsg{Owner: &struct{}{}})
|
||||
res, err := keeper.QuerySmart(ctx, maskAddr, ownerQuery)
|
||||
// for control, let's make some queries directly on the reflect
|
||||
ownerQuery := buildReflectQuery(t, &ReflectQueryMsg{Owner: &struct{}{}})
|
||||
res, err := keeper.QuerySmart(ctx, reflectAddr, ownerQuery)
|
||||
require.NoError(t, err)
|
||||
var ownerRes OwnerResponse
|
||||
mustParse(t, res, &ownerRes)
|
||||
@@ -404,20 +414,20 @@ func TestMaskReflectWasmQueries(t *testing.T) {
|
||||
|
||||
// and a raw query: cosmwasm_storage::Singleton uses 2 byte big-endian length-prefixed to store data
|
||||
configKey := append([]byte{0, 6}, []byte("config")...)
|
||||
raw := keeper.QueryRaw(ctx, maskAddr, configKey)
|
||||
var stateRes maskState
|
||||
raw := keeper.QueryRaw(ctx, reflectAddr, configKey)
|
||||
var stateRes reflectState
|
||||
mustParse(t, raw, &stateRes)
|
||||
require.Equal(t, stateRes.Owner, []byte(creator))
|
||||
|
||||
// now, let's reflect a smart query into the x/wasm handlers and see if we get the same result
|
||||
reflectOwnerQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{
|
||||
reflectOwnerQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{
|
||||
Smart: &wasmvmtypes.SmartQuery{
|
||||
ContractAddr: maskAddr.String(),
|
||||
ContractAddr: reflectAddr.String(),
|
||||
Msg: ownerQuery,
|
||||
},
|
||||
}}}}
|
||||
reflectOwnerBin := buildMaskQuery(t, &reflectOwnerQuery)
|
||||
res, err = keeper.QuerySmart(ctx, maskAddr, reflectOwnerBin)
|
||||
reflectOwnerBin := buildReflectQuery(t, &reflectOwnerQuery)
|
||||
res, err = keeper.QuerySmart(ctx, reflectAddr, reflectOwnerBin)
|
||||
require.NoError(t, err)
|
||||
// first we pull out the data from chain response, before parsing the original response
|
||||
var reflectRes ChainResponse
|
||||
@@ -427,58 +437,58 @@ func TestMaskReflectWasmQueries(t *testing.T) {
|
||||
require.Equal(t, reflectOwnerRes.Owner, creator.String())
|
||||
|
||||
// and with queryRaw
|
||||
reflectStateQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{
|
||||
reflectStateQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{
|
||||
Raw: &wasmvmtypes.RawQuery{
|
||||
ContractAddr: maskAddr.String(),
|
||||
ContractAddr: reflectAddr.String(),
|
||||
Key: configKey,
|
||||
},
|
||||
}}}}
|
||||
reflectStateBin := buildMaskQuery(t, &reflectStateQuery)
|
||||
res, err = keeper.QuerySmart(ctx, maskAddr, reflectStateBin)
|
||||
reflectStateBin := buildReflectQuery(t, &reflectStateQuery)
|
||||
res, err = keeper.QuerySmart(ctx, reflectAddr, reflectStateBin)
|
||||
require.NoError(t, err)
|
||||
// first we pull out the data from chain response, before parsing the original response
|
||||
var reflectRawRes ChainResponse
|
||||
mustParse(t, res, &reflectRawRes)
|
||||
// now, with the raw data, we can parse it into state
|
||||
var reflectStateRes maskState
|
||||
var reflectStateRes reflectState
|
||||
mustParse(t, reflectRawRes.Data, &reflectStateRes)
|
||||
require.Equal(t, reflectStateRes.Owner, []byte(creator))
|
||||
}
|
||||
|
||||
func TestWasmRawQueryWithNil(t *testing.T) {
|
||||
ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(MakeTestCodec(t)), nil)
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(MakeTestCodec(t)), nil)
|
||||
accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper
|
||||
|
||||
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, keepers.BankKeeper, deposit)
|
||||
|
||||
// upload mask code
|
||||
maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
// upload reflect code
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil)
|
||||
reflectID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), maskID)
|
||||
require.Equal(t, uint64(1), reflectID)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
maskAddr, _, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart)
|
||||
reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, maskAddr)
|
||||
require.NotEmpty(t, reflectAddr)
|
||||
|
||||
// control: query directly
|
||||
missingKey := []byte{0, 1, 2, 3, 4}
|
||||
raw := keeper.QueryRaw(ctx, maskAddr, missingKey)
|
||||
raw := keeper.QueryRaw(ctx, reflectAddr, missingKey)
|
||||
require.Nil(t, raw)
|
||||
|
||||
// and with queryRaw
|
||||
reflectQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{
|
||||
reflectQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{
|
||||
Raw: &wasmvmtypes.RawQuery{
|
||||
ContractAddr: maskAddr.String(),
|
||||
ContractAddr: reflectAddr.String(),
|
||||
Key: missingKey,
|
||||
},
|
||||
}}}}
|
||||
reflectStateBin := buildMaskQuery(t, &reflectQuery)
|
||||
res, err := keeper.QuerySmart(ctx, maskAddr, reflectStateBin)
|
||||
reflectStateBin := buildReflectQuery(t, &reflectQuery)
|
||||
res, err := keeper.QuerySmart(ctx, reflectAddr, reflectStateBin)
|
||||
require.NoError(t, err)
|
||||
|
||||
// first we pull out the data from chain response, before parsing the original response
|
||||
@@ -507,14 +517,14 @@ func checkAccount(t *testing.T, ctx sdk.Context, accKeeper authkeeper.AccountKee
|
||||
|
||||
/**** Code to support custom messages *****/
|
||||
|
||||
type maskCustomMsg struct {
|
||||
type reflectCustomMsg struct {
|
||||
Debug string `json:"debug,omitempty"`
|
||||
Raw []byte `json:"raw,omitempty"`
|
||||
}
|
||||
|
||||
// toMaskRawMsg encodes an sdk msg using any type with json encoding.
|
||||
// toReflectRawMsg encodes an sdk msg using any type with json encoding.
|
||||
// Then wraps it as an opaque message
|
||||
func toMaskRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, error) {
|
||||
func toReflectRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, error) {
|
||||
any, err := codectypes.NewAnyWithValue(msg)
|
||||
if err != nil {
|
||||
return wasmvmtypes.CosmosMsg{}, err
|
||||
@@ -523,7 +533,7 @@ func toMaskRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, erro
|
||||
if err != nil {
|
||||
return wasmvmtypes.CosmosMsg{}, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||
}
|
||||
customMsg, err := json.Marshal(maskCustomMsg{
|
||||
customMsg, err := json.Marshal(reflectCustomMsg{
|
||||
Raw: rawBz,
|
||||
})
|
||||
res := wasmvmtypes.CosmosMsg{
|
||||
@@ -532,18 +542,18 @@ func toMaskRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, erro
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// maskEncoders needs to be registered in test setup to handle custom message callbacks
|
||||
func maskEncoders(cdc codec.Marshaler) *MessageEncoders {
|
||||
// reflectEncoders needs to be registered in test setup to handle custom message callbacks
|
||||
func reflectEncoders(cdc codec.Marshaler) *MessageEncoders {
|
||||
return &MessageEncoders{
|
||||
Custom: fromMaskRawMsg(cdc),
|
||||
Custom: fromReflectRawMsg(cdc),
|
||||
}
|
||||
}
|
||||
|
||||
// fromMaskRawMsg decodes msg.Data to an sdk.Msg using proto Any and json encoding.
|
||||
// fromReflectRawMsg decodes msg.Data to an sdk.Msg using proto Any and json encoding.
|
||||
// this needs to be registered on the Encoders
|
||||
func fromMaskRawMsg(cdc codec.Marshaler) CustomEncoder {
|
||||
func fromReflectRawMsg(cdc codec.Marshaler) CustomEncoder {
|
||||
return func(_sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) {
|
||||
var custom maskCustomMsg
|
||||
var custom reflectCustomMsg
|
||||
err := json.Unmarshal(msg, &custom)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
@@ -566,7 +576,7 @@ func fromMaskRawMsg(cdc codec.Marshaler) CustomEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
type maskCustomQuery struct {
|
||||
type reflectCustomQuery struct {
|
||||
Ping *struct{} `json:"ping,omitempty"`
|
||||
Capitalized *Text `json:"capitalized,omitempty"`
|
||||
}
|
||||
@@ -589,15 +599,15 @@ type chainResponse struct {
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
// maskPlugins needs to be registered in test setup to handle custom query callbacks
|
||||
func maskPlugins() *QueryPlugins {
|
||||
// reflectPlugins needs to be registered in test setup to handle custom query callbacks
|
||||
func reflectPlugins() *QueryPlugins {
|
||||
return &QueryPlugins{
|
||||
Custom: performCustomQuery,
|
||||
}
|
||||
}
|
||||
|
||||
func performCustomQuery(_ sdk.Context, request json.RawMessage) ([]byte, error) {
|
||||
var custom maskCustomQuery
|
||||
var custom reflectCustomQuery
|
||||
err := json.Unmarshal(request, &custom)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
|
||||
@@ -66,7 +66,8 @@ func (k Keeper) OnConnectChannel(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil {
|
||||
// TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -102,7 +103,8 @@ func (k Keeper) OnCloseChannel(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil {
|
||||
// TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -138,7 +140,8 @@ func (k Keeper) OnRecvPacket(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil {
|
||||
// TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Acknowledgement, nil
|
||||
@@ -175,7 +178,8 @@ func (k Keeper) OnAckPacket(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil {
|
||||
// TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -208,7 +212,8 @@ func (k Keeper) OnTimeoutPacket(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil {
|
||||
// TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -125,11 +125,7 @@ func TestOnConnectChannel(t *testing.T) {
|
||||
Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}},
|
||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||
},
|
||||
overwriteMessenger: &wasmtesting.MockMessageHandler{
|
||||
DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
return errors.New("test, ignore")
|
||||
},
|
||||
},
|
||||
overwriteMessenger: wasmtesting.NewErroringMessageHandler(),
|
||||
expErr: true,
|
||||
expContractEventAttrs: 1,
|
||||
},
|
||||
@@ -239,11 +235,7 @@ func TestOnCloseChannel(t *testing.T) {
|
||||
Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}},
|
||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||
},
|
||||
overwriteMessenger: &wasmtesting.MockMessageHandler{
|
||||
DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
return errors.New("test, ignore")
|
||||
},
|
||||
},
|
||||
overwriteMessenger: wasmtesting.NewErroringMessageHandler(),
|
||||
expErr: true,
|
||||
expContractEventAttrs: 1,
|
||||
},
|
||||
@@ -364,11 +356,7 @@ func TestOnRecvPacket(t *testing.T) {
|
||||
Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}},
|
||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||
},
|
||||
overwriteMessenger: &wasmtesting.MockMessageHandler{
|
||||
DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
return errors.New("test, ignore")
|
||||
},
|
||||
},
|
||||
overwriteMessenger: wasmtesting.NewErroringMessageHandler(),
|
||||
expErr: true,
|
||||
expContractEventAttrs: 1,
|
||||
},
|
||||
@@ -482,11 +470,7 @@ func TestOnAckPacket(t *testing.T) {
|
||||
Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}},
|
||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||
},
|
||||
overwriteMessenger: &wasmtesting.MockMessageHandler{
|
||||
DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
return errors.New("test, ignore")
|
||||
},
|
||||
},
|
||||
overwriteMessenger: wasmtesting.NewErroringMessageHandler(),
|
||||
expErr: true,
|
||||
expContractEventAttrs: 1,
|
||||
},
|
||||
@@ -597,11 +581,7 @@ func TestOnTimeoutPacket(t *testing.T) {
|
||||
Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}},
|
||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||
},
|
||||
overwriteMessenger: &wasmtesting.MockMessageHandler{
|
||||
DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
return errors.New("test, ignore")
|
||||
},
|
||||
},
|
||||
overwriteMessenger: wasmtesting.NewErroringMessageHandler(),
|
||||
expErr: true,
|
||||
expContractEventAttrs: 1,
|
||||
},
|
||||
|
||||
@@ -453,10 +453,10 @@ func TestQueryStakingInfo(t *testing.T) {
|
||||
|
||||
// STEP 3: now, let's reflect some queries.
|
||||
// let's get the bonded denom
|
||||
reflectBondedQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
reflectBondedQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
BondedDenom: &struct{}{},
|
||||
}}}}
|
||||
reflectBondedBin := buildMaskQuery(t, &reflectBondedQuery)
|
||||
reflectBondedBin := buildReflectQuery(t, &reflectBondedQuery)
|
||||
res, err := keeper.QuerySmart(ctx, maskAddr, reflectBondedBin)
|
||||
require.NoError(t, err)
|
||||
// first we pull out the data from chain response, before parsing the original response
|
||||
@@ -467,10 +467,10 @@ func TestQueryStakingInfo(t *testing.T) {
|
||||
assert.Equal(t, "stake", bondedRes.Denom)
|
||||
|
||||
// now, let's reflect a smart query into the x/wasm handlers and see if we get the same result
|
||||
reflectValidatorsQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
reflectValidatorsQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
Validators: &wasmvmtypes.ValidatorsQuery{},
|
||||
}}}}
|
||||
reflectValidatorsBin := buildMaskQuery(t, &reflectValidatorsQuery)
|
||||
reflectValidatorsBin := buildReflectQuery(t, &reflectValidatorsQuery)
|
||||
res, err = keeper.QuerySmart(ctx, maskAddr, reflectValidatorsBin)
|
||||
require.NoError(t, err)
|
||||
// first we pull out the data from chain response, before parsing the original response
|
||||
@@ -486,12 +486,12 @@ func TestQueryStakingInfo(t *testing.T) {
|
||||
require.Contains(t, valInfo.MaxChangeRate, "0.010")
|
||||
|
||||
// test to get all my delegations
|
||||
reflectAllDelegationsQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
reflectAllDelegationsQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
AllDelegations: &wasmvmtypes.AllDelegationsQuery{
|
||||
Delegator: contractAddr.String(),
|
||||
},
|
||||
}}}}
|
||||
reflectAllDelegationsBin := buildMaskQuery(t, &reflectAllDelegationsQuery)
|
||||
reflectAllDelegationsBin := buildReflectQuery(t, &reflectAllDelegationsQuery)
|
||||
res, err = keeper.QuerySmart(ctx, maskAddr, reflectAllDelegationsBin)
|
||||
require.NoError(t, err)
|
||||
// first we pull out the data from chain response, before parsing the original response
|
||||
@@ -509,13 +509,13 @@ func TestQueryStakingInfo(t *testing.T) {
|
||||
require.Equal(t, funds[0].Amount.String(), delInfo.Amount.Amount)
|
||||
|
||||
// test to get one delegations
|
||||
reflectDelegationQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
reflectDelegationQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{
|
||||
Delegation: &wasmvmtypes.DelegationQuery{
|
||||
Validator: valAddr.String(),
|
||||
Delegator: contractAddr.String(),
|
||||
},
|
||||
}}}}
|
||||
reflectDelegationBin := buildMaskQuery(t, &reflectDelegationQuery)
|
||||
reflectDelegationBin := buildReflectQuery(t, &reflectDelegationQuery)
|
||||
res, err = keeper.QuerySmart(ctx, maskAddr, reflectDelegationBin)
|
||||
require.NoError(t, err)
|
||||
// first we pull out the data from chain response, before parsing the original response
|
||||
|
||||
452
x/wasm/internal/keeper/submsg_test.go
Normal file
452
x/wasm/internal/keeper/submsg_test.go
Normal file
@@ -0,0 +1,452 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// test handing of submessages, very closely related to the reflect_test
|
||||
|
||||
// Try a simple send, no gas limit to for a sanity check before trying table tests
|
||||
func TestDispatchSubMsgSuccessCase(t *testing.T) {
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, nil, nil)
|
||||
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
|
||||
|
||||
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
||||
contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
|
||||
creatorBalance := deposit.Sub(contractStart)
|
||||
_, _, fred := keyPubAddr()
|
||||
|
||||
// upload code
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), codeID)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, contractAddr)
|
||||
|
||||
// check some account values
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, contractStart)
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance)
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, fred, nil)
|
||||
|
||||
// creator can send contract's tokens to fred (using SendMsg)
|
||||
msg := wasmvmtypes.CosmosMsg{
|
||||
Bank: &wasmvmtypes.BankMsg{
|
||||
Send: &wasmvmtypes.SendMsg{
|
||||
ToAddress: fred.String(),
|
||||
Amount: []wasmvmtypes.Coin{{
|
||||
Denom: "denom",
|
||||
Amount: "15000",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
reflectSend := ReflectHandleMsg{
|
||||
ReflectSubCall: &reflectSubPayload{
|
||||
Msgs: []wasmvmtypes.SubMsg{{
|
||||
ID: 7,
|
||||
Msg: msg,
|
||||
}},
|
||||
},
|
||||
}
|
||||
reflectSendBz, err := json.Marshal(reflectSend)
|
||||
require.NoError(t, err)
|
||||
_, err = keeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// fred got coins
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 15000)))
|
||||
// contract lost them
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 25000)))
|
||||
checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance)
|
||||
|
||||
// query the reflect state to ensure the result was stored
|
||||
query := ReflectQueryMsg{
|
||||
SubCallResult: &SubCall{ID: 7},
|
||||
}
|
||||
queryBz, err := json.Marshal(query)
|
||||
require.NoError(t, err)
|
||||
queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz)
|
||||
require.NoError(t, err)
|
||||
|
||||
var res wasmvmtypes.Reply
|
||||
err = json.Unmarshal(queryRes, &res)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(7), res.ID)
|
||||
assert.Empty(t, res.Result.Err)
|
||||
require.NotNil(t, res.Result.Ok)
|
||||
sub := res.Result.Ok
|
||||
assert.Empty(t, sub.Data)
|
||||
require.Len(t, sub.Events, 3)
|
||||
|
||||
transfer := sub.Events[0]
|
||||
assert.Equal(t, "transfer", transfer.Type)
|
||||
assert.Equal(t, wasmvmtypes.EventAttribute{
|
||||
Key: "recipient",
|
||||
Value: fred.String(),
|
||||
}, transfer.Attributes[0])
|
||||
|
||||
sender := sub.Events[1]
|
||||
assert.Equal(t, "message", sender.Type)
|
||||
assert.Equal(t, wasmvmtypes.EventAttribute{
|
||||
Key: "sender",
|
||||
Value: contractAddr.String(),
|
||||
}, sender.Attributes[0])
|
||||
|
||||
// where does this come from?
|
||||
module := sub.Events[2]
|
||||
assert.Equal(t, "message", module.Type)
|
||||
assert.Equal(t, wasmvmtypes.EventAttribute{
|
||||
Key: "module",
|
||||
Value: "bank",
|
||||
}, module.Attributes[0])
|
||||
|
||||
}
|
||||
|
||||
func TestDispatchSubMsgErrorHandling(t *testing.T) {
|
||||
fundedDenom := "funds"
|
||||
fundedAmount := 1_000_000
|
||||
ctxGasLimit := uint64(1_000_000)
|
||||
subGasLimit := uint64(300_000)
|
||||
|
||||
// prep - create one chain and upload the code
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, nil, nil)
|
||||
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
||||
ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())
|
||||
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
|
||||
contractStart := sdk.NewCoins(sdk.NewInt64Coin(fundedDenom, int64(fundedAmount)))
|
||||
uploader := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, contractStart.Add(contractStart...))
|
||||
|
||||
// upload code
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
reflectID, err := keeper.Create(ctx, uploader, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create hackatom contract for testing (for infinite loop)
|
||||
hackatomCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
|
||||
require.NoError(t, err)
|
||||
hackatomID, err := keeper.Create(ctx, uploader, hackatomCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
_, _, bob := keyPubAddr()
|
||||
_, _, fred := keyPubAddr()
|
||||
initMsg := HackatomExampleInitMsg{
|
||||
Verifier: fred,
|
||||
Beneficiary: bob,
|
||||
}
|
||||
initMsgBz, err := json.Marshal(initMsg)
|
||||
require.NoError(t, err)
|
||||
hackatomAddr, _, err := keeper.Instantiate(ctx, hackatomID, uploader, nil, initMsgBz, "hackatom demo", contractStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
validBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
|
||||
return wasmvmtypes.CosmosMsg{
|
||||
Bank: &wasmvmtypes.BankMsg{
|
||||
Send: &wasmvmtypes.SendMsg{
|
||||
ToAddress: emptyAccount,
|
||||
Amount: []wasmvmtypes.Coin{{
|
||||
Denom: fundedDenom,
|
||||
Amount: strconv.Itoa(fundedAmount / 2),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
invalidBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
|
||||
return wasmvmtypes.CosmosMsg{
|
||||
Bank: &wasmvmtypes.BankMsg{
|
||||
Send: &wasmvmtypes.SendMsg{
|
||||
ToAddress: emptyAccount,
|
||||
Amount: []wasmvmtypes.Coin{{
|
||||
Denom: fundedDenom,
|
||||
Amount: strconv.Itoa(fundedAmount * 2),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
infiniteLoop := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
|
||||
return wasmvmtypes.CosmosMsg{
|
||||
Wasm: &wasmvmtypes.WasmMsg{
|
||||
Execute: &wasmvmtypes.ExecuteMsg{
|
||||
ContractAddr: hackatomAddr.String(),
|
||||
Msg: []byte(`{"cpu_loop":{}}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
instantiateContract := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg {
|
||||
return wasmvmtypes.CosmosMsg{
|
||||
Wasm: &wasmvmtypes.WasmMsg{
|
||||
Instantiate: &wasmvmtypes.InstantiateMsg{
|
||||
CodeID: reflectID,
|
||||
Msg: []byte("{}"),
|
||||
Label: "subcall reflect",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type assertion func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult)
|
||||
|
||||
assertReturnedEvents := func(expectedEvents int) assertion {
|
||||
return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) {
|
||||
assert.Len(t, response.Ok.Events, expectedEvents)
|
||||
}
|
||||
}
|
||||
|
||||
assertGasUsed := func(minGas, maxGas uint64) assertion {
|
||||
return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) {
|
||||
gasUsed := ctx.GasMeter().GasConsumed()
|
||||
assert.True(t, gasUsed >= minGas, "Used %d gas (less than expected %d)", gasUsed, minGas)
|
||||
assert.True(t, gasUsed <= maxGas, "Used %d gas (more than expected %d)", gasUsed, maxGas)
|
||||
}
|
||||
}
|
||||
|
||||
assertErrorString := func(shouldContain string) assertion {
|
||||
return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) {
|
||||
assert.Contains(t, response.Err, shouldContain)
|
||||
}
|
||||
}
|
||||
|
||||
assertGotContractAddr := func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) {
|
||||
// should get the events emitted on new contract
|
||||
event := response.Ok.Events[0]
|
||||
assert.Equal(t, event.Type, "wasm")
|
||||
assert.Equal(t, event.Attributes[0].Key, "contract_address")
|
||||
eventAddr := event.Attributes[0].Value
|
||||
assert.NotEqual(t, contract, eventAddr)
|
||||
|
||||
// data field is the raw canonical address
|
||||
// QUESTION: why not types.MsgInstantiateContractResponse? difference between calling Router and Service?
|
||||
assert.Len(t, response.Ok.Data, 20)
|
||||
resAddr := sdk.AccAddress(response.Ok.Data)
|
||||
assert.Equal(t, eventAddr, resAddr.String())
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
submsgID uint64
|
||||
// we will generate message from the
|
||||
msg func(contract, emptyAccount string) wasmvmtypes.CosmosMsg
|
||||
gasLimit *uint64
|
||||
|
||||
// true if we expect this to throw out of gas panic
|
||||
isOutOfGasPanic bool
|
||||
// true if we expect this execute to return an error (can be false when submessage errors)
|
||||
executeError bool
|
||||
// true if we expect submessage to return an error (but execute to return success)
|
||||
subMsgError bool
|
||||
// make assertions after dispatch
|
||||
resultAssertions []assertion
|
||||
}{
|
||||
"send tokens": {
|
||||
submsgID: 5,
|
||||
msg: validBankSend,
|
||||
// note we charge another 40k for the reply call
|
||||
resultAssertions: []assertion{assertReturnedEvents(3), assertGasUsed(123000, 125000)},
|
||||
},
|
||||
"not enough tokens": {
|
||||
submsgID: 6,
|
||||
msg: invalidBankSend,
|
||||
subMsgError: true,
|
||||
// uses less gas than the send tokens (cost of bank transfer)
|
||||
resultAssertions: []assertion{assertGasUsed(97000, 99000), assertErrorString("insufficient funds")},
|
||||
},
|
||||
"out of gas panic with no gas limit": {
|
||||
submsgID: 7,
|
||||
msg: infiniteLoop,
|
||||
isOutOfGasPanic: true,
|
||||
},
|
||||
|
||||
"send tokens with limit": {
|
||||
submsgID: 15,
|
||||
msg: validBankSend,
|
||||
gasLimit: &subGasLimit,
|
||||
// uses same gas as call without limit
|
||||
resultAssertions: []assertion{assertReturnedEvents(3), assertGasUsed(123000, 125000)},
|
||||
},
|
||||
"not enough tokens with limit": {
|
||||
submsgID: 16,
|
||||
msg: invalidBankSend,
|
||||
subMsgError: true,
|
||||
gasLimit: &subGasLimit,
|
||||
// uses same gas as call without limit
|
||||
resultAssertions: []assertion{assertGasUsed(97000, 99000), assertErrorString("insufficient funds")},
|
||||
},
|
||||
"out of gas caught with gas limit": {
|
||||
submsgID: 17,
|
||||
msg: infiniteLoop,
|
||||
subMsgError: true,
|
||||
gasLimit: &subGasLimit,
|
||||
// uses all the subGasLimit, plus the 92k or so for the main contract
|
||||
resultAssertions: []assertion{assertGasUsed(subGasLimit+92000, subGasLimit+94000), assertErrorString("out of gas")},
|
||||
},
|
||||
|
||||
"instantiate contract gets address in data and events": {
|
||||
submsgID: 21,
|
||||
msg: instantiateContract,
|
||||
resultAssertions: []assertion{assertReturnedEvents(1), assertGotContractAddr},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, contractStart)
|
||||
_, _, empty := keyPubAddr()
|
||||
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), fmt.Sprintf("contract %s", name), contractStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
msg := tc.msg(contractAddr.String(), empty.String())
|
||||
reflectSend := ReflectHandleMsg{
|
||||
ReflectSubCall: &reflectSubPayload{
|
||||
Msgs: []wasmvmtypes.SubMsg{{
|
||||
ID: tc.submsgID,
|
||||
Msg: msg,
|
||||
GasLimit: tc.gasLimit,
|
||||
}},
|
||||
},
|
||||
}
|
||||
reflectSendBz, err := json.Marshal(reflectSend)
|
||||
require.NoError(t, err)
|
||||
|
||||
execCtx := ctx.WithGasMeter(sdk.NewGasMeter(ctxGasLimit))
|
||||
defer func() {
|
||||
if tc.isOutOfGasPanic {
|
||||
r := recover()
|
||||
require.NotNil(t, r, "expected panic")
|
||||
if _, ok := r.(sdk.ErrorOutOfGas); !ok {
|
||||
t.Fatalf("Expected OutOfGas panic, got: %#v\n", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
_, err = keeper.Execute(execCtx, contractAddr, creator, reflectSendBz, nil)
|
||||
|
||||
if tc.executeError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
// query the reply
|
||||
query := ReflectQueryMsg{
|
||||
SubCallResult: &SubCall{ID: tc.submsgID},
|
||||
}
|
||||
queryBz, err := json.Marshal(query)
|
||||
require.NoError(t, err)
|
||||
queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz)
|
||||
require.NoError(t, err)
|
||||
var res wasmvmtypes.Reply
|
||||
err = json.Unmarshal(queryRes, &res)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.submsgID, res.ID)
|
||||
|
||||
if tc.subMsgError {
|
||||
require.NotEmpty(t, res.Result.Err)
|
||||
require.Nil(t, res.Result.Ok)
|
||||
} else {
|
||||
require.Empty(t, res.Result.Err)
|
||||
require.NotNil(t, res.Result.Ok)
|
||||
}
|
||||
|
||||
for _, assertion := range tc.resultAssertions {
|
||||
assertion(t, execCtx, contractAddr.String(), empty.String(), res.Result)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test an error case, where the Encoded doesn't return any sdk.Msg and we trigger(ed) a null pointer exception.
|
||||
// This occurs with the IBC encoder. Test this.
|
||||
func TestDispatchSubMsgEncodeToNoSdkMsg(t *testing.T) {
|
||||
// fake out the bank handle to return success with no data
|
||||
nilEncoder := func(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]sdk.Msg, error) {
|
||||
return nil, nil
|
||||
}
|
||||
customEncoders := &MessageEncoders{
|
||||
Bank: nilEncoder,
|
||||
}
|
||||
|
||||
ctx, keepers := CreateTestInput(t, false, ReflectFeatures, customEncoders, nil)
|
||||
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
|
||||
|
||||
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
||||
contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
|
||||
|
||||
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
|
||||
_, _, fred := keyPubAddr()
|
||||
|
||||
// upload code
|
||||
reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm")
|
||||
require.NoError(t, err)
|
||||
codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// creator instantiates a contract and gives it tokens
|
||||
contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, contractAddr)
|
||||
|
||||
// creator can send contract's tokens to fred (using SendMsg)
|
||||
msg := wasmvmtypes.CosmosMsg{
|
||||
Bank: &wasmvmtypes.BankMsg{
|
||||
Send: &wasmvmtypes.SendMsg{
|
||||
ToAddress: fred.String(),
|
||||
Amount: []wasmvmtypes.Coin{{
|
||||
Denom: "denom",
|
||||
Amount: "15000",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
reflectSend := ReflectHandleMsg{
|
||||
ReflectSubCall: &reflectSubPayload{
|
||||
Msgs: []wasmvmtypes.SubMsg{{
|
||||
ID: 7,
|
||||
Msg: msg,
|
||||
}},
|
||||
},
|
||||
}
|
||||
reflectSendBz, err := json.Marshal(reflectSend)
|
||||
require.NoError(t, err)
|
||||
_, err = keeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// query the reflect state to ensure the result was stored
|
||||
query := ReflectQueryMsg{
|
||||
SubCallResult: &SubCall{ID: 7},
|
||||
}
|
||||
queryBz, err := json.Marshal(query)
|
||||
require.NoError(t, err)
|
||||
queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz)
|
||||
require.NoError(t, err)
|
||||
|
||||
var res wasmvmtypes.Reply
|
||||
err = json.Unmarshal(queryRes, &res)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(7), res.ID)
|
||||
assert.Empty(t, res.Result.Err)
|
||||
require.NotNil(t, res.Result.Ok)
|
||||
sub := res.Result.Ok
|
||||
assert.Empty(t, sub.Data)
|
||||
require.Len(t, sub.Events, 0)
|
||||
}
|
||||
@@ -1,24 +1,35 @@
|
||||
package wasmtesting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type MockMessageHandler struct {
|
||||
DispatchFn func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error
|
||||
DispatchMsgFn func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error)
|
||||
}
|
||||
|
||||
func (m *MockMessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
return m.DispatchFn(ctx, contractAddr, contractIBCPortID, msgs...)
|
||||
func (m *MockMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
return m.DispatchMsgFn(ctx, contractAddr, contractIBCPortID, msg)
|
||||
}
|
||||
|
||||
func NewCapturingMessageHandler() (*MockMessageHandler, *[]wasmvmtypes.CosmosMsg) {
|
||||
var messages []wasmvmtypes.CosmosMsg
|
||||
return &MockMessageHandler{
|
||||
DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error {
|
||||
messages = append(messages, msgs...)
|
||||
return nil
|
||||
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
messages = append(messages, msg)
|
||||
// return one data item so that this doesn't cause an error in submessage processing (it takes the first element from data)
|
||||
return nil, [][]byte{{1}}, nil
|
||||
},
|
||||
}, &messages
|
||||
}
|
||||
|
||||
func NewErroringMessageHandler() *MockMessageHandler {
|
||||
return &MockMessageHandler{
|
||||
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
return nil, nil, errors.New("test, ignore")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type MockWasmer struct {
|
||||
QueryFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) ([]byte, uint64, error)
|
||||
MigrateFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error)
|
||||
SudoFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error)
|
||||
ReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error)
|
||||
GetCodeFn func(codeID wasmvm.Checksum) (wasmvm.WasmCode, error)
|
||||
CleanupFn func()
|
||||
IBCChannelOpenFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, channel wasmvmtypes.IBCChannel, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (uint64, error)
|
||||
@@ -126,6 +127,13 @@ func (m *MockWasmer) Sudo(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg [
|
||||
|
||||
}
|
||||
|
||||
func (m *MockWasmer) Reply(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error) {
|
||||
if m.ReplyFn == nil {
|
||||
panic("not supposed to be called!")
|
||||
}
|
||||
return m.ReplyFn(codeID, env, reply, store, goapi, querier, gasMeter, gasLimit)
|
||||
}
|
||||
|
||||
func (m *MockWasmer) GetCode(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) {
|
||||
if m.GetCodeFn == nil {
|
||||
panic("not supposed to be called!")
|
||||
|
||||
@@ -107,6 +107,18 @@ type WasmerEngine interface {
|
||||
gasLimit uint64,
|
||||
) (*wasmvmtypes.Response, uint64, error)
|
||||
|
||||
// Reply is called on the original dispatching contract after running a submessage
|
||||
Reply(
|
||||
codeID wasmvm.Checksum,
|
||||
env wasmvmtypes.Env,
|
||||
reply wasmvmtypes.Reply,
|
||||
store wasmvm.KVStore,
|
||||
goapi wasmvm.GoAPI,
|
||||
querier wasmvm.Querier,
|
||||
gasMeter wasmvm.GasMeter,
|
||||
gasLimit uint64,
|
||||
) (*wasmvmtypes.Response, 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.
|
||||
|
||||
Reference in New Issue
Block a user