laconicd/x/faucet/keeper/keeper.go
Federico Kunze 0351bef644
testnet faucet (#281)
* testnet faucet

* commands

* updates

* faucet module

* genesis state

* fixes

* module.go

* add module to app

* update Fund

* querier route

* querier

* CLI query

* fix query

* add rest routes

* update cli query
2020-05-18 17:33:08 -04:00

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
}