diff --git a/CHANGELOG.md b/CHANGELOG.md index 3706134b62a2..464d497b4525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,8 +41,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features * (baseapp) [#205](https://github.com/crypto-org-chain/cosmos-sdk/pull/205) Add `TxExecutor` baseapp option, add `TxIndex`/`TxCount`/`MsgIndex`/`BlockGasUsed` fields to `Context, to support tx parallel execution. -* (baseapp) [#206](https://github.com/crypto-org-chain/cosmos-sdk/pull/206) Support mount object store in baseapp, add `ObjectStore` api in context.. +* (baseapp) [#206](https://github.com/crypto-org-chain/cosmos-sdk/pull/206) Support mount object store in baseapp, add `ObjectStore` api in context. * (bank) [#237](https://github.com/crypto-org-chain/cosmos-sdk/pull/237) Support virtual accounts in sending coins. +* (x/bank) [#239](https://github.com/crypto-org-chain/cosmos-sdk/pull/239) Add low level `AddBalance`,`SubBalance` APIs to bank keeper. ## [Unreleased-Upstream] diff --git a/x/bank/keeper/keeper.go b/x/bank/keeper/keeper.go index 26dceca9b66b..1a31ffc0f220 100644 --- a/x/bank/keeper/keeper.go +++ b/x/bank/keeper/keeper.go @@ -419,6 +419,41 @@ func (k BaseKeeper) BurnCoins(ctx context.Context, moduleName string, amounts sd return nil } +// AddBalance is low level api to update balance directly, mainly used by evm integration, +// caller should make sure the total supply of the denom not changed and the account exists. +// it's for evm integration, call at your own risk. +// it emits mint event. +func (k BaseKeeper) AddBalance(ctx context.Context, addr sdk.AccAddress, coin sdk.Coin) error { + if err := k.addCoin(ctx, addr, coin); err != nil { + return err + } + + // emit mint event + sdkCtx := sdk.UnwrapSDKContext(ctx) + sdkCtx.EventManager().EmitEvent( + types.NewCoinMintEvent(addr, sdk.NewCoins(coin)), + ) + return nil +} + +// SubBalance is low level api to update balance directly, mainly used by evm integration, +// caller should make sure the total supply of the denom not changed. +// it's for evm integration, call at your own risk. +// it emits burn event. +func (k BaseKeeper) SubBalance(ctx context.Context, addr sdk.AccAddress, coin sdk.Coin) error { + lockedCoins := k.LockedCoins(ctx, addr) + if err := k.subCoin(ctx, addr, coin, lockedCoins); err != nil { + return err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + // emit burn event + sdkCtx.EventManager().EmitEvent( + types.NewCoinBurnEvent(addr, sdk.NewCoins(coin)), + ) + return nil +} + // setSupply sets the supply for the given coin func (k BaseKeeper) setSupply(ctx context.Context, coin sdk.Coin) { // Bank invariants and IBC requires to remove zero coins. diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 7f9bd6016506..037bc83265a8 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -8,7 +8,6 @@ import ( "cosmossdk.io/core/store" errorsmod "cosmossdk.io/errors" "cosmossdk.io/log" - "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" @@ -273,29 +272,7 @@ func (k BaseSendKeeper) subUnlockedCoins(ctx context.Context, addr sdk.AccAddres lockedCoins := k.LockedCoins(ctx, addr) for _, coin := range amt { - balance := k.GetBalance(ctx, addr, coin.Denom) - locked := sdk.NewCoin(coin.Denom, lockedCoins.AmountOf(coin.Denom)) - - spendable, hasNeg := sdk.Coins{balance}.SafeSub(locked) - if hasNeg { - return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, - "locked amount exceeds account balance funds: %s > %s", locked, balance) - } - - if _, hasNeg := spendable.SafeSub(coin); hasNeg { - if len(spendable) == 0 { - spendable = sdk.Coins{sdk.NewCoin(coin.Denom, math.ZeroInt())} - } - return errorsmod.Wrapf( - sdkerrors.ErrInsufficientFunds, - "spendable balance %s is smaller than %s", - spendable, coin, - ) - } - - newBalance := balance.Sub(coin) - - if err := k.setBalance(ctx, addr, newBalance); err != nil { + if err := k.subCoin(ctx, addr, coin, lockedCoins); err != nil { return err } } @@ -308,6 +285,32 @@ func (k BaseSendKeeper) subUnlockedCoins(ctx context.Context, addr sdk.AccAddres return nil } +func (k BaseSendKeeper) subCoin(ctx context.Context, addr sdk.AccAddress, coin sdk.Coin, lockedCoins sdk.Coins) error { + var ( + spendable sdk.Coin + err error + ) + balance := k.GetBalance(ctx, addr, coin.Denom) + locked := sdk.NewCoin(coin.Denom, lockedCoins.AmountOf(coin.Denom)) + if locked.IsZero() { + spendable = balance + } else { + spendable, err = balance.SafeSub(locked) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, + "locked amount exceeds account balance funds: %s > %s", locked, balance) + } + } + if spendable.Amount.LT(coin.Amount) { + return errorsmod.Wrapf( + sdkerrors.ErrInsufficientFunds, + "spendable balance %s is smaller than %s", + spendable, coin, + ) + } + return k.setBalance(ctx, addr, balance.Sub(coin)) +} + // addCoins increase the addr balance by the given amt. Fails if the provided // amt is invalid. It emits a coin received event. func (k BaseSendKeeper) addCoins(ctx context.Context, addr sdk.AccAddress, amt sdk.Coins) error { @@ -316,11 +319,7 @@ func (k BaseSendKeeper) addCoins(ctx context.Context, addr sdk.AccAddress, amt s } for _, coin := range amt { - balance := k.GetBalance(ctx, addr, coin.Denom) - newBalance := balance.Add(coin) - - err := k.setBalance(ctx, addr, newBalance) - if err != nil { + if err := k.addCoin(ctx, addr, coin); err != nil { return err } } @@ -334,6 +333,12 @@ func (k BaseSendKeeper) addCoins(ctx context.Context, addr sdk.AccAddress, amt s return nil } +func (k BaseSendKeeper) addCoin(ctx context.Context, addr sdk.AccAddress, coin sdk.Coin) error { + balance := k.GetBalance(ctx, addr, coin.Denom) + newBalance := balance.Add(coin) + return k.setBalance(ctx, addr, newBalance) +} + // setBalance sets the coin balance for an account by address. func (k BaseSendKeeper) setBalance(ctx context.Context, addr sdk.AccAddress, balance sdk.Coin) error { if !balance.IsValid() {