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
This commit is contained in:
Federico Kunze
2020-05-18 17:33:08 -04:00
committed by GitHub
parent 16df7725c5
commit 0351bef644
19 changed files with 878 additions and 145 deletions
+24
View File
@@ -0,0 +1,24 @@
package faucet
import (
"github.com/cosmos/ethermint/x/faucet/keeper"
"github.com/cosmos/ethermint/x/faucet/types"
)
const (
ModuleName = types.ModuleName
RouterKey = types.RouterKey
StoreKey = types.StoreKey
QuerierRoute = types.QuerierRoute
)
var (
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
ModuleCdc = types.ModuleCdc
RegisterCodec = types.RegisterCodec
)
type (
Keeper = keeper.Keeper
)
+52
View File
@@ -0,0 +1,52 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/x/faucet/types"
)
// GetQueryCmd defines evm module queries through the cli
func GetQueryCmd(cdc *codec.Codec) *cobra.Command {
faucetQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
faucetQueryCmd.AddCommand(flags.GetCommands(
GetCmdFunded(cdc),
)...)
return faucetQueryCmd
}
// GetCmdFunded queries the total amount funded by the faucet.
func GetCmdFunded(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "funded",
Short: "Gets storage for an account at a given key",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, height, err := cliCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
if err != nil {
return err
}
var out sdk.Coins
cdc.MustUnmarshalJSON(res, &out)
cliCtx = cliCtx.WithHeight(height)
return cliCtx.PrintOutput(out)
},
}
}
+71
View File
@@ -0,0 +1,71 @@
package cli
import (
"bufio"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
"github.com/cosmos/ethermint/x/faucet/types"
)
// GetTxCmd return faucet sub-command for tx
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
faucetTxCmd := &cobra.Command{
Use: types.ModuleName,
Short: "faucet transaction subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
faucetTxCmd.AddCommand(flags.PostCommands(
GetCmdFund(cdc),
)...)
return faucetTxCmd
}
// GetCmdFund is the CLI command to fund an address with the requested coins
func GetCmdFund(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "fund [amount] [other-recipient (optional)]",
Short: "fund an address with the requested coins",
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
amount, err := sdk.ParseCoins(args[0])
if err != nil {
return err
}
var recipient sdk.AccAddress
if len(args) == 1 {
recipient = cliCtx.GetFromAddress()
} else {
recipient, err = sdk.AccAddressFromBech32(args[1])
}
if err != nil {
return err
}
msg := types.NewMsgFund(amount, cliCtx.GetFromAddress(), recipient)
if err := msg.ValidateBasic(); err != nil {
return err
}
return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
+82
View File
@@ -0,0 +1,82 @@
package rest
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
"github.com/cosmos/ethermint/x/faucet/types"
)
// RegisterRoutes register REST endpoints for the faucet module
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/%s/fund", types.ModuleName), fundHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/%s/funded", types.ModuleName), fundedHandlerFn(cliCtx)).Methods("GET")
}
// PostFundRequest defines fund request's body.
type PostFundRequest struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Amount sdk.Coins `json:"amount" yaml:"amount"`
Recipient string `json:"receipient" yaml:"receipient"`
}
func fundHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req PostFundRequest
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
return
}
baseReq := req.BaseReq.Sanitize()
if !baseReq.ValidateBasic(w) {
return
}
sender, err := sdk.AccAddressFromBech32(baseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
var recipient sdk.AccAddress
if req.Recipient == "" {
recipient = sender
} else {
recipient, err = sdk.AccAddressFromBech32(req.Recipient)
}
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
msg := types.NewMsgFund(req.Amount, sender, recipient)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
authclient.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
}
}
func fundedHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
res, height, err := cliCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
if rest.CheckInternalServerError(w, err) {
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}
+33
View File
@@ -0,0 +1,33 @@
package faucet
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/x/faucet/types"
abci "github.com/tendermint/tendermint/abci/types"
)
// InitGenesis initializes genesis state based on exported genesis
func InitGenesis(ctx sdk.Context, k Keeper, data types.GenesisState) []abci.ValidatorUpdate {
if acc := k.GetFaucetAccount(ctx); acc == nil {
panic(fmt.Sprintf("%s module account has not been set", ModuleName))
}
k.SetEnabled(ctx, data.EnableFaucet)
k.SetTimout(ctx, data.Timeout)
k.SetCap(ctx, data.FaucetCap)
k.SetMaxPerRequest(ctx, data.MaxAmountPerRequest)
return []abci.ValidatorUpdate{}
}
// ExportGenesis exports genesis state
func ExportGenesis(ctx sdk.Context, k Keeper) types.GenesisState {
return types.GenesisState{
EnableFaucet: k.IsEnabled(ctx),
Timeout: k.GetTimeout(ctx),
FaucetCap: k.GetCap(ctx),
MaxAmountPerRequest: k.GetMaxPerRequest(ctx),
}
}
+43
View File
@@ -0,0 +1,43 @@
package faucet
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/ethermint/x/faucet/types"
)
// NewHandler returns a handler for faucet messages.
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
case types.MsgFund:
return handleMsgFund(ctx, keeper, msg)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
}
}
}
// handleMsgFund handles a message to fund an address
func handleMsgFund(ctx sdk.Context, keeper Keeper, msg types.MsgFund) (*sdk.Result, error) {
err := keeper.Fund(ctx, msg.Amount, msg.Recipient)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
sdk.NewAttribute(types.AttributeRecipient, msg.Recipient.String()),
),
)
return &sdk.Result{
Events: ctx.EventManager().ABCIEvents(),
}, nil
}
+209
View File
@@ -0,0 +1,209 @@
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
}
+33
View File
@@ -0,0 +1,33 @@
package keeper
import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/ethermint/x/faucet/types"
)
// NewQuerier is the module level router for state queries
func NewQuerier(k Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) {
switch path[0] {
case types.QueryFunded:
return queryFunded(ctx, req, k)
default:
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown query endpoint")
}
}
}
func queryFunded(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
funded := k.GetFunded(ctx)
bz, err := codec.MarshalJSONIndent(k.cdc, funded)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
+137
View File
@@ -0,0 +1,137 @@
package faucet
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/ethermint/x/faucet/client/cli"
"github.com/cosmos/ethermint/x/faucet/client/rest"
"github.com/cosmos/ethermint/x/faucet/types"
)
// type check to ensure the interface is properly implemented
var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
)
// AppModuleBasic defines the basic application module used by the faucet module.
type AppModuleBasic struct{}
// Name returns the faucet module's name.
func (AppModuleBasic) Name() string {
return types.ModuleName
}
// RegisterCodec registers the faucet module's types for the given codec.
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
RegisterCodec(cdc)
}
// DefaultGenesis returns default genesis state as raw bytes for the faucet
// module.
func (AppModuleBasic) DefaultGenesis(cdc codec.JSONMarshaler) json.RawMessage {
return cdc.MustMarshalJSON(types.DefaultGenesisState())
}
// ValidateGenesis performs genesis state validation for the faucet module.
func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, bz json.RawMessage) error {
var genesisState types.GenesisState
if err := cdc.UnmarshalJSON(bz, &genesisState); err != nil {
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
}
return genesisState.Validate()
}
// RegisterRESTRoutes registers the REST routes for the faucet module.
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
rest.RegisterRoutes(ctx, rtr)
}
// GetTxCmd returns the root tx command for the faucet module.
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
return cli.GetTxCmd(cdc)
}
// GetQueryCmd returns no root query command for the faucet module.
func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
return cli.GetQueryCmd(cdc)
}
type AppModule struct {
AppModuleBasic
keeper Keeper
}
// NewAppModule creates a new AppModule Object
func NewAppModule(k Keeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
keeper: k,
}
}
// Name returns the faucet module's name.
func (AppModule) Name() string {
return ModuleName
}
// RegisterInvariants registers the faucet module invariants.
func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
// Route returns the message routing key for the faucet module.
func (AppModule) Route() string {
return RouterKey
}
// NewHandler returns an sdk.Handler for the faucet module.
func (am AppModule) NewHandler() sdk.Handler {
return NewHandler(am.keeper)
}
// QuerierRoute returns the faucet module's querier route name.
func (AppModule) QuerierRoute() string {
return QuerierRoute
}
// NewQuerierHandler returns the faucet module sdk.Querier.
func (am AppModule) NewQuerierHandler() sdk.Querier {
return NewQuerier(am.keeper)
}
// InitGenesis performs genesis initialization for the faucet module. It returns
// no validator updates.
func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, data json.RawMessage) []abci.ValidatorUpdate {
var genesisState types.GenesisState
ModuleCdc.MustUnmarshalJSON(data, &genesisState)
InitGenesis(ctx, am.keeper, genesisState)
return []abci.ValidatorUpdate{}
}
// ExportGenesis returns the exported genesis state as raw bytes for the faucet
// module.
func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage {
gs := ExportGenesis(ctx, am.keeper)
return cdc.MustMarshalJSON(gs)
}
// BeginBlock returns the begin blocker for the faucet module.
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
}
// EndBlock returns the end blocker for the faucet module. It returns no validator
// updates.
func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}
+17
View File
@@ -0,0 +1,17 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
)
// ModuleCdc is the codec for the module
var ModuleCdc = codec.New()
func init() {
RegisterCodec(ModuleCdc)
}
// RegisterCodec registers concrete types on the Amino codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgFund{}, "ethermint/MsgFund", nil)
}
+10
View File
@@ -0,0 +1,10 @@
package types
import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var (
// ErrWithdrawTooOften withdraw too often
ErrWithdrawTooOften = sdkerrors.Register(ModuleName, 2, "each address can withdraw only once")
)
+11
View File
@@ -0,0 +1,11 @@
package types
// Faucet module events
const (
AttributeRecipient string = "recipient"
)
// Supported endpoints
const (
QueryFunded = "funded"
)
+20
View File
@@ -0,0 +1,20 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
// SupplyKeeper is required for mining coin
type SupplyKeeper interface {
MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
SendCoinsFromModuleToAccount(
ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins,
) error
GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI
}
// StakingKeeper is required for getting Denom
type StakingKeeper interface {
BondDenom(ctx sdk.Context) string
}
+45
View File
@@ -0,0 +1,45 @@
package types
import (
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// GenesisState defines the application's genesis state. It contains all the
// information required and accounts to initialize the blockchain.
type GenesisState struct {
// enable faucet funding
EnableFaucet bool `json:"enable_faucet" yaml:"enable_faucet"`
// addresses can send requests every <Timeout> duration
Timeout time.Duration `json:"timeout" yaml:"timeout"`
// max total amount to be funded by the faucet
FaucetCap sdk.Int `json:"faucet_cap" yaml:"faucet_cap"`
// max amount per request (i.e sum of all requested coin amounts).
MaxAmountPerRequest sdk.Int `json:"max_amount_per_request" yaml:"max_amount_per_request"`
}
// Validate performs a basic validation of the GenesisState fields.
func (gs GenesisState) Validate() error {
if gs.Timeout < 0 {
return fmt.Errorf("timeout cannot be negative: %s", gs.Timeout)
}
if gs.FaucetCap.IsNegative() {
return fmt.Errorf("faucet cap cannot be negative: %d", gs.FaucetCap)
}
if gs.MaxAmountPerRequest.IsNegative() {
return fmt.Errorf("max amount per request cannot be negative: %d", gs.MaxAmountPerRequest)
}
return nil
}
// DefaultGenesisState sets default evm genesis config
func DefaultGenesisState() GenesisState {
return GenesisState{
EnableFaucet: false,
Timeout: 20 * time.Minute,
FaucetCap: sdk.NewInt(1000000000),
MaxAmountPerRequest: sdk.NewInt(1000),
}
}
+23
View File
@@ -0,0 +1,23 @@
package types
const (
// ModuleName is the name of the module
ModuleName = "faucet"
// StoreKey to be used when creating the KVStore
StoreKey = ModuleName
// RouterKey uses module name for tx routing
RouterKey = ModuleName
// QuerierRoute uses module name for query routing
QuerierRoute = ModuleName
)
var (
EnableFaucetKey = []byte{0x01}
TimeoutKey = []byte{0x02}
CapKey = []byte{0x03}
MaxPerRequestKey = []byte{0x04}
FundedKey = []byte{0x05}
)
+52
View File
@@ -0,0 +1,52 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// MsgFund funds a recipient address
type MsgFund struct {
Amount sdk.Coins `json:"amount" yaml:"amount"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Recipient sdk.AccAddress `json:"receipient" yaml:"receipient"`
}
// NewMsgFund is a constructor function for NewMsgFund
func NewMsgFund(amount sdk.Coins, sender, recipient sdk.AccAddress) MsgFund {
return MsgFund{
Amount: amount,
Sender: sender,
Recipient: recipient,
}
}
// Route should return the name of the module
func (msg MsgFund) Route() string { return RouterKey }
// Type should return the action
func (msg MsgFund) Type() string { return "fund" }
// ValidateBasic runs stateless checks on the message
func (msg MsgFund) ValidateBasic() error {
if !msg.Amount.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String())
}
if msg.Sender.Empty() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "sender %s", msg.Sender.String())
}
if msg.Recipient.Empty() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "recipient %s", msg.Recipient.String())
}
return nil
}
// GetSignBytes encodes the message for signing
func (msg MsgFund) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
}
// GetSigners defines whose signature is required
func (msg MsgFund) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}