Skip to content

Commit a59a3d4

Browse files
authored
feat: initial airdrop support (cosmos#94)
1 parent 1227bf3 commit a59a3d4

31 files changed

+5510
-144
lines changed

docs/core/proto-docs.md

+429-144
Large diffs are not rendered by default.
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
syntax = "proto3";
2+
package cosmos.airdrop.v1beta1;
3+
4+
import "gogoproto/gogo.proto";
5+
import "cosmos_proto/cosmos.proto";
6+
import "cosmos/base/v1beta1/coin.proto";
7+
8+
option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";
9+
10+
// Fund defines a structure for a fund that is being distributed to network stakers
11+
message Fund {
12+
option (gogoproto.equal) = true;
13+
14+
// The amount of fund that is remaining
15+
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.moretags) = "yaml:\"amount\""];
16+
17+
// The amount of funds that should be removed from the fund every block
18+
string drip_amount = 2 [
19+
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
20+
(gogoproto.nullable) = false,
21+
(gogoproto.moretags) = "yaml:\"drip_amount\""
22+
];
23+
}
24+
25+
// ActiveFund describes an active fund on the network
26+
message ActiveFund {
27+
string sender = 1 [(gogoproto.moretags) = "yaml:\"sender\""];
28+
Fund fund = 2 [(gogoproto.moretags) = "yaml:\"blocks_remaining\""];
29+
}
30+
31+
// Params define the module parameters
32+
message Params {
33+
// The set of addresses which are allowed to create are drop funds
34+
repeated string allow_list = 1 [(gogoproto.moretags) = "yaml:\"allow_list\""];
35+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
syntax = "proto3";
2+
package cosmos.airdrop.v1beta1;
3+
4+
import "gogoproto/gogo.proto";
5+
import "cosmos/airdrop/v1beta1/airdrop.proto";
6+
7+
option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";
8+
9+
// GenesisState defines the bank module's genesis state.
10+
message GenesisState {
11+
// params defines all the parameters of the module.
12+
Params params = 1 [(gogoproto.nullable) = false];
13+
14+
// balances is an array containing the balances of all the accounts.
15+
repeated ActiveFund funds = 2 [(gogoproto.nullable) = false];
16+
}
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
syntax = "proto3";
2+
package cosmos.airdrop.v1beta1;
3+
4+
import "gogoproto/gogo.proto";
5+
import "google/api/annotations.proto";
6+
import "cosmos/base/query/v1beta1/pagination.proto";
7+
import "cosmos/airdrop/v1beta1/airdrop.proto";
8+
9+
option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";
10+
11+
// Query defines the gRPC querier service.
12+
service Query {
13+
14+
// AllFunds queries all active airdrop funds
15+
rpc AllFunds(QueryAllFundsRequest) returns (QueryAllFundsResponse) {
16+
option (google.api.http).get = "/cosmos/airdrop/v1beta1/funds";
17+
}
18+
19+
// Fund queries a specific airdrop fund
20+
rpc Fund(QueryFundRequest) returns (QueryFundResponse) {
21+
option (google.api.http).get = "/cosmos/airdrop/v1beta1/funds/{address}";
22+
}
23+
24+
// Params queries the current modules parameters
25+
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
26+
option (google.api.http).get = "/cosmos/airdrop/v1beta1/params";
27+
}
28+
}
29+
30+
// QueryAllFundsRequest defines the request for querying all the funds
31+
message QueryAllFundsRequest {
32+
cosmos.base.query.v1beta1.PageRequest pagination = 1;
33+
}
34+
35+
// QueryAllFundsResponse defines the response for querying all the funds
36+
message QueryAllFundsResponse {
37+
repeated cosmos.airdrop.v1beta1.ActiveFund funds = 1;
38+
cosmos.base.query.v1beta1.PageResponse pagination = 2;
39+
}
40+
41+
// QueryFundRequest defines the request for querying a specific fund
42+
message QueryFundRequest {
43+
string address = 1 [(gogoproto.nullable) = true];
44+
}
45+
46+
// QueryFundResponse defines the response for querying a specific fund
47+
message QueryFundResponse {
48+
cosmos.airdrop.v1beta1.Fund fund = 1;
49+
}
50+
51+
// QueryParamsRequest defines the request type for querying x/airdrop parameters.
52+
message QueryParamsRequest {}
53+
54+
// QueryParamsResponse defines the response type for querying x/airdrop parameters.
55+
message QueryParamsResponse {
56+
Params params = 1 [(gogoproto.nullable) = false];
57+
}

proto/cosmos/airdrop/v1beta1/tx.proto

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
syntax = "proto3";
2+
package cosmos.airdrop.v1beta1;
3+
4+
import "gogoproto/gogo.proto";
5+
import "cosmos/airdrop/v1beta1/airdrop.proto";
6+
7+
option go_package = "github.com/cosmos/cosmos-sdk/x/airdrop/types";
8+
9+
// Msg defines the airdrop Msg service.
10+
service Msg {
11+
12+
// AirDrop defines a method for sending coins to the airdrop module for distribution
13+
rpc AirDrop(MsgAirDrop) returns (MsgAirDropResponse);
14+
}
15+
16+
// MsgAirDrop represents a message to create an airdrop fund for distribution
17+
message MsgAirDrop {
18+
option (gogoproto.equal) = false;
19+
option (gogoproto.goproto_getters) = false;
20+
21+
string from_address = 1 [(gogoproto.moretags) = "yaml:\"from_address\""];
22+
Fund fund = 2 [(gogoproto.moretags) = "yaml:\"fund\""];
23+
}
24+
25+
// MsgAirDropResponse represents a message for the response
26+
message MsgAirDropResponse {}

simapp/app.go

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package simapp
22

33
import (
4+
airdroptypes "github.com/cosmos/cosmos-sdk/x/airdrop/types"
45
"io"
56
"net/http"
67
"os"
@@ -30,6 +31,7 @@ import (
3031
sdk "github.com/cosmos/cosmos-sdk/types"
3132
"github.com/cosmos/cosmos-sdk/types/module"
3233
"github.com/cosmos/cosmos-sdk/version"
34+
airdropkeeper "github.com/cosmos/cosmos-sdk/x/airdrop/keeper"
3335
"github.com/cosmos/cosmos-sdk/x/auth"
3436
"github.com/cosmos/cosmos-sdk/x/auth/ante"
3537
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
@@ -125,6 +127,7 @@ var (
125127
maccPerms = map[string][]string{
126128
authtypes.FeeCollectorName: nil,
127129
distrtypes.ModuleName: nil,
130+
airdroptypes.ModuleName: nil,
128131
minttypes.ModuleName: {authtypes.Minter},
129132
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
130133
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
@@ -169,6 +172,7 @@ type SimApp struct {
169172
IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
170173
EvidenceKeeper evidencekeeper.Keeper
171174
TransferKeeper ibctransferkeeper.Keeper
175+
AirdropKeeper *airdropkeeper.Keeper
172176

173177
// make scoped keepers public for test purposes
174178
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
@@ -213,6 +217,7 @@ func NewSimApp(
213217
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey,
214218
govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey,
215219
evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey,
220+
airdroptypes.StoreKey,
216221
)
217222
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
218223
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
@@ -296,6 +301,11 @@ func NewSimApp(
296301
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper,
297302
app.AccountKeeper, app.BankKeeper, scopedTransferKeeper,
298303
)
304+
app.AirdropKeeper = airdropkeeper.NewKeeper(
305+
appCodec, keys[airdroptypes.StoreKey], app.GetSubspace(airdroptypes.ModuleName), app.BankKeeper,
306+
authtypes.FeeCollectorName,
307+
)
308+
299309
transferModule := transfer.NewAppModule(app.TransferKeeper)
300310

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

606617
return paramsKeeper
607618
}

x/airdrop/abci.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package airdrop
2+
3+
import (
4+
"github.com/cosmos/cosmos-sdk/telemetry"
5+
sdk "github.com/cosmos/cosmos-sdk/types"
6+
"github.com/cosmos/cosmos-sdk/x/airdrop/keeper"
7+
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
8+
"time"
9+
)
10+
11+
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
12+
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
13+
14+
_, err := k.DripAllFunds(ctx)
15+
if err != nil {
16+
ctx.Logger().Error("Unable to perform airdrop drip", "err", err.Error())
17+
}
18+
}

x/airdrop/client/cli/query.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"github.com/cosmos/cosmos-sdk/client"
6+
"github.com/cosmos/cosmos-sdk/client/flags"
7+
"github.com/cosmos/cosmos-sdk/version"
8+
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
9+
"github.com/spf13/cobra"
10+
"strings"
11+
)
12+
13+
func GetQueryCmd() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: types.ModuleName,
16+
Short: "Querying commands for the bank module",
17+
DisableFlagParsing: true,
18+
SuggestionsMinimumDistance: 2,
19+
RunE: client.ValidateCmd,
20+
}
21+
22+
cmd.AddCommand(
23+
GetFunds(),
24+
)
25+
26+
return cmd
27+
}
28+
29+
func GetFunds() *cobra.Command {
30+
cmd := &cobra.Command{
31+
Use: "funds",
32+
Short: "Query the currently active airdrop funds",
33+
Long: strings.TrimSpace(
34+
fmt.Sprintf(`Query the currently active airdrop funds.
35+
36+
Example:
37+
$ %s query %s funds
38+
`,
39+
version.AppName, types.ModuleName,
40+
),
41+
),
42+
Args: cobra.ExactArgs(0),
43+
RunE: func(cmd *cobra.Command, args []string) error {
44+
clientCtx, err := client.GetClientQueryContext(cmd)
45+
if err != nil {
46+
return err
47+
}
48+
49+
queryClient := types.NewQueryClient(clientCtx)
50+
51+
pageReq, err := client.ReadPageRequest(cmd.Flags())
52+
if err != nil {
53+
return err
54+
}
55+
56+
params := types.NewQueryAllFundsRequest(pageReq)
57+
58+
res, err := queryClient.AllFunds(cmd.Context(), params)
59+
if err != nil {
60+
return err
61+
}
62+
return clientCtx.PrintProto(res)
63+
},
64+
}
65+
66+
flags.AddQueryFlagsToCmd(cmd)
67+
flags.AddPaginationFlagsToCmd(cmd, "all funds")
68+
69+
return cmd
70+
}

x/airdrop/client/cli/tx.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"github.com/cosmos/cosmos-sdk/client"
6+
"github.com/cosmos/cosmos-sdk/client/flags"
7+
"github.com/cosmos/cosmos-sdk/client/tx"
8+
sdk "github.com/cosmos/cosmos-sdk/types"
9+
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
10+
"github.com/cosmos/cosmos-sdk/version"
11+
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
12+
"github.com/spf13/cobra"
13+
"strings"
14+
)
15+
16+
func GetTxCmd() *cobra.Command {
17+
txCmd := &cobra.Command{
18+
Use: types.ModuleName,
19+
Short: "Airdrop transaction subcommands",
20+
DisableFlagParsing: true,
21+
SuggestionsMinimumDistance: 2,
22+
RunE: client.ValidateCmd,
23+
}
24+
25+
txCmd.AddCommand(NewCreateTxCmd())
26+
27+
return txCmd
28+
}
29+
30+
func NewCreateTxCmd() *cobra.Command {
31+
cmd := &cobra.Command{
32+
Use: "create [from_key_or_address] [amount] [drip_amount]",
33+
Short: `Creates a new airdrop fund. Note, the '--from' flag is
34+
ignored as it is implied from [from_key_or_address].`,
35+
Long: strings.TrimSpace(
36+
fmt.Sprintf(`Creates a new airdrop fund.
37+
38+
When creating an airdrop fund, the sender transfers a specified amount of funds to the block chain along with
39+
the drip amount. Every block up to a maximum of drip_amount of funds are added to the rewards of the current block.
40+
The maximum duration of the airdrop is therefore calculated as amount / drip_amount blocks.
41+
42+
Example:
43+
$ %s tx %s create [address] [amount] [drip_amount]
44+
$ %s tx %s create fetch1se8mjg4mtvy8zaf4599m84xz4atn59dlqmwhnl 200000000000000000000afet 2000000000000000000
45+
`,
46+
version.AppName, types.ModuleName, version.AppName, types.ModuleName,
47+
),
48+
),
49+
Args: cobra.ExactArgs(3),
50+
RunE: func(cmd *cobra.Command, args []string) error {
51+
cmd.Flags().Set(flags.FlagFrom, args[0])
52+
clientCtx, err := client.GetClientTxContext(cmd)
53+
if err != nil {
54+
return err
55+
}
56+
57+
coin, err := sdk.ParseCoinNormalized(args[1])
58+
if err != nil {
59+
return err
60+
}
61+
62+
dripAmount, okay := sdk.NewIntFromString(args[2])
63+
if !okay || !dripAmount.IsPositive() {
64+
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s is not a valid drip rate", args[2])
65+
}
66+
67+
msg := types.NewMsgAirDrop(clientCtx.GetFromAddress(), coin, dripAmount)
68+
if err := msg.ValidateBasic(); err != nil {
69+
return err
70+
}
71+
72+
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
73+
},
74+
}
75+
76+
flags.AddTxFlagsToCmd(cmd)
77+
78+
return cmd
79+
}

x/airdrop/handler.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package airdrop
2+
3+
import (
4+
sdk "github.com/cosmos/cosmos-sdk/types"
5+
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
6+
"github.com/cosmos/cosmos-sdk/x/airdrop/keeper"
7+
"github.com/cosmos/cosmos-sdk/x/airdrop/types"
8+
)
9+
10+
func NewHandler(k keeper.Keeper) sdk.Handler {
11+
msgServer := keeper.NewMsgServerImpl(k)
12+
13+
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
14+
ctx = ctx.WithEventManager(sdk.NewEventManager())
15+
16+
switch msg := msg.(type) {
17+
case *types.MsgAirDrop:
18+
res, err := msgServer.AirDrop(sdk.WrapSDKContext(ctx), msg)
19+
return sdk.WrapServiceResult(ctx, res, err)
20+
21+
default:
22+
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized airdrop message type: %T", msg)
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)