github.com/Finschia/finschia-sdk@v0.48.1/x/bank/keeper/send.go (about)

     1  package keeper
     2  
     3  import (
     4  	"github.com/Finschia/finschia-sdk/codec"
     5  	"github.com/Finschia/finschia-sdk/telemetry"
     6  	sdk "github.com/Finschia/finschia-sdk/types"
     7  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
     8  	"github.com/Finschia/finschia-sdk/x/bank/types"
     9  	paramtypes "github.com/Finschia/finschia-sdk/x/params/types"
    10  )
    11  
    12  // SendKeeper defines a module interface that facilitates the transfer of coins
    13  // between accounts without the possibility of creating coins.
    14  type SendKeeper interface {
    15  	ViewKeeper
    16  
    17  	InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error
    18  	SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
    19  
    20  	GetParams(ctx sdk.Context) types.Params
    21  	SetParams(ctx sdk.Context, params types.Params)
    22  
    23  	IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool
    24  	IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error
    25  
    26  	BlockedAddr(addr sdk.AccAddress) bool
    27  }
    28  
    29  var _ SendKeeper = (*BaseSendKeeper)(nil)
    30  
    31  // BaseSendKeeper only allows transfers between accounts without the possibility of
    32  // creating coins. It implements the SendKeeper interface.
    33  type BaseSendKeeper struct {
    34  	BaseViewKeeper
    35  
    36  	cdc        codec.BinaryCodec
    37  	ak         types.AccountKeeper
    38  	storeKey   sdk.StoreKey
    39  	paramSpace paramtypes.Subspace
    40  
    41  	// list of addresses that are restricted from receiving transactions
    42  	blockedAddrs map[string]bool
    43  }
    44  
    45  func NewBaseSendKeeper(
    46  	cdc codec.BinaryCodec, storeKey sdk.StoreKey, ak types.AccountKeeper, paramSpace paramtypes.Subspace, blockedAddrs map[string]bool,
    47  ) BaseSendKeeper {
    48  	return BaseSendKeeper{
    49  		BaseViewKeeper: NewBaseViewKeeper(cdc, storeKey, ak),
    50  		cdc:            cdc,
    51  		ak:             ak,
    52  		storeKey:       storeKey,
    53  		paramSpace:     paramSpace,
    54  		blockedAddrs:   blockedAddrs,
    55  	}
    56  }
    57  
    58  // GetParams returns the total set of bank parameters.
    59  func (k BaseSendKeeper) GetParams(ctx sdk.Context) (params types.Params) {
    60  	k.paramSpace.GetParamSet(ctx, &params)
    61  	return params
    62  }
    63  
    64  // SetParams sets the total set of bank parameters.
    65  func (k BaseSendKeeper) SetParams(ctx sdk.Context, params types.Params) {
    66  	k.paramSpace.SetParamSet(ctx, &params)
    67  }
    68  
    69  // InputOutputCoins performs multi-send functionality. It accepts a series of
    70  // inputs that correspond to a series of outputs. It returns an error if the
    71  // inputs and outputs don't lineup or if any single transfer of tokens fails.
    72  func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error {
    73  	// Safety check ensuring that when sending coins the keeper must maintain the
    74  	// Check supply invariant and validity of Coins.
    75  	if err := types.ValidateInputsOutputs(inputs, outputs); err != nil {
    76  		return err
    77  	}
    78  
    79  	for _, in := range inputs {
    80  		inAddress, err := sdk.AccAddressFromBech32(in.Address)
    81  		if err != nil {
    82  			return err
    83  		}
    84  
    85  		err = k.subUnlockedCoins(ctx, inAddress, in.Coins)
    86  		if err != nil {
    87  			return err
    88  		}
    89  	}
    90  
    91  	for _, out := range outputs {
    92  		outAddress, err := sdk.AccAddressFromBech32(out.Address)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		err = k.addCoins(ctx, outAddress, out.Coins)
    97  		if err != nil {
    98  			return err
    99  		}
   100  
   101  		ctx.EventManager().EmitEvent(
   102  			sdk.NewEvent(
   103  				types.EventTypeTransfer,
   104  				sdk.NewAttribute(types.AttributeKeyRecipient, out.Address),
   105  				sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()),
   106  			),
   107  		)
   108  
   109  		// Create account if recipient does not exist.
   110  		//
   111  		// NOTE: This should ultimately be removed in favor a more flexible approach
   112  		// such as delegated fee messages.
   113  		accExists := k.ak.HasAccount(ctx, outAddress)
   114  		if !accExists {
   115  			defer telemetry.IncrCounter(1, "new", "account")
   116  			k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, outAddress))
   117  		}
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  // SendCoins transfers amt coins from a sending account to a receiving account.
   124  // An error is returned upon failure.
   125  func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error {
   126  	err := k.subUnlockedCoins(ctx, fromAddr, amt)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	err = k.addCoins(ctx, toAddr, amt)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	// Create account if recipient does not exist.
   137  	//
   138  	// NOTE: This should ultimately be removed in favor a more flexible approach
   139  	// such as delegated fee messages.
   140  	accExists := k.ak.HasAccount(ctx, toAddr)
   141  	if !accExists {
   142  		defer telemetry.IncrCounter(1, "new", "account")
   143  		k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr))
   144  	}
   145  
   146  	ctx.EventManager().EmitEvent(
   147  		sdk.NewEvent(
   148  			types.EventTypeTransfer,
   149  			sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()),
   150  			sdk.NewAttribute(types.AttributeKeySender, fromAddr.String()),
   151  			sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()),
   152  		),
   153  	)
   154  
   155  	return nil
   156  }
   157  
   158  // subUnlockedCoins removes the unlocked amt coins of the given account. An error is
   159  // returned if the resulting balance is negative or the initial amount is invalid.
   160  // A coin_spent event is emitted after.
   161  func (k BaseSendKeeper) subUnlockedCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
   162  	if !amt.IsValid() {
   163  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   164  	}
   165  
   166  	lockedCoins := k.LockedCoins(ctx, addr)
   167  
   168  	for _, coin := range amt {
   169  		balance := k.GetBalance(ctx, addr, coin.Denom)
   170  		locked := sdk.NewCoin(coin.Denom, lockedCoins.AmountOf(coin.Denom))
   171  		spendable := balance.Sub(locked)
   172  
   173  		_, hasNeg := sdk.Coins{spendable}.SafeSub(sdk.Coins{coin})
   174  		if hasNeg {
   175  			return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "%s is smaller than %s", spendable, coin)
   176  		}
   177  
   178  		newBalance := balance.Sub(coin)
   179  
   180  		err := k.setBalance(ctx, addr, newBalance)
   181  		if err != nil {
   182  			return err
   183  		}
   184  	}
   185  
   186  	// emit coin spent event
   187  	ctx.EventManager().EmitEvent(
   188  		types.NewCoinSpentEvent(addr, amt),
   189  	)
   190  	return nil
   191  }
   192  
   193  // addCoins increase the addr balance by the given amt. Fails if the provided amt is invalid.
   194  // It emits a coin received event.
   195  func (k BaseSendKeeper) addCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
   196  	if !amt.IsValid() {
   197  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   198  	}
   199  
   200  	for _, coin := range amt {
   201  		balance := k.GetBalance(ctx, addr, coin.Denom)
   202  		newBalance := balance.Add(coin)
   203  
   204  		err := k.setBalance(ctx, addr, newBalance)
   205  		if err != nil {
   206  			return err
   207  		}
   208  	}
   209  
   210  	// emit coin received event
   211  	ctx.EventManager().EmitEvent(
   212  		types.NewCoinReceivedEvent(addr, amt),
   213  	)
   214  
   215  	return nil
   216  }
   217  
   218  // initBalances sets the balance (multiple coins) for an account by address.
   219  // An error is returned upon failure.
   220  func (k BaseSendKeeper) initBalances(ctx sdk.Context, addr sdk.AccAddress, balances sdk.Coins) error {
   221  	accountStore := k.getAccountStore(ctx, addr)
   222  	for i := range balances {
   223  		balance := balances[i]
   224  		if !balance.IsValid() {
   225  			return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, balance.String())
   226  		}
   227  
   228  		// Bank invariants require to not store zero balances.
   229  		if !balance.IsZero() {
   230  			bz := k.cdc.MustMarshal(&balance)
   231  			accountStore.Set([]byte(balance.Denom), bz)
   232  		}
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  // setBalance sets the coin balance for an account by address.
   239  func (k BaseSendKeeper) setBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error {
   240  	if !balance.IsValid() {
   241  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, balance.String())
   242  	}
   243  
   244  	accountStore := k.getAccountStore(ctx, addr)
   245  
   246  	// Bank invariants require to not store zero balances.
   247  	if balance.IsZero() {
   248  		accountStore.Delete([]byte(balance.Denom))
   249  	} else {
   250  		bz := k.cdc.MustMarshal(&balance)
   251  		accountStore.Set([]byte(balance.Denom), bz)
   252  	}
   253  
   254  	return nil
   255  }
   256  
   257  // IsSendEnabledCoins checks the coins provide and returns an ErrSendDisabled if
   258  // any of the coins are not configured for sending.  Returns nil if sending is enabled
   259  // for all provided coin
   260  func (k BaseSendKeeper) IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error {
   261  	for _, coin := range coins {
   262  		if !k.IsSendEnabledCoin(ctx, coin) {
   263  			return sdkerrors.Wrapf(types.ErrSendDisabled, "%s transfers are currently disabled", coin.Denom)
   264  		}
   265  	}
   266  	return nil
   267  }
   268  
   269  // IsSendEnabledCoin returns the current SendEnabled status of the provided coin's denom
   270  func (k BaseSendKeeper) IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool {
   271  	return k.GetParams(ctx).SendEnabledDenom(coin.Denom)
   272  }
   273  
   274  // BlockedAddr checks if a given address is restricted from
   275  // receiving funds.
   276  func (k BaseSendKeeper) BlockedAddr(addr sdk.AccAddress) bool {
   277  	return k.blockedAddrs[addr.String()]
   278  }