Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tunnel] implement route - IBC Hook #485

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,054 changes: 1,032 additions & 22 deletions api/band/tunnel/v1beta1/route.pulsar.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ func NewAppKeeper(
appKeepers.IBCFeeKeeper,
appKeepers.IBCKeeper.PortKeeper,
appKeepers.ScopedTunnelKeeper,
appKeepers.TransferKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)

Expand Down
2 changes: 1 addition & 1 deletion app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ var maccPerms = map[string][]string{
ibcfeetypes.ModuleName: nil,
bandtsstypes.ModuleName: nil,
restaketypes.ModuleName: nil,
tunneltypes.ModuleName: nil,
tunneltypes.ModuleName: {authtypes.Minter},
}

func appModules(
Expand Down
18 changes: 18 additions & 0 deletions proto/band/tunnel/v1beta1/route.proto
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,21 @@ message TunnelPricesPacketData {
// created_at is the timestamp when the packet is created
int64 created_at = 4;
}

// IBCHookRoute is the type for an IBC hook route
message IBCHookRoute {
option (cosmos_proto.implements_interface) = "RouteI";

// channel_id is the IBC channel ID
string channel_id = 1 [(gogoproto.customname) = "ChannelID"];
// destination_contract_address is the destination contract address
string destination_contract_address = 2;
}

// IBCHookPacketReceipt represents a receipt for a IBC hook packet and implements the PacketReceiptI interface.
message IBCHookPacketReceipt {
option (cosmos_proto.implements_interface) = "PacketContentI";

// sequence is representing the sequence of the IBC packet.
uint64 sequence = 1;
}
1 change: 1 addition & 0 deletions scripts/tunnel/create_ibc_hook_tunnel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bandd tx tunnel create-tunnel ibc-hook channel-0 wasm1vjq0k3fj47s8wns4a7zw5c4lsjd8l6r2kzzlpk 1 1uband 10 ./scripts/tunnel/signal_deviations.json --from requester --keyring-backend test --gas-prices 0.0025uband -y --chain-id bandchain
89 changes: 89 additions & 0 deletions x/tunnel/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func GetTxCmdCreateTunnel() *cobra.Command {
txCmd.AddCommand(
GetTxCmdCreateTSSTunnel(),
GetTxCmdCreateIBCTunnel(),
GetTxCmdCreateIBCHookTunnel(),
)

return txCmd
Expand Down Expand Up @@ -156,6 +157,55 @@ func GetTxCmdCreateIBCTunnel() *cobra.Command {
return cmd
}

func GetTxCmdCreateIBCHookTunnel() *cobra.Command {
cmd := &cobra.Command{
Use: "ibc-hook [channel-id] [destination-contract-address] [initial-deposit] [interval] [signalInfos-json-file]",
Short: "Create a new IBC hook tunnel",
Args: cobra.ExactArgs(5),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

channelID := args[0]
destinationContractAddress := args[1]

initialDeposit, err := sdk.ParseCoinsNormalized(args[2])
if err != nil {
return err
}

interval, err := strconv.ParseUint(args[3], 10, 64)
if err != nil {
return err
}

signalInfos, err := parseSignalDeviations(args[4])
if err != nil {
return err
}

msg, err := types.NewMsgCreateIBCHookTunnel(
signalInfos.ToSignalDeviations(),
interval,
channelID,
destinationContractAddress,
initialDeposit,
clientCtx.GetFromAddress().String(),
)
if err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)
return cmd
}

func GetTxCmdUpdateRoute() *cobra.Command {
txCmd := &cobra.Command{
Use: "update-route",
Expand All @@ -167,6 +217,7 @@ func GetTxCmdUpdateRoute() *cobra.Command {
// add create tunnel subcommands
txCmd.AddCommand(
GetTxCmdUpdateIBCRoute(),
GetTxCmdUpdateIBCHookRoute(),
)

return txCmd
Expand Down Expand Up @@ -206,6 +257,44 @@ func GetTxCmdUpdateIBCRoute() *cobra.Command {
return cmd
}

func GetTxCmdUpdateIBCHookRoute() *cobra.Command {
cmd := &cobra.Command{
Use: "ibc-hook [tunnel-id] [channel-id] [destination-contract-address]",
Short: "Update IBC route of a IBC tunnel",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

id, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return err
}

channelID := args[1]
destContractAddr := args[2]

msg, err := types.NewMsgUpdateIBCHookRoute(
id,
channelID,
destContractAddr,
clientCtx.GetFromAddress().String(),
)
if err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}

func GetTxCmdUpdateSignalsAndInterval() *cobra.Command {
cmd := &cobra.Command{
Use: "update-signals-and-interval [tunnel-id] [interval] [signalDeviations-json-file] ",
Expand Down
41 changes: 22 additions & 19 deletions x/tunnel/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ type Keeper struct {
cdc codec.BinaryCodec
storeKey storetypes.StoreKey

authKeeper types.AccountKeeper
bankKeeper types.BankKeeper
feedsKeeper types.FeedsKeeper
bandtssKeeper types.BandtssKeeper
channelKeeper types.ChannelKeeper
ics4Wrapper types.ICS4Wrapper
portKeeper types.PortKeeper
scopedKeeper types.ScopedKeeper
authKeeper types.AccountKeeper
bankKeeper types.BankKeeper
feedsKeeper types.FeedsKeeper
bandtssKeeper types.BandtssKeeper
channelKeeper types.ChannelKeeper
ics4Wrapper types.ICS4Wrapper
portKeeper types.PortKeeper
scopedKeeper types.ScopedKeeper
transferKeeper types.TransferKeeper

authority string
}
Expand All @@ -40,6 +41,7 @@ func NewKeeper(
ics4Wrapper types.ICS4Wrapper,
portKeeper types.PortKeeper,
scopedKeeper types.ScopedKeeper,
transferKeeper types.TransferKeeper,
authority string,
) Keeper {
// ensure tunnel module account is set
Expand All @@ -53,17 +55,18 @@ func NewKeeper(
}

return Keeper{
cdc: cdc,
storeKey: key,
authKeeper: authKeeper,
bankKeeper: bankKeeper,
feedsKeeper: feedsKeeper,
bandtssKeeper: bandtssKeeper,
channelKeeper: channelKeeper,
ics4Wrapper: ics4Wrapper,
portKeeper: portKeeper,
scopedKeeper: scopedKeeper,
authority: authority,
cdc: cdc,
storeKey: key,
authKeeper: authKeeper,
bankKeeper: bankKeeper,
feedsKeeper: feedsKeeper,
bandtssKeeper: bandtssKeeper,
channelKeeper: channelKeeper,
ics4Wrapper: ics4Wrapper,
portKeeper: portKeeper,
scopedKeeper: scopedKeeper,
transferKeeper: transferKeeper,
authority: authority,
}
}

Expand Down
2 changes: 2 additions & 0 deletions x/tunnel/keeper/keeper_packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ func (k Keeper) SendPacket(ctx sdk.Context, packet types.Packet) (err error) {
)
case *types.IBCRoute:
receipt, err = k.SendIBCPacket(ctx, r, packet, tunnel.Interval)
case *types.IBCHookRoute:
receipt, err = k.SendIBCHookPacket(ctx, r, packet, sdk.MustAccAddressFromBech32(tunnel.FeePayer), tunnel.Interval)
default:
return types.ErrInvalidRoute.Wrapf("no route found for tunnel ID: %d", tunnel.ID)
}
Expand Down
81 changes: 81 additions & 0 deletions x/tunnel/keeper/keeper_packet_ibc_hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package keeper

import (
"time"

ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/bandprotocol/chain/v3/x/tunnel/types"
)

// SendIBCHookPacket sends a packet to the destination chain using IBC Hook
func (k Keeper) SendIBCHookPacket(
ctx sdk.Context,
route *types.IBCHookRoute,
packet types.Packet,
feePayer sdk.AccAddress,
interval uint64,
) (types.PacketReceiptI, error) {
// create memo string for ibc transfer
memoStr, err := types.NewIBCHookMemo(
route.DestinationContractAddress,
packet.TunnelID,
packet.Sequence,
packet.Prices,
packet.CreatedAt,
).String()
if err != nil {
return nil, err
}

// mint coin to the fee payer
err = k.MintIBCHookCoinToAccount(ctx, packet.TunnelID, feePayer)
if err != nil {
return nil, err
}

// create ibc transfer message with the memo string
msg := ibctransfertypes.NewMsgTransfer(
ibctransfertypes.PortID,
route.ChannelID,
sdk.NewInt64Coin(types.FormatHookDenomIdentifier(packet.TunnelID), types.HookTransferAmount),
feePayer.String(),
route.DestinationContractAddress,
clienttypes.ZeroHeight(),
uint64(ctx.BlockTime().UnixNano())+interval*uint64(time.Second)*2,
memoStr,
)

// send packet
res, err := k.transferKeeper.Transfer(ctx, msg)
if err != nil {
return nil, err
}

return types.NewIBCHookPacketReceipt(res.Sequence), nil
}

// MintIBCHookCoinToAccount mints hook coin to the account
func (k Keeper) MintIBCHookCoinToAccount(ctx sdk.Context, tunnelID uint64, account sdk.AccAddress) error {
// create hook coins
hookCoins := sdk.NewCoins(
sdk.NewInt64Coin(types.FormatHookDenomIdentifier(tunnelID), types.HookTransferAmount),
)

// mint coins to the module account
err := k.bankKeeper.MintCoins(ctx, types.ModuleName, hookCoins)
if err != nil {
return err
}

// send coins to the account
return k.bankKeeper.SendCoinsFromModuleToAccount(
ctx,
types.ModuleName,
account,
hookCoins,
)
}
79 changes: 79 additions & 0 deletions x/tunnel/keeper/keeper_packet_ibc_hook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package keeper_test

import (
"time"

"go.uber.org/mock/gomock"

ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"

sdk "github.com/cosmos/cosmos-sdk/types"

feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types"
"github.com/bandprotocol/chain/v3/x/tunnel/types"
)

func (s *KeeperTestSuite) TestSendIBCHookPacket() {
ctx, k := s.ctx, s.keeper

tunnelID := uint64(1)
route := &types.IBCHookRoute{
ChannelID: "channel-0",
DestinationContractAddress: "wasm1vjq0k3fj47s8wns4a7zw5c4lsjd8l6r2kzzlpk",
}
packet := types.Packet{
TunnelID: tunnelID,
Sequence: 1,
Prices: []feedstypes.Price{},
CreatedAt: time.Now().Unix(),
}
interval := uint64(60)
feePayer := sdk.AccAddress([]byte("feePayer"))
hookCoins := sdk.NewCoins(
sdk.NewInt64Coin(types.FormatHookDenomIdentifier(tunnelID), types.HookTransferAmount),
)

expectedPacketReceipt := types.IBCHookPacketReceipt{
Sequence: 1,
}

s.transferKeeper.EXPECT().Transfer(ctx, gomock.Any()).Return(&ibctransfertypes.MsgTransferResponse{
Sequence: 1,
}, nil)
s.bankKeeper.EXPECT().MintCoins(ctx, types.ModuleName, hookCoins).Return(nil)
s.bankKeeper.EXPECT().
SendCoinsFromModuleToAccount(ctx, types.ModuleName, feePayer, hookCoins).
Return(nil)

content, err := k.SendIBCHookPacket(
ctx,
route,
packet,
feePayer,
interval,
)
s.Require().NoError(err)

receipt, ok := content.(*types.IBCHookPacketReceipt)
s.Require().True(ok)
s.Require().Equal(expectedPacketReceipt, *receipt)
}

func (s *KeeperTestSuite) TestMintIBCHookCoinToAccount() {
ctx, k := s.ctx, s.keeper

tunnelID := uint64(1)
account := sdk.AccAddress([]byte("test_account"))
hookCoins := sdk.NewCoins(
sdk.NewInt64Coin(types.FormatHookDenomIdentifier(tunnelID), types.HookTransferAmount),
)

s.bankKeeper.EXPECT().MintCoins(ctx, types.ModuleName, hookCoins).Return(nil)
s.bankKeeper.EXPECT().
SendCoinsFromModuleToAccount(ctx, types.ModuleName, account, hookCoins).
Return(nil)

// Mint coins to the account
err := k.MintIBCHookCoinToAccount(ctx, tunnelID, account)
s.Require().NoError(err)
}
Loading
Loading