forked from cerc-io/laconicd-deprecated
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:
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package types
|
||||
|
||||
// Faucet module events
|
||||
const (
|
||||
AttributeRecipient string = "recipient"
|
||||
)
|
||||
|
||||
// Supported endpoints
|
||||
const (
|
||||
QueryFunded = "funded"
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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}
|
||||
}
|
||||
Reference in New Issue
Block a user