Merge pull request #1353 from CosmWasm/ibc_packet_recv
Redesign IBC on packet recv error/ result.Err handling
This commit is contained in:
@@ -264,26 +264,26 @@ func (i IBCHandler) OnRecvPacket(
|
|||||||
) ibcexported.Acknowledgement {
|
) ibcexported.Acknowledgement {
|
||||||
contractAddr, err := ContractFromPortID(packet.DestinationPort)
|
contractAddr, err := ContractFromPortID(packet.DestinationPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return channeltypes.NewErrorAcknowledgement(errorsmod.Wrapf(err, "contract port id"))
|
// this must not happen as ports were registered before
|
||||||
|
panic(errorsmod.Wrapf(err, "contract port id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
em := sdk.NewEventManager()
|
||||||
msg := wasmvmtypes.IBCPacketReceiveMsg{Packet: newIBCPacket(packet), Relayer: relayer.String()}
|
msg := wasmvmtypes.IBCPacketReceiveMsg{Packet: newIBCPacket(packet), Relayer: relayer.String()}
|
||||||
ack, err := i.keeper.OnRecvPacket(ctx, contractAddr, msg)
|
ack, err := i.keeper.OnRecvPacket(ctx.WithEventManager(em), contractAddr, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return channeltypes.NewErrorAcknowledgement(err)
|
ack = channeltypes.NewErrorAcknowledgement(err)
|
||||||
|
types.EmitAcknowledgementEvent(ctx, contractAddr, ack, err)
|
||||||
|
// the state gets reverted, so we drop all captured events
|
||||||
|
return ack
|
||||||
}
|
}
|
||||||
return ContractConfirmStateAck(ack)
|
if ack == nil || ack.Success() {
|
||||||
}
|
// emit all contract and submessage events on success
|
||||||
|
// nil ack is a success case, see: https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/keeper/msg_server.go#L453
|
||||||
var _ ibcexported.Acknowledgement = ContractConfirmStateAck{}
|
ctx.EventManager().EmitEvents(em.Events())
|
||||||
|
}
|
||||||
type ContractConfirmStateAck []byte
|
types.EmitAcknowledgementEvent(ctx, contractAddr, ack, nil)
|
||||||
|
return ack
|
||||||
func (w ContractConfirmStateAck) Success() bool {
|
|
||||||
return true // always commit state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w ContractConfirmStateAck) Acknowledgement() []byte {
|
|
||||||
return w
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnAcknowledgementPacket implements the IBCModule interface
|
// OnAcknowledgementPacket implements the IBCModule interface
|
||||||
|
|||||||
@@ -4,11 +4,125 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||||
|
abci "github.com/cometbft/cometbft/abci/types"
|
||||||
|
"github.com/cometbft/cometbft/libs/rand"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
|
clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
|
||||||
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
|
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
|
||||||
|
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/CosmWasm/wasmd/x/wasm/keeper"
|
||||||
|
"github.com/CosmWasm/wasmd/x/wasm/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestOnRecvPacket(t *testing.T) {
|
||||||
|
anyRelayerAddr := sdk.AccAddress(rand.Bytes(address.Len))
|
||||||
|
anyContractIBCPkg := IBCPacketFixture(func(p *channeltypes.Packet) {
|
||||||
|
p.DestinationPort = "wasm.cosmos1w09vr7rpe2agu0kg2zlpkdckce865l3zps8mxjurxthfh3m7035qe5hh7f"
|
||||||
|
})
|
||||||
|
myCustomEvent := sdk.NewEvent("testing")
|
||||||
|
specs := map[string]struct {
|
||||||
|
ibcPkg channeltypes.Packet
|
||||||
|
contractRsp ibcexported.Acknowledgement
|
||||||
|
contractErr error
|
||||||
|
expEvents sdk.Events
|
||||||
|
expPanic bool
|
||||||
|
expAck ibcexported.Acknowledgement
|
||||||
|
}{
|
||||||
|
"contract returns success response": {
|
||||||
|
ibcPkg: anyContractIBCPkg,
|
||||||
|
contractRsp: keeper.ContractConfirmStateAck([]byte{1}),
|
||||||
|
expAck: keeper.ContractConfirmStateAck([]byte{1}),
|
||||||
|
expEvents: sdk.Events{
|
||||||
|
myCustomEvent,
|
||||||
|
{
|
||||||
|
Type: "ibc_packet_received",
|
||||||
|
Attributes: []abci.EventAttribute{
|
||||||
|
{Key: "module", Value: "wasm"},
|
||||||
|
{Key: "_contract_address", Value: "cosmos1w09vr7rpe2agu0kg2zlpkdckce865l3zps8mxjurxthfh3m7035qe5hh7f"},
|
||||||
|
{Key: "success", Value: "true"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"contract returns err response": {
|
||||||
|
ibcPkg: anyContractIBCPkg,
|
||||||
|
contractRsp: channeltypes.NewErrorAcknowledgement(types.ErrInvalid.Wrap("testing")),
|
||||||
|
expAck: channeltypes.NewErrorAcknowledgement(types.ErrInvalid.Wrap("testing")),
|
||||||
|
expEvents: sdk.Events{
|
||||||
|
{
|
||||||
|
Type: "ibc_packet_received",
|
||||||
|
Attributes: []abci.EventAttribute{
|
||||||
|
{Key: "module", Value: "wasm"},
|
||||||
|
{Key: "_contract_address", Value: "cosmos1w09vr7rpe2agu0kg2zlpkdckce865l3zps8mxjurxthfh3m7035qe5hh7f"},
|
||||||
|
{Key: "success", Value: "false"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil considered success response": { // regression only
|
||||||
|
ibcPkg: anyContractIBCPkg,
|
||||||
|
expEvents: sdk.Events{
|
||||||
|
myCustomEvent,
|
||||||
|
{
|
||||||
|
Type: "ibc_packet_received",
|
||||||
|
Attributes: []abci.EventAttribute{
|
||||||
|
{Key: "module", Value: "wasm"},
|
||||||
|
{Key: "_contract_address", Value: "cosmos1w09vr7rpe2agu0kg2zlpkdckce865l3zps8mxjurxthfh3m7035qe5hh7f"},
|
||||||
|
{Key: "success", Value: "true"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"unknown contract port": {
|
||||||
|
ibcPkg: IBCPacketFixture(func(p *channeltypes.Packet) {
|
||||||
|
p.DestinationPort = "not-a-contract-port"
|
||||||
|
}),
|
||||||
|
expPanic: true,
|
||||||
|
},
|
||||||
|
"contract executed with error": {
|
||||||
|
ibcPkg: anyContractIBCPkg,
|
||||||
|
contractErr: types.ErrInvalid.Wrap("testing"),
|
||||||
|
expAck: channeltypes.NewErrorAcknowledgement(types.ErrInvalid.Wrap("testing")),
|
||||||
|
expEvents: sdk.Events{{
|
||||||
|
Type: "ibc_packet_received",
|
||||||
|
Attributes: []abci.EventAttribute{
|
||||||
|
{Key: "module", Value: "wasm"},
|
||||||
|
{Key: "_contract_address", Value: "cosmos1w09vr7rpe2agu0kg2zlpkdckce865l3zps8mxjurxthfh3m7035qe5hh7f"},
|
||||||
|
{Key: "success", Value: "false"},
|
||||||
|
{Key: "error", Value: "testing: invalid"}, // not redacted
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, spec := range specs {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
mock := IBCContractKeeperMock{
|
||||||
|
OnRecvPacketFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmvmtypes.IBCPacketReceiveMsg) (ibcexported.Acknowledgement, error) {
|
||||||
|
// additional custom event to confirm event handling on state commit/ rollback
|
||||||
|
ctx.EventManager().EmitEvent(myCustomEvent)
|
||||||
|
return spec.contractRsp, spec.contractErr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := NewIBCHandler(mock, nil, nil)
|
||||||
|
em := &sdk.EventManager{}
|
||||||
|
ctx := sdk.Context{}.WithEventManager(em)
|
||||||
|
if spec.expPanic {
|
||||||
|
require.Panics(t, func() {
|
||||||
|
_ = h.OnRecvPacket(ctx, spec.ibcPkg, anyRelayerAddr)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotAck := h.OnRecvPacket(ctx, spec.ibcPkg, anyRelayerAddr)
|
||||||
|
assert.Equal(t, spec.expAck, gotAck)
|
||||||
|
assert.Equal(t, spec.expEvents, em.Events())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMapToWasmVMIBCPacket(t *testing.T) {
|
func TestMapToWasmVMIBCPacket(t *testing.T) {
|
||||||
var myTimestamp uint64 = 1
|
var myTimestamp uint64 = 1
|
||||||
specs := map[string]struct {
|
specs := map[string]struct {
|
||||||
@@ -80,3 +194,17 @@ func IBCPacketFixture(mutators ...func(p *channeltypes.Packet)) channeltypes.Pac
|
|||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ types.IBCContractKeeper = &IBCContractKeeperMock{}
|
||||||
|
|
||||||
|
type IBCContractKeeperMock struct {
|
||||||
|
types.IBCContractKeeper
|
||||||
|
OnRecvPacketFn func(ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmvmtypes.IBCPacketReceiveMsg) (ibcexported.Acknowledgement, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m IBCContractKeeperMock) OnRecvPacket(ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmvmtypes.IBCPacketReceiveMsg) (ibcexported.Acknowledgement, error) {
|
||||||
|
if m.OnRecvPacketFn == nil {
|
||||||
|
panic("not expected to be called")
|
||||||
|
}
|
||||||
|
return m.OnRecvPacketFn(ctx, contractAddr, msg)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package keeper
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
|
||||||
|
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
|
||||||
|
|
||||||
errorsmod "cosmossdk.io/errors"
|
errorsmod "cosmossdk.io/errors"
|
||||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||||
"github.com/cosmos/cosmos-sdk/telemetry"
|
"github.com/cosmos/cosmos-sdk/telemetry"
|
||||||
@@ -116,7 +119,7 @@ func (k Keeper) OnRecvPacket(
|
|||||||
ctx sdk.Context,
|
ctx sdk.Context,
|
||||||
contractAddr sdk.AccAddress,
|
contractAddr sdk.AccAddress,
|
||||||
msg wasmvmtypes.IBCPacketReceiveMsg,
|
msg wasmvmtypes.IBCPacketReceiveMsg,
|
||||||
) ([]byte, error) {
|
) (ibcexported.Acknowledgement, error) {
|
||||||
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-recv-packet")
|
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-recv-packet")
|
||||||
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr)
|
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,13 +133,34 @@ func (k Keeper) OnRecvPacket(
|
|||||||
res, gasUsed, execErr := k.wasmVM.IBCPacketReceive(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization)
|
res, gasUsed, execErr := k.wasmVM.IBCPacketReceive(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization)
|
||||||
k.consumeRuntimeGas(ctx, gasUsed)
|
k.consumeRuntimeGas(ctx, gasUsed)
|
||||||
if execErr != nil {
|
if execErr != nil {
|
||||||
panic(execErr)
|
panic(execErr) // let contract fully abort IBC receive in certain case
|
||||||
}
|
}
|
||||||
if res.Err != "" { // handle error case as before https://github.com/CosmWasm/wasmvm/commit/c300106fe5c9426a495f8e10821e00a9330c56c6
|
if res.Err != "" {
|
||||||
return nil, errorsmod.Wrap(types.ErrExecuteFailed, res.Err)
|
// return error ACK with non-redacted contract message, state will be reverted
|
||||||
|
return channeltypes.Acknowledgement{
|
||||||
|
Response: &channeltypes.Acknowledgement_Error{Error: res.Err},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
// note submessage reply results can overwrite the `Acknowledgement` data
|
// note submessage reply results can overwrite the `Acknowledgement` data
|
||||||
return k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events)
|
data, err := k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events)
|
||||||
|
if err != nil {
|
||||||
|
// submessage errors result in error ACK with state reverted. Error message is redacted
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// success ACK, state will be committed
|
||||||
|
return ContractConfirmStateAck(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ibcexported.Acknowledgement = ContractConfirmStateAck{}
|
||||||
|
|
||||||
|
type ContractConfirmStateAck []byte
|
||||||
|
|
||||||
|
func (w ContractConfirmStateAck) Success() bool {
|
||||||
|
return true // always commit state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w ContractConfirmStateAck) Acknowledgement() []byte {
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnAckPacket calls the contract to handle the "acknowledgement" data which can contain success or failure of a packet
|
// OnAckPacket calls the contract to handle the "acknowledgement" data which can contain success or failure of a packet
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ func TestOnRecvPacket(t *testing.T) {
|
|||||||
|
|
||||||
specs := map[string]struct {
|
specs := map[string]struct {
|
||||||
contractAddr sdk.AccAddress
|
contractAddr sdk.AccAddress
|
||||||
contractResp *wasmvmtypes.IBCReceiveResponse
|
contractResp *wasmvmtypes.IBCReceiveResult
|
||||||
contractErr error
|
contractErr error
|
||||||
overwriteMessenger *wasmtesting.MockMessageHandler
|
overwriteMessenger *wasmtesting.MockMessageHandler
|
||||||
mockReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error)
|
mockReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error)
|
||||||
@@ -329,26 +329,38 @@ func TestOnRecvPacket(t *testing.T) {
|
|||||||
expPanic bool
|
expPanic bool
|
||||||
expEventTypes []string
|
expEventTypes []string
|
||||||
}{
|
}{
|
||||||
"consume contract gas": {
|
"contract returns success ack": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas,
|
expContractGas: myContractGas,
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
Acknowledgement: []byte("myAck"),
|
Ok: &wasmvmtypes.IBCReceiveResponse{Acknowledgement: []byte("myAck")},
|
||||||
},
|
},
|
||||||
expAck: []byte("myAck"),
|
expAck: []byte("myAck"),
|
||||||
},
|
},
|
||||||
"can return empty ack": {
|
"can return empty ack data": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas,
|
expContractGas: myContractGas,
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{},
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
|
Ok: &wasmvmtypes.IBCReceiveResponse{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"consume gas on error, ignore events + messages": {
|
"contract Err result converted to error Ack": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas,
|
expContractGas: myContractGas,
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
Acknowledgement: []byte("myAck"),
|
Err: "my-error",
|
||||||
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}},
|
},
|
||||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
expAck: []byte(`{"error":"my-error"}`), // without error msg redaction
|
||||||
|
},
|
||||||
|
"contract aborts tx with error": {
|
||||||
|
contractAddr: example.Contract,
|
||||||
|
expContractGas: myContractGas,
|
||||||
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
|
Ok: &wasmvmtypes.IBCReceiveResponse{
|
||||||
|
Acknowledgement: []byte("myAck"),
|
||||||
|
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}},
|
||||||
|
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
contractErr: errors.New("test, ignore"),
|
contractErr: errors.New("test, ignore"),
|
||||||
expPanic: true,
|
expPanic: true,
|
||||||
@@ -356,18 +368,22 @@ func TestOnRecvPacket(t *testing.T) {
|
|||||||
"dispatch contract messages on success": {
|
"dispatch contract messages on success": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas,
|
expContractGas: myContractGas,
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
Acknowledgement: []byte("myAck"),
|
Ok: &wasmvmtypes.IBCReceiveResponse{
|
||||||
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}},
|
Acknowledgement: []byte("myAck"),
|
||||||
|
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expAck: []byte("myAck"),
|
expAck: []byte("myAck"),
|
||||||
},
|
},
|
||||||
"emit contract attributes on success": {
|
"emit contract attributes on success": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas + 10,
|
expContractGas: myContractGas + 10,
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
Acknowledgement: []byte("myAck"),
|
Ok: &wasmvmtypes.IBCReceiveResponse{
|
||||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
Acknowledgement: []byte("myAck"),
|
||||||
|
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expEventTypes: []string{types.WasmModuleEventType},
|
expEventTypes: []string{types.WasmModuleEventType},
|
||||||
expAck: []byte("myAck"),
|
expAck: []byte("myAck"),
|
||||||
@@ -375,27 +391,31 @@ func TestOnRecvPacket(t *testing.T) {
|
|||||||
"emit contract events on success": {
|
"emit contract events on success": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas + 46, // charge or custom event as well
|
expContractGas: myContractGas + 46, // charge or custom event as well
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
Acknowledgement: []byte("myAck"),
|
Ok: &wasmvmtypes.IBCReceiveResponse{
|
||||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
Acknowledgement: []byte("myAck"),
|
||||||
Events: []wasmvmtypes.Event{{
|
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||||
Type: "custom",
|
Events: []wasmvmtypes.Event{{
|
||||||
Attributes: []wasmvmtypes.EventAttribute{{
|
Type: "custom",
|
||||||
Key: "message",
|
Attributes: []wasmvmtypes.EventAttribute{{
|
||||||
Value: "to rudi",
|
Key: "message",
|
||||||
|
Value: "to rudi",
|
||||||
|
}},
|
||||||
}},
|
}},
|
||||||
}},
|
},
|
||||||
},
|
},
|
||||||
expEventTypes: []string{types.WasmModuleEventType, "wasm-custom"},
|
expEventTypes: []string{types.WasmModuleEventType, "wasm-custom"},
|
||||||
expAck: []byte("myAck"),
|
expAck: []byte("myAck"),
|
||||||
},
|
},
|
||||||
"messenger errors returned, events stored": {
|
"messenger errors returned": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas + 10,
|
expContractGas: myContractGas + 10,
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
Acknowledgement: []byte("myAck"),
|
Ok: &wasmvmtypes.IBCReceiveResponse{
|
||||||
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}},
|
Acknowledgement: []byte("myAck"),
|
||||||
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}},
|
||||||
|
Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
overwriteMessenger: wasmtesting.NewErroringMessageHandler(),
|
overwriteMessenger: wasmtesting.NewErroringMessageHandler(),
|
||||||
expErr: true,
|
expErr: true,
|
||||||
@@ -404,9 +424,11 @@ func TestOnRecvPacket(t *testing.T) {
|
|||||||
"submessage reply can overwrite ack data": {
|
"submessage reply can overwrite ack data": {
|
||||||
contractAddr: example.Contract,
|
contractAddr: example.Contract,
|
||||||
expContractGas: myContractGas + storageCosts,
|
expContractGas: myContractGas + storageCosts,
|
||||||
contractResp: &wasmvmtypes.IBCReceiveResponse{
|
contractResp: &wasmvmtypes.IBCReceiveResult{
|
||||||
Acknowledgement: []byte("myAck"),
|
Ok: &wasmvmtypes.IBCReceiveResponse{
|
||||||
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyAlways, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}},
|
Acknowledgement: []byte("myAck"),
|
||||||
|
Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyAlways, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mockReplyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
|
mockReplyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
|
||||||
return &wasmvmtypes.Response{Data: []byte("myBetterAck")}, 0, nil
|
return &wasmvmtypes.Response{Data: []byte("myBetterAck")}, 0, nil
|
||||||
@@ -425,7 +447,7 @@ func TestOnRecvPacket(t *testing.T) {
|
|||||||
|
|
||||||
m.IBCPacketReceiveFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketReceiveMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCReceiveResult, uint64, error) {
|
m.IBCPacketReceiveFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketReceiveMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCReceiveResult, uint64, error) {
|
||||||
assert.Equal(t, myPacket, msg.Packet)
|
assert.Equal(t, myPacket, msg.Packet)
|
||||||
return &wasmvmtypes.IBCReceiveResult{Ok: spec.contractResp}, myContractGas * DefaultGasMultiplier, spec.contractErr
|
return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr
|
||||||
}
|
}
|
||||||
if spec.mockReplyFn != nil {
|
if spec.mockReplyFn != nil {
|
||||||
m.ReplyFn = spec.mockReplyFn
|
m.ReplyFn = spec.mockReplyFn
|
||||||
@@ -462,17 +484,23 @@ func TestOnRecvPacket(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, spec.expAck, gotAck)
|
require.Equal(t, spec.expAck, gotAck.Acknowledgement())
|
||||||
|
|
||||||
// verify gas consumed
|
// verify gas consumed
|
||||||
const storageCosts = sdk.Gas(2903)
|
const storageCosts = sdk.Gas(2903)
|
||||||
assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts)
|
assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts)
|
||||||
// verify msgs dispatched
|
|
||||||
require.Len(t, *capturedMsgs, len(spec.contractResp.Messages))
|
// verify msgs dispatched on success/ err response
|
||||||
for i, m := range spec.contractResp.Messages {
|
if spec.contractResp.Err != "" {
|
||||||
assert.Equal(t, (*capturedMsgs)[i], m.Msg)
|
assert.Empty(t, capturedMsgs) // no messages captured on err response
|
||||||
|
assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events()))
|
||||||
|
} else {
|
||||||
|
require.Len(t, *capturedMsgs, len(spec.contractResp.Ok.Messages))
|
||||||
|
for i, m := range spec.contractResp.Ok.Messages {
|
||||||
|
assert.Equal(t, (*capturedMsgs)[i], m.Msg)
|
||||||
|
}
|
||||||
|
assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events()))
|
||||||
}
|
}
|
||||||
assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events()))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/ibc-go/v7/modules/core/exported"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// WasmModuleEventType is stored with any contract TX that returns non empty EventAttributes
|
// WasmModuleEventType is stored with any contract TX that returns non empty EventAttributes
|
||||||
WasmModuleEventType = "wasm"
|
WasmModuleEventType = "wasm"
|
||||||
@@ -17,8 +24,32 @@ const (
|
|||||||
EventTypeGovContractResult = "gov_contract_result"
|
EventTypeGovContractResult = "gov_contract_result"
|
||||||
EventTypeUpdateContractAdmin = "update_contract_admin"
|
EventTypeUpdateContractAdmin = "update_contract_admin"
|
||||||
EventTypeUpdateCodeAccessConfig = "update_code_access_config"
|
EventTypeUpdateCodeAccessConfig = "update_code_access_config"
|
||||||
|
EventTypePacketRecv = "ibc_packet_received"
|
||||||
|
// add new types to IsAcceptedEventOnRecvPacketErrorAck
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// EmitAcknowledgementEvent emits an event signalling a successful or failed acknowledgement and including the error
|
||||||
|
// details if any.
|
||||||
|
func EmitAcknowledgementEvent(ctx sdk.Context, contractAddr sdk.AccAddress, ack exported.Acknowledgement, err error) {
|
||||||
|
success := err == nil && (ack == nil || ack.Success())
|
||||||
|
attributes := []sdk.Attribute{
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName),
|
||||||
|
sdk.NewAttribute(AttributeKeyContractAddr, contractAddr.String()),
|
||||||
|
sdk.NewAttribute(AttributeKeyAckSuccess, fmt.Sprintf("%t", success)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
attributes = append(attributes, sdk.NewAttribute(AttributeKeyAckError, err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
EventTypePacketRecv,
|
||||||
|
attributes...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// event attributes returned from contract execution
|
// event attributes returned from contract execution
|
||||||
const (
|
const (
|
||||||
AttributeReservedPrefix = "_"
|
AttributeReservedPrefix = "_"
|
||||||
@@ -31,4 +62,6 @@ const (
|
|||||||
AttributeKeyNewAdmin = "new_admin_address"
|
AttributeKeyNewAdmin = "new_admin_address"
|
||||||
AttributeKeyCodePermission = "code_permission"
|
AttributeKeyCodePermission = "code_permission"
|
||||||
AttributeKeyAuthorizedAddresses = "authorized_addresses"
|
AttributeKeyAuthorizedAddresses = "authorized_addresses"
|
||||||
|
AttributeKeyAckSuccess = "success"
|
||||||
|
AttributeKeyAckError = "error"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
|
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
|
||||||
|
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ViewKeeper provides read only operations
|
// ViewKeeper provides read only operations
|
||||||
@@ -100,7 +101,7 @@ type IBCContractKeeper interface {
|
|||||||
ctx sdk.Context,
|
ctx sdk.Context,
|
||||||
contractAddr sdk.AccAddress,
|
contractAddr sdk.AccAddress,
|
||||||
msg wasmvmtypes.IBCPacketReceiveMsg,
|
msg wasmvmtypes.IBCPacketReceiveMsg,
|
||||||
) ([]byte, error)
|
) (ibcexported.Acknowledgement, error)
|
||||||
OnAckPacket(
|
OnAckPacket(
|
||||||
ctx sdk.Context,
|
ctx sdk.Context,
|
||||||
contractAddr sdk.AccAddress,
|
contractAddr sdk.AccAddress,
|
||||||
|
|||||||
Reference in New Issue
Block a user