Skip to content

Commit

Permalink
Add SendManyCoins, SendCoinsFromModuleToManyAccount method to bank Mo…
Browse files Browse the repository at this point in the history
…dule (#33)

* Add SenManyCoins method

* Add method SendCoinsFromModuleToManyAccounts in Bank module

* Lint
  • Loading branch information
mattverse authored and sunnya97 committed Nov 17, 2021
1 parent 8e89646 commit 1844456
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 1 deletion.
2 changes: 1 addition & 1 deletion types/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func (s *decimalTestSuite) TestPower() {
for i, tc := range testCases {
res := tc.input.Power(tc.power)
s.Require().True(tc.expected.Sub(res).Abs().LTE(sdk.SmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input)
s.Require().True(tc.expected.Sub(tc.input.PowerMut(tc.power)).Abs().LTE(sdk.SmallestDec()),
s.Require().True(tc.expected.Sub(tc.input.PowerMut(tc.power)).Abs().LTE(sdk.SmallestDec()),
"unexpected result for test case %d, input %v", i, tc.input)
s.Require().True(res.Equal(tc.input), "unexpected result for test case %d, input: %v", i, tc.input)
}
Expand Down
27 changes: 27 additions & 0 deletions x/bank/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type Keeper interface {
IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool)

SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromModuleToManyAccounts(
ctx sdk.Context, senderModule string, recipientAddrs []sdk.AccAddress, amts []sdk.Coins,
) error
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
Expand Down Expand Up @@ -290,6 +293,30 @@ func (k BaseKeeper) SendCoinsFromModuleToAccount(
return k.SendCoins(ctx, senderAddr, recipientAddr, amt)
}

// SendCoinsFromModuleToManyAccounts transfers coins from a ModuleAccount to multiple AccAddresses.
// It will panic if the module account does not exist. An error is returned if
// the recipient address is black-listed or if sending the tokens fails.
func (k BaseKeeper) SendCoinsFromModuleToManyAccounts(
ctx sdk.Context, senderModule string, recipientAddrs []sdk.AccAddress, amts []sdk.Coins,
) error {
if len(recipientAddrs) != len(amts) {
panic(fmt.Errorf("addresses and amounts numbers does not match"))
}

senderAddr := k.ak.GetModuleAddress(senderModule)
if senderAddr == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
}

for _, recipientAddr := range recipientAddrs {
if k.BlockedAddr(recipientAddr) {
return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", recipientAddr)
}
}

return k.SendManyCoins(ctx, senderAddr, recipientAddrs, amts)
}

// SendCoinsFromModuleToModule transfers coins from a ModuleAccount to another.
// It will panic if either module account does not exist.
func (k BaseKeeper) SendCoinsFromModuleToModule(
Expand Down
61 changes: 61 additions & 0 deletions x/bank/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ func (suite *IntegrationTestSuite) TestSendCoinsFromModuleToAccount_Blocklist()
suite.Require().Error(keeper.SendCoinsFromModuleToAccount(
ctx, minttypes.ModuleName, addr1, initCoins,
))
suite.Require().Error(keeper.SendCoinsFromModuleToManyAccounts(
ctx, minttypes.ModuleName, []sdk.AccAddress{addr1}, []sdk.Coins{initCoins},
))
}

func (suite *IntegrationTestSuite) TestSupply_SendCoins() {
Expand Down Expand Up @@ -186,9 +189,16 @@ func (suite *IntegrationTestSuite) TestSupply_SendCoins() {
_ = keeper.SendCoinsFromModuleToAccount(ctx, "", baseAcc.GetAddress(), initCoins) // nolint:errcheck
})

suite.Require().Panics(func() {
keeper.SendCoinsFromModuleToManyAccounts(ctx, authtypes.Burner, []sdk.AccAddress{baseAcc.GetAddress()}, []sdk.Coins{initCoins, initCoins})
})

suite.Require().Error(
keeper.SendCoinsFromModuleToAccount(ctx, holderAcc.GetName(), baseAcc.GetAddress(), initCoins.Add(initCoins...)),
)
suite.Require().Error(
keeper.SendCoinsFromModuleToManyAccounts(ctx, holderAcc.GetName(), []sdk.AccAddress{baseAcc.GetAddress()}, []sdk.Coins{initCoins.Add(initCoins...)}),
)

suite.Require().NoError(
keeper.SendCoinsFromModuleToModule(ctx, holderAcc.GetName(), authtypes.Burner, initCoins),
Expand All @@ -205,6 +215,12 @@ func (suite *IntegrationTestSuite) TestSupply_SendCoins() {
suite.Require().NoError(keeper.SendCoinsFromAccountToModule(ctx, baseAcc.GetAddress(), authtypes.Burner, initCoins))
suite.Require().Equal(sdk.NewCoins().String(), keeper.GetAllBalances(ctx, baseAcc.GetAddress()).String())
suite.Require().Equal(initCoins, getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner))

suite.Require().NoError(
keeper.SendCoinsFromModuleToManyAccounts(ctx, authtypes.Burner, []sdk.AccAddress{baseAcc.GetAddress()}, []sdk.Coins{initCoins}),
)
suite.Require().Equal(sdk.Coins(nil), getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner))
suite.Require().Equal(initCoins, keeper.GetAllBalances(ctx, baseAcc.GetAddress()))
}

func (suite *IntegrationTestSuite) TestSupply_MintCoins() {
Expand Down Expand Up @@ -454,6 +470,51 @@ func (suite *IntegrationTestSuite) TestSendCoins() {
suite.Require().Equal(newBarCoin(25), coins[0], "expected only bar coins in the account balance, got: %v", coins)
}

func (suite *IntegrationTestSuite) TestSendManyCoins() {
app, ctx := suite.app, suite.ctx
balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50))

addr1 := sdk.AccAddress([]byte("addr1_______________"))
acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1)
app.AccountKeeper.SetAccount(ctx, acc1)

addr2 := sdk.AccAddress([]byte("addr2_______________"))
acc2 := app.AccountKeeper.NewAccountWithAddress(ctx, addr2)
app.AccountKeeper.SetAccount(ctx, acc2)
suite.Require().NoError(app.BankKeeper.SetBalances(ctx, addr2, balances))

addr3 := sdk.AccAddress([]byte("addr3_______________"))
acc3 := app.AccountKeeper.NewAccountWithAddress(ctx, addr3)
app.AccountKeeper.SetAccount(ctx, acc3)

toAddrs := []sdk.AccAddress{}
toAddrs = append(toAddrs, addr2)

sendAmts := []sdk.Coins{}
sendAmts = append(sendAmts, sdk.NewCoins(newFooCoin(50), newBarCoin(25)))
sendAmts = append(sendAmts, sdk.NewCoins(newFooCoin(50)))

suite.Require().Error(app.BankKeeper.SendManyCoins(ctx, addr1, toAddrs, sendAmts))

suite.Require().NoError(app.BankKeeper.SetBalances(ctx, addr1, balances))
suite.Require().Error(app.BankKeeper.SendManyCoins(ctx, addr1, toAddrs, sendAmts))

toAddrs = append(toAddrs, addr3)
suite.Require().NoError(app.BankKeeper.SendManyCoins(ctx, addr1, toAddrs, sendAmts))

acc1Balances := app.BankKeeper.GetAllBalances(ctx, addr1)
expected := sdk.NewCoins(newFooCoin(0), newBarCoin(25))
suite.Require().Equal(expected, acc1Balances)

acc2Balances := app.BankKeeper.GetAllBalances(ctx, addr2)
expected = sdk.NewCoins(newFooCoin(150), newBarCoin(75))
suite.Require().Equal(expected, acc2Balances)

acc3Balances := app.BankKeeper.GetAllBalances(ctx, addr3)
expected = sdk.NewCoins(newFooCoin(50))
suite.Require().Equal(expected, acc3Balances)
}

func (suite *IntegrationTestSuite) TestValidateBalance() {
app, ctx := suite.app, suite.ctx
now := tmtime.Now()
Expand Down
50 changes: 50 additions & 0 deletions x/bank/keeper/send.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"fmt"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -16,6 +18,7 @@ type SendKeeper interface {

InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error
SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
SendManyCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddrs []sdk.AccAddress, amts []sdk.Coins) error

GetParams(ctx sdk.Context) types.Params
SetParams(ctx sdk.Context, params types.Params)
Expand Down Expand Up @@ -167,6 +170,53 @@ func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAd
return nil
}

// SendManyCoins transfer multiple amt coins from a sending account to multiple receiving accounts.
// An error is returned upon failure.
func (k BaseSendKeeper) SendManyCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddrs []sdk.AccAddress, amts []sdk.Coins) error {
if len(toAddrs) != len(amts) {
return fmt.Errorf("addresses and amounts numbers does not match")
}

totalAmt := sdk.Coins{}
for _, amt := range amts {
totalAmt = sdk.Coins.Add(totalAmt, amt...)
}

err := k.subUnlockedCoins(ctx, fromAddr, totalAmt)
if err != nil {
return err
}

fromAddrString := fromAddr.String()
for i, toAddr := range toAddrs {
amt := amts[i]

err := k.addCoins(ctx, toAddr, amt)
if err != nil {
return err
}

acc := k.ak.GetAccount(ctx, toAddr)
if acc == nil {
defer telemetry.IncrCounter(1, "new", "account")
k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr))
}

ctx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeTransfer,
sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()),
sdk.NewAttribute(types.AttributeKeySender, fromAddrString),
sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()),
))
}

ctx.EventManager().EmitEvent(sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(types.AttributeKeySender, fromAddrString),
))
return nil
}

// subUnlockedCoins removes the unlocked amt coins of the given account. An error is
// returned if the resulting balance is negative or the initial amount is invalid.
// A coin_spent event is emitted after.
Expand Down

0 comments on commit 1844456

Please sign in to comment.