210 lines
5.3 KiB
Go
210 lines
5.3 KiB
Go
|
package keeper
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/tendermint/tendermint/libs/log"
|
||
|
|
||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||
|
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||
|
|
||
|
"github.com/cosmos/ethermint/x/faucet/types"
|
||
|
)
|
||
|
|
||
|
// Keeper defines the faucet Keeper.
|
||
|
type Keeper struct {
|
||
|
cdc *codec.Codec
|
||
|
storeKey sdk.StoreKey
|
||
|
supplyKeeper types.SupplyKeeper
|
||
|
|
||
|
// History of users and their funding timeouts. They are reset if the app is reinitialized.
|
||
|
timeouts map[string]time.Time
|
||
|
}
|
||
|
|
||
|
// NewKeeper creates a new faucet Keeper instance.
|
||
|
func NewKeeper(
|
||
|
cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.SupplyKeeper,
|
||
|
) Keeper {
|
||
|
return Keeper{
|
||
|
cdc: cdc,
|
||
|
storeKey: storeKey,
|
||
|
supplyKeeper: supplyKeeper,
|
||
|
timeouts: make(map[string]time.Time),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Logger returns a module-specific logger.
|
||
|
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
||
|
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
|
||
|
}
|
||
|
|
||
|
// GetFaucetAccount returns the faucet ModuleAccount
|
||
|
func (k Keeper) GetFaucetAccount(ctx sdk.Context) supplyexported.ModuleAccountI {
|
||
|
return k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName)
|
||
|
}
|
||
|
|
||
|
// Fund checks for timeout and max thresholds and then mints coins and transfers
|
||
|
// coins to the recipient.
|
||
|
func (k Keeper) Fund(ctx sdk.Context, amount sdk.Coins, recipient sdk.AccAddress) error {
|
||
|
if !k.IsEnabled(ctx) {
|
||
|
return errors.New("faucet is not enabled. Restart the application and set faucet's 'enable_faucet' genesis field to true")
|
||
|
}
|
||
|
|
||
|
if err := k.rateLimit(ctx, recipient.String()); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
totalRequested := sdk.ZeroInt()
|
||
|
for _, coin := range amount {
|
||
|
totalRequested = totalRequested.Add(coin.Amount)
|
||
|
}
|
||
|
|
||
|
maxPerReq := k.GetMaxPerRequest(ctx)
|
||
|
if totalRequested.GT(maxPerReq) {
|
||
|
return fmt.Errorf("canot fund more than %s per request. requested %s", maxPerReq, totalRequested)
|
||
|
}
|
||
|
|
||
|
funded := k.GetFunded(ctx)
|
||
|
totalFunded := sdk.ZeroInt()
|
||
|
for _, coin := range funded {
|
||
|
totalFunded = totalFunded.Add(coin.Amount)
|
||
|
}
|
||
|
|
||
|
cap := k.GetCap(ctx)
|
||
|
|
||
|
if totalFunded.Add(totalRequested).GT(cap) {
|
||
|
return fmt.Errorf("maximum cap of %s reached. Cannot continue funding", cap)
|
||
|
}
|
||
|
|
||
|
if err := k.supplyKeeper.MintCoins(ctx, types.ModuleName, amount); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, amount); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
k.SetFunded(ctx, funded.Add(amount...))
|
||
|
|
||
|
k.Logger(ctx).Info(fmt.Sprintf("funded %s to %s", amount, recipient))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (k Keeper) GetTimeout(ctx sdk.Context) time.Duration {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := store.Get(types.TimeoutKey)
|
||
|
if len(bz) == 0 {
|
||
|
return time.Duration(0)
|
||
|
}
|
||
|
|
||
|
var timeout time.Duration
|
||
|
k.cdc.MustUnmarshalBinaryBare(bz, &timeout)
|
||
|
|
||
|
return timeout
|
||
|
}
|
||
|
|
||
|
func (k Keeper) SetTimout(ctx sdk.Context, timeout time.Duration) {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := k.cdc.MustMarshalBinaryBare(timeout)
|
||
|
store.Set(types.TimeoutKey, bz)
|
||
|
}
|
||
|
|
||
|
func (k Keeper) IsEnabled(ctx sdk.Context) bool {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := store.Get(types.EnableFaucetKey)
|
||
|
if len(bz) == 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
var enabled bool
|
||
|
k.cdc.MustUnmarshalBinaryBare(bz, &enabled)
|
||
|
return enabled
|
||
|
}
|
||
|
|
||
|
func (k Keeper) SetEnabled(ctx sdk.Context, enabled bool) {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := k.cdc.MustMarshalBinaryBare(enabled)
|
||
|
store.Set(types.EnableFaucetKey, bz)
|
||
|
}
|
||
|
|
||
|
func (k Keeper) GetCap(ctx sdk.Context) sdk.Int {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := store.Get(types.CapKey)
|
||
|
if len(bz) == 0 {
|
||
|
return sdk.ZeroInt()
|
||
|
}
|
||
|
|
||
|
var cap sdk.Int
|
||
|
k.cdc.MustUnmarshalBinaryBare(bz, &cap)
|
||
|
|
||
|
return cap
|
||
|
}
|
||
|
|
||
|
func (k Keeper) SetCap(ctx sdk.Context, cap sdk.Int) {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := k.cdc.MustMarshalBinaryBare(cap)
|
||
|
store.Set(types.CapKey, bz)
|
||
|
}
|
||
|
|
||
|
func (k Keeper) GetMaxPerRequest(ctx sdk.Context) sdk.Int {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := store.Get(types.MaxPerRequestKey)
|
||
|
if len(bz) == 0 {
|
||
|
return sdk.ZeroInt()
|
||
|
}
|
||
|
|
||
|
var maxPerReq sdk.Int
|
||
|
k.cdc.MustUnmarshalBinaryBare(bz, &maxPerReq)
|
||
|
|
||
|
return maxPerReq
|
||
|
}
|
||
|
|
||
|
func (k Keeper) SetMaxPerRequest(ctx sdk.Context, maxPerReq sdk.Int) {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := k.cdc.MustMarshalBinaryBare(maxPerReq)
|
||
|
store.Set(types.MaxPerRequestKey, bz)
|
||
|
}
|
||
|
|
||
|
func (k Keeper) GetFunded(ctx sdk.Context) sdk.Coins {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := store.Get(types.FundedKey)
|
||
|
if len(bz) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var funded sdk.Coins
|
||
|
k.cdc.MustUnmarshalBinaryBare(bz, &funded)
|
||
|
|
||
|
return funded
|
||
|
}
|
||
|
|
||
|
func (k Keeper) SetFunded(ctx sdk.Context, funded sdk.Coins) {
|
||
|
store := ctx.KVStore(k.storeKey)
|
||
|
bz := k.cdc.MustMarshalBinaryBare(funded)
|
||
|
store.Set(types.FundedKey, bz)
|
||
|
}
|
||
|
|
||
|
func (k Keeper) rateLimit(ctx sdk.Context, address string) error {
|
||
|
// first time requester, can send request
|
||
|
lastRequest, ok := k.timeouts[address]
|
||
|
if !ok {
|
||
|
k.timeouts[address] = time.Now().UTC()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
defaultTimeout := k.GetTimeout(ctx)
|
||
|
sinceLastRequest := time.Since(lastRequest)
|
||
|
|
||
|
if defaultTimeout > sinceLastRequest {
|
||
|
wait := defaultTimeout - sinceLastRequest
|
||
|
return fmt.Errorf("%s has requested funds within the last %s, wait %s before trying again", address, defaultTimeout.String(), wait.String())
|
||
|
}
|
||
|
|
||
|
// user able to send funds since they have waited for period
|
||
|
k.timeouts[address] = time.Now().UTC()
|
||
|
return nil
|
||
|
}
|