Skip to content

Commit 9b2b246

Browse files
authored
Merge pull request #1946 from CosmWasm/co/ibc-fees
IBC Fees
2 parents 40b8010 + 248baf8 commit 9b2b246

File tree

6 files changed

+248
-1
lines changed

6 files changed

+248
-1
lines changed

tests/e2e/ibc_fees_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/CosmWasm/wasmd/app"
2424
wasmibctesting "github.com/CosmWasm/wasmd/tests/ibctesting"
2525
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
26+
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
2627
)
2728

2829
func TestIBCFeesTransfer(t *testing.T) {
@@ -219,3 +220,119 @@ func TestIBCFeesWasm(t *testing.T) {
219220
payeeBalance = chainB.AllBalances(payee)
220221
assert.Equal(t, sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(2)).String(), payeeBalance.String())
221222
}
223+
224+
func TestIBCFeesReflect(t *testing.T) {
225+
// scenario:
226+
// given 2 chains with reflect on chain A
227+
// and an ibc channel established
228+
// when ibc-reflect sends a PayPacketFee and a PayPacketFeeAsync msg
229+
// then the relayer's payee is receiving the fee(s) on success
230+
231+
marshaler := app.MakeEncodingConfig(t).Codec
232+
coord := wasmibctesting.NewCoordinator(t, 2)
233+
chainA := coord.GetChain(wasmibctesting.GetChainID(1))
234+
chainB := coord.GetChain(ibctesting.GetChainID(2))
235+
actorChainA := sdk.AccAddress(chainA.SenderPrivKey.PubKey().Address())
236+
actorChainB := sdk.AccAddress(chainB.SenderPrivKey.PubKey().Address())
237+
238+
// setup chain A
239+
codeID := chainA.StoreCodeFile("./testdata/reflect_2_2.wasm").CodeID
240+
241+
initMsg := []byte("{}")
242+
reflectContractAddr := chainA.InstantiateContract(codeID, initMsg)
243+
244+
payee := sdk.AccAddress(bytes.Repeat([]byte{2}, address.Len))
245+
oneToken := []wasmvmtypes.Coin{wasmvmtypes.NewCoin(1, sdk.DefaultBondDenom)}
246+
247+
path := wasmibctesting.NewPath(chainA, chainB)
248+
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
249+
PortID: ibctransfertypes.PortID,
250+
Version: string(marshaler.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.Version})),
251+
Order: channeltypes.UNORDERED,
252+
}
253+
path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{
254+
PortID: ibctransfertypes.PortID,
255+
Version: string(marshaler.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.Version})),
256+
Order: channeltypes.UNORDERED,
257+
}
258+
// with an ics-29 fee enabled channel setup between both chains
259+
coord.Setup(path)
260+
appA := chainA.App.(*app.WasmApp)
261+
appB := chainB.App.(*app.WasmApp)
262+
require.True(t, appA.IBCFeeKeeper.IsFeeEnabled(chainA.GetContext(), ibctransfertypes.PortID, path.EndpointA.ChannelID))
263+
require.True(t, appB.IBCFeeKeeper.IsFeeEnabled(chainB.GetContext(), ibctransfertypes.PortID, path.EndpointB.ChannelID))
264+
// and with a payee registered for A -> B
265+
_, err := chainA.SendMsgs(ibcfee.NewMsgRegisterPayee(ibctransfertypes.PortID, path.EndpointA.ChannelID, actorChainA.String(), payee.String()))
266+
require.NoError(t, err)
267+
_, err = chainB.SendMsgs(ibcfee.NewMsgRegisterCounterpartyPayee(ibctransfertypes.PortID, path.EndpointB.ChannelID, actorChainB.String(), payee.String()))
268+
require.NoError(t, err)
269+
270+
// when reflect contract on A sends a PayPacketFee msg, followed by a transfer
271+
_, err = ExecViaReflectContract(t, chainA, reflectContractAddr, []wasmvmtypes.CosmosMsg{
272+
{
273+
IBC: &wasmvmtypes.IBCMsg{
274+
PayPacketFee: &wasmvmtypes.PayPacketFeeMsg{
275+
Fee: wasmvmtypes.IBCFee{
276+
AckFee: oneToken,
277+
ReceiveFee: oneToken,
278+
TimeoutFee: []wasmvmtypes.Coin{},
279+
},
280+
Relayers: []string{},
281+
PortID: ibctransfertypes.PortID,
282+
ChannelID: path.EndpointA.ChannelID,
283+
},
284+
},
285+
},
286+
{
287+
IBC: &wasmvmtypes.IBCMsg{
288+
Transfer: &wasmvmtypes.TransferMsg{
289+
ChannelID: path.EndpointA.ChannelID,
290+
ToAddress: actorChainB.String(),
291+
Amount: wasmvmtypes.NewCoin(10, sdk.DefaultBondDenom),
292+
Timeout: wasmvmtypes.IBCTimeout{
293+
Timestamp: 9999999999999999999,
294+
},
295+
},
296+
},
297+
},
298+
})
299+
require.NoError(t, err)
300+
301+
pendingIncentivisedPackages := appA.IBCFeeKeeper.GetIdentifiedPacketFeesForChannel(chainA.GetContext(), ibctransfertypes.PortID, path.EndpointA.ChannelID)
302+
assert.Len(t, pendingIncentivisedPackages, 1)
303+
304+
// and sends an PayPacketFeeAsync msg
305+
_, err = ExecViaReflectContract(t, chainA, reflectContractAddr, []wasmvmtypes.CosmosMsg{
306+
{
307+
IBC: &wasmvmtypes.IBCMsg{
308+
PayPacketFeeAsync: &wasmvmtypes.PayPacketFeeAsyncMsg{
309+
Fee: wasmvmtypes.IBCFee{
310+
AckFee: []wasmvmtypes.Coin{},
311+
ReceiveFee: oneToken,
312+
TimeoutFee: oneToken,
313+
},
314+
Relayers: []string{},
315+
Sequence: pendingIncentivisedPackages[0].PacketId.Sequence,
316+
PortID: ibctransfertypes.PortID,
317+
ChannelID: path.EndpointA.ChannelID,
318+
},
319+
},
320+
},
321+
})
322+
require.NoError(t, err)
323+
324+
// and packages relayed
325+
require.NoError(t, coord.RelayAndAckPendingPackets(path))
326+
327+
// then
328+
// on chain A
329+
payeeBalance := chainA.AllBalances(payee)
330+
// 2 tokens from the PayPacketFee and 1 token from the PayPacketFeeAsync
331+
assert.Equal(t, sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(3)).String(), payeeBalance.String())
332+
// and on chain B
333+
pendingIncentivisedPackages = appA.IBCFeeKeeper.GetIdentifiedPacketFeesForChannel(chainA.GetContext(), ibctransfertypes.PortID, path.EndpointA.ChannelID)
334+
assert.Len(t, pendingIncentivisedPackages, 0)
335+
expBalance := ibctransfertypes.GetTransferCoin(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, sdk.DefaultBondDenom, sdkmath.NewInt(10))
336+
gotBalance := chainB.Balance(actorChainB, expBalance.Denom)
337+
assert.Equal(t, expBalance.String(), gotBalance.String(), chainB.AllBalances(actorChainB))
338+
}

tests/e2e/testdata/reflect_2_2.wasm

394 KB
Binary file not shown.

tests/integration/module_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func setupTest(t *testing.T) testData {
6666

6767
ctx, keepers := keeper.CreateTestInput(t, false, []string{
6868
"iterator", "staking", "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3",
69-
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1",
69+
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1", "cosmwasm_2_2",
7070
})
7171
encConf := keeper.MakeEncodingConfig(t)
7272
queryRouter := baseapp.NewGRPCQueryRouter()

x/wasm/keeper/capabilities.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ func BuiltInCapabilities() []string {
1717
"cosmwasm_1_4",
1818
"cosmwasm_2_0",
1919
"cosmwasm_2_1",
20+
"cosmwasm_2_2",
2021
}
2122
}

x/wasm/keeper/handler_plugin_encoders.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
8+
ibcfeetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types"
89
ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
910
ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
1011
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
@@ -320,6 +321,33 @@ func EncodeIBCMsg(portSource types.ICS20TransferPortSource) func(ctx sdk.Context
320321
Memo: msg.Transfer.Memo,
321322
}
322323
return []sdk.Msg{msg}, nil
324+
case msg.PayPacketFee != nil:
325+
fee, err := ConvertIBCFee(&msg.PayPacketFee.Fee)
326+
if err != nil {
327+
return nil, errorsmod.Wrap(err, "fee")
328+
}
329+
msg := &ibcfeetypes.MsgPayPacketFee{
330+
Fee: fee,
331+
SourcePortId: msg.PayPacketFee.PortID,
332+
SourceChannelId: msg.PayPacketFee.ChannelID,
333+
Signer: sender.String(),
334+
Relayers: msg.PayPacketFee.Relayers,
335+
}
336+
return []sdk.Msg{msg}, nil
337+
case msg.PayPacketFeeAsync != nil:
338+
fee, err := ConvertIBCFee(&msg.PayPacketFeeAsync.Fee)
339+
if err != nil {
340+
return nil, errorsmod.Wrap(err, "fee")
341+
}
342+
msg := &ibcfeetypes.MsgPayPacketFeeAsync{
343+
PacketId: channeltypes.PacketId{
344+
PortId: msg.PayPacketFeeAsync.PortID,
345+
ChannelId: msg.PayPacketFeeAsync.ChannelID,
346+
Sequence: msg.PayPacketFeeAsync.Sequence,
347+
},
348+
PacketFee: ibcfeetypes.NewPacketFee(fee, sender.String(), msg.PayPacketFeeAsync.Relayers),
349+
}
350+
return []sdk.Msg{msg}, nil
323351
default:
324352
return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of IBC")
325353
}
@@ -406,3 +434,23 @@ func ConvertWasmCoinToSdkCoin(coin wasmvmtypes.Coin) (sdk.Coin, error) {
406434
}
407435
return r, r.Validate()
408436
}
437+
438+
func ConvertIBCFee(fee *wasmvmtypes.IBCFee) (ibcfeetypes.Fee, error) {
439+
ackFee, err := ConvertWasmCoinsToSdkCoins(fee.AckFee)
440+
if err != nil {
441+
return ibcfeetypes.Fee{}, err
442+
}
443+
recvFee, err := ConvertWasmCoinsToSdkCoins(fee.ReceiveFee)
444+
if err != nil {
445+
return ibcfeetypes.Fee{}, err
446+
}
447+
timeoutFee, err := ConvertWasmCoinsToSdkCoins(fee.TimeoutFee)
448+
if err != nil {
449+
return ibcfeetypes.Fee{}, err
450+
}
451+
return ibcfeetypes.Fee{
452+
AckFee: ackFee,
453+
RecvFee: recvFee,
454+
TimeoutFee: timeoutFee,
455+
}, nil
456+
}

x/wasm/keeper/handler_plugin_encoders_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
77
"github.com/cosmos/gogoproto/proto"
8+
ibcfee "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types"
89
ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
910
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck
1011
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
@@ -613,6 +614,86 @@ func TestEncodeIbcMsg(t *testing.T) {
613614
},
614615
},
615616
},
617+
"IBC PayPacketFee": {
618+
sender: addr1,
619+
srcContractIBCPort: "myIBCPort",
620+
srcMsg: wasmvmtypes.CosmosMsg{
621+
IBC: &wasmvmtypes.IBCMsg{
622+
PayPacketFee: &wasmvmtypes.PayPacketFeeMsg{
623+
ChannelID: "myChannelID",
624+
Fee: wasmvmtypes.IBCFee{
625+
TimeoutFee: []wasmvmtypes.Coin{
626+
{
627+
Denom: "ALX",
628+
Amount: "1",
629+
},
630+
},
631+
},
632+
PortID: "myIBCPort",
633+
Relayers: []string{},
634+
},
635+
},
636+
},
637+
output: []sdk.Msg{
638+
&ibcfee.MsgPayPacketFee{
639+
Fee: ibcfee.Fee{
640+
TimeoutFee: []sdk.Coin{
641+
{
642+
Denom: "ALX",
643+
Amount: sdkmath.NewInt(1),
644+
},
645+
},
646+
},
647+
SourcePortId: "myIBCPort",
648+
SourceChannelId: "myChannelID",
649+
Signer: addr1.String(),
650+
Relayers: []string{},
651+
},
652+
},
653+
},
654+
"IBC PayPacketFeeAsync": {
655+
sender: addr1,
656+
srcContractIBCPort: "myIBCPort",
657+
srcMsg: wasmvmtypes.CosmosMsg{
658+
IBC: &wasmvmtypes.IBCMsg{
659+
PayPacketFeeAsync: &wasmvmtypes.PayPacketFeeAsyncMsg{
660+
ChannelID: "myChannelID",
661+
Fee: wasmvmtypes.IBCFee{
662+
TimeoutFee: []wasmvmtypes.Coin{
663+
{
664+
Denom: "ALX",
665+
Amount: "1",
666+
},
667+
},
668+
},
669+
PortID: "myIBCPort",
670+
Relayers: []string{},
671+
Sequence: 42,
672+
},
673+
},
674+
},
675+
output: []sdk.Msg{
676+
&ibcfee.MsgPayPacketFeeAsync{
677+
PacketId: channeltypes.PacketId{
678+
PortId: "myIBCPort",
679+
ChannelId: "myChannelID",
680+
Sequence: 42,
681+
},
682+
PacketFee: ibcfee.PacketFee{
683+
Fee: ibcfee.Fee{
684+
TimeoutFee: []sdk.Coin{
685+
{
686+
Denom: "ALX",
687+
Amount: sdkmath.NewInt(1),
688+
},
689+
},
690+
},
691+
RefundAddress: addr1.String(),
692+
Relayers: []string{},
693+
},
694+
},
695+
},
696+
},
616697
}
617698
encodingConfig := MakeEncodingConfig(t)
618699
for name, tc := range cases {

0 commit comments

Comments
 (0)