Skip to content

Commit

Permalink
feat: initial airdrop support (cosmos#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
ejfitzgerald committed Jul 5, 2021
1 parent 1227bf3 commit a59a3d4
Show file tree
Hide file tree
Showing 31 changed files with 5,510 additions and 144 deletions.
573 changes: 429 additions & 144 deletions docs/core/proto-docs.md

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions proto/cosmos/airdrop/v1beta1/airdrop.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
syntax = "proto3";
package cosmos.airdrop.v1beta1;

import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";

// Fund defines a structure for a fund that is being distributed to network stakers
message Fund {
option (gogoproto.equal) = true;

// The amount of fund that is remaining
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.moretags) = "yaml:\"amount\""];

// The amount of funds that should be removed from the fund every block
string drip_amount = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"drip_amount\""
];
}

// ActiveFund describes an active fund on the network
message ActiveFund {
string sender = 1 [(gogoproto.moretags) = "yaml:\"sender\""];
Fund fund = 2 [(gogoproto.moretags) = "yaml:\"blocks_remaining\""];
}

// Params define the module parameters
message Params {
// The set of addresses which are allowed to create are drop funds
repeated string allow_list = 1 [(gogoproto.moretags) = "yaml:\"allow_list\""];
}
16 changes: 16 additions & 0 deletions proto/cosmos/airdrop/v1beta1/genesis.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";
package cosmos.airdrop.v1beta1;

import "gogoproto/gogo.proto";
import "cosmos/airdrop/v1beta1/airdrop.proto";

option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";

// GenesisState defines the bank module's genesis state.
message GenesisState {
// params defines all the parameters of the module.
Params params = 1 [(gogoproto.nullable) = false];

// balances is an array containing the balances of all the accounts.
repeated ActiveFund funds = 2 [(gogoproto.nullable) = false];
}
57 changes: 57 additions & 0 deletions proto/cosmos/airdrop/v1beta1/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
syntax = "proto3";
package cosmos.airdrop.v1beta1;

import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "cosmos/airdrop/v1beta1/airdrop.proto";

option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";

// Query defines the gRPC querier service.
service Query {

// AllFunds queries all active airdrop funds
rpc AllFunds(QueryAllFundsRequest) returns (QueryAllFundsResponse) {
option (google.api.http).get = "/cosmos/airdrop/v1beta1/funds";
}

// Fund queries a specific airdrop fund
rpc Fund(QueryFundRequest) returns (QueryFundResponse) {
option (google.api.http).get = "/cosmos/airdrop/v1beta1/funds/{address}";
}

// Params queries the current modules parameters
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/cosmos/airdrop/v1beta1/params";
}
}

// QueryAllFundsRequest defines the request for querying all the funds
message QueryAllFundsRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

// QueryAllFundsResponse defines the response for querying all the funds
message QueryAllFundsResponse {
repeated cosmos.airdrop.v1beta1.ActiveFund funds = 1;
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryFundRequest defines the request for querying a specific fund
message QueryFundRequest {
string address = 1 [(gogoproto.nullable) = true];
}

// QueryFundResponse defines the response for querying a specific fund
message QueryFundResponse {
cosmos.airdrop.v1beta1.Fund fund = 1;
}

// QueryParamsRequest defines the request type for querying x/airdrop parameters.
message QueryParamsRequest {}

// QueryParamsResponse defines the response type for querying x/airdrop parameters.
message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}
26 changes: 26 additions & 0 deletions proto/cosmos/airdrop/v1beta1/tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
syntax = "proto3";
package cosmos.airdrop.v1beta1;

import "gogoproto/gogo.proto";
import "cosmos/airdrop/v1beta1/airdrop.proto";

option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";

// Msg defines the airdrop Msg service.
service Msg {

// AirDrop defines a method for sending coins to the airdrop module for distribution
rpc AirDrop(MsgAirDrop) returns (MsgAirDropResponse);
}

// MsgAirDrop represents a message to create an airdrop fund for distribution
message MsgAirDrop {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string from_address = 1 [(gogoproto.moretags) = "yaml:\"from_address\""];
Fund fund = 2 [(gogoproto.moretags) = "yaml:\"fund\""];
}

// MsgAirDropResponse represents a message for the response
message MsgAirDropResponse {}
11 changes: 11 additions & 0 deletions simapp/app.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package simapp

import (
airdroptypes "github.com/cosmos/cosmos-sdk/x/airdrop/types"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -30,6 +31,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/version"
airdropkeeper "github.com/cosmos/cosmos-sdk/x/airdrop/keeper"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
Expand Down Expand Up @@ -125,6 +127,7 @@ var (
maccPerms = map[string][]string{
authtypes.FeeCollectorName: nil,
distrtypes.ModuleName: nil,
airdroptypes.ModuleName: nil,
minttypes.ModuleName: {authtypes.Minter},
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
Expand Down Expand Up @@ -169,6 +172,7 @@ type SimApp struct {
IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
EvidenceKeeper evidencekeeper.Keeper
TransferKeeper ibctransferkeeper.Keeper
AirdropKeeper *airdropkeeper.Keeper

// make scoped keepers public for test purposes
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
Expand Down Expand Up @@ -213,6 +217,7 @@ func NewSimApp(
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey,
evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey,
airdroptypes.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
Expand Down Expand Up @@ -296,6 +301,11 @@ func NewSimApp(
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper,
app.AccountKeeper, app.BankKeeper, scopedTransferKeeper,
)
app.AirdropKeeper = airdropkeeper.NewKeeper(
appCodec, keys[airdroptypes.StoreKey], app.GetSubspace(airdroptypes.ModuleName), app.BankKeeper,
authtypes.FeeCollectorName,
)

transferModule := transfer.NewAppModule(app.TransferKeeper)

// NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do
Expand Down Expand Up @@ -602,6 +612,7 @@ func initParamsKeeper(appCodec codec.BinaryMarshaler, legacyAmino *codec.LegacyA
paramsKeeper.Subspace(crisistypes.ModuleName)
paramsKeeper.Subspace(ibctransfertypes.ModuleName)
paramsKeeper.Subspace(ibchost.ModuleName)
paramsKeeper.Subspace(airdroptypes.ModuleName)

return paramsKeeper
}
18 changes: 18 additions & 0 deletions x/airdrop/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package airdrop

import (
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/airdrop/keeper"
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
"time"
)

func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

_, err := k.DripAllFunds(ctx)
if err != nil {
ctx.Logger().Error("Unable to perform airdrop drip", "err", err.Error())
}
}
70 changes: 70 additions & 0 deletions x/airdrop/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cli

import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
"github.com/spf13/cobra"
"strings"
)

func GetQueryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the bank module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

cmd.AddCommand(
GetFunds(),
)

return cmd
}

func GetFunds() *cobra.Command {
cmd := &cobra.Command{
Use: "funds",
Short: "Query the currently active airdrop funds",
Long: strings.TrimSpace(
fmt.Sprintf(`Query the currently active airdrop funds.
Example:
$ %s query %s funds
`,
version.AppName, types.ModuleName,
),
),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

params := types.NewQueryAllFundsRequest(pageReq)

res, err := queryClient.AllFunds(cmd.Context(), params)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "all funds")

return cmd
}
79 changes: 79 additions & 0 deletions x/airdrop/client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cli

import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
"github.com/spf13/cobra"
"strings"
)

func GetTxCmd() *cobra.Command {
txCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Airdrop transaction subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

txCmd.AddCommand(NewCreateTxCmd())

return txCmd
}

func NewCreateTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create [from_key_or_address] [amount] [drip_amount]",
Short: `Creates a new airdrop fund. Note, the '--from' flag is
ignored as it is implied from [from_key_or_address].`,
Long: strings.TrimSpace(
fmt.Sprintf(`Creates a new airdrop fund.
When creating an airdrop fund, the sender transfers a specified amount of funds to the block chain along with
the drip amount. Every block up to a maximum of drip_amount of funds are added to the rewards of the current block.
The maximum duration of the airdrop is therefore calculated as amount / drip_amount blocks.
Example:
$ %s tx %s create [address] [amount] [drip_amount]
$ %s tx %s create fetch1se8mjg4mtvy8zaf4599m84xz4atn59dlqmwhnl 200000000000000000000afet 2000000000000000000
`,
version.AppName, types.ModuleName, version.AppName, types.ModuleName,
),
),
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Flags().Set(flags.FlagFrom, args[0])
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

coin, err := sdk.ParseCoinNormalized(args[1])
if err != nil {
return err
}

dripAmount, okay := sdk.NewIntFromString(args[2])
if !okay || !dripAmount.IsPositive() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s is not a valid drip rate", args[2])
}

msg := types.NewMsgAirDrop(clientCtx.GetFromAddress(), coin, dripAmount)
if err := msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
25 changes: 25 additions & 0 deletions x/airdrop/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package airdrop

import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/airdrop/keeper"
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
)

func NewHandler(k keeper.Keeper) sdk.Handler {
msgServer := keeper.NewMsgServerImpl(k)

return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())

switch msg := msg.(type) {
case *types.MsgAirDrop:
res, err := msgServer.AirDrop(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized airdrop message type: %T", msg)
}
}
}
Loading

0 comments on commit a59a3d4

Please sign in to comment.