Skip to content

Commit

Permalink
Chore: Make a state entry to find total amount of native assets IBC'd…
Browse files Browse the repository at this point in the history
… out (backport #3019) (#3505)

* feat: make a state entry to find total amount of native assets IBC'd out (#3019)

(cherry picked from commit fa9418f)

# Conflicts:
#	.github/workflows/e2e-upgrade.yaml
#	e2e/go.mod
#	e2e/tests/transfer/base_test.go
#	e2e/tests/upgrades/upgrade_test.go
#	e2e/testsuite/grpc_query.go
#	modules/apps/transfer/types/genesis.pb.go
#	proto/ibc/applications/transfer/v1/genesis.proto
#	proto/ibc/applications/transfer/v1/transfer.proto

* fixing conflicts

* linter fix

* fix

* remove api breaking change

* address review comments

* chore: remove unnecessary file

---------

Co-authored-by: Sishir Giri <sishirg27@gmail.com>
Co-authored-by: Carlos Rodriguez <carlos@interchain.io>
Co-authored-by: Colin Axnér <25233464+colin-axner@users.noreply.github.com>
  • Loading branch information
4 people committed May 3, 2023
1 parent 4526afc commit 6ced3dc
Show file tree
Hide file tree
Showing 22 changed files with 1,742 additions and 158 deletions.
1 change: 1 addition & 0 deletions modules/apps/transfer/client/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func GetQueryCmd() *cobra.Command {
GetCmdParams(),
GetCmdQueryEscrowAddress(),
GetCmdQueryDenomHash(),
GetCmdQueryTotalEscrowForDenom(),
)

return queryCmd
Expand Down
33 changes: 33 additions & 0 deletions modules/apps/transfer/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,36 @@ func GetCmdQueryDenomHash() *cobra.Command {
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

// GetCmdQueryTotalEscrowForDenom defines the command to query the total amount of tokens in escrow for a denom
func GetCmdQueryTotalEscrowForDenom() *cobra.Command {
cmd := &cobra.Command{
Use: "token-escrow [denom]",
Short: "Query the total amount of tokens in escrow for a denom",
Long: "Query the total amount of tokens in escrow for a denom",
Example: fmt.Sprintf("%s query ibc-transfer token-escrow uosmo", version.AppName),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

req := &types.QueryTotalEscrowForDenomRequest{
Denom: args[0],
}

res, err := queryClient.TotalEscrowForDenom(cmd.Context(), req)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
return cmd
}
13 changes: 10 additions & 3 deletions modules/apps/transfer/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) {
}

k.SetParams(ctx, state.Params)

// Every denom will have only one total escrow amount, since any
// duplicate entry will fail validation in Validate of GenesisState
for _, denomEscrow := range state.TotalEscrowed {
k.SetTotalEscrowForDenom(ctx, denomEscrow.Denom, denomEscrow.Amount)
}
}

// ExportGenesis exports ibc-transfer module's portID and denom trace info into its genesis state.
func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState {
return &types.GenesisState{
PortId: k.GetPort(ctx),
DenomTraces: k.GetAllDenomTraces(ctx),
Params: k.GetParams(ctx),
PortId: k.GetPort(ctx),
DenomTraces: k.GetAllDenomTraces(ctx),
Params: k.GetParams(ctx),
TotalEscrowed: k.GetAllTotalEscrowed(ctx),
}
}
39 changes: 28 additions & 11 deletions modules/apps/transfer/keeper/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,52 @@ package keeper_test
import (
"fmt"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
)

func (suite *KeeperTestSuite) TestGenesis() {
var (
path string
traces types.Traces
)
getTrace := func(index uint) string {
return fmt.Sprintf("transfer/channelToChain%d", index)
}

for i := 0; i < 5; i++ {
prefix := fmt.Sprintf("transfer/channelToChain%d", i)
if i == 0 {
path = prefix
} else {
path = prefix + "/" + path
var (
traces types.Traces
escrows sdk.Coins
pathsAndEscrowAmounts = []struct {
path string
escrow string
}{
{getTrace(0), "10"},
{fmt.Sprintf("%s/%s", getTrace(1), getTrace(0)), "100000"},
{fmt.Sprintf("%s/%s/%s", getTrace(2), getTrace(1), getTrace(0)), "10000000000"},
{fmt.Sprintf("%s/%s/%s/%s", getTrace(3), getTrace(2), getTrace(1), getTrace(0)), "1000000000000000"},
{fmt.Sprintf("%s/%s/%s/%s/%s", getTrace(4), getTrace(3), getTrace(2), getTrace(1), getTrace(0)), "100000000000000000000"},
}
)

for _, pathAndEscrowMount := range pathsAndEscrowAmounts {
denomTrace := types.DenomTrace{
BaseDenom: "uatom",
Path: path,
Path: pathAndEscrowMount.path,
}
traces = append(types.Traces{denomTrace}, traces...)
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), denomTrace)

denom := denomTrace.IBCDenom()
amount, ok := math.NewIntFromString(pathAndEscrowMount.escrow)
suite.Require().True(ok)
escrows = append(sdk.NewCoins(sdk.NewCoin(denom, amount)), escrows...)
suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), denom, amount)
}

genesis := suite.chainA.GetSimApp().TransferKeeper.ExportGenesis(suite.chainA.GetContext())

suite.Require().Equal(types.PortID, genesis.PortId)
suite.Require().Equal(traces.Sort(), genesis.DenomTraces)
suite.Require().Equal(escrows.Sort(), genesis.TotalEscrowed)

suite.Require().NotPanics(func() {
suite.chainA.GetSimApp().TransferKeeper.InitGenesis(suite.chainA.GetContext(), *genesis)
Expand Down
19 changes: 19 additions & 0 deletions modules/apps/transfer/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,22 @@ func (q Keeper) EscrowAddress(c context.Context, req *types.QueryEscrowAddressRe
EscrowAddress: addr.String(),
}, nil
}

// TotalEscrowForDenom implements the TotalEscrowForDenom gRPC method.
func (q Keeper) TotalEscrowForDenom(c context.Context, req *types.QueryTotalEscrowForDenomRequest) (*types.QueryTotalEscrowForDenomResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

ctx := sdk.UnwrapSDKContext(c)

if err := sdk.ValidateDenom(req.Denom); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

denomAmount := q.GetTotalEscrowForDenom(ctx, req.Denom)

return &types.QueryTotalEscrowForDenomResponse{
Amount: denomAmount,
}, nil
}
97 changes: 97 additions & 0 deletions modules/apps/transfer/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper_test
import (
"fmt"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"

Expand Down Expand Up @@ -261,3 +262,99 @@ func (suite *KeeperTestSuite) TestEscrowAddress() {
})
}
}

func (suite *KeeperTestSuite) TestTotalEscrowForDenom() {
var (
req *types.QueryTotalEscrowForDenomRequest
expEscrowAmount math.Int
)

testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"valid native denom with escrow amount < 2^63",
func() {
req = &types.QueryTotalEscrowForDenomRequest{
Denom: sdk.DefaultBondDenom,
}

expEscrowAmount = math.NewInt(100)
suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.DefaultBondDenom, expEscrowAmount)
},
true,
},
{
"valid ibc denom with escrow amount > 2^63",
func() {
denomTrace := types.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: sdk.DefaultBondDenom,
}

suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), denomTrace)
expEscrowAmount, ok := math.NewIntFromString("100000000000000000000")
suite.Require().True(ok)
suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.DefaultBondDenom, expEscrowAmount)

req = &types.QueryTotalEscrowForDenomRequest{
Denom: denomTrace.IBCDenom(),
}
},
true,
},
{
"valid ibc denom treated as native denom",
func() {
denomTrace := types.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: sdk.DefaultBondDenom,
}

req = &types.QueryTotalEscrowForDenomRequest{
Denom: denomTrace.IBCDenom(),
}
},
true, // denom trace is not found, thus the denom is considered a native token
},
{
"invalid ibc denom treated as valid native denom",
func() {
req = &types.QueryTotalEscrowForDenomRequest{
Denom: "ibc/123",
}
},
true, // the ibc denom does not contain a valid hash, thus the denom is considered a native token
},
{
"invalid denom",
func() {
req = &types.QueryTotalEscrowForDenomRequest{
Denom: "??𓃠🐾??",
}
},
false,
},
}

for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset

expEscrowAmount = math.ZeroInt()
tc.malleate()
ctx := sdk.WrapSDKContext(suite.chainA.GetContext())

res, err := suite.chainA.GetSimApp().TransferKeeper.TotalEscrowForDenom(ctx, req)

if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(expEscrowAmount, res.Amount)
} else {
suite.Require().Error(err)
}
})
}
}
73 changes: 72 additions & 1 deletion modules/apps/transfer/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package keeper

import (
"fmt"
"strings"

"cosmossdk.io/math"
tmbytes "github.com/cometbft/cometbft/libs/bytes"
"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
Expand Down Expand Up @@ -133,14 +137,81 @@ func (k Keeper) IterateDenomTraces(ctx sdk.Context, cb func(denomTrace types.Den

defer sdk.LogDeferred(ctx.Logger(), func() error { return iterator.Close() })
for ; iterator.Valid(); iterator.Next() {

denomTrace := k.MustUnmarshalDenomTrace(iterator.Value())
if cb(denomTrace) {
break
}
}
}

// GetTotalEscrowForDenom gets the total amount of source chain tokens that
// are in escrow, keyed by the denomination.
func (k Keeper) GetTotalEscrowForDenom(ctx sdk.Context, denom string) math.Int {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.TotalEscrowForDenomKey(denom))
if bz == nil {
return math.ZeroInt()
}

amount := sdk.IntProto{}
k.cdc.MustUnmarshal(bz, &amount)

return amount.Int
}

// SetTotalEscrowForDenom stores the total amount of source chain tokens that are in escrow.
func (k Keeper) SetTotalEscrowForDenom(ctx sdk.Context, denom string, amount math.Int) {
if amount.IsNegative() {
panic(fmt.Sprintf("amount cannot be negative: %s", amount))
}

store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&sdk.IntProto{Int: amount})
store.Set(types.TotalEscrowForDenomKey(denom), bz)
}

// GetAllTotalEscrowed returns the escrow information for all the denominations.
func (k Keeper) GetAllTotalEscrowed(ctx sdk.Context) sdk.Coins {
var escrows sdk.Coins
k.IterateTokensInEscrow(ctx, []byte(types.KeyTotalEscrowPrefix), func(denomEscrow sdk.Coin) bool {
escrows = append(escrows, denomEscrow)
return false
})

return escrows
}

// IterateTokensInEscrow iterates over the denomination escrows in the store
// and performs a callback function. Denominations for which an invalid value
// (i.e. not integer) is stored, will be skipped.
func (k Keeper) IterateTokensInEscrow(ctx sdk.Context, prefix []byte, cb func(denomEscrow sdk.Coin) bool) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, prefix)

defer sdk.LogDeferred(ctx.Logger(), func() error { return iterator.Close() })
for ; iterator.Valid(); iterator.Next() {
keySplit := strings.Split(string(iterator.Key()), "/")
if len(keySplit) < 2 {
continue // key doesn't conform to expected format
}

denom := strings.Join(keySplit[1:], "/")
if strings.TrimSpace(denom) == "" {
continue // denom is empty
}

amount := sdk.IntProto{}
if err := k.cdc.Unmarshal(iterator.Value(), &amount); err != nil {
continue // total escrow amount cannot be unmarshalled to integer
}

denomEscrow := sdk.NewCoin(denom, amount.Int)
if cb(denomEscrow) {
break
}
}
}

// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function
func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool {
return k.scopedKeeper.AuthenticateCapability(ctx, cap, name)
Expand Down
Loading

0 comments on commit 6ced3dc

Please sign in to comment.