diff --git a/simapp/app.go b/simapp/app.go index f4734dc4e957..f15ca11ea7bd 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -297,6 +297,7 @@ func NewSimApp( app.AccountKeeper, BlockedAddresses(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), + app.AccountKeeper.GetAddressCodec(), ) app.StakingKeeper = stakingkeeper.NewKeeper( appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), diff --git a/testutil/bech32_codec.go b/testutil/bech32_codec.go new file mode 100644 index 000000000000..276b1853b2d9 --- /dev/null +++ b/testutil/bech32_codec.go @@ -0,0 +1,48 @@ +package testutil + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + "github.com/cosmos/cosmos-sdk/types/bech32" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +type bech32Codec struct { + bech32Prefix string +} + +var _ address.Codec = &bech32Codec{} + +func NewBech32Codec() address.Codec { + return bech32Codec{"cosmos"} +} + +// StringToBytes encodes text to bytes +func (bc bech32Codec) StringToBytes(text string) ([]byte, error) { + hrp, bz, err := bech32.DecodeAndConvert(text) + if err != nil { + return nil, err + } + + if hrp != bc.bech32Prefix { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "hrp does not match bech32Prefix") + } + + if err := sdk.VerifyAddressFormat(bz); err != nil { + return nil, err + } + + return bz, nil +} + +// BytesToString decodes bytes to text +func (bc bech32Codec) BytesToString(bz []byte) (string, error) { + text, err := bech32.ConvertAndEncode(bc.bech32Prefix, bz) + if err != nil { + return "", err + } + + return text, nil +} diff --git a/x/bank/keeper/genesis_test.go b/x/bank/keeper/genesis_test.go index 7e7e9e8162a3..5a5d9227d25a 100644 --- a/x/bank/keeper/genesis_test.go +++ b/x/bank/keeper/genesis_test.go @@ -1,6 +1,8 @@ package keeper_test import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -20,7 +22,7 @@ func (suite *KeeperTestSuite) TestExportGenesis() { for i := range []int{1, 2} { suite.bankKeeper.SetDenomMetaData(ctx, expectedMetadata[i]) - accAddr, err1 := sdk.AccAddressFromBech32(expectedBalances[i].Address) + accAddr, err1 := suite.bankKeeper.StringToBytes(expectedBalances[i].Address) if err1 != nil { panic(err1) } @@ -47,8 +49,8 @@ func (suite *KeeperTestSuite) TestExportGenesis() { } func (suite *KeeperTestSuite) getTestBalancesAndSupply() ([]types.Balance, sdk.Coins) { - addr2, _ := sdk.AccAddressFromBech32("cosmos1f9xjhxm0plzrh9cskf4qee4pc2xwp0n0556gh0") - addr1, _ := sdk.AccAddressFromBech32("cosmos1t5u0jfg3ljsjrh2m9e47d4ny2hea7eehxrzdgd") + addr2, _ := suite.bankKeeper.StringToBytes("cosmos1f9xjhxm0plzrh9cskf4qee4pc2xwp0n0556gh0") + addr1, _ := suite.bankKeeper.StringToBytes("cosmos1t5u0jfg3ljsjrh2m9e47d4ny2hea7eehxrzdgd") addr1Balance := sdk.Coins{sdk.NewInt64Coin("testcoin3", 10)} addr2Balance := sdk.Coins{sdk.NewInt64Coin("testcoin1", 32), sdk.NewInt64Coin("testcoin2", 34)} @@ -56,8 +58,8 @@ func (suite *KeeperTestSuite) getTestBalancesAndSupply() ([]types.Balance, sdk.C totalSupply = totalSupply.Add(addr2Balance...) return []types.Balance{ - {Address: addr2.String(), Coins: addr2Balance}, - {Address: addr1.String(), Coins: addr1Balance}, + {Address: fmt.Sprintf("%X", addr2), Coins: addr2Balance}, + {Address: fmt.Sprintf("%X", addr1), Coins: addr1Balance}, }, totalSupply } diff --git a/x/bank/keeper/grpc_query.go b/x/bank/keeper/grpc_query.go index 49fa628090c7..e3ef4672a338 100644 --- a/x/bank/keeper/grpc_query.go +++ b/x/bank/keeper/grpc_query.go @@ -27,7 +27,7 @@ func (k BaseKeeper) Balance(ctx context.Context, req *types.QueryBalanceRequest) } sdkCtx := sdk.UnwrapSDKContext(ctx) - address, err := sdk.AccAddressFromBech32(req.Address) + address, err := k.StringToBytes(req.Address) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error()) } @@ -43,7 +43,7 @@ func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalances return nil, status.Error(codes.InvalidArgument, "empty request") } - addr, err := sdk.AccAddressFromBech32(req.Address) + addr, err := k.StringToBytes(req.Address) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error()) } @@ -85,7 +85,7 @@ func (k BaseKeeper) SpendableBalances(ctx context.Context, req *types.QuerySpend return nil, status.Error(codes.InvalidArgument, "empty request") } - addr, err := sdk.AccAddressFromBech32(req.Address) + addr, err := k.StringToBytes(req.Address) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error()) } @@ -121,7 +121,7 @@ func (k BaseKeeper) SpendableBalanceByDenom(ctx context.Context, req *types.Quer return nil, status.Error(codes.InvalidArgument, "empty request") } - addr, err := sdk.AccAddressFromBech32(req.Address) + addr, err := k.StringToBytes(req.Address) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error()) } diff --git a/x/bank/keeper/keeper.go b/x/bank/keeper/keeper.go index d767ae5f31bb..501b00e3fcf5 100644 --- a/x/bank/keeper/keeper.go +++ b/x/bank/keeper/keeper.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/query" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -48,6 +49,11 @@ type Keeper interface { DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error + // StringToBytes decodes text to bytes + StringToBytes(text string) ([]byte, error) + // BytesToString encodes bytes to text + BytesToString(bz []byte) (string, error) + types.QueryServer } @@ -59,6 +65,8 @@ type BaseKeeper struct { cdc codec.BinaryCodec storeKey storetypes.StoreKey mintCoinsRestrictionFn MintingRestrictionFn + + addressCodec address.Codec } type MintingRestrictionFn func(ctx sdk.Context, coins sdk.Coins) error @@ -89,17 +97,19 @@ func NewBaseKeeper( ak types.AccountKeeper, blockedAddrs map[string]bool, authority string, + addressCodec address.Codec, ) BaseKeeper { - if _, err := sdk.AccAddressFromBech32(authority); err != nil { + if _, err := addressCodec.StringToBytes(authority); err != nil { panic(fmt.Errorf("invalid bank authority address: %w", err)) } return BaseKeeper{ - BaseSendKeeper: NewBaseSendKeeper(cdc, storeKey, ak, blockedAddrs, authority), + BaseSendKeeper: NewBaseSendKeeper(cdc, storeKey, ak, blockedAddrs, authority, addressCodec), ak: ak, cdc: cdc, storeKey: storeKey, mintCoinsRestrictionFn: func(ctx sdk.Context, coins sdk.Coins) error { return nil }, + addressCodec: addressCodec, } } @@ -467,3 +477,13 @@ func (k BaseViewKeeper) IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bo return cb(sdk.NewCoin(s, m)) }) } + +// StringToBytes encodes text to bytes +func (k BaseKeeper) StringToBytes(text string) ([]byte, error) { + return k.addressCodec.StringToBytes(text) +} + +// BytesToString decodes bytes to text +func (k BaseKeeper) BytesToString(bz []byte) (string, error) { + return k.addressCodec.BytesToString(bz) +} diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 69e0584ed8f5..231b8c3a4b43 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -139,6 +139,7 @@ func (suite *KeeperTestSuite) SetupTest() { suite.authKeeper, map[string]bool{accAddrs[4].String(): true}, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + testutil.NewBech32Codec(), ) banktypes.RegisterInterfaces(encCfg.InterfaceRegistry) @@ -240,6 +241,7 @@ func (suite *KeeperTestSuite) TestGetAuthority() { nil, nil, authority, + testutil.NewBech32Codec(), ) } @@ -1242,16 +1244,16 @@ func (suite *KeeperTestSuite) TestBalanceTrackingEvents() { case banktypes.EventTypeCoinSpent: coinsSpent, err := sdk.ParseCoinsNormalized(e.Attributes[1].Value) require.NoError(err) - spender, err := sdk.AccAddressFromBech32(e.Attributes[0].Value) + spender, err := suite.bankKeeper.StringToBytes(e.Attributes[0].Value) require.NoError(err) - balances[spender.String()] = balances[spender.String()].Sub(coinsSpent...) + balances[fmt.Sprintf("%X", spender)] = balances[fmt.Sprintf("%X", spender)].Sub(coinsSpent...) case banktypes.EventTypeCoinReceived: coinsRecv, err := sdk.ParseCoinsNormalized(e.Attributes[1].Value) require.NoError(err) - receiver, err := sdk.AccAddressFromBech32(e.Attributes[0].Value) + receiver, err := suite.bankKeeper.StringToBytes(e.Attributes[0].Value) require.NoError(err) - balances[receiver.String()] = balances[receiver.String()].Add(coinsRecv...) + balances[fmt.Sprintf("%X", receiver)] = balances[fmt.Sprintf("%X", receiver)].Add(coinsRecv...) } } diff --git a/x/bank/keeper/msg_server.go b/x/bank/keeper/msg_server.go index 77d58eefd1a5..469fde78f681 100644 --- a/x/bank/keeper/msg_server.go +++ b/x/bank/keeper/msg_server.go @@ -33,11 +33,11 @@ func (k msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types.MsgSe return nil, err } - from, err := sdk.AccAddressFromBech32(msg.FromAddress) + from, err := k.StringToBytes(msg.FromAddress) if err != nil { return nil, err } - to, err := sdk.AccAddressFromBech32(msg.ToAddress) + to, err := k.StringToBytes(msg.ToAddress) if err != nil { return nil, err } @@ -77,7 +77,10 @@ func (k msgServer) MultiSend(goCtx context.Context, msg *types.MsgMultiSend) (*t } for _, out := range msg.Outputs { - accAddr := sdk.MustAccAddressFromBech32(out.Address) + accAddr, err := k.StringToBytes(out.Address) + if err != nil { + return nil, err + } if k.BlockedAddr(accAddr) { return nil, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", out.Address) diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 3903cf482f05..82df406e4f66 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -60,6 +60,8 @@ type BaseSendKeeper struct { // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string + + addressCodec address.Codec } func NewBaseSendKeeper( @@ -68,8 +70,9 @@ func NewBaseSendKeeper( ak types.AccountKeeper, blockedAddrs map[string]bool, authority string, + addressCodec address.Codec, ) BaseSendKeeper { - if _, err := sdk.AccAddressFromBech32(authority); err != nil { + if _, err := addressCodec.StringToBytes(authority); err != nil { panic(fmt.Errorf("invalid bank authority address: %w", err)) } @@ -80,6 +83,7 @@ func NewBaseSendKeeper( storeKey: storeKey, blockedAddrs: blockedAddrs, authority: authority, + addressCodec: addressCodec, } } @@ -120,10 +124,16 @@ func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, input types.Input, out return err } - inAddress, err := sdk.AccAddressFromBech32(input.Address) - if err != nil { - return err - } + for _, in := range inputs { + inAddress, err := k.addressCodec.StringToBytes(input.Address) + if err != nil { + return err + } + + err = k.subUnlockedCoins(ctx, inAddress, in.Coins) + if err != nil { + return err + } err = k.subUnlockedCoins(ctx, inAddress, input.Coins) if err != nil { @@ -138,7 +148,7 @@ func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, input types.Input, out ) for _, out := range outputs { - outAddress, err := sdk.AccAddressFromBech32(out.Address) + outAddress, err := k.addressCodec.StringToBytes(out.Address) if err != nil { return err } diff --git a/x/bank/module.go b/x/bank/module.go index 63d6319ff479..60378eea6105 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -20,6 +20,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -215,6 +216,7 @@ type BankInputs struct { Key *store.KVStoreKey AccountKeeper types.AccountKeeper + AddressCodec address.Codec // LegacySubspace is used solely for migration of x/params managed parameters LegacySubspace exported.Subspace `optional:"true"` @@ -256,6 +258,7 @@ func ProvideModule(in BankInputs) BankOutputs { in.AccountKeeper, blockedAddresses, authority.String(), + in.AddressCodec, ) m := NewAppModule(in.Cdc, bankKeeper, in.AccountKeeper, in.LegacySubspace)