Skip to content

Commit

Permalink
Add authz-specific logic to ante handler
Browse files Browse the repository at this point in the history
  • Loading branch information
yvonnezhangc committed Mar 1, 2024
1 parent 9b252e1 commit 977e364
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 153 deletions.
56 changes: 55 additions & 1 deletion e2e/noble_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package e2e
import (
"context"
"encoding/json"
"fmt"
"testing"

"github.com/circlefin/noble-fiattokenfactory/x/fiattokenfactory/types"
Expand Down Expand Up @@ -153,12 +154,19 @@ func nobleTokenfactory_e2e(t *testing.T, ctx context.Context, tokenfactoryModNam
require.NoError(t, err, "failed to get user balance")
require.Equal(t, int64(100), userBalance, "user balance should not have incremented while blacklisted")

// authz send to blacklisted account
testAuthZSendFail(t, ctx, nobleValidator, mintingDenom, noble, extraWallets.User2, extraWallets.User, extraWallets.Alice)
// authz send from blacklisted account
testAuthZSendFail(t, ctx, nobleValidator, mintingDenom, noble, extraWallets.User, extraWallets.User2, extraWallets.Alice)
// authz send with blacklisted grantee
testAuthZSendFail(t, ctx, nobleValidator, mintingDenom, noble, extraWallets.User2, extraWallets.Alice, extraWallets.User)

err = nobleValidator.SendFunds(ctx, extraWallets.User2.KeyName(), ibc.WalletAmount{
Address: extraWallets.User.FormattedAddress(),
Denom: "token",
Amount: 100,
})
require.NoError(t, err, "The tx should have been successfull as that is no the minting denom")
require.NoError(t, err, "The tx should have been successfull as that is not the minting denom")

_, err = nobleValidator.ExecTx(ctx, roles.Blacklister.KeyName(),
tokenfactoryModName, "unblacklist", extraWallets.User.FormattedAddress(), "-b", "block",
Expand Down Expand Up @@ -263,6 +271,9 @@ func nobleTokenfactory_e2e(t *testing.T, ctx context.Context, tokenfactoryModNam
)
require.NoError(t, err, "minters should be able to be removed while in paused state")

// authz send fails when chain is paused
testAuthZSendFail(t, ctx, nobleValidator, mintingDenom, noble, extraWallets.User2, extraWallets.User, extraWallets.Alice)

_, err = nobleValidator.ExecTx(ctx, roles.Pauser.KeyName(),
tokenfactoryModName, "unpause", "-b", "block",
)
Expand All @@ -282,4 +293,47 @@ func nobleTokenfactory_e2e(t *testing.T, ctx context.Context, tokenfactoryModNam
aliceBalance, err = noble.GetBalance(ctx, extraWallets.Alice.FormattedAddress(), mintingDenom)
require.NoError(t, err, "failed to get alice balance")
require.Equal(t, int64(100), aliceBalance, "alice balance should not have increased while chain is paused")

testAuthZSendSucceed(t, ctx, nobleValidator, mintingDenom, noble, extraWallets.User, extraWallets.User2, extraWallets.Alice)
}

func testAuthZSend(t *testing.T, ctx context.Context, nobleValidator *cosmos.ChainNode, mintingDenom string, noble *cosmos.CosmosChain, fromWallet ibc.Wallet, toWallet ibc.Wallet, granteeWallet ibc.Wallet) (string, error) {
grantAuthorization(t, ctx, nobleValidator, mintingDenom, noble, fromWallet, granteeWallet)

bz, _, _ := nobleValidator.ExecBin(ctx, "tx", "bank", "send", fromWallet.FormattedAddress(), toWallet.FormattedAddress(), fmt.Sprintf("%d%s", 50, mintingDenom), "--chain-id", noble.Config().ChainID, "--generate-only")
_ = nobleValidator.WriteFile(ctx, bz, "tx.json")

return nobleValidator.ExecTx(ctx, granteeWallet.KeyName(), "authz", "exec", "/var/cosmos-chain/noble-1/tx.json")
}

func testAuthZSendFail(t *testing.T, ctx context.Context, nobleValidator *cosmos.ChainNode, mintingDenom string, noble *cosmos.CosmosChain, fromWallet ibc.Wallet, toWallet ibc.Wallet, granteeWallet ibc.Wallet) {
toWalletInitialBalance := getBalance(t, ctx, nobleValidator, mintingDenom, noble, toWallet)

_, err := testAuthZSend(t, ctx, nobleValidator, mintingDenom, noble, fromWallet, toWallet, granteeWallet)

require.Error(t, err, "failed to block transactions")
toWalletBalance := getBalance(t, ctx, nobleValidator, mintingDenom, noble, toWallet)
require.Equal(t, toWalletInitialBalance, toWalletBalance, "toWallet balance should not have incremented")
}

func testAuthZSendSucceed(t *testing.T, ctx context.Context, nobleValidator *cosmos.ChainNode, mintingDenom string, noble *cosmos.CosmosChain, fromWallet ibc.Wallet, toWallet ibc.Wallet, granteeWallet ibc.Wallet) {
toWalletInitialBalance := getBalance(t, ctx, nobleValidator, mintingDenom, noble, toWallet)

_, err := testAuthZSend(t, ctx, nobleValidator, mintingDenom, noble, fromWallet, toWallet, granteeWallet)

require.NoError(t, err, "failed to execute authz message")
toWalletBalance := getBalance(t, ctx, nobleValidator, mintingDenom, noble, toWallet)
require.Equal(t, toWalletInitialBalance+50, toWalletBalance, "toWallet balance should have incremented")
}


func grantAuthorization(t *testing.T, ctx context.Context, nobleValidator *cosmos.ChainNode, mintingDenom string, noble *cosmos.CosmosChain, grantor ibc.Wallet, grantee ibc.Wallet) {
_, err := nobleValidator.ExecTx(ctx, grantor.KeyName(), "authz", "grant", grantee.FormattedAddress(), "send", "--spend-limit", fmt.Sprintf("%d%s", 100, mintingDenom))
require.NoError(t, err, "failed to grant permissions")
}

func getBalance(t *testing.T, ctx context.Context, nobleValidator *cosmos.ChainNode, mintingDenom string, noble *cosmos.CosmosChain, wallet ibc.Wallet) int64 {
bal, err := noble.GetBalance(ctx, wallet.FormattedAddress(), mintingDenom)
require.NoError(t, err, "failed to get user balance")
return bal
}
155 changes: 3 additions & 152 deletions simapp/ante_handler.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package simapp

import (
"github.com/circlefin/noble-fiattokenfactory/x/fiattokenfactory"
fiattokenfactorykeeper "github.com/circlefin/noble-fiattokenfactory/x/fiattokenfactory/keeper"
fiattokenfactorytypes "github.com/circlefin/noble-fiattokenfactory/x/fiattokenfactory/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types"
ibcante "github.com/cosmos/ibc-go/v4/modules/core/ante"
"github.com/cosmos/ibc-go/v4/modules/core/keeper"
)
Expand All @@ -23,152 +20,6 @@ type HandlerOptions struct {
FiatTokenFactoryKeeper *fiattokenfactorykeeper.Keeper
}

type IsPausedDecorator struct {
fiatTokenFactory *fiattokenfactorykeeper.Keeper
}

func NewIsPausedDecorator(ctf *fiattokenfactorykeeper.Keeper) IsPausedDecorator {
return IsPausedDecorator{
fiatTokenFactory: ctf,
}
}

func (ad IsPausedDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
msgs := tx.GetMsgs()
for _, m := range msgs {
switch m := m.(type) {
case *banktypes.MsgSend, *banktypes.MsgMultiSend, *transfertypes.MsgTransfer:
switch m := m.(type) {
case *banktypes.MsgSend:
for _, c := range m.Amount {
paused, err := checkPausedStatebyTokenFactory(ctx, c, ad.fiatTokenFactory)
if paused {
return ctx, sdkerrors.Wrapf(err, "can not perform token transfers")
}
}
case *banktypes.MsgMultiSend:
for _, i := range m.Inputs {
for _, c := range i.Coins {
paused, err := checkPausedStatebyTokenFactory(ctx, c, ad.fiatTokenFactory)
if paused {
return ctx, sdkerrors.Wrapf(err, "can not perform token transfers")
}
}
}
case *transfertypes.MsgTransfer:
paused, err := checkPausedStatebyTokenFactory(ctx, m.Token, ad.fiatTokenFactory)
if paused {
return ctx, sdkerrors.Wrapf(err, "can not perform token transfers")
}
default:
continue
}
default:
continue
}
}
return next(ctx, tx, simulate)
}

func checkPausedStatebyTokenFactory(ctx sdk.Context, c sdk.Coin, ctf *fiattokenfactorykeeper.Keeper) (bool, *sdkerrors.Error) {
ctfMintingDenom := ctf.GetMintingDenom(ctx)
if c.Denom == ctfMintingDenom.Denom {
paused := ctf.GetPaused(ctx)
if paused.Paused {
return true, fiattokenfactorytypes.ErrPaused
}
}
return false, nil
}

type IsBlacklistedDecorator struct {
fiattokenfactory *fiattokenfactorykeeper.Keeper
}

func NewIsBlacklistedDecorator(ctf *fiattokenfactorykeeper.Keeper) IsBlacklistedDecorator {
return IsBlacklistedDecorator{
fiattokenfactory: ctf,
}
}

func (ad IsBlacklistedDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
msgs := tx.GetMsgs()
for _, m := range msgs {
switch m := m.(type) {
case *banktypes.MsgSend, *banktypes.MsgMultiSend, *transfertypes.MsgTransfer:
switch m := m.(type) {
case *banktypes.MsgSend:
for _, c := range m.Amount {
addresses := []string{m.ToAddress, m.FromAddress}
blacklisted, address, err := checkForBlacklistedAddressByTokenFactory(ctx, addresses, c, ad.fiattokenfactory)
if blacklisted {
return ctx, sdkerrors.Wrapf(err, "an address (%s) is blacklisted and can not send or receive tokens", address)
}
if err != nil {
return ctx, sdkerrors.Wrapf(err, "error decoding address (%s)", address)
}
}
case *banktypes.MsgMultiSend:
for _, i := range m.Inputs {
for _, c := range i.Coins {
addresses := []string{i.Address}
blacklisted, address, err := checkForBlacklistedAddressByTokenFactory(ctx, addresses, c, ad.fiattokenfactory)
if blacklisted {
return ctx, sdkerrors.Wrapf(err, "an address (%s) is blacklisted and can not send or receive tokens", address)
}
if err != nil {
return ctx, sdkerrors.Wrapf(err, "error decoding address (%s)", address)
}
}
}
for _, o := range m.Outputs {
for _, c := range o.Coins {
addresses := []string{o.Address}
blacklisted, address, err := checkForBlacklistedAddressByTokenFactory(ctx, addresses, c, ad.fiattokenfactory)
if blacklisted {
return ctx, sdkerrors.Wrapf(err, "an address (%s) is blacklisted and can not send or receive tokens", address)
}
if err != nil {
return ctx, sdkerrors.Wrapf(err, "error decoding address (%s)", address)
}
}
}
case *transfertypes.MsgTransfer:
addresses := []string{m.Sender, m.Receiver}
blacklisted, address, err := checkForBlacklistedAddressByTokenFactory(ctx, addresses, m.Token, ad.fiattokenfactory)
if blacklisted {
return ctx, sdkerrors.Wrapf(err, "an address (%s) is blacklisted and can not send or receive tokens", address)
}
if err != nil {
return ctx, sdkerrors.Wrapf(err, "error decoding address (%s)", address)
}
}
default:
continue
}
}
return next(ctx, tx, simulate)
}

// checkForBlacklistedAddressByTokenFactory first checks if the denom being transacted is a mintable asset from a TokenFactory,
// if it is, it checks if the addresses involved in the tx are blacklisted by that specific TokenFactory.
func checkForBlacklistedAddressByTokenFactory(ctx sdk.Context, addresses []string, c sdk.Coin, ctf *fiattokenfactorykeeper.Keeper) (blacklisted bool, blacklistedAddress string, err error) {
ctfMintingDenom := ctf.GetMintingDenom(ctx)
if c.Denom == ctfMintingDenom.Denom {
for _, address := range addresses {
_, addressBz, err := bech32.DecodeAndConvert(address)
if err != nil {
return false, address, err
}
_, found := ctf.GetBlacklisted(ctx, addressBz)
if found {
return true, address, fiattokenfactorytypes.ErrUnauthorized
}
}
}
return false, "", nil
}

// NewAnteHandler creates a new ante handler
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if options.AccountKeeper == nil {
Expand All @@ -192,8 +43,8 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(),
ante.NewRejectExtensionOptionsDecorator(),
NewIsBlacklistedDecorator(options.FiatTokenFactoryKeeper),
NewIsPausedDecorator(options.FiatTokenFactoryKeeper),
fiattokenfactory.NewIsBlacklistedDecorator(options.FiatTokenFactoryKeeper),
fiattokenfactory.NewIsPausedDecorator(options.FiatTokenFactoryKeeper),
ante.NewMempoolFeeDecorator(),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
Expand Down
Loading

0 comments on commit 977e364

Please sign in to comment.