From 1e0f366008f31688952720c98eebdc552d1a6d72 Mon Sep 17 00:00:00 2001 From: Petr Ivanov Date: Mon, 12 Feb 2024 23:53:57 +0400 Subject: [PATCH] [v1.7.2] Liquid vesting module (#275) * scaffold liquidvesting module * fix linter * fix linter * outline liquidvesting proto interface * liquidate tx implementation * increase test coverage for liquidvesting module * implement liquid denom queries * add tests for liquidate handler and some other stuff * refactor convert into vesting account * redeem handler first iteration * fix build * redeem tests and bug fixes * adds erc20 checks * add review fixes * add review fixes pt 2 * minor review fixes * add module param minimumLiquidationAmount * add auto erc20 conversion during redeem * fix linter * disable token pair after it is went down to zero total supply * add enable check on token pair * fix lint * fix lint * chore: add upgrade handler * change default param * change default param * fix: cli query denoms for liquid vesting * fix: msg_server reduce vesting periods * chore: add force conversion into erc20 tokens on liquidation * chore: fix tests * chore(liquidation): update store key for param * chore: inlcude escrowed liquid vesting balance into total locked stats --------- Co-authored-by: Petr Ivanov Co-authored-by: Yuri Surbashev Co-authored-by: Evgeniy Abramov --- Makefile | 2 +- app/app.go | 36 +- app/upgrades/v1.7.2/constants.go | 6 + app/upgrades/v1.7.2/upgrades.go | 20 + proto/haqq/liquidvesting/v1/genesis.proto | 20 + .../haqq/liquidvesting/v1/liquidvesting.proto | 33 + proto/haqq/liquidvesting/v1/query.proto | 55 + proto/haqq/liquidvesting/v1/tx.proto | 48 + tests/e2e/upgrade/manager.go | 1 - x/liquidvesting/client/cli/query.go | 95 ++ x/liquidvesting/client/cli/tx.go | 113 ++ x/liquidvesting/genesis.go | 21 + x/liquidvesting/handler.go | 26 + x/liquidvesting/keeper/denom.go | 100 ++ x/liquidvesting/keeper/grpc_query.go | 57 + x/liquidvesting/keeper/keeper.go | 45 + x/liquidvesting/keeper/msg_server.go | 284 +++++ x/liquidvesting/keeper/msg_server_test.go | 374 ++++++ x/liquidvesting/keeper/params.go | 17 + x/liquidvesting/keeper/params_test.go | 17 + x/liquidvesting/keeper/setup_test.go | 131 ++ x/liquidvesting/module.go | 163 +++ x/liquidvesting/types/codec.go | 15 + x/liquidvesting/types/denom.go | 18 + x/liquidvesting/types/denom_test.go | 57 + x/liquidvesting/types/errors.go | 13 + x/liquidvesting/types/genesis.go | 21 + x/liquidvesting/types/genesis.pb.go | 496 ++++++++ x/liquidvesting/types/interfaces.go | 67 + x/liquidvesting/types/keys.go | 21 + x/liquidvesting/types/liquidvesting.pb.go | 615 ++++++++++ x/liquidvesting/types/msg.go | 90 ++ x/liquidvesting/types/msg_test.go | 1 + x/liquidvesting/types/params.go | 56 + x/liquidvesting/types/query.pb.go | 1062 ++++++++++++++++ x/liquidvesting/types/query.pb.gw.go | 254 ++++ x/liquidvesting/types/schedule.go | 102 ++ x/liquidvesting/types/schedule_test.go | 321 +++++ x/liquidvesting/types/tx.pb.go | 1086 +++++++++++++++++ x/liquidvesting/types/tx.pb.gw.go | 254 ++++ x/vesting/client/cli/tx.go | 2 +- x/vesting/keeper/grpc_query.go | 11 + x/vesting/keeper/msg_server.go | 81 +- x/vesting/keeper/schedule.go | 95 ++ x/vesting/types/errors.go | 1 + x/vesting/types/interfaces.go | 2 + 46 files changed, 6330 insertions(+), 75 deletions(-) create mode 100644 app/upgrades/v1.7.2/constants.go create mode 100644 app/upgrades/v1.7.2/upgrades.go create mode 100644 proto/haqq/liquidvesting/v1/genesis.proto create mode 100644 proto/haqq/liquidvesting/v1/liquidvesting.proto create mode 100644 proto/haqq/liquidvesting/v1/query.proto create mode 100644 proto/haqq/liquidvesting/v1/tx.proto create mode 100644 x/liquidvesting/client/cli/query.go create mode 100644 x/liquidvesting/client/cli/tx.go create mode 100644 x/liquidvesting/genesis.go create mode 100644 x/liquidvesting/handler.go create mode 100644 x/liquidvesting/keeper/denom.go create mode 100644 x/liquidvesting/keeper/grpc_query.go create mode 100644 x/liquidvesting/keeper/keeper.go create mode 100644 x/liquidvesting/keeper/msg_server.go create mode 100644 x/liquidvesting/keeper/msg_server_test.go create mode 100644 x/liquidvesting/keeper/params.go create mode 100644 x/liquidvesting/keeper/params_test.go create mode 100644 x/liquidvesting/keeper/setup_test.go create mode 100644 x/liquidvesting/module.go create mode 100644 x/liquidvesting/types/codec.go create mode 100644 x/liquidvesting/types/denom.go create mode 100644 x/liquidvesting/types/denom_test.go create mode 100644 x/liquidvesting/types/errors.go create mode 100644 x/liquidvesting/types/genesis.go create mode 100644 x/liquidvesting/types/genesis.pb.go create mode 100644 x/liquidvesting/types/interfaces.go create mode 100644 x/liquidvesting/types/keys.go create mode 100644 x/liquidvesting/types/liquidvesting.pb.go create mode 100644 x/liquidvesting/types/msg.go create mode 100644 x/liquidvesting/types/msg_test.go create mode 100644 x/liquidvesting/types/params.go create mode 100644 x/liquidvesting/types/query.pb.go create mode 100644 x/liquidvesting/types/query.pb.gw.go create mode 100644 x/liquidvesting/types/schedule.go create mode 100644 x/liquidvesting/types/schedule_test.go create mode 100644 x/liquidvesting/types/tx.pb.go create mode 100644 x/liquidvesting/types/tx.pb.gw.go create mode 100644 x/vesting/keeper/schedule.go diff --git a/Makefile b/Makefile index 39cc10970..13bbde699 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DIFF_TAG=$(shell git rev-list --tags="v*" --max-count=1 --not $(shell git rev-li DEFAULT_TAG=$(shell git rev-list --tags="v*" --max-count=1) # VERSION ?= $(shell echo $(shell git describe --tags $(or $(DIFF_TAG), $(DEFAULT_TAG))) | sed 's/^v//') -VERSION := "1.7.1" +VERSION := "1.7.2" CBFTVERSION := $(shell go list -m github.com/cometbft/cometbft | sed 's:.* ::') COMMIT := $(shell git log -1 --format='%H') LEDGER_ENABLED ?= true diff --git a/app/app.go b/app/app.go index 6ea119c76..dd6ac3004 100644 --- a/app/app.go +++ b/app/app.go @@ -146,6 +146,9 @@ import ( erc20client "github.com/haqq-network/haqq/x/erc20/client" erc20keeper "github.com/haqq-network/haqq/x/erc20/keeper" erc20types "github.com/haqq-network/haqq/x/erc20/types" + "github.com/haqq-network/haqq/x/liquidvesting" + liquidvestingkeeper "github.com/haqq-network/haqq/x/liquidvesting/keeper" + liquidvestingtypes "github.com/haqq-network/haqq/x/liquidvesting/types" "github.com/haqq-network/haqq/x/vesting" vestingkeeper "github.com/haqq-network/haqq/x/vesting/keeper" vestingtypes "github.com/haqq-network/haqq/x/vesting/types" @@ -157,6 +160,7 @@ import ( v164 "github.com/haqq-network/haqq/app/upgrades/v1.6.4" v170 "github.com/haqq-network/haqq/app/upgrades/v1.7.0" v171 "github.com/haqq-network/haqq/app/upgrades/v1.7.1" + v172 "github.com/haqq-network/haqq/app/upgrades/v1.7.2" // NOTE: override ICS20 keeper to support IBC transfers of ERC20 tokens "github.com/haqq-network/haqq/x/ibc/transfer" @@ -232,6 +236,7 @@ var ( erc20.AppModuleBasic{}, epochs.AppModuleBasic{}, consensus.AppModuleBasic{}, + liquidvesting.AppModuleBasic{}, ) // module account permissions @@ -247,6 +252,7 @@ var ( erc20types.ModuleName: {authtypes.Minter, authtypes.Burner}, coinomicstypes.ModuleName: {authtypes.Minter}, vestingtypes.ModuleName: nil, // Add vesting module account + liquidvestingtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, } // module accounts that are allowed to receive tokens @@ -307,9 +313,10 @@ type Haqq struct { FeeMarketKeeper feemarketkeeper.Keeper // Evmos keepers - Erc20Keeper erc20keeper.Keeper - EpochsKeeper epochskeeper.Keeper - VestingKeeper vestingkeeper.Keeper + Erc20Keeper erc20keeper.Keeper + EpochsKeeper epochskeeper.Keeper + VestingKeeper vestingkeeper.Keeper + LiquidVestingKeeper liquidvestingkeeper.Keeper // Haqq keepers CoinomicsKeeper coinomicskeeper.Keeper @@ -384,6 +391,7 @@ func NewHaqq( epochstypes.StoreKey, vestingtypes.StoreKey, // haqq keys coinomicstypes.StoreKey, + liquidvestingtypes.StoreKey, ) // Add the EVM transient store key @@ -528,6 +536,11 @@ func NewHaqq( app.AccountKeeper, app.BankKeeper, app.EvmKeeper, app.StakingKeeper, ) + app.LiquidVestingKeeper = liquidvestingkeeper.NewKeeper( + keys[vestingtypes.StoreKey], appCodec, app.GetSubspace(liquidvestingtypes.ModuleName), + app.AccountKeeper, app.BankKeeper, app.Erc20Keeper, app.VestingKeeper, + ) + epochsKeeper := epochskeeper.NewKeeper(appCodec, keys[epochstypes.StoreKey]) app.EpochsKeeper = *epochsKeeper.SetHooks( epochskeeper.NewMultiEpochHooks( @@ -649,6 +662,7 @@ func NewHaqq( erc20.NewAppModule(app.Erc20Keeper, app.AccountKeeper, app.GetSubspace(erc20types.ModuleName)), epochs.NewAppModule(appCodec, app.EpochsKeeper), vesting.NewAppModule(app.VestingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + liquidvesting.NewAppModule(appCodec, app.LiquidVestingKeeper, app.AccountKeeper, app.BankKeeper, app.Erc20Keeper), // Haqq app modules coinomics.NewAppModule(app.CoinomicsKeeper, app.AccountKeeper, app.StakingKeeper), @@ -687,6 +701,7 @@ func NewHaqq( erc20types.ModuleName, coinomicstypes.ModuleName, consensusparamtypes.ModuleName, + liquidvestingtypes.ModuleName, ) // NOTE: fee market module must go last in order to retrieve the block gas used. @@ -721,6 +736,7 @@ func NewHaqq( // Haqq modules coinomicstypes.ModuleName, consensusparamtypes.ModuleName, + liquidvestingtypes.ModuleName, ) // NOTE: The genutils module must occur after staking so that pools are @@ -755,6 +771,7 @@ func NewHaqq( upgradetypes.ModuleName, // Evmos modules vestingtypes.ModuleName, + liquidvestingtypes.ModuleName, coinomicstypes.ModuleName, erc20types.ModuleName, epochstypes.ModuleName, @@ -1118,6 +1135,7 @@ func initParamsKeeper( paramsKeeper.Subspace(erc20types.ModuleName) // haqq subspaces paramsKeeper.Subspace(coinomicstypes.ModuleName) + paramsKeeper.Subspace(liquidvestingtypes.ModuleName) return paramsKeeper } @@ -1196,6 +1214,12 @@ func (app *Haqq) setupUpgradeHandlers() { v171.CreateUpgradeHandler(app.mm, app.configurator), ) + // v1.7.2 Add Liquid Vesting Module + app.UpgradeKeeper.SetUpgradeHandler( + v172.UpgradeName, + v172.CreateUpgradeHandler(app.mm, app.configurator), + ) + // When a planned update height is reached, the old binary will panic // writing on disk the height and name of the update that triggered it // This will read that value, and execute the preparations for the upgrade. @@ -1224,6 +1248,12 @@ func (app *Haqq) setupUpgradeHandlers() { crisistypes.ModuleName, }, } + case v172.UpgradeName: + storeUpgrades = &storetypes.StoreUpgrades{ + Added: []string{ + liquidvestingtypes.ModuleName, + }, + } } if storeUpgrades != nil { diff --git a/app/upgrades/v1.7.2/constants.go b/app/upgrades/v1.7.2/constants.go new file mode 100644 index 000000000..a7508f515 --- /dev/null +++ b/app/upgrades/v1.7.2/constants.go @@ -0,0 +1,6 @@ +package v172 + +const ( + // UpgradeName is the shared upgrade plan name for mainnet and testnet + UpgradeName = "v1.7.2" +) diff --git a/app/upgrades/v1.7.2/upgrades.go b/app/upgrades/v1.7.2/upgrades.go new file mode 100644 index 000000000..1389920e5 --- /dev/null +++ b/app/upgrades/v1.7.2/upgrades.go @@ -0,0 +1,20 @@ +package v172 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" +) + +// CreateUpgradeHandler creates an SDK upgrade handler for v1.7.2 +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + logger := ctx.Logger() + logger.Info("run migration v1.7.2") + + return mm.RunMigrations(ctx, configurator, vm) + } +} diff --git a/proto/haqq/liquidvesting/v1/genesis.proto b/proto/haqq/liquidvesting/v1/genesis.proto new file mode 100644 index 000000000..a6c4ec774 --- /dev/null +++ b/proto/haqq/liquidvesting/v1/genesis.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package haqq.liquidvesting.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/haqq-network/haqq/x/liquidvesting/types"; + +// GenesisState defines the liquidvesting module's genesis state. +message GenesisState { + // params defines all the paramaters of the module. + Params params = 1 [ (gogoproto.nullable) = false ]; +} + +// Params holds parameters for the liquidvesting module. +message Params { + string minimum_liquidation_amount = 1 [ + (gogoproto.customtype) = "cosmossdk.io/math.Int", + (gogoproto.nullable) = false + ]; +} diff --git a/proto/haqq/liquidvesting/v1/liquidvesting.proto b/proto/haqq/liquidvesting/v1/liquidvesting.proto new file mode 100644 index 000000000..393191f33 --- /dev/null +++ b/proto/haqq/liquidvesting/v1/liquidvesting.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; +package haqq.liquidvesting.v1; + +import "amino/amino.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/vesting/v1beta1/vesting.proto"; +import "google/protobuf/timestamp.proto"; +import "cosmos/base/v1beta1/coin.proto"; + + +option go_package = "github.com/haqq-network/haqq/x/liquidvesting/types"; + +// Denom represents liquid token bonded to some specific vesting schedule +message Denom { + // base_denom main identifier for the denom, used to query it from store. + string base_denom = 1; + // display_denom identifier used for display name for broad audience + string display_denom = 2; + // original_denom which liquid denom derived from + string original_denom = 3; + // start date + google.protobuf.Timestamp start_time = 4 + [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; + // end_date + google.protobuf.Timestamp end_time = 5 + [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ]; + // lockup periods + repeated cosmos.vesting.v1beta1.Period lockup_periods = 6 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = + "github.com/cosmos/cosmos-sdk/x/auth/vesting/types.Periods" + ]; +} \ No newline at end of file diff --git a/proto/haqq/liquidvesting/v1/query.proto b/proto/haqq/liquidvesting/v1/query.proto new file mode 100644 index 000000000..8e38ac221 --- /dev/null +++ b/proto/haqq/liquidvesting/v1/query.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; +package haqq.liquidvesting.v1; + +import "amino/amino.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "haqq/liquidvesting/v1/liquidvesting.proto"; + +option go_package = "github.com/haqq-network/haqq/x/liquidvesting/types"; + +// Query defines the gRPC querier service. +service Query { + // Denom queries liquid vesting token info by denom + rpc Denom(QueryDenomRequest) returns (QueryDenomResponse) { + option (google.api.http).get = "/haqq/liquidvesting/v1/denom"; + }; + // Denoms queries liquid vesting tokens info + rpc Denoms(QueryDenomsRequest) returns (QueryDenomsResponse) { + option (google.api.http).get = "/haqq/liquidvesting/v1/denoms"; + }; +} + +// QueryDenomRequest is request fo Denom rpc method +message QueryDenomRequest { + // denom is liquidated vesting token + string denom = 1; +} + +// QueryDenomResponse is response for Denom rpc method +message QueryDenomResponse { + // denom is liquidated vesting token + Denom denom = 1 [ + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true + ]; +} + +// QueryDenomsRequest is request for Denoms rpc method +message QueryDenomsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryDenomsResponse is response for Denoms rpc method +message QueryDenomsResponse { + // denoms are liquidated vesting tokens + repeated Denom denoms = 1 [ + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true + ]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/proto/haqq/liquidvesting/v1/tx.proto b/proto/haqq/liquidvesting/v1/tx.proto new file mode 100644 index 000000000..c998735e7 --- /dev/null +++ b/proto/haqq/liquidvesting/v1/tx.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; +package haqq.liquidvesting.v1; + +import "amino/amino.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/base/v1beta1/coin.proto"; + + +option go_package = "github.com/haqq-network/haqq/x/liquidvesting/types"; + +// Msg defines the Msg service. +service Msg { + // Liquidate transforms specified amount of tokens locked on vesting account into a new liquid token + rpc Liquidate(MsgLiquidate) returns (MsgLiquidateResponse) { + option (google.api.http).post = "/haqq/liquidvesting/v1/tx/liquidate"; + }; + + // Redeem burns liquid token and deposits corresponding amount of vesting token to the specified account + rpc Redeem (MsgRedeem) returns (MsgRedeemResponse) { + option (google.api.http).post = "/haqq/liquidvesting/v1/tx/redeem"; + }; +} + +// MsgLiquidate represents message to liquidate arbitrary amount of tokens locked in vesting +message MsgLiquidate { + // account for liquidation of locked vesting tokens + string liquidate_from = 1; + // account to send resulted liquid token + string liquidate_to = 2; + // amount of tokens subject for liquidation + cosmos.base.v1beta1.Coin amount = 3 [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true]; +} + +// MsgLiquidateResponse defines the Msg/Liquidate response type +message MsgLiquidateResponse {} + +// MsgLiquidate represents message to redeem arbitrary amount of liquid vesting tokens +message MsgRedeem { + string redeem_from = 1; + // destination address for vesting tokens + string redeem_to = 2; + // amount of vesting tokens to redeem from liquidation module + cosmos.base.v1beta1.Coin amount = 3 [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true]; +} + +// MsgRedeemResponse defines the Msg/Redeem response type +message MsgRedeemResponse {} \ No newline at end of file diff --git a/tests/e2e/upgrade/manager.go b/tests/e2e/upgrade/manager.go index bb53d084c..ad64c75f4 100644 --- a/tests/e2e/upgrade/manager.go +++ b/tests/e2e/upgrade/manager.go @@ -132,7 +132,6 @@ func (m *Manager) RunNode(node *Node) error { return nil }, ) - if err != nil { stdOut, stdErr, _ := m.GetLogs(resource.Container.ID) return fmt.Errorf( diff --git a/x/liquidvesting/client/cli/query.go b/x/liquidvesting/client/cli/query.go new file mode 100644 index 000000000..9fab4577e --- /dev/null +++ b/x/liquidvesting/client/cli/query.go @@ -0,0 +1,95 @@ +package cli + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/haqq-network/haqq/x/liquidvesting/types" + "github.com/spf13/cobra" +) + +// GetQueryCmd returns the parent command for all liquidvesting CLI query commands. +func GetQueryCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the liquidvesting module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand(GetDenomCmd()) + cmd.AddCommand(GetDenomsCmd()) + + return cmd +} + +// GetDenomCmd returns command for querying liquid denom +func GetDenomCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "denom DENOM", + Short: "Gets information about denom of liquid vesting token", + Long: "Gets information about denom of liquid vesting token", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryDenomRequest{ + Denom: args[0], + } + + res, err := queryClient.Denom(context.Background(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetDenomsCmd return command for querying all liquid denoms +func GetDenomsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "denoms", + Short: "Gets information about all denoms of liquid vesting tokens", + Long: "Gets information about all denoms of liquid vesting tokens", + Args: cobra.NoArgs, + 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 + } + + req := &types.QueryDenomsRequest{ + Pagination: pageReq, + } + + res, err := queryClient.Denoms(context.Background(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/liquidvesting/client/cli/tx.go b/x/liquidvesting/client/cli/tx.go new file mode 100644 index 000000000..90f425e49 --- /dev/null +++ b/x/liquidvesting/client/cli/tx.go @@ -0,0 +1,113 @@ +package cli + +import ( + "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" + "github.com/haqq-network/haqq/x/liquidvesting/types" + "github.com/spf13/cobra" +) + +// NewTxCmd returns a root CLI command handler for certain modules/liquidvesting +// transaction commands. +func NewTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Liquidvesting transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + NewMsgLiquidateCmd(), + NewMsgRedeemCmd(), + ) + + return txCmd +} + +// NewMsgLiquidateCmd returns command for composing MsgLiquidate and sending it to blockchain +func NewMsgLiquidateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "liquidate AMOUNT [RECEIVER]", + Short: "Liquidate locked tokens from vesting account into erc20 token", + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + coin, err := sdk.ParseCoinNormalized(args[0]) + if err != nil { + return err + } + + var liquidateTo sdk.AccAddress + liquidateFrom := cliCtx.GetFromAddress() + + if len(args) == 2 { + liquidateTo, err = sdk.AccAddressFromBech32(args[1]) + if err != nil { + return err + } + } else { + liquidateTo = liquidateFrom + } + + msg := types.NewMsgLiquidate(liquidateFrom, liquidateTo, coin) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// NewMsgRedeemCmd returns command for composing MsgRedeem and sending it to blockchain +func NewMsgRedeemCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "redeem AMOUNT [RECEIVER]", + Short: "Redeem liquid token into locked vesting tokens", + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + coin, err := sdk.ParseCoinNormalized(args[0]) + if err != nil { + return err + } + + var redeemTo sdk.AccAddress + redeemFrom := cliCtx.GetFromAddress() + + if len(args) == 2 { + redeemTo, err = sdk.AccAddressFromBech32(args[1]) + if err != nil { + return err + } + } else { + redeemTo = redeemFrom + } + + msg := types.NewMsgRedeem(redeemFrom, redeemTo, coin) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/x/liquidvesting/genesis.go b/x/liquidvesting/genesis.go new file mode 100644 index 000000000..aa0f9177e --- /dev/null +++ b/x/liquidvesting/genesis.go @@ -0,0 +1,21 @@ +package liquidvesting + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/haqq-network/haqq/x/liquidvesting/keeper" + "github.com/haqq-network/haqq/x/liquidvesting/types" +) + +// InitGenesis import module genesis +func InitGenesis(ctx sdk.Context, k keeper.Keeper, data types.GenesisState) { + // Set genesis state + params := data.Params + k.SetParams(ctx, params) +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + return &types.GenesisState{ + Params: k.GetParams(ctx), + } +} diff --git a/x/liquidvesting/handler.go b/x/liquidvesting/handler.go new file mode 100644 index 000000000..b57ee10db --- /dev/null +++ b/x/liquidvesting/handler.go @@ -0,0 +1,26 @@ +package liquidvesting + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/haqq-network/haqq/x/liquidvesting/types" +) + +// NewHandler defines the vesting module handler instance +func NewHandler(server types.MsgServer) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case *types.MsgLiquidate: + res, err := server.Liquidate(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgRedeem: + res, err := server.Redeem(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) + default: + return nil, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) + } + } +} diff --git a/x/liquidvesting/keeper/denom.go b/x/liquidvesting/keeper/denom.go new file mode 100644 index 000000000..6fa9fa5d7 --- /dev/null +++ b/x/liquidvesting/keeper/denom.go @@ -0,0 +1,100 @@ +package keeper + +import ( + "encoding/binary" + "time" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/haqq-network/haqq/x/liquidvesting/types" +) + +// CreateDenom creates new liquid denom and stores it +func (k Keeper) CreateDenom( + ctx sdk.Context, + originalDenom string, + startTime int64, + periods sdkvesting.Periods, +) (types.Denom, error) { + counter := k.GetDenomCounter(ctx) + + denom := types.Denom{ + StartTime: time.Unix(startTime, 0), + LockupPeriods: periods, + OriginalDenom: originalDenom, + EndTime: time.Unix(startTime+periods.TotalLength(), 0), + BaseDenom: types.DenomBaseNameFromID(counter), + DisplayDenom: types.DenomDisplayNameFromID(counter), + } + + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomKeyPrefix) + appendedValue := k.cdc.MustMarshal(&denom) + store.Set([]byte(denom.GetBaseDenom()), appendedValue) + + // Update denom counter + k.SetDenomCounter(ctx, counter+1) + + return denom, nil +} + +// UpdateDenomPeriods updates schedule periods bound to liquid denom +func (k Keeper) UpdateDenomPeriods(ctx sdk.Context, baseDenom string, newPeriods sdkvesting.Periods) error { + d, found := k.GetDenom(ctx, baseDenom) + if !found { + return types.ErrDenomNotFound + } + d.LockupPeriods = newPeriods + k.SetDenom(ctx, d) + return nil +} + +// DeleteDenom deletes denom from the storage +func (k Keeper) DeleteDenom(ctx sdk.Context, baseDenom string) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomKeyPrefix) + store.Delete([]byte(baseDenom)) +} + +// GetDenom queries denom from the store +func (k Keeper) GetDenom(ctx sdk.Context, baseDenom string) (val types.Denom, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomKeyPrefix) + + b := store.Get([]byte(baseDenom)) + if b == nil { + return val, false + } + + k.cdc.MustUnmarshal(b, &val) + return val, true +} + +// SetDenom sets denom in the store +func (k Keeper) SetDenom(ctx sdk.Context, denom types.Denom) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomKeyPrefix) + b := k.cdc.MustMarshal(&denom) + store.Set([]byte(denom.GetBaseDenom()), b) +} + +// GetDenomCounter get the counter for denoms +func (k Keeper) GetDenomCounter(ctx sdk.Context) uint64 { + store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{}) + byteKey := types.DenomCounterKey + bz := store.Get(byteKey) + + // Counter doesn't exist: no element + if bz == nil { + return 0 + } + + // Parse bytes + return binary.BigEndian.Uint64(bz) +} + +// SetDenomCounter set the counter for denoms +func (k Keeper) SetDenomCounter(ctx sdk.Context, counter uint64) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{}) + byteKey := types.DenomCounterKey + bz := make([]byte, 8) + binary.BigEndian.PutUint64(bz, counter) + store.Set(byteKey, bz) +} diff --git a/x/liquidvesting/keeper/grpc_query.go b/x/liquidvesting/keeper/grpc_query.go new file mode 100644 index 000000000..0b842dfbe --- /dev/null +++ b/x/liquidvesting/keeper/grpc_query.go @@ -0,0 +1,57 @@ +package keeper + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/haqq-network/haqq/x/liquidvesting/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var _ types.QueryServer = Keeper{} + +// Denom retrieves liquid token denom by its name +func (k Keeper) Denom(goCtx context.Context, req *types.QueryDenomRequest) (*types.QueryDenomResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + denom, found := k.GetDenom(ctx, req.Denom) + if !found { + return nil, status.Error(codes.NotFound, "not found") + } + + return &types.QueryDenomResponse{Denom: denom}, nil +} + +// Denoms retrieves liquid tokens denoms +func (k Keeper) Denoms(goCtx context.Context, req *types.QueryDenomsRequest) (*types.QueryDenomsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + var chains []types.Denom + ctx := sdk.UnwrapSDKContext(goCtx) + + store := ctx.KVStore(k.storeKey) + chainStore := prefix.NewStore(store, types.DenomKeyPrefix) + + pageRes, err := query.Paginate(chainStore, req.Pagination, func(key []byte, value []byte) error { + var chain types.Denom + if err := k.cdc.Unmarshal(value, &chain); err != nil { + return err + } + + chains = append(chains, chain) + return nil + }) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryDenomsResponse{Denoms: chains, Pagination: pageRes}, nil +} diff --git a/x/liquidvesting/keeper/keeper.go b/x/liquidvesting/keeper/keeper.go new file mode 100644 index 000000000..381f5d503 --- /dev/null +++ b/x/liquidvesting/keeper/keeper.go @@ -0,0 +1,45 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/haqq-network/haqq/x/liquidvesting/types" +) + +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + paramstore paramtypes.Subspace + + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + erc20Keeper types.ERC20Keeper + vestingKeeper types.VestingKeeper +} + +// NewKeeper creates new Keeper +func NewKeeper( + storeKey storetypes.StoreKey, + cdc codec.BinaryCodec, + ps paramtypes.Subspace, + ak types.AccountKeeper, + bk types.BankKeeper, + erc20 types.ERC20Keeper, + vk types.VestingKeeper, +) Keeper { + // set KeyTable if it has not already been set + if !ps.HasKeyTable() { + ps = ps.WithKeyTable(types.ParamKeyTable()) + } + + return Keeper{ + cdc: cdc, + storeKey: storeKey, + paramstore: ps, + accountKeeper: ak, + bankKeeper: bk, + erc20Keeper: erc20, + vestingKeeper: vk, + } +} diff --git a/x/liquidvesting/keeper/msg_server.go b/x/liquidvesting/keeper/msg_server.go new file mode 100644 index 000000000..8e6184f31 --- /dev/null +++ b/x/liquidvesting/keeper/msg_server.go @@ -0,0 +1,284 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/common" + "github.com/haqq-network/haqq/contracts" + erc20types "github.com/haqq-network/haqq/x/erc20/types" + "github.com/haqq-network/haqq/x/liquidvesting/types" + vestingtypes "github.com/haqq-network/haqq/x/vesting/types" +) + +var _ types.MsgServer = Keeper{} + +// Liquidate liquidates specified amount of token locked in vesting into liquid token +func (k Keeper) Liquidate(goCtx context.Context, msg *types.MsgLiquidate) (*types.MsgLiquidateResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // check amount denom + if msg.Amount.Denom != "aISLM" { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "unable to liquidate any other coin except aISLM") + } + + // check amount + minLiquidation := k.GetParams(ctx).MinimumLiquidationAmount + if msg.Amount.IsLT(sdk.NewCoin("aISLM", minLiquidation)) { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "unable to liquidate amount lesser than %d", minLiquidation) + } + + // get account + liquidateFromAddress := sdk.MustAccAddressFromBech32(msg.LiquidateFrom) + liquidateFromAccount := k.accountKeeper.GetAccount(ctx, liquidateFromAddress) + if liquidateFromAccount == nil { + return nil, errorsmod.Wrapf(errortypes.ErrNotFound, "account %s does not exist", msg.LiquidateFrom) + } + + // set to address + liquidateToAddress := liquidateFromAddress + if msg.LiquidateTo != msg.LiquidateFrom { + liquidateToAddress = sdk.MustAccAddressFromBech32(msg.LiquidateTo) + } + + // check from account is vesting account + va, isClawback := liquidateFromAccount.(*vestingtypes.ClawbackVestingAccount) + if !isClawback { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "account %s is regular nothing to liquidate", msg.LiquidateFrom) + } + + // check there is not vesting periods on the schedule + if !va.GetUnvestedOnly(ctx.BlockTime()).IsZero() { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "account %s has vesting ongoing periods, unable to liquidate unvested coins", msg.LiquidateFrom) + } + + // check account has liquidation target denom locked in vesting + hasTargetDenom, lockedBalance := va.GetLockedOnly(ctx.BlockTime()).Find(msg.Amount.Denom) + if !(hasTargetDenom) { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "account %s doesn't contain coin specified as liquidation target", msg.LiquidateFrom) + } + + // validate current locked periods have sufficient amount to be liquidated + if lockedBalance.IsLT(msg.Amount) { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "account %s doesn't have sufficient amount of target coin for liquidation", msg.LiquidateFrom) + } + + // calculate new schedule + upcomingPeriods := types.ExtractUpcomingPeriods(va.GetStartTime(), va.GetEndTime(), va.LockupPeriods, ctx.BlockTime().Unix()) + decreasedPeriods, diffPeriods, err := types.SubtractAmountFromPeriods(upcomingPeriods, msg.Amount) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrLiquidationFailed, "failed to calculate new schedule: %s", err.Error()) + } + va.LockupPeriods = types.ReplacePeriodsTail(va.LockupPeriods, decreasedPeriods) + va.OriginalVesting = va.OriginalVesting.Sub(msg.Amount) + + // all vesting periods are completed at this point, so we can reduce amounts without additional extracting logic + decreasedVestingPeriods, _, err := types.SubtractAmountFromPeriods(va.VestingPeriods, msg.Amount) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrLiquidationFailed, "failed to calculate new schedule: %s", err.Error()) + } + + va.VestingPeriods = types.ReplacePeriodsTail(va.VestingPeriods, decreasedVestingPeriods) + + k.accountKeeper.SetAccount(ctx, va) + + // transfer liquidated amount to liquid vesting module account + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, liquidateFromAddress, types.ModuleName, sdk.NewCoins(msg.Amount)) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrLiquidationFailed, "failed to transfer liquidated locked coins from account to module: %s", err.Error()) + } + + diffPeriods[0].Length -= types.CurrentPeriodShift(va.StartTime.Unix(), ctx.BlockTime().Unix(), va.LockupPeriods) + liquidDenom, err := k.CreateDenom(ctx, msg.Amount.Denom, ctx.BlockTime().Unix(), diffPeriods) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrLiquidationFailed, "failed to create denom for liquid token: %s", err.Error()) + } + + // create new sdk denom for liquidated locked coins + liquidTokenMetadata := banktypes.Metadata{ + Description: "Liquid vesting token", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: liquidDenom.GetBaseDenom(), + Exponent: 0, + }, + { + Denom: liquidDenom.GetDisplayDenom(), + Exponent: 18, + }, + }, + Base: liquidDenom.GetBaseDenom(), + Display: liquidDenom.GetDisplayDenom(), + Name: liquidDenom.GetDisplayDenom(), + Symbol: liquidDenom.GetDisplayDenom(), + } + + liquidTokenCoin := sdk.NewCoin(liquidDenom.GetBaseDenom(), msg.Amount.Amount) + liquidTokenCoins := sdk.NewCoins(liquidTokenCoin) + err = k.bankKeeper.MintCoins(ctx, types.ModuleName, liquidTokenCoins) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrLiquidationFailed, "failed to mint liquid token: %s", err.Error()) + } + + k.bankKeeper.SetDenomMetaData(ctx, liquidTokenMetadata) + + err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, liquidateToAddress, liquidTokenCoins) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrLiquidationFailed, "failed to transfer liquid tokens to account %s", err.Error()) + } + + // bind newly created denom to erc20 token + _, err = k.erc20Keeper.RegisterCoin(ctx, liquidTokenMetadata) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrLiquidationFailed, "failed to create erc20 token pair: %s", err.Error()) + } + + // convert new liquid token to erc20 token + // Build MsgConvertCoin, from recipient to recipient since Liquidation already occurred + evmLiquidateToAddress := common.BytesToAddress(liquidateToAddress.Bytes()) + msgConvert := erc20types.NewMsgConvertCoin(liquidTokenCoin, evmLiquidateToAddress, liquidateToAddress) + if _, err := k.erc20Keeper.ConvertCoin(sdk.WrapSDKContext(ctx), msgConvert); err != nil { + return nil, errorsmod.Wrap(err, "failed to convert liquid tokens into erc20 tokens") + } + + return &types.MsgLiquidateResponse{}, nil +} + +// Redeem redeems specified amount of liquid token into original locked token and adds them to account +func (k Keeper) Redeem(goCtx context.Context, msg *types.MsgRedeem) (*types.MsgRedeemResponse, error) { + // get accounts + ctx := sdk.UnwrapSDKContext(goCtx) + + fromAddress := sdk.MustAccAddressFromBech32(msg.RedeemFrom) + fromAccount := k.accountKeeper.GetAccount(ctx, fromAddress) + if fromAccount == nil { + return nil, errorsmod.Wrapf(errortypes.ErrNotFound, "account %s does not exist", msg.RedeemFrom) + } + + toAddress := sdk.MustAccAddressFromBech32(msg.RedeemTo) + + // query liquid token info + liquidDenom, found := k.GetDenom(ctx, msg.Amount.Denom) + if !found { + return nil, errorsmod.Wrapf(errortypes.ErrNotFound, "liquidDenom %s does not exist", msg.Amount.Denom) + } + + // get token pair + tokenPairID := k.erc20Keeper.GetTokenPairID(ctx, msg.Amount.Denom) + if len(tokenPairID) == 0 { + return nil, errorsmod.Wrapf(errortypes.ErrNotFound, "token pair for denom %s not found", msg.Amount.Denom) + } + tokenPair, found := k.erc20Keeper.GetTokenPair(ctx, tokenPairID) + if !found || !tokenPair.Enabled { + return nil, errorsmod.Wrapf(errortypes.ErrNotFound, "token pair for denom %s not found", msg.Amount.Denom) + } + + // check fromAccount has enough liquid token in balance + if balance := k.bankKeeper.GetBalance(ctx, fromAddress, msg.Amount.Denom); balance.IsLT(msg.Amount) { + // get erc20 liquid token balance + contract := tokenPair.GetERC20Contract() + erc20LiquidTokenBalance := math.NewIntFromBigInt(k.erc20Keeper.BalanceOf( + ctx, + contracts.ERC20MinterBurnerDecimalsContract.ABI, + contract, + common.BytesToAddress(fromAddress.Bytes()), + )) + + // check if erc20 + cosmos tokens are sufficient for redeem + if erc20LiquidTokenBalance.Add(balance.Amount).LT(msg.Amount.Amount) { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "from account has insufficient balance") + } + + // transfer token from erc20 layer to get sufficient amount + amountToConvert := msg.Amount.Amount.Sub(balance.Amount) + msgConvert := erc20types.NewMsgConvertERC20( + amountToConvert, + fromAddress, + contract, + common.BytesToAddress(fromAddress.Bytes()), + ) + _, err := k.erc20Keeper.ConvertERC20(sdk.WrapSDKContext(ctx), msgConvert) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to convert erc20 token: %s", err.Error()) + } + } + + // transfer liquid denom to liquidvesting module + err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, fromAddress, types.ModuleName, sdk.NewCoins(msg.Amount)) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to transfer liquid token to module: %s", err.Error()) + } + + // burn liquid token specified amount + err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(msg.Amount)) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to burn liquid tokens: %s", err.Error()) + } + + // subtract burned amount from token schedule + originalDenomCoin := sdk.NewCoin(liquidDenom.GetOriginalDenom(), msg.Amount.Amount) + decreasedPeriods, diffPeriods, err := types.SubtractAmountFromPeriods(liquidDenom.LockupPeriods, originalDenomCoin) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to calculate new liquid denom schedule: %s", err.Error()) + } + // save modified token schedule + if decreasedPeriods.TotalAmount().IsZero() { + k.DeleteDenom(ctx, liquidDenom.GetBaseDenom()) + if tokenPair.Enabled { + _, err := k.erc20Keeper.ToggleConversion(ctx, msg.Amount.Denom) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to disable conversion: %s", err.Error()) + } + } + } else { + err = k.UpdateDenomPeriods(ctx, liquidDenom.GetBaseDenom(), decreasedPeriods) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to update liquid denom schedule: %s", err.Error()) + } + } + + // transfer original token to account + err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, toAddress, sdk.NewCoins(originalDenomCoin)) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to transfer original denom to target account: %s", err.Error()) + } + + upcomingPeriods := types.ExtractUpcomingPeriods( + liquidDenom.GetStartTime().Unix(), + liquidDenom.GetEndTime().Unix(), + diffPeriods, + ctx.BlockTime().Unix(), + ) + + // if there are upcoming periods, apply vesting schedule on target account + if len(upcomingPeriods) > 0 { + funder := k.accountKeeper.GetModuleAddress(types.ModuleName) + // check if toAddress already a vesting account to apply current funder + toAccount := k.accountKeeper.GetAccount(ctx, toAddress) + toVestingAcc, isClawback := toAccount.(*vestingtypes.ClawbackVestingAccount) + if isClawback { + funder = sdk.MustAccAddressFromBech32(toVestingAcc.FunderAddress) + } + + _, _, _, err = k.vestingKeeper.ApplyVestingSchedule( + ctx, + funder, + toAddress, + sdk.NewCoins(originalDenomCoin), + liquidDenom.GetStartTime(), + diffPeriods, + sdkvesting.Periods{{Length: 0, Amount: sdk.NewCoins(originalDenomCoin)}}, + true, + ) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrRedeemFailed, "failed to apply vesting schedule to account %s: %s", toAddress, err.Error()) + } + } + + return &types.MsgRedeemResponse{}, nil +} diff --git a/x/liquidvesting/keeper/msg_server_test.go b/x/liquidvesting/keeper/msg_server_test.go new file mode 100644 index 000000000..a10684835 --- /dev/null +++ b/x/liquidvesting/keeper/msg_server_test.go @@ -0,0 +1,374 @@ +package keeper_test + +import ( + "time" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/haqq-network/haqq/contracts" + "github.com/haqq-network/haqq/tests" + "github.com/haqq-network/haqq/testutil" + haqqtypes "github.com/haqq-network/haqq/types" + erc20types "github.com/haqq-network/haqq/x/erc20/types" + "github.com/haqq-network/haqq/x/liquidvesting/types" + vestingtypes "github.com/haqq-network/haqq/x/vesting/types" +) + +var ( + amount = sdk.NewCoins(sdk.NewInt64Coin("aISLM", 3_000_000)) + third = sdk.NewCoins(sdk.NewInt64Coin("aISLM", 1_000_000)) + + liquidDenomAmount = sdk.NewCoins(sdk.NewInt64Coin("aLIQUID0", 3_000_000)) + + lockupPeriods = sdkvesting.Periods{ + {Length: 100, Amount: third}, + {Length: 100, Amount: third}, + {Length: 100, Amount: third}, + } + vestingPeriods = sdkvesting.Periods{ + {Length: 0, Amount: amount}, + } + addr1 = sdk.AccAddress(tests.GenerateAddress().Bytes()) + addr2 = sdk.AccAddress(tests.GenerateAddress().Bytes()) +) + +func (suite *KeeperTestSuite) TestLiquidate() { + testCases := []struct { + name string + malleate func() + from sdk.AccAddress + to sdk.AccAddress + amount sdk.Coin + expectPass bool + }{ + { + name: "ok - standard liquidation one third", + malleate: func() { + funder := sdk.AccAddress(types.ModuleName) + baseAccount := authtypes.NewBaseAccountWithAddress(addr1) + startTime := suite.ctx.BlockTime().Add(-10 * time.Second) + clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil) + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck + s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount) + }, + from: addr1, + to: addr2, + amount: sdk.NewCoin("aISLM", third.AmountOf("aISLM")), + expectPass: true, + }, + { + name: "ok - standard liquidation full liquidation", + malleate: func() { + funder := sdk.AccAddress(types.ModuleName) + baseAccount := authtypes.NewBaseAccountWithAddress(addr1) + startTime := suite.ctx.BlockTime().Add(-10 * time.Second) + clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil) + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck + s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount) + }, + from: addr1, + to: addr2, + amount: sdk.NewCoin("aISLM", amount.AmountOf("aISLM")), + expectPass: true, + }, + { + name: "fail - amount exceeded", + malleate: func() { + funder := sdk.AccAddress(types.ModuleName) + baseAccount := authtypes.NewBaseAccountWithAddress(addr1) + clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, suite.ctx.BlockTime(), lockupPeriods, vestingPeriods, nil) + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck + s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount) + }, + from: addr1, + to: addr2, + amount: sdk.NewInt64Coin("aISLM", 4_000_000), + expectPass: false, + }, + { + name: "fail - denom is not aISLM", + malleate: func() { + funder := sdk.AccAddress(types.ModuleName) + baseAccount := authtypes.NewBaseAccountWithAddress(addr1) + clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, suite.ctx.BlockTime(), lockupPeriods, vestingPeriods, nil) + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck + s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount) + }, + from: addr1, + to: addr2, + amount: sdk.NewCoin("nonaISLM", math.NewInt(2_000_000)), + expectPass: false, + }, + { + name: "fail - vesting periods have length", + malleate: func() { + funder := sdk.AccAddress(types.ModuleName) + baseAccount := authtypes.NewBaseAccountWithAddress(addr1) + vestingPeriods := sdkvesting.Periods{{Length: 100, Amount: amount}} + startTime := suite.ctx.BlockTime().Add(-10 * time.Second) + clawbackAccount := vestingtypes.NewClawbackVestingAccount(baseAccount, funder, amount, startTime, lockupPeriods, vestingPeriods, nil) + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, amount) //nolint:errcheck + s.app.AccountKeeper.SetAccount(s.ctx, clawbackAccount) + }, + from: addr1, + to: addr2, + amount: sdk.NewInt64Coin("aISLM", 2_000_000), + expectPass: false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // Reset + ctx := sdk.WrapSDKContext(suite.ctx) + + tc.malleate() + + msg := types.NewMsgLiquidate(tc.from, tc.to, tc.amount) + resp, err := suite.app.LiquidVestingKeeper.Liquidate(ctx, msg) + expRes := &types.MsgLiquidateResponse{} + + if tc.expectPass { + // check returns + suite.Require().NoError(err) + suite.Require().Equal(expRes, resp) + + // check target account exists and has liquid token + accIto := suite.app.AccountKeeper.GetAccount(suite.ctx, tc.to) + suite.Require().NotNil(accIto) + balanceTarget := suite.app.BankKeeper.GetBalance(suite.ctx, tc.to, types.DenomBaseNameFromID(0)) + suite.Require().Equal(sdk.NewCoin(types.DenomBaseNameFromID(0), math.ZeroInt()).String(), balanceTarget.String()) + + // check liquidated vesting locked coins are decreased on initial account + accIFrom := suite.app.AccountKeeper.GetAccount(suite.ctx, tc.from) + suite.Require().NotNil(accIFrom) + cva, isClawback := accIFrom.(*vestingtypes.ClawbackVestingAccount) + suite.Require().True(isClawback) + suite.Require().Equal(cva.GetLockedOnly(suite.ctx.BlockTime()), lockupPeriods.TotalAmount().Sub(tc.amount)) + + // check erc20 token contract + pairResp, err := s.app.Erc20Keeper.TokenPair(s.ctx, &erc20types.QueryTokenPairRequest{Token: types.DenomBaseNameFromID(0)}) + s.Require().NoError(err) + s.Require().True(pairResp.TokenPair.Enabled) + ethAccTo, isEthAccount := accIto.(*haqqtypes.EthAccount) + s.Require().True(isEthAccount) + balanceOfLiquidTokeErc20Pair := s.app.Erc20Keeper.BalanceOf( + s.ctx, + contracts.ERC20MinterBurnerDecimalsContract.ABI, + pairResp.TokenPair.GetERC20Contract(), + common.BytesToAddress(ethAccTo.GetAddress().Bytes()), + ) + s.Require().Equal(tc.amount.Amount.String(), balanceOfLiquidTokeErc20Pair.String()) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestRedeem() { + testCases := []struct { + name string + malleate func() + redeemFrom sdk.AccAddress + redeemTo sdk.AccAddress + redeemAmount int64 + expectedLockedAmount int64 + expectPass bool + }{ + { + name: "ok - standard redeem, fully unlocked schedule", + malleate: func() { + // fund liquid vesting module + testutil.FundModuleAccount(s.ctx, s.app.BankKeeper, types.ModuleName, amount) //nolint:errcheck + // create liquid vesting denom + s.app.LiquidVestingKeeper.SetDenom(s.ctx, types.Denom{ + BaseDenom: "aLIQUID0", + DisplayDenom: "LIQUID0", + OriginalDenom: "aISLM", + LockupPeriods: lockupPeriods, + }) + // create accounts + s.app.AccountKeeper.SetAccount(s.ctx, authtypes.NewBaseAccountWithAddress(addr1)) + s.app.AccountKeeper.SetAccount(s.ctx, authtypes.NewBaseAccountWithAddress(addr2)) + // fund account with liquid denom token + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, liquidDenomAmount) //nolint:errcheck + liquidTokenMetadata := banktypes.Metadata{ + Description: "Liquid vesting token", + DenomUnits: []*banktypes.DenomUnit{{Denom: "aLIQUID0", Exponent: 0}, {Denom: "LIQUID0", Exponent: 18}}, + Base: "aLIQUID0", + Display: "LIQUID0", + Name: "LIQUID0", + Symbol: "LIQUID0", + } + + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, liquidTokenMetadata) + suite.app.Erc20Keeper.RegisterCoin(suite.ctx, liquidTokenMetadata) //nolint:errcheck + }, + redeemFrom: addr1, + redeemTo: addr2, + redeemAmount: 3_000_000, + expectPass: true, + }, + { + name: "ok - standard redeem, partially locked", + malleate: func() { + // fund liquid vesting module + testutil.FundModuleAccount(s.ctx, s.app.BankKeeper, types.ModuleName, amount) //nolint:errcheck + // create liquid vesting denom + // subs 150 second, it is the half of the second period now + startTime := s.ctx.BlockTime().Add(-150 * time.Second) + s.app.LiquidVestingKeeper.SetDenom(s.ctx, types.Denom{ + BaseDenom: "aLIQUID0", + DisplayDenom: "LIQUID0", + OriginalDenom: "aISLM", + StartTime: startTime, + EndTime: startTime.Add(lockupPeriods.TotalDuration()), + LockupPeriods: lockupPeriods, + }) + // create accounts + acc1 := &haqqtypes.EthAccount{ + BaseAccount: authtypes.NewBaseAccountWithAddress(addr1), + CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(), + } + s.app.AccountKeeper.SetAccount(s.ctx, acc1) + acc2 := &haqqtypes.EthAccount{ + BaseAccount: authtypes.NewBaseAccountWithAddress(addr2), + CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(), + } + s.app.AccountKeeper.SetAccount(s.ctx, acc2) + // fund account with liquid denom token + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, liquidDenomAmount) //nolint:errcheck + + liquidTokenMetadata := banktypes.Metadata{ + Description: "Liquid vesting token", + DenomUnits: []*banktypes.DenomUnit{{Denom: "aLIQUID0", Exponent: 0}, {Denom: "LIQUID0", Exponent: 18}}, + Base: "aLIQUID0", + Display: "LIQUID0", + Name: "LIQUID0", + Symbol: "LIQUID0", + } + + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, liquidTokenMetadata) + suite.app.Erc20Keeper.RegisterCoin(suite.ctx, liquidTokenMetadata) //nolint:errcheck + }, + redeemFrom: addr1, + redeemTo: addr2, + redeemAmount: 600_000, + expectedLockedAmount: 400_000, + expectPass: true, + }, + { + name: "fail - insufficient liquid token balance", + malleate: func() { + // fund liquid vesting module + testutil.FundModuleAccount(s.ctx, s.app.BankKeeper, types.ModuleName, amount) //nolint:errcheck + // create liquid vesting denom + s.app.LiquidVestingKeeper.SetDenom(s.ctx, types.Denom{ + BaseDenom: "aLIQUID0", + DisplayDenom: "LIQUID0", + OriginalDenom: "aISLM", + LockupPeriods: lockupPeriods, + }) + // create accounts + s.app.AccountKeeper.SetAccount(s.ctx, authtypes.NewBaseAccountWithAddress(addr1)) + s.app.AccountKeeper.SetAccount(s.ctx, authtypes.NewBaseAccountWithAddress(addr2)) + // fund account with liquid denom token + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, liquidDenomAmount) //nolint:errcheck + liquidTokenMetadata := banktypes.Metadata{ + Description: "Liquid vesting token", + DenomUnits: []*banktypes.DenomUnit{{Denom: "aLIQUID0", Exponent: 0}, {Denom: "LIQUID0", Exponent: 18}}, + Base: "aLIQUID0", + Display: "LIQUID0", + Name: "LIQUID0", + Symbol: "LIQUID0", + } + + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, liquidTokenMetadata) + suite.app.Erc20Keeper.RegisterCoin(suite.ctx, liquidTokenMetadata) //nolint:errcheck + }, + redeemFrom: addr1, + redeemTo: addr2, + redeemAmount: 4_000_000, + expectPass: false, + }, + { + name: "fail - liquid denom does not exist", + malleate: func() { + // fund liquid vesting module + testutil.FundModuleAccount(s.ctx, s.app.BankKeeper, types.ModuleName, amount) //nolint:errcheck + // create liquid vesting denom + s.app.LiquidVestingKeeper.SetDenom(s.ctx, types.Denom{ + BaseDenom: "solid", + DisplayDenom: "solid18", + OriginalDenom: "aISLM", + LockupPeriods: lockupPeriods, + }) + // create accounts + s.app.AccountKeeper.SetAccount(s.ctx, authtypes.NewBaseAccountWithAddress(addr1)) + s.app.AccountKeeper.SetAccount(s.ctx, authtypes.NewBaseAccountWithAddress(addr2)) + // fund account with liquid denom token + testutil.FundAccount(s.ctx, s.app.BankKeeper, addr1, liquidDenomAmount) //nolint:errcheck + liquidTokenMetadata := banktypes.Metadata{ + Description: "Liquid vesting token", + DenomUnits: []*banktypes.DenomUnit{{Denom: "aLIQUID0", Exponent: 0}, {Denom: "LIQUID0", Exponent: 18}}, + Base: "aLIQUID0", + Display: "LIQUID0", + Name: "LIQUID0", + Symbol: "LIQUID0", + } + + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, liquidTokenMetadata) + suite.app.Erc20Keeper.RegisterCoin(suite.ctx, liquidTokenMetadata) //nolint:errcheck + }, + redeemFrom: addr1, + redeemTo: addr2, + redeemAmount: 4_000_000, + expectPass: false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // Reset + ctx := sdk.WrapSDKContext(suite.ctx) + + tc.malleate() + redeemCoin := sdk.NewInt64Coin("aLIQUID0", tc.redeemAmount) + msg := types.NewMsgRedeem(tc.redeemFrom, tc.redeemTo, redeemCoin) + resp, err := suite.app.LiquidVestingKeeper.Redeem(ctx, msg) + expRes := &types.MsgRedeemResponse{} + if tc.expectPass { + // check returns + suite.Require().NoError(err) + suite.Require().Equal(expRes, resp) + + // check target account has original tokens + accIto := suite.app.AccountKeeper.GetAccount(suite.ctx, tc.redeemTo) + suite.Require().NotNil(accIto) + balanceTarget := suite.app.BankKeeper.SpendableCoin(suite.ctx, tc.redeemTo, "aISLM") + suite.Require().Equal(sdk.NewInt64Coin("aISLM", tc.redeemAmount-tc.expectedLockedAmount).String(), balanceTarget.String()) + if tc.expectedLockedAmount > 0 { + cva, isClawback := accIto.(*vestingtypes.ClawbackVestingAccount) + suite.Require().True(isClawback) + expectedLockedCoins := sdk.NewCoins(sdk.NewInt64Coin("aISLM", tc.expectedLockedAmount)) + actualLockedCoins := cva.GetLockedOnly(s.ctx.BlockTime()) + s.Require().Equal(expectedLockedCoins.String(), actualLockedCoins.String()) + } + + // check liquid tokens are burnt + _, liquidDenomCoin := liquidDenomAmount.Find("aLIQUID0") + expectedLiquidTokenSupply := liquidDenomCoin.Sub(redeemCoin) + actualLiquidTokenSupply := s.app.BankKeeper.GetSupply(s.ctx, "aLIQUID0") + s.Require().Equal(expectedLiquidTokenSupply.String(), actualLiquidTokenSupply.String()) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/x/liquidvesting/keeper/params.go b/x/liquidvesting/keeper/params.go new file mode 100644 index 000000000..2cdaf3306 --- /dev/null +++ b/x/liquidvesting/keeper/params.go @@ -0,0 +1,17 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/haqq-network/haqq/x/liquidvesting/types" +) + +// GetParams returns the total set of liquidvesting parameters. +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + k.paramstore.GetParamSet(ctx, ¶ms) + return params +} + +// SetParams sets the liquidvesting parameters to the param space. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramstore.SetParamSet(ctx, ¶ms) +} diff --git a/x/liquidvesting/keeper/params_test.go b/x/liquidvesting/keeper/params_test.go new file mode 100644 index 000000000..ee73883b9 --- /dev/null +++ b/x/liquidvesting/keeper/params_test.go @@ -0,0 +1,17 @@ +package keeper_test + +import ( + "cosmossdk.io/math" + "github.com/haqq-network/haqq/x/liquidvesting/types" +) + +func (suite *KeeperTestSuite) TestParams() { + params := suite.app.LiquidVestingKeeper.GetParams(suite.ctx) + expParams := types.NewParams(math.NewInt(1_000_000)) + + suite.Require().Equal(expParams, params) + + suite.app.LiquidVestingKeeper.SetParams(suite.ctx, params) + newParams := suite.app.LiquidVestingKeeper.GetParams(suite.ctx) + suite.Require().Equal(newParams, params) +} diff --git a/x/liquidvesting/keeper/setup_test.go b/x/liquidvesting/keeper/setup_test.go new file mode 100644 index 000000000..a7458ed5b --- /dev/null +++ b/x/liquidvesting/keeper/setup_test.go @@ -0,0 +1,131 @@ +package keeper_test + +import ( + "math" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/haqq-network/haqq/app" + "github.com/haqq-network/haqq/crypto/ethsecp256k1" + "github.com/haqq-network/haqq/encoding" + "github.com/haqq-network/haqq/testutil" + utiltx "github.com/haqq-network/haqq/testutil/tx" + haqqtypes "github.com/haqq-network/haqq/types" + "github.com/haqq-network/haqq/utils" + epochstypes "github.com/haqq-network/haqq/x/epochs/types" + "github.com/haqq-network/haqq/x/liquidvesting/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type KeeperTestSuite struct { + suite.Suite + + ctx sdk.Context + app *app.Haqq + queryClient types.QueryClient + address common.Address + consAddress sdk.ConsAddress + validator stakingtypes.Validator + clientCtx client.Context + ethSigner ethtypes.Signer + priv cryptotypes.PrivKey + signer keyring.Signer +} + +var s *KeeperTestSuite + +func TestKeeperTestSuite(t *testing.T) { + s = new(KeeperTestSuite) + suite.Run(t, s) +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.DoSetupTest(suite.T()) +} + +func (suite *KeeperTestSuite) DoSetupTest(t *testing.T) { + // account key + priv, err := ethsecp256k1.GenerateKey() + require.NoError(t, err) + suite.address = common.BytesToAddress(priv.PubKey().Address().Bytes()) + suite.signer = utiltx.NewSigner(priv) + suite.priv = priv + + // consensus key + priv, err = ethsecp256k1.GenerateKey() + require.NoError(t, err) + suite.consAddress = sdk.ConsAddress(priv.PubKey().Address()) + + // Init app + suite.app, _ = app.Setup(false, nil) + + // Set Context + header := testutil.NewHeader( + 1, time.Now().UTC(), utils.MainNetChainID+"-1", suite.consAddress, nil, nil, + ) + suite.ctx = suite.app.BaseApp.NewContext(false, header) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, suite.app.LiquidVestingKeeper) + suite.queryClient = types.NewQueryClient(queryHelper) + + // Set epoch start time and height for all epoch identifiers from the epoch + // module + identifiers := []string{epochstypes.WeekEpochID, epochstypes.DayEpochID} + for _, identifier := range identifiers { + epoch, found := suite.app.EpochsKeeper.GetEpochInfo(suite.ctx, identifier) + suite.Require().True(found) + epoch.StartTime = suite.ctx.BlockTime() + epoch.CurrentEpochStartHeight = suite.ctx.BlockHeight() + suite.app.EpochsKeeper.SetEpochInfo(suite.ctx, epoch) + } + + acc := &haqqtypes.EthAccount{ + BaseAccount: authtypes.NewBaseAccount(suite.address.Bytes(), nil, 0, 0), + CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(), + } + + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + // fund signer acc to pay for tx fees + amt := sdk.NewInt(int64(math.Pow10(18) * 2)) + err = testutil.FundAccount( + suite.ctx, + suite.app.BankKeeper, + suite.priv.PubKey().Address().Bytes(), + sdk.NewCoins(sdk.NewCoin(suite.app.StakingKeeper.BondDenom(suite.ctx), amt)), + ) + suite.Require().NoError(err) + + // Set minimum liquidation amount to 10^6 + suite.app.LiquidVestingKeeper.SetParams(suite.ctx, types.NewParams(sdkmath.NewInt(1_000_000))) + + // Set Validator + valAddr := sdk.ValAddress(suite.address.Bytes()) + validator, err := stakingtypes.NewValidator(valAddr, priv.PubKey(), stakingtypes.Description{}) + require.NoError(t, err) + validator = stakingkeeper.TestingUpdateValidator(&suite.app.StakingKeeper, suite.ctx, validator, true) + err = suite.app.StakingKeeper.Hooks().AfterValidatorCreated(suite.ctx, validator.GetOperator()) + require.NoError(t, err) + err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) + require.NoError(t, err) + validators := s.app.StakingKeeper.GetValidators(s.ctx, 1) + suite.validator = validators[0] + + encodingConfig := encoding.MakeConfig(app.ModuleBasics) + suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) + suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) +} diff --git a/x/liquidvesting/module.go b/x/liquidvesting/module.go new file mode 100644 index 000000000..62b434f83 --- /dev/null +++ b/x/liquidvesting/module.go @@ -0,0 +1,163 @@ +package liquidvesting + +import ( + "context" + "encoding/json" + "fmt" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/haqq-network/haqq/x/liquidvesting/client/cli" + "github.com/haqq-network/haqq/x/liquidvesting/keeper" + "github.com/haqq-network/haqq/x/liquidvesting/types" + "github.com/spf13/cobra" +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface that defines the +// independent methods a Cosmos SDK module needs to implement. +type AppModuleBasic struct { + cdc codec.BinaryCodec +} + +func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + +// Name returns the name of the module as a string. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// GetTxCmd returns the root tx command for the auth module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + +// GetQueryCmd returns the module's root query command. Currently, this is a no-op. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// RegisterLegacyAminoCodec registers the amino codec for the module, which is used +// to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore. +func (AppModuleBasic) RegisterLegacyAminoCodec(_ *codec.LegacyAmino) {} + +// RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message. +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis returns a default GenesisState for the module, marshaled to json.RawMessage. +// The default GenesisState need to be defined by the module developer and is primarily used for testing. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, 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() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { + panic(err) + } +} + +// GetTxCmd returns the root Tx command for the module. +// These commands enrich the AutoCLI tx commands. +// When creating non AutoCLI commands, add the following: +// func (a AppModuleBasic) GetTxCmd() *cobra.Command { +// return cli.GetTxCmd() +// } + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + erc20Keeper types.ERC20Keeper +} + +// NewAppModule creates new AppModule +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + erc20Keeper types.ERC20Keeper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(cdc), + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + erc20Keeper: erc20Keeper, + } +} + +// RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted) +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the module's genesis initialization. It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + + cdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion is a sequence number for state-breaking change of the module. +// It should be incremented on each consensus-breaking change introduced by the module. +// To avoid wrong/empty versions, the initial version should be set to 1. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock contains the logic that is automatically triggered at the beginning of each block. +// The begin block implementation is optional. +func (am AppModule) BeginBlock(_ context.Context) error { + return nil +} + +// EndBlock contains the logic that is automatically triggered at the end of each block. +// The end block implementation is optional. +func (am AppModule) EndBlock(_ context.Context) error { + return nil +} + +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} diff --git a/x/liquidvesting/types/codec.go b/x/liquidvesting/types/codec.go new file mode 100644 index 000000000..5121b86c1 --- /dev/null +++ b/x/liquidvesting/types/codec.go @@ -0,0 +1,15 @@ +package types + +import ( + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgLiquidate{}, + &MsgRedeem{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/x/liquidvesting/types/denom.go b/x/liquidvesting/types/denom.go new file mode 100644 index 000000000..04c105f6c --- /dev/null +++ b/x/liquidvesting/types/denom.go @@ -0,0 +1,18 @@ +package types + +import "fmt" + +const ( + denomBaseNamePrefix = "aLIQUID" + denomDisplayNamePrefix = "LIQUID" +) + +// DenomBaseNameFromID compose denom name based on id for exponent 0 +func DenomBaseNameFromID(id uint64) string { + return fmt.Sprintf("%s%d", denomBaseNamePrefix, id) +} + +// DenomDisplayNameFromID compose denom name based on id for exponent 18 +func DenomDisplayNameFromID(id uint64) string { + return fmt.Sprintf("%s%d", denomDisplayNamePrefix, id) +} diff --git a/x/liquidvesting/types/denom_test.go b/x/liquidvesting/types/denom_test.go new file mode 100644 index 000000000..10bb6c6a7 --- /dev/null +++ b/x/liquidvesting/types/denom_test.go @@ -0,0 +1,57 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type DenomTestSuite struct { + suite.Suite +} + +func TestDenomSuite(t *testing.T) { + suite.Run(t, new(DenomTestSuite)) +} + +func (suite *DenomTestSuite) TestDenomBaseNameFromID() { + testCases := []struct { + name string + ID uint64 + expectedName string + }{ + { + name: "Simple id", + ID: 1, + expectedName: "aLIQUID1", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + result := DenomBaseNameFromID(tc.ID) + suite.Require().Equal(tc.expectedName, result) + }) + } +} + +func (suite *DenomTestSuite) TestDenomDisplayNameFromID() { + testCases := []struct { + name string + ID uint64 + expectedName string + }{ + { + name: "Simple id", + ID: 1, + expectedName: "LIQUID1", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + result := DenomDisplayNameFromID(tc.ID) + suite.Require().Equal(tc.expectedName, result) + }) + } +} diff --git a/x/liquidvesting/types/errors.go b/x/liquidvesting/types/errors.go new file mode 100644 index 000000000..100f0006f --- /dev/null +++ b/x/liquidvesting/types/errors.go @@ -0,0 +1,13 @@ +package types + +// DONTCOVER + +import ( + sdkerrors "cosmossdk.io/errors" +) + +var ( + ErrLiquidationFailed = sdkerrors.Register(ModuleName, 1102, "liquidation failed") + ErrRedeemFailed = sdkerrors.Register(ModuleName, 1103, "redeem failed") + ErrDenomNotFound = sdkerrors.Register(ModuleName, 1104, "denom not found") +) diff --git a/x/liquidvesting/types/genesis.go b/x/liquidvesting/types/genesis.go new file mode 100644 index 000000000..2b9d532eb --- /dev/null +++ b/x/liquidvesting/types/genesis.go @@ -0,0 +1,21 @@ +package types + +func NewGenesisState( + params Params, +) GenesisState { + return GenesisState{ + Params: params, + } +} + +func DefaultGenesisState() *GenesisState { + params := DefaultParams() + return &GenesisState{ + Params: params, + } +} + +// Validate genesis state +func (gs GenesisState) Validate() error { + return gs.Params.Validate() +} diff --git a/x/liquidvesting/types/genesis.pb.go b/x/liquidvesting/types/genesis.pb.go new file mode 100644 index 000000000..cbcf6bd59 --- /dev/null +++ b/x/liquidvesting/types/genesis.pb.go @@ -0,0 +1,496 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: haqq/liquidvesting/v1/genesis.proto + +package types + +import ( + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the liquidvesting module's genesis state. +type GenesisState struct { + // params defines all the paramaters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_811653311f50d122, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// Params holds parameters for the liquidvesting module. +type Params struct { + MinimumLiquidationAmount cosmossdk_io_math.Int `protobuf:"bytes,1,opt,name=minimum_liquidation_amount,json=minimumLiquidationAmount,proto3,customtype=cosmossdk.io/math.Int" json:"minimum_liquidation_amount"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_811653311f50d122, []int{1} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func init() { + proto.RegisterType((*GenesisState)(nil), "haqq.liquidvesting.v1.GenesisState") + proto.RegisterType((*Params)(nil), "haqq.liquidvesting.v1.Params") +} + +func init() { + proto.RegisterFile("haqq/liquidvesting/v1/genesis.proto", fileDescriptor_811653311f50d122) +} + +var fileDescriptor_811653311f50d122 = []byte{ + // 272 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x31, 0x4b, 0xc3, 0x40, + 0x14, 0xc7, 0x13, 0x90, 0x82, 0xa7, 0x53, 0xb0, 0x50, 0x0a, 0xbd, 0x4a, 0x5d, 0x5c, 0xbc, 0xa3, + 0x75, 0x74, 0x32, 0x8b, 0x88, 0x1d, 0xa4, 0x6e, 0x3a, 0x94, 0x6b, 0x7b, 0x5c, 0x8e, 0x7a, 0xf7, + 0x92, 0xdc, 0x4b, 0xd4, 0x6f, 0xe1, 0xc7, 0xea, 0xd8, 0x51, 0x1c, 0x8a, 0x24, 0x5f, 0x44, 0x72, + 0x29, 0x88, 0xe2, 0xf6, 0xde, 0xe3, 0xf7, 0xfb, 0xf3, 0xf8, 0x93, 0xb3, 0x44, 0x64, 0x19, 0x7f, + 0xd6, 0x59, 0xa1, 0x57, 0xa5, 0x74, 0xa8, 0xad, 0xe2, 0xe5, 0x98, 0x2b, 0x69, 0xa5, 0xd3, 0x8e, + 0xa5, 0x39, 0x20, 0x44, 0xdd, 0x06, 0x62, 0xbf, 0x20, 0x56, 0x8e, 0xfb, 0x27, 0x0a, 0x14, 0x78, + 0x82, 0x37, 0x53, 0x0b, 0x8f, 0xee, 0xc8, 0xf1, 0x4d, 0x6b, 0x3f, 0xa0, 0x40, 0x19, 0x5d, 0x91, + 0x4e, 0x2a, 0x72, 0x61, 0x5c, 0x2f, 0x3c, 0x0d, 0xcf, 0x8f, 0x26, 0x03, 0xf6, 0x6f, 0x1a, 0xbb, + 0xf7, 0x50, 0x7c, 0xb0, 0xd9, 0x0d, 0x83, 0xd9, 0x5e, 0x19, 0x49, 0xd2, 0x69, 0xef, 0xd1, 0x13, + 0xe9, 0x1b, 0x6d, 0xb5, 0x29, 0xcc, 0xbc, 0x55, 0x05, 0x6a, 0xb0, 0x73, 0x61, 0xa0, 0xb0, 0xe8, + 0xa3, 0x0f, 0xe3, 0x41, 0xe3, 0x7e, 0xee, 0x86, 0xdd, 0x25, 0x38, 0x03, 0xce, 0xad, 0xd6, 0x4c, + 0x03, 0x37, 0x02, 0x13, 0x76, 0x6b, 0x71, 0xd6, 0xdb, 0x07, 0x4c, 0x7f, 0xfc, 0x6b, 0xaf, 0xc7, + 0xd3, 0x4d, 0x45, 0xc3, 0x6d, 0x45, 0xc3, 0xaf, 0x8a, 0x86, 0xef, 0x35, 0x0d, 0xb6, 0x35, 0x0d, + 0x3e, 0x6a, 0x1a, 0x3c, 0x4e, 0x94, 0xc6, 0xa4, 0x58, 0xb0, 0x25, 0x18, 0xde, 0xfc, 0x7d, 0x61, + 0x25, 0xbe, 0x40, 0xbe, 0xf6, 0x0b, 0x7f, 0xfd, 0xd3, 0x1c, 0xbe, 0xa5, 0xd2, 0x2d, 0x3a, 0xbe, + 0x88, 0xcb, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xb8, 0x49, 0xf8, 0x5c, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.MinimumLiquidationAmount.Size() + i -= size + if _, err := m.MinimumLiquidationAmount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.MinimumLiquidationAmount.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumLiquidationAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinimumLiquidationAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/liquidvesting/types/interfaces.go b/x/liquidvesting/types/interfaces.go new file mode 100644 index 000000000..9e6784aeb --- /dev/null +++ b/x/liquidvesting/types/interfaces.go @@ -0,0 +1,67 @@ +package types + +import ( + "context" + "math/big" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + erc20types "github.com/haqq-network/haqq/x/erc20/types" + vestingtypes "github.com/haqq-network/haqq/x/vesting/types" +) + +// AccountKeeper defines the expected interface for the Account module. +type AccountKeeper interface { + GetAccount(sdk.Context, sdk.AccAddress) authtypes.AccountI // only used for simulation + SetAccount(sdk.Context, authtypes.AccountI) + NewAccount(ctx sdk.Context, acc authtypes.AccountI) authtypes.AccountI + GetModuleAddress(moduleName string) sdk.AccAddress + + // Methods imported from account should be defined here +} + +// BankKeeper defines the expected interface for the Bank module. +type BankKeeper interface { + BlockedAddr(addr sdk.AccAddress) bool + + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + + HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool + + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + + BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) +} + +// ERC20Keeper defines the expected interface for the ERC20 module. +type ERC20Keeper interface { + ToggleConversion(ctx sdk.Context, token string) (erc20types.TokenPair, error) + GetTokenPairID(ctx sdk.Context, token string) []byte + GetTokenPair(ctx sdk.Context, id []byte) (erc20types.TokenPair, bool) + BalanceOf(ctx sdk.Context, abi abi.ABI, contract, account common.Address) *big.Int + ConvertCoin(goCtx context.Context, msg *erc20types.MsgConvertCoin) (*erc20types.MsgConvertCoinResponse, error) + ConvertERC20(context.Context, *erc20types.MsgConvertERC20) (*erc20types.MsgConvertERC20Response, error) + RegisterCoin(ctx sdk.Context, coinMetadata banktypes.Metadata) (*erc20types.TokenPair, error) +} + +// VestingKeeper defines the expected interface for the Vesting module. +type VestingKeeper interface { + ApplyVestingSchedule( + ctx sdk.Context, + funder, funded sdk.AccAddress, + coins sdk.Coins, + startTime time.Time, + lockupPeriods, vestingPeriods sdkvesting.Periods, + merge bool, + ) (vestingAcc *vestingtypes.ClawbackVestingAccount, newAccountCreated, wasMerged bool, err error) +} diff --git a/x/liquidvesting/types/keys.go b/x/liquidvesting/types/keys.go new file mode 100644 index 000000000..dbfbd64ca --- /dev/null +++ b/x/liquidvesting/types/keys.go @@ -0,0 +1,21 @@ +package types + +const ( + // ModuleName defines the module name + ModuleName = "liquidvesting" + + // StoreKey defines the primary module store key + StoreKey = ModuleName + + RouterKey = ModuleName +) + +const ( + prefixDenom = iota + 1 + denomCounter +) + +var ( + DenomKeyPrefix = []byte{prefixDenom} + DenomCounterKey = []byte{denomCounter} +) diff --git a/x/liquidvesting/types/liquidvesting.pb.go b/x/liquidvesting/types/liquidvesting.pb.go new file mode 100644 index 000000000..823a1035a --- /dev/null +++ b/x/liquidvesting/types/liquidvesting.pb.go @@ -0,0 +1,615 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: haqq/liquidvesting/v1/liquidvesting.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + github_com_cosmos_cosmos_sdk_x_auth_vesting_types "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + types "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Denom represents liquid token bonded to some specific vesting schedule +type Denom struct { + // base_denom main identifier for the denom, used to query it from store. + BaseDenom string `protobuf:"bytes,1,opt,name=base_denom,json=baseDenom,proto3" json:"base_denom,omitempty"` + // display_denom identifier used for display name for broad audience + DisplayDenom string `protobuf:"bytes,2,opt,name=display_denom,json=displayDenom,proto3" json:"display_denom,omitempty"` + // original_denom which liquid denom derived from + OriginalDenom string `protobuf:"bytes,3,opt,name=original_denom,json=originalDenom,proto3" json:"original_denom,omitempty"` + // start date + StartTime time.Time `protobuf:"bytes,4,opt,name=start_time,json=startTime,proto3,stdtime" json:"start_time"` + // end_date + EndTime time.Time `protobuf:"bytes,5,opt,name=end_time,json=endTime,proto3,stdtime" json:"end_time"` + // lockup periods + LockupPeriods github_com_cosmos_cosmos_sdk_x_auth_vesting_types.Periods `protobuf:"bytes,6,rep,name=lockup_periods,json=lockupPeriods,proto3,castrepeated=github.com/cosmos/cosmos-sdk/x/auth/vesting/types.Periods" json:"lockup_periods"` +} + +func (m *Denom) Reset() { *m = Denom{} } +func (m *Denom) String() string { return proto.CompactTextString(m) } +func (*Denom) ProtoMessage() {} +func (*Denom) Descriptor() ([]byte, []int) { + return fileDescriptor_ce2378517a6b5c6c, []int{0} +} +func (m *Denom) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Denom) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Denom.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Denom) XXX_Merge(src proto.Message) { + xxx_messageInfo_Denom.Merge(m, src) +} +func (m *Denom) XXX_Size() int { + return m.Size() +} +func (m *Denom) XXX_DiscardUnknown() { + xxx_messageInfo_Denom.DiscardUnknown(m) +} + +var xxx_messageInfo_Denom proto.InternalMessageInfo + +func (m *Denom) GetBaseDenom() string { + if m != nil { + return m.BaseDenom + } + return "" +} + +func (m *Denom) GetDisplayDenom() string { + if m != nil { + return m.DisplayDenom + } + return "" +} + +func (m *Denom) GetOriginalDenom() string { + if m != nil { + return m.OriginalDenom + } + return "" +} + +func (m *Denom) GetStartTime() time.Time { + if m != nil { + return m.StartTime + } + return time.Time{} +} + +func (m *Denom) GetEndTime() time.Time { + if m != nil { + return m.EndTime + } + return time.Time{} +} + +func (m *Denom) GetLockupPeriods() github_com_cosmos_cosmos_sdk_x_auth_vesting_types.Periods { + if m != nil { + return m.LockupPeriods + } + return nil +} + +func init() { + proto.RegisterType((*Denom)(nil), "haqq.liquidvesting.v1.Denom") +} + +func init() { + proto.RegisterFile("haqq/liquidvesting/v1/liquidvesting.proto", fileDescriptor_ce2378517a6b5c6c) +} + +var fileDescriptor_ce2378517a6b5c6c = []byte{ + // 418 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x3f, 0x8f, 0xd3, 0x30, + 0x18, 0xc6, 0x63, 0xca, 0x1d, 0x57, 0x1f, 0x3d, 0x89, 0x0a, 0xa4, 0x2a, 0x12, 0x4e, 0xc5, 0x1f, + 0xa9, 0x0c, 0x67, 0xab, 0x65, 0x62, 0x42, 0x14, 0x46, 0x06, 0x54, 0x31, 0xb1, 0x54, 0x4e, 0x62, + 0x52, 0xab, 0x89, 0xdf, 0x34, 0x76, 0xca, 0xdd, 0xcc, 0xc2, 0x78, 0x9f, 0x83, 0x4f, 0x72, 0xe3, + 0x8d, 0x4c, 0x1c, 0x6a, 0xbf, 0x08, 0xf2, 0x9f, 0xde, 0x51, 0xb6, 0x5b, 0x12, 0xbf, 0xcf, 0xfb, + 0xcb, 0xf3, 0xe4, 0xb5, 0x8d, 0x5f, 0x2d, 0xf8, 0x6a, 0xc5, 0x4a, 0xb9, 0x6a, 0x65, 0xbe, 0x16, + 0xda, 0x48, 0x55, 0xb0, 0xf5, 0x78, 0x5f, 0xa0, 0x75, 0x03, 0x06, 0xfa, 0x4f, 0x2c, 0x4a, 0xf7, + 0x3b, 0xeb, 0x71, 0xfc, 0x88, 0x57, 0x52, 0x01, 0x73, 0x4f, 0x4f, 0xc6, 0x8f, 0x0b, 0x28, 0xc0, + 0x2d, 0x99, 0x5d, 0x05, 0xf5, 0x45, 0x06, 0xba, 0x02, 0xcd, 0x6e, 0x63, 0x52, 0x61, 0xf8, 0x98, + 0xed, 0xa5, 0xc4, 0x49, 0x01, 0x50, 0x94, 0x82, 0xb9, 0x2a, 0x6d, 0xbf, 0x32, 0x23, 0x2b, 0xa1, + 0x0d, 0xaf, 0xea, 0x00, 0x90, 0x60, 0x93, 0x72, 0x2d, 0x6e, 0x3c, 0x32, 0x90, 0xca, 0xf7, 0x9f, + 0x7d, 0xef, 0xe0, 0x83, 0x0f, 0x42, 0x41, 0xd5, 0x7f, 0x8a, 0xb1, 0x85, 0xe6, 0xb9, 0xad, 0x06, + 0x68, 0x88, 0x46, 0xdd, 0x59, 0xd7, 0x2a, 0xbe, 0xfd, 0x1c, 0xf7, 0x72, 0xa9, 0xeb, 0x92, 0x9f, + 0x07, 0xe2, 0x9e, 0x23, 0x1e, 0x06, 0xd1, 0x43, 0x2f, 0xf1, 0x09, 0x34, 0xb2, 0x90, 0x8a, 0x97, + 0x81, 0xea, 0x38, 0xaa, 0xb7, 0x53, 0x3d, 0xf6, 0x1e, 0x63, 0x6d, 0x78, 0x63, 0xe6, 0xf6, 0x6f, + 0x07, 0xf7, 0x87, 0x68, 0x74, 0x3c, 0x89, 0xa9, 0x1f, 0x85, 0xee, 0x46, 0xa1, 0x9f, 0x77, 0xa3, + 0x4c, 0x8f, 0x2e, 0x7f, 0x27, 0xd1, 0xc5, 0x75, 0x82, 0x66, 0x5d, 0xf7, 0x9d, 0xed, 0xf4, 0xdf, + 0xe2, 0x23, 0xa1, 0x72, 0x6f, 0x71, 0x70, 0x07, 0x8b, 0x07, 0x42, 0xe5, 0xce, 0xe0, 0x07, 0xc2, + 0x27, 0x25, 0x64, 0xcb, 0xb6, 0x9e, 0xd7, 0xa2, 0x91, 0x90, 0xeb, 0xc1, 0xe1, 0xb0, 0x33, 0x3a, + 0x9e, 0x10, 0xea, 0x37, 0x8d, 0xde, 0x9e, 0x9b, 0xdb, 0x37, 0xfa, 0xc9, 0x61, 0xd3, 0x77, 0xd6, + 0xeb, 0xe7, 0x75, 0xf2, 0xa6, 0x90, 0x66, 0xd1, 0xa6, 0x34, 0x83, 0x8a, 0x85, 0x6d, 0xf6, 0xaf, + 0x53, 0x9d, 0x2f, 0xd9, 0x19, 0xe3, 0xad, 0x59, 0xdc, 0x9c, 0x9f, 0x39, 0xaf, 0x85, 0x0e, 0x0e, + 0x7a, 0xd6, 0xf3, 0xc1, 0xa1, 0x9c, 0x7e, 0xbc, 0xdc, 0x10, 0x74, 0xb5, 0x21, 0xe8, 0xcf, 0x86, + 0xa0, 0x8b, 0x2d, 0x89, 0xae, 0xb6, 0x24, 0xfa, 0xb5, 0x25, 0xd1, 0x97, 0xc9, 0x3f, 0x19, 0xf6, + 0x46, 0x9d, 0x2a, 0x61, 0xbe, 0x41, 0xb3, 0x74, 0x05, 0x3b, 0xfb, 0xef, 0x2e, 0xba, 0x90, 0xf4, + 0xd0, 0xcd, 0xff, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x68, 0xb6, 0x57, 0x3f, 0xae, 0x02, + 0x00, 0x00, +} + +func (m *Denom) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Denom) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Denom) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.LockupPeriods) > 0 { + for iNdEx := len(m.LockupPeriods) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.LockupPeriods[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLiquidvesting(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.EndTime):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintLiquidvesting(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x2a + n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.StartTime):]) + if err2 != nil { + return 0, err2 + } + i -= n2 + i = encodeVarintLiquidvesting(dAtA, i, uint64(n2)) + i-- + dAtA[i] = 0x22 + if len(m.OriginalDenom) > 0 { + i -= len(m.OriginalDenom) + copy(dAtA[i:], m.OriginalDenom) + i = encodeVarintLiquidvesting(dAtA, i, uint64(len(m.OriginalDenom))) + i-- + dAtA[i] = 0x1a + } + if len(m.DisplayDenom) > 0 { + i -= len(m.DisplayDenom) + copy(dAtA[i:], m.DisplayDenom) + i = encodeVarintLiquidvesting(dAtA, i, uint64(len(m.DisplayDenom))) + i-- + dAtA[i] = 0x12 + } + if len(m.BaseDenom) > 0 { + i -= len(m.BaseDenom) + copy(dAtA[i:], m.BaseDenom) + i = encodeVarintLiquidvesting(dAtA, i, uint64(len(m.BaseDenom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintLiquidvesting(dAtA []byte, offset int, v uint64) int { + offset -= sovLiquidvesting(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Denom) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.BaseDenom) + if l > 0 { + n += 1 + l + sovLiquidvesting(uint64(l)) + } + l = len(m.DisplayDenom) + if l > 0 { + n += 1 + l + sovLiquidvesting(uint64(l)) + } + l = len(m.OriginalDenom) + if l > 0 { + n += 1 + l + sovLiquidvesting(uint64(l)) + } + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.StartTime) + n += 1 + l + sovLiquidvesting(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.EndTime) + n += 1 + l + sovLiquidvesting(uint64(l)) + if len(m.LockupPeriods) > 0 { + for _, e := range m.LockupPeriods { + l = e.Size() + n += 1 + l + sovLiquidvesting(uint64(l)) + } + } + return n +} + +func sovLiquidvesting(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozLiquidvesting(x uint64) (n int) { + return sovLiquidvesting(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Denom) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Denom: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Denom: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BaseDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLiquidvesting + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLiquidvesting + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BaseDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DisplayDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLiquidvesting + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLiquidvesting + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DisplayDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OriginalDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLiquidvesting + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLiquidvesting + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OriginalDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLiquidvesting + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLiquidvesting + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.StartTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EndTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLiquidvesting + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLiquidvesting + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.EndTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LockupPeriods", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLiquidvesting + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLiquidvesting + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LockupPeriods = append(m.LockupPeriods, types.Period{}) + if err := m.LockupPeriods[len(m.LockupPeriods)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLiquidvesting(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthLiquidvesting + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipLiquidvesting(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLiquidvesting + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthLiquidvesting + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupLiquidvesting + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthLiquidvesting + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthLiquidvesting = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowLiquidvesting = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupLiquidvesting = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/liquidvesting/types/msg.go b/x/liquidvesting/types/msg.go new file mode 100644 index 000000000..9a9eedc9f --- /dev/null +++ b/x/liquidvesting/types/msg.go @@ -0,0 +1,90 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + TypeMsgLiquidate = "liquidate" + TypeMsgRedeem = "redeem" +) + +// NewMsgLiquidate creates new instance of MsgLiquidate +func NewMsgLiquidate(liquidateFrom, liquidateTo sdk.AccAddress, amount sdk.Coin) *MsgLiquidate { + return &MsgLiquidate{ + LiquidateFrom: liquidateFrom.String(), + LiquidateTo: liquidateTo.String(), + Amount: amount, + } +} + +// Route returns the name of the module +func (msg MsgLiquidate) Route() string { return RouterKey } + +// Type returns the action type +func (msg MsgLiquidate) Type() string { return TypeMsgLiquidate } + +// ValidateBasic runs stateless checks on the message +func (msg MsgLiquidate) ValidateBasic() error { + if !msg.Amount.Amount.IsPositive() { + return errorsmod.Wrapf(errortypes.ErrInvalidCoins, "cannot liquidate non-positive amount") + } + + _, err := sdk.AccAddressFromBech32(msg.LiquidateFrom) + if err != nil { + return errorsmod.Wrap(err, "invalid account address liquidateFrom") + } + + _, err = sdk.AccAddressFromBech32(msg.LiquidateTo) + if err != nil { + return errorsmod.Wrap(err, "invalid account address liquidateTo") + } + return nil +} + +// GetSigners defines whose signature is required +func (msg MsgLiquidate) GetSigners() []sdk.AccAddress { + addr := sdk.MustAccAddressFromBech32(msg.LiquidateFrom) + return []sdk.AccAddress{addr} +} + +// NewMsgRedeem creates new instance of MsgLiquidate +func NewMsgRedeem(redeemFrom, redeemTo sdk.AccAddress, amount sdk.Coin) *MsgRedeem { + return &MsgRedeem{ + RedeemFrom: redeemFrom.String(), + RedeemTo: redeemTo.String(), + Amount: amount, + } +} + +// Route returns the name of the module +func (msg MsgRedeem) Route() string { return RouterKey } + +// Type returns the action type +func (msg MsgRedeem) Type() string { return TypeMsgRedeem } + +// ValidateBasic runs stateless checks on the message +func (msg MsgRedeem) ValidateBasic() error { + if !msg.Amount.Amount.IsPositive() { + return errorsmod.Wrapf(errortypes.ErrInvalidCoins, "cannot liquidate non-positive amount") + } + + _, err := sdk.AccAddressFromBech32(msg.RedeemFrom) + if err != nil { + return errorsmod.Wrap(err, "invalid account address redeemFrom") + } + + _, err = sdk.AccAddressFromBech32(msg.RedeemTo) + if err != nil { + return errorsmod.Wrap(err, "invalid account address redeemTo") + } + return nil +} + +// GetSigners defines whose signature is required +func (msg MsgRedeem) GetSigners() []sdk.AccAddress { + addr := sdk.MustAccAddressFromBech32(msg.RedeemFrom) + return []sdk.AccAddress{addr} +} diff --git a/x/liquidvesting/types/msg_test.go b/x/liquidvesting/types/msg_test.go new file mode 100644 index 000000000..ab1254f4c --- /dev/null +++ b/x/liquidvesting/types/msg_test.go @@ -0,0 +1 @@ +package types diff --git a/x/liquidvesting/types/params.go b/x/liquidvesting/types/params.go new file mode 100644 index 000000000..ec30be758 --- /dev/null +++ b/x/liquidvesting/types/params.go @@ -0,0 +1,56 @@ +package types + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// DefaultMinimumLiquidationAmountISLM default parameter value in ISLM +const DefaultMinimumLiquidationAmountISLM = 1000 + +// AttoMultiplier 10^18 +var AttoMultiplier = math.NewIntFromBigInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) + +// DefaultMinimumLiquidationAmount default atto parameter value +var DefaultMinimumLiquidationAmount = math.NewInt(DefaultMinimumLiquidationAmountISLM).Mul(AttoMultiplier) + +// ParamStoreKeyMinimumLiquidationAmount Parameter store keys +var ParamStoreKeyMinimumLiquidationAmount = []byte("MinimumLiquidationAmount") + +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +func NewParams(minimumLiquidationAmount math.Int) Params { + return Params{MinimumLiquidationAmount: minimumLiquidationAmount} +} + +func DefaultParams() Params { + return Params{MinimumLiquidationAmount: DefaultMinimumLiquidationAmount} +} + +// ParamSetPairs Implements params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(ParamStoreKeyMinimumLiquidationAmount, &p.MinimumLiquidationAmount, validateMathIntPositive), + } +} + +func validateMathIntPositive(i interface{}) error { + n, ok := i.(math.Int) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + if !n.IsPositive() { + return fmt.Errorf("parameter value must be positive, got: %s", n) + } + + return nil +} + +func (p Params) Validate() error { + return validateMathIntPositive(p.MinimumLiquidationAmount) +} diff --git a/x/liquidvesting/types/query.pb.go b/x/liquidvesting/types/query.pb.go new file mode 100644 index 000000000..d859553cc --- /dev/null +++ b/x/liquidvesting/types/query.pb.go @@ -0,0 +1,1062 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: haqq/liquidvesting/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryDenomRequest is request fo Denom rpc method +type QueryDenomRequest struct { + // denom is liquidated vesting token + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` +} + +func (m *QueryDenomRequest) Reset() { *m = QueryDenomRequest{} } +func (m *QueryDenomRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomRequest) ProtoMessage() {} +func (*QueryDenomRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_0d414430b780fcf7, []int{0} +} +func (m *QueryDenomRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomRequest.Merge(m, src) +} +func (m *QueryDenomRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomRequest proto.InternalMessageInfo + +func (m *QueryDenomRequest) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + +// QueryDenomResponse is response for Denom rpc method +type QueryDenomResponse struct { + // denom is liquidated vesting token + Denom Denom `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom"` +} + +func (m *QueryDenomResponse) Reset() { *m = QueryDenomResponse{} } +func (m *QueryDenomResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomResponse) ProtoMessage() {} +func (*QueryDenomResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_0d414430b780fcf7, []int{1} +} +func (m *QueryDenomResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomResponse.Merge(m, src) +} +func (m *QueryDenomResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomResponse proto.InternalMessageInfo + +func (m *QueryDenomResponse) GetDenom() Denom { + if m != nil { + return m.Denom + } + return Denom{} +} + +// QueryDenomsRequest is request for Denoms rpc method +type QueryDenomsRequest struct { + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryDenomsRequest) Reset() { *m = QueryDenomsRequest{} } +func (m *QueryDenomsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomsRequest) ProtoMessage() {} +func (*QueryDenomsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_0d414430b780fcf7, []int{2} +} +func (m *QueryDenomsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomsRequest.Merge(m, src) +} +func (m *QueryDenomsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomsRequest proto.InternalMessageInfo + +func (m *QueryDenomsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryDenomsResponse is response for Denoms rpc method +type QueryDenomsResponse struct { + // denoms are liquidated vesting tokens + Denoms []Denom `protobuf:"bytes,1,rep,name=denoms,proto3" json:"denoms"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryDenomsResponse) Reset() { *m = QueryDenomsResponse{} } +func (m *QueryDenomsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomsResponse) ProtoMessage() {} +func (*QueryDenomsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_0d414430b780fcf7, []int{3} +} +func (m *QueryDenomsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomsResponse.Merge(m, src) +} +func (m *QueryDenomsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomsResponse proto.InternalMessageInfo + +func (m *QueryDenomsResponse) GetDenoms() []Denom { + if m != nil { + return m.Denoms + } + return nil +} + +func (m *QueryDenomsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func init() { + proto.RegisterType((*QueryDenomRequest)(nil), "haqq.liquidvesting.v1.QueryDenomRequest") + proto.RegisterType((*QueryDenomResponse)(nil), "haqq.liquidvesting.v1.QueryDenomResponse") + proto.RegisterType((*QueryDenomsRequest)(nil), "haqq.liquidvesting.v1.QueryDenomsRequest") + proto.RegisterType((*QueryDenomsResponse)(nil), "haqq.liquidvesting.v1.QueryDenomsResponse") +} + +func init() { proto.RegisterFile("haqq/liquidvesting/v1/query.proto", fileDescriptor_0d414430b780fcf7) } + +var fileDescriptor_0d414430b780fcf7 = []byte{ + // 442 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x8b, 0x13, 0x31, + 0x1c, 0xc5, 0x27, 0x95, 0x16, 0x36, 0x9e, 0x36, 0xae, 0xb0, 0x0c, 0x75, 0x76, 0x1d, 0xfc, 0xb1, + 0x2d, 0x98, 0x30, 0xf5, 0x2c, 0xc2, 0x22, 0x7a, 0xf1, 0xa0, 0xf5, 0x26, 0x5e, 0x32, 0xbb, 0x21, + 0x1b, 0xdc, 0x49, 0x66, 0x9a, 0xcc, 0xe8, 0x5e, 0x7b, 0xf0, 0x2c, 0x88, 0x67, 0xaf, 0x1e, 0xfd, + 0x33, 0x7a, 0x2c, 0x78, 0xf1, 0x24, 0xd2, 0x0a, 0xfe, 0x1b, 0x32, 0x49, 0x8a, 0x33, 0x5a, 0x6d, + 0xbd, 0x94, 0xa4, 0xf3, 0xbe, 0xef, 0x7d, 0xf2, 0x12, 0x78, 0xfd, 0x8c, 0x16, 0x05, 0x39, 0x17, + 0x45, 0x29, 0x4e, 0x2b, 0xa6, 0x8d, 0x90, 0x9c, 0x54, 0x09, 0x29, 0x4a, 0x36, 0xb9, 0xc0, 0xf9, + 0x44, 0x19, 0x85, 0xae, 0xd6, 0x12, 0xdc, 0x92, 0xe0, 0x2a, 0x09, 0x77, 0x69, 0x26, 0xa4, 0x22, + 0xf6, 0xd7, 0x29, 0xc3, 0x3d, 0xae, 0xb8, 0xb2, 0x4b, 0x52, 0xaf, 0xfc, 0xbf, 0x7d, 0xae, 0x14, + 0x3f, 0x67, 0x84, 0xe6, 0x82, 0x50, 0x29, 0x95, 0xa1, 0x46, 0x28, 0xa9, 0xfd, 0xd7, 0xe1, 0x89, + 0xd2, 0x99, 0xd2, 0x24, 0xa5, 0x9a, 0xb9, 0x58, 0x52, 0x25, 0x29, 0x33, 0x34, 0x21, 0x39, 0xe5, + 0x42, 0x5a, 0xb1, 0xd7, 0x0e, 0xd6, 0xc3, 0xb6, 0xd1, 0xac, 0x34, 0x1e, 0xc0, 0xdd, 0xa7, 0xb5, + 0xd9, 0x03, 0x26, 0x55, 0x36, 0x66, 0x45, 0xc9, 0xb4, 0x41, 0x7b, 0xb0, 0x7b, 0x5a, 0xef, 0xf7, + 0xc1, 0x21, 0x38, 0xda, 0x19, 0xbb, 0x4d, 0xfc, 0x0c, 0xa2, 0xa6, 0x54, 0xe7, 0x4a, 0x6a, 0x86, + 0xee, 0x35, 0xb5, 0x97, 0x47, 0x7d, 0xbc, 0xb6, 0x05, 0x6c, 0x87, 0x8e, 0x77, 0x66, 0x5f, 0x0f, + 0x82, 0x8f, 0x3f, 0x3e, 0x0d, 0xc1, 0xca, 0xf4, 0x45, 0xd3, 0x54, 0xaf, 0x00, 0x1e, 0x42, 0xf8, + 0xeb, 0x50, 0xde, 0xf9, 0x16, 0x76, 0x0d, 0xe0, 0xba, 0x01, 0xec, 0x8a, 0xf7, 0x0d, 0xe0, 0x27, + 0x94, 0x33, 0x3f, 0x3b, 0x6e, 0x4c, 0xc6, 0x1f, 0x00, 0xbc, 0xd2, 0xb2, 0xf7, 0xd0, 0xf7, 0x61, + 0xcf, 0xc6, 0xeb, 0x7d, 0x70, 0x78, 0xe9, 0x7f, 0xa8, 0xfd, 0x18, 0x7a, 0xd4, 0x02, 0xec, 0x58, + 0xc0, 0xdb, 0x1b, 0x01, 0x5d, 0x7a, 0x93, 0x70, 0xf4, 0xbe, 0x03, 0xbb, 0x96, 0x10, 0x4d, 0x01, + 0xec, 0xda, 0x3c, 0x74, 0xf4, 0x17, 0x9a, 0x3f, 0x2e, 0x2a, 0x1c, 0x6c, 0xa1, 0x74, 0xa1, 0xf1, + 0x8d, 0xe9, 0xe7, 0xef, 0xef, 0x3a, 0x11, 0xea, 0x93, 0xf5, 0x8f, 0xc3, 0x1e, 0x0c, 0xbd, 0x01, + 0xb0, 0xe7, 0xba, 0x42, 0x9b, 0xbd, 0x57, 0xd7, 0x15, 0x0e, 0xb7, 0x91, 0x7a, 0x8e, 0x9b, 0x96, + 0xe3, 0x00, 0x5d, 0xfb, 0x17, 0x87, 0x3e, 0x7e, 0x3c, 0x5b, 0x44, 0x60, 0xbe, 0x88, 0xc0, 0xb7, + 0x45, 0x04, 0xde, 0x2e, 0xa3, 0x60, 0xbe, 0x8c, 0x82, 0x2f, 0xcb, 0x28, 0x78, 0x3e, 0xe2, 0xc2, + 0x9c, 0x95, 0x29, 0x3e, 0x51, 0x99, 0xb5, 0xb8, 0x23, 0x99, 0x79, 0xa5, 0x26, 0x2f, 0x9d, 0xdf, + 0xeb, 0xdf, 0x1c, 0xcd, 0x45, 0xce, 0x74, 0xda, 0xb3, 0x8f, 0xfd, 0xee, 0xcf, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x83, 0x59, 0xeb, 0x98, 0xc6, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Denom queries liquid vesting token info by denom + Denom(ctx context.Context, in *QueryDenomRequest, opts ...grpc.CallOption) (*QueryDenomResponse, error) + // Denoms queries liquid vesting tokens info + Denoms(ctx context.Context, in *QueryDenomsRequest, opts ...grpc.CallOption) (*QueryDenomsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Denom(ctx context.Context, in *QueryDenomRequest, opts ...grpc.CallOption) (*QueryDenomResponse, error) { + out := new(QueryDenomResponse) + err := c.cc.Invoke(ctx, "/haqq.liquidvesting.v1.Query/Denom", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Denoms(ctx context.Context, in *QueryDenomsRequest, opts ...grpc.CallOption) (*QueryDenomsResponse, error) { + out := new(QueryDenomsResponse) + err := c.cc.Invoke(ctx, "/haqq.liquidvesting.v1.Query/Denoms", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Denom queries liquid vesting token info by denom + Denom(context.Context, *QueryDenomRequest) (*QueryDenomResponse, error) + // Denoms queries liquid vesting tokens info + Denoms(context.Context, *QueryDenomsRequest) (*QueryDenomsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Denom(ctx context.Context, req *QueryDenomRequest) (*QueryDenomResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Denom not implemented") +} +func (*UnimplementedQueryServer) Denoms(ctx context.Context, req *QueryDenomsRequest) (*QueryDenomsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Denoms not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Denom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Denom(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/haqq.liquidvesting.v1.Query/Denom", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Denom(ctx, req.(*QueryDenomRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Denoms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Denoms(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/haqq.liquidvesting.v1.Query/Denoms", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Denoms(ctx, req.(*QueryDenomsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "haqq.liquidvesting.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Denom", + Handler: _Query_Denom_Handler, + }, + { + MethodName: "Denoms", + Handler: _Query_Denoms_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "haqq/liquidvesting/v1/query.proto", +} + +func (m *QueryDenomRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Denom.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryDenomsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Denoms) > 0 { + for iNdEx := len(m.Denoms) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Denoms[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryDenomRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Denom.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryDenomsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Denoms) > 0 { + for _, e := range m.Denoms { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryDenomRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Denom.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denoms", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denoms = append(m.Denoms, Denom{}) + if err := m.Denoms[len(m.Denoms)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/liquidvesting/types/query.pb.gw.go b/x/liquidvesting/types/query.pb.gw.go new file mode 100644 index 000000000..6c90d3e56 --- /dev/null +++ b/x/liquidvesting/types/query.pb.gw.go @@ -0,0 +1,254 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: haqq/liquidvesting/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +var ( + filter_Query_Denom_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Denom_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Denom_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Denom(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Denom_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Denom_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Denom(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_Denoms_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Denoms_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Denoms_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Denoms(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Denoms_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Denoms_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Denoms(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Denom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Denom_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denom_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Denoms_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Denoms_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denoms_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Denom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Denom_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denom_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Denoms_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Denoms_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denoms_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Denom_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"haqq", "liquidvesting", "v1", "denom"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Denoms_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"haqq", "liquidvesting", "v1", "denoms"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Denom_0 = runtime.ForwardResponseMessage + + forward_Query_Denoms_0 = runtime.ForwardResponseMessage +) diff --git a/x/liquidvesting/types/schedule.go b/x/liquidvesting/types/schedule.go new file mode 100644 index 000000000..88c484209 --- /dev/null +++ b/x/liquidvesting/types/schedule.go @@ -0,0 +1,102 @@ +package types + +import ( + "errors" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + vestingTypes "github.com/haqq-network/haqq/x/vesting/types" +) + +// SubtractAmountFromPeriods subtracts coin amount from given periods proportionally, +// returns decreased periods and diff of initial periods and decreased periods +func SubtractAmountFromPeriods( + minuendPeriods sdkvesting.Periods, + subtrahend sdk.Coin, +) (decreasedPeriods, diffPeriods sdkvesting.Periods, err error) { + minuendDenom := subtrahend.Denom + minuendTotalAmount := minuendPeriods.TotalAmount().AmountOf(minuendDenom) + subtrahendAmount := subtrahend.Amount + + if minuendTotalAmount.LT(subtrahendAmount) { + return nil, nil, errors.New("insufficient locked up funds") + } + + decreasedPeriods = make(sdkvesting.Periods, len(minuendPeriods)) + diffPeriods = make(sdkvesting.Periods, 0, len(minuendPeriods)) + copy(decreasedPeriods, minuendPeriods) + + totalSubtracted := math.NewInt(0) + for i, minuendPeriod := range decreasedPeriods { + minuendCoinAmount := minuendPeriod.Amount.AmountOf(minuendDenom) + subtractedAmount := minuendCoinAmount.Mul(subtrahendAmount).Quo(minuendTotalAmount) + subtrahendCoin := sdk.NewCoin(minuendDenom, subtractedAmount) + decreasedPeriods[i].Amount = minuendPeriod.Amount.Sub(subtrahendCoin) + totalSubtracted = totalSubtracted.Add(subtractedAmount) + diffPeriods = append(diffPeriods, sdkvesting.Period{ + Length: minuendPeriod.Length, + Amount: sdk.NewCoins(subtrahendCoin), + }) + } + + // subtract residue from decreased periods tail + // and add it to diff tail + if len(decreasedPeriods) > 0 { + residue := subtrahendAmount.Sub(totalSubtracted) + for i := len(decreasedPeriods) - 1; i >= 0; i-- { + periodMinuendDenomAmount := decreasedPeriods[i].Amount.AmountOf(minuendDenom) + if periodMinuendDenomAmount.LT(residue) { + decreasedPeriods[i].Amount = decreasedPeriods[i].Amount.Sub(sdk.NewCoin(minuendDenom, periodMinuendDenomAmount)) + diffPeriods[i].Amount = diffPeriods[i].Amount.Add(sdk.NewCoin(minuendDenom, periodMinuendDenomAmount)) + residue = residue.Sub(periodMinuendDenomAmount) + continue + } + decreasedPeriods[i].Amount = decreasedPeriods[i].Amount.Sub(sdk.NewCoin(minuendDenom, residue)) + diffPeriods[i].Amount = diffPeriods[i].Amount.Add(sdk.NewCoin(minuendDenom, residue)) + break + } + } + + return decreasedPeriods, diffPeriods, nil +} + +// ExtractUpcomingPeriods takes the list of periods with started time and +// returns list of periods which are currently upcoming +func ExtractUpcomingPeriods(startDate, endDate int64, periods sdkvesting.Periods, readTime int64) sdkvesting.Periods { + pastPeriodsCount := vestingTypes.ReadPastPeriodCount(startDate, endDate, periods, readTime) + upcomingPeriods := make(sdkvesting.Periods, len(periods)-pastPeriodsCount) + copy(upcomingPeriods, periods[pastPeriodsCount:]) + + return upcomingPeriods +} + +// ReplacePeriodsTail replaces the last N periods in original periods list with replacements period list +// where N is length of replacement list +func ReplacePeriodsTail(periods, replacement sdkvesting.Periods) sdkvesting.Periods { + replacedPeriods := make(sdkvesting.Periods, 0, len(periods)) + if len(replacement) >= len(periods) { + replacedPeriods = append(replacedPeriods, replacement...) + return replacedPeriods + } + replacedPeriods = append(replacedPeriods, periods[:len(periods)-len(replacement)]...) + replacedPeriods = append(replacedPeriods, replacement...) + + return replacedPeriods +} + +// CurrentPeriodShift calculates how much time has passed since the beginning of the current period +func CurrentPeriodShift(startTime, currentTime int64, periods sdkvesting.Periods) int64 { + if startTime >= currentTime { + return 0 + } + elapsedTime := startTime + for _, period := range periods { + if elapsedTime+period.Length >= currentTime { + return currentTime - elapsedTime + } + elapsedTime += period.Length + } + + return 0 +} diff --git a/x/liquidvesting/types/schedule_test.go b/x/liquidvesting/types/schedule_test.go new file mode 100644 index 000000000..1f06ffbe8 --- /dev/null +++ b/x/liquidvesting/types/schedule_test.go @@ -0,0 +1,321 @@ +package types + +import ( + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/stretchr/testify/suite" +) + +type ScheduleTestSuite struct { + suite.Suite +} + +func TestScheduleSuite(t *testing.T) { + suite.Run(t, new(ScheduleTestSuite)) +} + +func (suite *ScheduleTestSuite) TestSubtractAmountFromPeriods() { + testCases := []struct { + name string + minuendPeriods sdkvesting.Periods + subtrahend sdk.Coin + expectedDecreased sdkvesting.Periods + expectedDiff sdkvesting.Periods + expectError bool + }{ + { + name: " OK Standard subtraction without residue", + minuendPeriods: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(300)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(300)))}, + }, + subtrahend: sdk.NewCoin("test", math.NewInt(200)), + expectedDecreased: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(200)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(200)))}, + }, + expectedDiff: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(100)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(100)))}, + }, + expectError: false, + }, + { + name: "OK Standard subtraction with residue", + minuendPeriods: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(10)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(20)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(30)))}, + }, + subtrahend: sdk.NewCoin("test", math.NewInt(20)), + expectedDecreased: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(7)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(14)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(19)))}, + }, + expectedDiff: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(3)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(6)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(11)))}, + }, + expectError: false, + }, + { + name: "OK Subtract zero from empty periods", + minuendPeriods: []sdkvesting.Period{}, + subtrahend: sdk.NewCoin("test", math.NewInt(0)), + expectedDecreased: []sdkvesting.Period{}, + expectedDiff: []sdkvesting.Period{}, + expectError: false, + }, + { + name: "OK Standard subtraction with residue and little last period to hold whole residue", + minuendPeriods: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(100)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(200)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(2)))}, + }, + subtrahend: sdk.NewCoin("test", math.NewInt(220)), + expectedDecreased: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(28)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(54)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(0)))}, + }, + expectedDiff: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(72)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(146)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(2)))}, + }, + expectError: false, + }, + { + name: "FAIL Subtrahend is bigger than total periods amount", + minuendPeriods: []sdkvesting.Period{ + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(10)))}, + {Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(20)))}, + }, + subtrahend: sdk.NewCoin("test", math.NewInt(50)), + expectedDecreased: []sdkvesting.Period{}, + expectedDiff: []sdkvesting.Period{}, + expectError: true, + }, + { + name: "FAIL Subtrahend is bigger than total periods amount, and periods are empty", + minuendPeriods: []sdkvesting.Period{}, + subtrahend: sdk.NewCoin("test", math.NewInt(50)), + expectedDecreased: []sdkvesting.Period{}, + expectedDiff: []sdkvesting.Period{}, + expectError: true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + decreased, diff, err := SubtractAmountFromPeriods(tc.minuendPeriods, tc.subtrahend) + + if tc.expectError { + suite.Error(err) + } else { + suite.NoError(err) + suite.Require().Equal(tc.expectedDecreased, decreased) + suite.Require().Equal(tc.expectedDiff, diff) + } + }) + } +} + +func (suite *ScheduleTestSuite) TestExtractUpcomingPeriods() { + testCases := []struct { + name string + startDate int64 + endDate int64 + periods sdkvesting.Periods + readTime int64 + expectedPeriods sdkvesting.Periods + }{ + { + name: "Standard extraction", + startDate: 1700000000, + endDate: 1700000400, + periods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + {Length: 100}, + }, + readTime: 1700000200, + expectedPeriods: []sdkvesting.Period{ + {Length: 100}, + }, + }, + { + name: "No upcoming periods", + startDate: 1700000000, + endDate: 1700000400, + periods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + {Length: 100}, + }, + readTime: 1700000500, + expectedPeriods: []sdkvesting.Period{}, + }, + { + name: "Full extraction", + startDate: 1700000000, + endDate: 1700000200, + periods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + }, + readTime: 1600000000, + expectedPeriods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + result := ExtractUpcomingPeriods(tc.startDate, tc.endDate, tc.periods, tc.readTime) + suite.Require().Equal(tc.expectedPeriods, result) + }) + } +} + +func (suite *ScheduleTestSuite) TestReplacePeriodsTail() { + testCases := []struct { + name string + periods sdkvesting.Periods + replacement sdkvesting.Periods + expectedPeriods sdkvesting.Periods + }{ + { + name: "Simple replacement", + periods: []sdkvesting.Period{ + {Length: 200}, + {Length: 200}, + {Length: 200}, + }, + replacement: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + }, + expectedPeriods: []sdkvesting.Period{ + {Length: 200}, + {Length: 100}, + {Length: 100}, + }, + }, + { + name: "Full replacement", + periods: []sdkvesting.Period{ + {Length: 200}, + {Length: 200}, + {Length: 200}, + }, + replacement: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + {Length: 100}, + }, + expectedPeriods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + {Length: 100}, + }, + }, + { + name: "Over replacement", + periods: []sdkvesting.Period{ + {Length: 200}, + {Length: 200}, + {Length: 200}, + }, + replacement: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + {Length: 100}, + {Length: 100}, + }, + expectedPeriods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + {Length: 100}, + {Length: 100}, + }, + }, + { + name: "No replacement", + periods: []sdkvesting.Period{ + {Length: 200}, + {Length: 200}, + {Length: 200}, + }, + replacement: []sdkvesting.Period{}, + expectedPeriods: []sdkvesting.Period{ + {Length: 200}, + {Length: 200}, + {Length: 200}, + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + result := ReplacePeriodsTail(tc.periods, tc.replacement) + suite.Require().Equal(tc.expectedPeriods, result) + }) + } +} + +func (suite *ScheduleTestSuite) TestCurrentPeriodShift() { + testCases := []struct { + name string + startTime int64 + currentTime int64 + periods sdkvesting.Periods + expectedShift int64 + }{ + { + name: "Standard shift", + startTime: 1700000000, + currentTime: 1700000150, + periods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + }, + expectedShift: 50, + }, + { + name: "Total length is less than start time and current time diff", + startTime: 1700000000, + currentTime: 1700000250, + periods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + }, + expectedShift: 0, + }, + { + name: "Current time before start time", + startTime: 1700000000, + currentTime: 1600000000, + periods: []sdkvesting.Period{ + {Length: 100}, + {Length: 100}, + }, + expectedShift: 0, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + shift := CurrentPeriodShift(tc.startTime, tc.currentTime, tc.periods) + suite.Require().Equal(tc.expectedShift, shift) + }) + } +} diff --git a/x/liquidvesting/types/tx.pb.go b/x/liquidvesting/types/tx.pb.go new file mode 100644 index 000000000..811d6eea2 --- /dev/null +++ b/x/liquidvesting/types/tx.pb.go @@ -0,0 +1,1086 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: haqq/liquidvesting/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgLiquidate represents message to liquidate arbitrary amount of tokens locked in vesting +type MsgLiquidate struct { + // account for liquidation of locked vesting tokens + LiquidateFrom string `protobuf:"bytes,1,opt,name=liquidate_from,json=liquidateFrom,proto3" json:"liquidate_from,omitempty"` + // account to send resulted liquid token + LiquidateTo string `protobuf:"bytes,2,opt,name=liquidate_to,json=liquidateTo,proto3" json:"liquidate_to,omitempty"` + // amount of tokens subject for liquidation + Amount types.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount"` +} + +func (m *MsgLiquidate) Reset() { *m = MsgLiquidate{} } +func (m *MsgLiquidate) String() string { return proto.CompactTextString(m) } +func (*MsgLiquidate) ProtoMessage() {} +func (*MsgLiquidate) Descriptor() ([]byte, []int) { + return fileDescriptor_cfdce5e8d421730a, []int{0} +} +func (m *MsgLiquidate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgLiquidate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgLiquidate.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgLiquidate) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgLiquidate.Merge(m, src) +} +func (m *MsgLiquidate) XXX_Size() int { + return m.Size() +} +func (m *MsgLiquidate) XXX_DiscardUnknown() { + xxx_messageInfo_MsgLiquidate.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgLiquidate proto.InternalMessageInfo + +func (m *MsgLiquidate) GetLiquidateFrom() string { + if m != nil { + return m.LiquidateFrom + } + return "" +} + +func (m *MsgLiquidate) GetLiquidateTo() string { + if m != nil { + return m.LiquidateTo + } + return "" +} + +func (m *MsgLiquidate) GetAmount() types.Coin { + if m != nil { + return m.Amount + } + return types.Coin{} +} + +// MsgLiquidateResponse defines the Msg/Liquidate response type +type MsgLiquidateResponse struct { +} + +func (m *MsgLiquidateResponse) Reset() { *m = MsgLiquidateResponse{} } +func (m *MsgLiquidateResponse) String() string { return proto.CompactTextString(m) } +func (*MsgLiquidateResponse) ProtoMessage() {} +func (*MsgLiquidateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cfdce5e8d421730a, []int{1} +} +func (m *MsgLiquidateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgLiquidateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgLiquidateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgLiquidateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgLiquidateResponse.Merge(m, src) +} +func (m *MsgLiquidateResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgLiquidateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgLiquidateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgLiquidateResponse proto.InternalMessageInfo + +// MsgLiquidate represents message to redeem arbitrary amount of liquid vesting tokens +type MsgRedeem struct { + RedeemFrom string `protobuf:"bytes,1,opt,name=redeem_from,json=redeemFrom,proto3" json:"redeem_from,omitempty"` + // destination address for vesting tokens + RedeemTo string `protobuf:"bytes,2,opt,name=redeem_to,json=redeemTo,proto3" json:"redeem_to,omitempty"` + // amount of vesting tokens to redeem from liquidation module + Amount types.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount"` +} + +func (m *MsgRedeem) Reset() { *m = MsgRedeem{} } +func (m *MsgRedeem) String() string { return proto.CompactTextString(m) } +func (*MsgRedeem) ProtoMessage() {} +func (*MsgRedeem) Descriptor() ([]byte, []int) { + return fileDescriptor_cfdce5e8d421730a, []int{2} +} +func (m *MsgRedeem) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRedeem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRedeem.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRedeem) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRedeem.Merge(m, src) +} +func (m *MsgRedeem) XXX_Size() int { + return m.Size() +} +func (m *MsgRedeem) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRedeem.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRedeem proto.InternalMessageInfo + +func (m *MsgRedeem) GetRedeemFrom() string { + if m != nil { + return m.RedeemFrom + } + return "" +} + +func (m *MsgRedeem) GetRedeemTo() string { + if m != nil { + return m.RedeemTo + } + return "" +} + +func (m *MsgRedeem) GetAmount() types.Coin { + if m != nil { + return m.Amount + } + return types.Coin{} +} + +// MsgRedeemResponse defines the Msg/Redeem response type +type MsgRedeemResponse struct { +} + +func (m *MsgRedeemResponse) Reset() { *m = MsgRedeemResponse{} } +func (m *MsgRedeemResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRedeemResponse) ProtoMessage() {} +func (*MsgRedeemResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cfdce5e8d421730a, []int{3} +} +func (m *MsgRedeemResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRedeemResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRedeemResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRedeemResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRedeemResponse.Merge(m, src) +} +func (m *MsgRedeemResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRedeemResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRedeemResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRedeemResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgLiquidate)(nil), "haqq.liquidvesting.v1.MsgLiquidate") + proto.RegisterType((*MsgLiquidateResponse)(nil), "haqq.liquidvesting.v1.MsgLiquidateResponse") + proto.RegisterType((*MsgRedeem)(nil), "haqq.liquidvesting.v1.MsgRedeem") + proto.RegisterType((*MsgRedeemResponse)(nil), "haqq.liquidvesting.v1.MsgRedeemResponse") +} + +func init() { proto.RegisterFile("haqq/liquidvesting/v1/tx.proto", fileDescriptor_cfdce5e8d421730a) } + +var fileDescriptor_cfdce5e8d421730a = []byte{ + // 444 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x92, 0x41, 0x6b, 0x13, 0x41, + 0x14, 0xc7, 0x33, 0x29, 0x04, 0x77, 0x52, 0x85, 0xae, 0x55, 0x62, 0x94, 0x69, 0xdc, 0x52, 0x58, + 0x2c, 0xce, 0xb0, 0xf1, 0xea, 0xa9, 0x82, 0xa7, 0xe6, 0xb2, 0x78, 0xf2, 0x22, 0xb3, 0xe9, 0x38, + 0x1d, 0xcc, 0xce, 0xdb, 0xec, 0x4c, 0xd6, 0x7a, 0xf1, 0xe0, 0x45, 0xf0, 0x24, 0x0a, 0x7e, 0x06, + 0x8f, 0x7e, 0x8c, 0x1e, 0x0b, 0x5e, 0x3c, 0x89, 0x24, 0x82, 0x5f, 0x43, 0x32, 0xb3, 0xdd, 0x46, + 0x31, 0x28, 0x78, 0x59, 0xde, 0xfe, 0xdf, 0x7f, 0xdf, 0xfc, 0xf6, 0x3f, 0x0f, 0x93, 0x63, 0x3e, + 0x9d, 0xb2, 0x89, 0x9a, 0xce, 0xd4, 0x51, 0x25, 0x8c, 0x55, 0x5a, 0xb2, 0x2a, 0x61, 0xf6, 0x84, + 0x16, 0x25, 0x58, 0x08, 0xaf, 0x2d, 0xfb, 0xf4, 0x97, 0x3e, 0xad, 0x92, 0xfe, 0x16, 0xcf, 0x95, + 0x06, 0xe6, 0x9e, 0xde, 0xd9, 0xdf, 0x96, 0x20, 0xc1, 0x95, 0x6c, 0x59, 0xd5, 0xea, 0x2d, 0x09, + 0x20, 0x27, 0x82, 0xf1, 0x42, 0x31, 0xae, 0x35, 0x58, 0x6e, 0x15, 0x68, 0x53, 0x77, 0xc9, 0x18, + 0x4c, 0x0e, 0x86, 0x65, 0xdc, 0x08, 0x56, 0x25, 0x99, 0xb0, 0x3c, 0x61, 0x63, 0x50, 0xda, 0xf7, + 0xa3, 0x0f, 0x08, 0x6f, 0x8e, 0x8c, 0x3c, 0x74, 0xc7, 0x73, 0x2b, 0xc2, 0x3d, 0x7c, 0x65, 0x72, + 0xfe, 0xf2, 0xe4, 0x69, 0x09, 0x79, 0x0f, 0x0d, 0x50, 0x1c, 0xa4, 0x97, 0x1b, 0xf5, 0x61, 0x09, + 0x79, 0x78, 0x1b, 0x6f, 0x5e, 0xd8, 0x2c, 0xf4, 0xda, 0xce, 0xd4, 0x6d, 0xb4, 0x47, 0x10, 0xde, + 0xc7, 0x1d, 0x9e, 0xc3, 0x4c, 0xdb, 0xde, 0xc6, 0x00, 0xc5, 0xdd, 0xe1, 0x0d, 0xea, 0x59, 0xe8, + 0x92, 0x85, 0xd6, 0x2c, 0xf4, 0x01, 0x28, 0x7d, 0x10, 0x9c, 0x7e, 0xdd, 0x69, 0x7d, 0xfc, 0xf1, + 0xe9, 0x0e, 0x4a, 0xeb, 0x6f, 0xa2, 0xeb, 0x78, 0x7b, 0x95, 0x2b, 0x15, 0xa6, 0x00, 0x6d, 0x44, + 0xf4, 0x1a, 0xe1, 0x60, 0x64, 0x64, 0x2a, 0x8e, 0x84, 0xc8, 0xc3, 0x1d, 0xdc, 0x2d, 0x5d, 0xb5, + 0x8a, 0x8a, 0xbd, 0xe4, 0x38, 0x6f, 0xe2, 0xa0, 0x36, 0x34, 0x90, 0x97, 0xbc, 0xf0, 0xdf, 0x84, + 0x57, 0xf1, 0x56, 0x03, 0x72, 0x8e, 0x37, 0x7c, 0xd7, 0xc6, 0x1b, 0x23, 0x23, 0xc3, 0x37, 0x08, + 0x07, 0x17, 0xa1, 0xee, 0xd2, 0x3f, 0x5e, 0x32, 0x5d, 0xfd, 0xc3, 0xfe, 0xfe, 0x3f, 0x98, 0x9a, + 0x18, 0xf6, 0x5f, 0x7d, 0xfe, 0xfe, 0xbe, 0xbd, 0x17, 0xed, 0xb2, 0x75, 0xeb, 0xc5, 0x9a, 0xbb, + 0x08, 0x5f, 0xe2, 0x4e, 0x9d, 0xd7, 0x60, 0xfd, 0x19, 0xde, 0xd1, 0x8f, 0xff, 0xe6, 0x68, 0x10, + 0x62, 0x87, 0x10, 0x45, 0x83, 0xf5, 0x08, 0x3e, 0xe9, 0x83, 0xc3, 0xd3, 0x39, 0x41, 0x67, 0x73, + 0x82, 0xbe, 0xcd, 0x09, 0x7a, 0xbb, 0x20, 0xad, 0xb3, 0x05, 0x69, 0x7d, 0x59, 0x90, 0xd6, 0xe3, + 0xa1, 0x54, 0xf6, 0x78, 0x96, 0xd1, 0x31, 0xe4, 0x6e, 0xca, 0x5d, 0x2d, 0xec, 0x73, 0x28, 0x9f, + 0xf9, 0x91, 0x27, 0xbf, 0x0d, 0xb5, 0x2f, 0x0a, 0x61, 0xb2, 0x8e, 0xdb, 0xdc, 0x7b, 0x3f, 0x03, + 0x00, 0x00, 0xff, 0xff, 0x0c, 0xa4, 0x9a, 0x8a, 0x59, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // Liquidate transforms specified amount of tokens locked on vesting account into a new liquid token + Liquidate(ctx context.Context, in *MsgLiquidate, opts ...grpc.CallOption) (*MsgLiquidateResponse, error) + // Redeem burns liquid token and deposits corresponding amount of vesting token to the specified account + Redeem(ctx context.Context, in *MsgRedeem, opts ...grpc.CallOption) (*MsgRedeemResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) Liquidate(ctx context.Context, in *MsgLiquidate, opts ...grpc.CallOption) (*MsgLiquidateResponse, error) { + out := new(MsgLiquidateResponse) + err := c.cc.Invoke(ctx, "/haqq.liquidvesting.v1.Msg/Liquidate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) Redeem(ctx context.Context, in *MsgRedeem, opts ...grpc.CallOption) (*MsgRedeemResponse, error) { + out := new(MsgRedeemResponse) + err := c.cc.Invoke(ctx, "/haqq.liquidvesting.v1.Msg/Redeem", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // Liquidate transforms specified amount of tokens locked on vesting account into a new liquid token + Liquidate(context.Context, *MsgLiquidate) (*MsgLiquidateResponse, error) + // Redeem burns liquid token and deposits corresponding amount of vesting token to the specified account + Redeem(context.Context, *MsgRedeem) (*MsgRedeemResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) Liquidate(ctx context.Context, req *MsgLiquidate) (*MsgLiquidateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Liquidate not implemented") +} +func (*UnimplementedMsgServer) Redeem(ctx context.Context, req *MsgRedeem) (*MsgRedeemResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Redeem not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_Liquidate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgLiquidate) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Liquidate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/haqq.liquidvesting.v1.Msg/Liquidate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Liquidate(ctx, req.(*MsgLiquidate)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_Redeem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRedeem) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Redeem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/haqq.liquidvesting.v1.Msg/Redeem", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Redeem(ctx, req.(*MsgRedeem)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "haqq.liquidvesting.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Liquidate", + Handler: _Msg_Liquidate_Handler, + }, + { + MethodName: "Redeem", + Handler: _Msg_Redeem_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "haqq/liquidvesting/v1/tx.proto", +} + +func (m *MsgLiquidate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgLiquidate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgLiquidate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.LiquidateTo) > 0 { + i -= len(m.LiquidateTo) + copy(dAtA[i:], m.LiquidateTo) + i = encodeVarintTx(dAtA, i, uint64(len(m.LiquidateTo))) + i-- + dAtA[i] = 0x12 + } + if len(m.LiquidateFrom) > 0 { + i -= len(m.LiquidateFrom) + copy(dAtA[i:], m.LiquidateFrom) + i = encodeVarintTx(dAtA, i, uint64(len(m.LiquidateFrom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgLiquidateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgLiquidateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgLiquidateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgRedeem) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRedeem) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRedeem) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.RedeemTo) > 0 { + i -= len(m.RedeemTo) + copy(dAtA[i:], m.RedeemTo) + i = encodeVarintTx(dAtA, i, uint64(len(m.RedeemTo))) + i-- + dAtA[i] = 0x12 + } + if len(m.RedeemFrom) > 0 { + i -= len(m.RedeemFrom) + copy(dAtA[i:], m.RedeemFrom) + i = encodeVarintTx(dAtA, i, uint64(len(m.RedeemFrom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRedeemResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRedeemResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRedeemResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgLiquidate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.LiquidateFrom) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.LiquidateTo) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgLiquidateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgRedeem) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RedeemFrom) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.RedeemTo) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgRedeemResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgLiquidate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgLiquidate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgLiquidate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LiquidateFrom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LiquidateFrom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LiquidateTo", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LiquidateTo = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgLiquidateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgLiquidateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgLiquidateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRedeem) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRedeem: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRedeem: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RedeemFrom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RedeemFrom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RedeemTo", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RedeemTo = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRedeemResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRedeemResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRedeemResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/liquidvesting/types/tx.pb.gw.go b/x/liquidvesting/types/tx.pb.gw.go new file mode 100644 index 000000000..813814b99 --- /dev/null +++ b/x/liquidvesting/types/tx.pb.gw.go @@ -0,0 +1,254 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: haqq/liquidvesting/v1/tx.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +var ( + filter_Msg_Liquidate_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Msg_Liquidate_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgLiquidate + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_Liquidate_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Liquidate(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Msg_Liquidate_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgLiquidate + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_Liquidate_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Liquidate(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Msg_Redeem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Msg_Redeem_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgRedeem + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_Redeem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Redeem(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Msg_Redeem_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgRedeem + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_Redeem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Redeem(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterMsgHandlerServer registers the http handlers for service Msg to "mux". +// UnaryRPC :call MsgServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterMsgHandlerFromEndpoint instead. +func RegisterMsgHandlerServer(ctx context.Context, mux *runtime.ServeMux, server MsgServer) error { + + mux.Handle("POST", pattern_Msg_Liquidate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Msg_Liquidate_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_Liquidate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Msg_Redeem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Msg_Redeem_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_Redeem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterMsgHandlerFromEndpoint is same as RegisterMsgHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterMsgHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterMsgHandler(ctx, mux, conn) +} + +// RegisterMsgHandler registers the http handlers for service Msg to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterMsgHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterMsgHandlerClient(ctx, mux, NewMsgClient(conn)) +} + +// RegisterMsgHandlerClient registers the http handlers for service Msg +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "MsgClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "MsgClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "MsgClient" to call the correct interceptors. +func RegisterMsgHandlerClient(ctx context.Context, mux *runtime.ServeMux, client MsgClient) error { + + mux.Handle("POST", pattern_Msg_Liquidate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Msg_Liquidate_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_Liquidate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Msg_Redeem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Msg_Redeem_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_Redeem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Msg_Liquidate_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"haqq", "liquidvesting", "v1", "tx", "liquidate"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Msg_Redeem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"haqq", "liquidvesting", "v1", "tx", "redeem"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Msg_Liquidate_0 = runtime.ForwardResponseMessage + + forward_Msg_Redeem_0 = runtime.ForwardResponseMessage +) diff --git a/x/vesting/client/cli/tx.go b/x/vesting/client/cli/tx.go index e72839898..a619a53b4 100644 --- a/x/vesting/client/cli/tx.go +++ b/x/vesting/client/cli/tx.go @@ -302,7 +302,7 @@ func NewMsgConvertIntoVestingAccountCmd() *cobra.Command { // valAddr, err := sdk.ValAddressFromBech32(args[0]) valAddrStr, err := cmd.Flags().GetString(FlagValidator) - if err != nil { + if err != nil || valAddrStr == "" { staking = false valAddr = sdk.ValAddress{} } else { diff --git a/x/vesting/keeper/grpc_query.go b/x/vesting/keeper/grpc_query.go index 705b2eee5..f16ae9afb 100644 --- a/x/vesting/keeper/grpc_query.go +++ b/x/vesting/keeper/grpc_query.go @@ -6,11 +6,14 @@ import ( "os" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/spf13/cast" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "github.com/haqq-network/haqq/utils" + liquidvestingtypes "github.com/haqq-network/haqq/x/liquidvesting/types" "github.com/haqq-network/haqq/x/vesting/types" ) @@ -98,6 +101,14 @@ func (k Keeper) TotalLocked( return false }) + lvmAcc := k.accountKeeper.GetModuleAccount(ctx, liquidvestingtypes.ModuleName) + if lvmAcc == nil { + panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", lvmAcc)) + } + + escrowedLiquidBalance := k.bankKeeper.GetBalance(ctx, lvmAcc.GetAddress(), utils.BaseDenom) + totalLocked = totalLocked.Add(escrowedLiquidBalance) + return &types.QueryTotalLockedResponse{ Locked: totalLocked, Unvested: totalUnvested, diff --git a/x/vesting/keeper/msg_server.go b/x/vesting/keeper/msg_server.go index 0e7e60889..9c5d8dde2 100644 --- a/x/vesting/keeper/msg_server.go +++ b/x/vesting/keeper/msg_server.go @@ -309,7 +309,6 @@ func (k Keeper) ConvertIntoVestingAccount( msg *types.MsgConvertIntoVestingAccount, ) (*types.MsgConvertIntoVestingAccountResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - ak := k.accountKeeper bk := k.bankKeeper var ( @@ -364,75 +363,19 @@ func (k Keeper) ConvertIntoVestingAccount( ) } - createNewAcc := false - targetAccount := k.accountKeeper.GetAccount(ctx, to) - - if targetAccount == nil { - createNewAcc = true - } - - madeNewAcc := false - var vestingAcc *types.ClawbackVestingAccount - var isClawback bool - - ethAcc, isEthAccount := targetAccount.(*ethtypes.EthAccount) - vestingAcc, isClawback = targetAccount.(*types.ClawbackVestingAccount) - - if isClawback && !msg.Merge { - return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "account %s already exists; consider using --merge", msg.ToAddress) - } - - codeHash := common.BytesToHash(crypto.Keccak256(nil)) - if isEthAccount { - codeHash = ethAcc.GetCodeHash() - } - - switch { - case createNewAcc: - baseAcc := authtypes.NewBaseAccountWithAddress(to) - vestingAcc = types.NewClawbackVestingAccount( - baseAcc, - from, - vestingCoins, - msg.StartTime, - msg.LockupPeriods, - msg.VestingPeriods, - &codeHash, - ) - acc := ak.NewAccount(ctx, vestingAcc) - ak.SetAccount(ctx, acc) - - madeNewAcc = true - case !isClawback && !isEthAccount: - return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "account %s already exists but can't be converted into vesting account", msg.ToAddress) - case !isClawback && isEthAccount: - baseAcc := ethAcc.GetBaseAccount() - vestingAcc = types.NewClawbackVestingAccount( - baseAcc, - from, - vestingCoins, - msg.StartTime, - msg.LockupPeriods, - msg.VestingPeriods, - &codeHash, - ) - acc := ak.NewAccount(ctx, vestingAcc) - ak.SetAccount(ctx, acc) - case isClawback && msg.Merge: - if msg.FromAddress != vestingAcc.FunderAddress { - return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "account %s can only accept grants from account %s", msg.ToAddress, vestingAcc.FunderAddress) - } - - err := k.addGrant(ctx, vestingAcc, types.Min64(msg.StartTime.Unix(), vestingAcc.StartTime.Unix()), msg.LockupPeriods, msg.VestingPeriods, vestingCoins) - if err != nil { - return nil, err - } - ak.SetAccount(ctx, vestingAcc) - default: - return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "failed to initiate vesting for account %s", msg.ToAddress) + vestingAcc, newAccountCreated, wasMerged, err := k.ApplyVestingSchedule( + ctx, + from, to, + vestingCoins, + msg.StartTime, + msg.LockupPeriods, msg.VestingPeriods, + msg.Merge, + ) + if err != nil { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "failed to apply schedule: %s", err.Error()) } - if madeNewAcc { + if newAccountCreated { defer func() { telemetry.IncrCounter(1, "new", "account") }() @@ -449,7 +392,7 @@ func (k Keeper) ConvertIntoVestingAccount( sdk.NewAttribute(sdk.AttributeKeySender, from.String()), sdk.NewAttribute(types.AttributeKeyCoins, vestingCoins.String()), sdk.NewAttribute(types.AttributeKeyStartTime, vestingAcc.StartTime.String()), - sdk.NewAttribute(types.AttributeKeyMerge, strconv.FormatBool(isClawback)), + sdk.NewAttribute(types.AttributeKeyMerge, strconv.FormatBool(wasMerged)), sdk.NewAttribute(types.AttributeKeyAccount, vestingAcc.Address), ), // sdk.NewEvent( diff --git a/x/vesting/keeper/schedule.go b/x/vesting/keeper/schedule.go new file mode 100644 index 000000000..171e7ecf1 --- /dev/null +++ b/x/vesting/keeper/schedule.go @@ -0,0 +1,95 @@ +package keeper + +import ( + "time" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + ethtypes "github.com/haqq-network/haqq/types" + "github.com/haqq-network/haqq/x/vesting/types" +) + +// ApplyVestingSchedule takes funder and funded addresses +func (k Keeper) ApplyVestingSchedule( + ctx sdk.Context, + funder, funded sdk.AccAddress, + coins sdk.Coins, + startTime time.Time, + lockupPeriods, vestingPeriods sdkvesting.Periods, + merge bool, +) (vestingAcc *types.ClawbackVestingAccount, newAccountCreated, wasMerged bool, err error) { + targetAccount := k.accountKeeper.GetAccount(ctx, funded) + createNewAcc := targetAccount == nil + + var isClawback bool + + ethAcc, isEthAccount := targetAccount.(*ethtypes.EthAccount) + vestingAcc, isClawback = targetAccount.(*types.ClawbackVestingAccount) + + if isClawback && !merge { + return nil, false, false, errorsmod.Wrapf(types.ErrApplyShedule, "account %s already exists; consider using --merge", funded) + } + + codeHash := common.BytesToHash(crypto.Keccak256(nil)) + if isEthAccount { + codeHash = ethAcc.GetCodeHash() + } + + switch { + case createNewAcc: + baseAcc := authtypes.NewBaseAccountWithAddress(funded) + vestingAcc = types.NewClawbackVestingAccount( + baseAcc, + funder, + coins, + startTime, + lockupPeriods, + vestingPeriods, + &codeHash, + ) + acc := k.accountKeeper.NewAccount(ctx, vestingAcc) + k.accountKeeper.SetAccount(ctx, acc) + + return vestingAcc, true, false, nil + case !isClawback && !isEthAccount: + return nil, false, false, errorsmod.Wrapf(types.ErrApplyShedule, "account %s already exists but can't be converted into vesting account", funded) + case !isClawback && isEthAccount: + baseAcc := ethAcc.GetBaseAccount() + vestingAcc = types.NewClawbackVestingAccount( + baseAcc, + funder, + coins, + startTime, + lockupPeriods, + vestingPeriods, + &codeHash, + ) + acc := k.accountKeeper.NewAccount(ctx, vestingAcc) + k.accountKeeper.SetAccount(ctx, acc) + return vestingAcc, false, false, nil + case isClawback && merge: + if funder.String() != vestingAcc.FunderAddress { + return nil, false, false, errorsmod.Wrapf(types.ErrApplyShedule, "account %s can only accept grants from account %s", funded, vestingAcc.FunderAddress) + } + + err := k.addGrant( + ctx, + vestingAcc, + types.Min64(startTime.Unix(), vestingAcc.StartTime.Unix()), + lockupPeriods, + vestingPeriods, + coins, + ) + if err != nil { + return nil, false, false, err + } + k.accountKeeper.SetAccount(ctx, vestingAcc) + return vestingAcc, false, true, nil + default: + return nil, false, true, errorsmod.Wrapf(types.ErrApplyShedule, "failed to initiate vesting for account %s", funded) + } +} diff --git a/x/vesting/types/errors.go b/x/vesting/types/errors.go index 8e5eb5459..abc58d2b4 100644 --- a/x/vesting/types/errors.go +++ b/x/vesting/types/errors.go @@ -9,4 +9,5 @@ var ( ErrInsufficientVestedCoins = errorsmod.Register(ModuleName, 2, "insufficient vested coins error") ErrVestingLockup = errorsmod.Register(ModuleName, 3, "vesting lockup error") ErrInsufficientUnlockedCoins = errorsmod.Register(ModuleName, 4, "insufficient unlocked coins error") + ErrApplyShedule = errorsmod.Register(ModuleName, 5, "failed to apply schedule") ) diff --git a/x/vesting/types/interfaces.go b/x/vesting/types/interfaces.go index c35cdabc9..779a73b2c 100644 --- a/x/vesting/types/interfaces.go +++ b/x/vesting/types/interfaces.go @@ -13,6 +13,7 @@ import ( // requires for storing accounts. type AccountKeeper interface { GetAllAccounts(ctx sdk.Context) (accounts []authtypes.AccountI) + GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI GetAccount(sdk.Context, sdk.AccAddress) authtypes.AccountI SetAccount(sdk.Context, authtypes.AccountI) NewAccount(ctx sdk.Context, acc authtypes.AccountI) authtypes.AccountI @@ -23,6 +24,7 @@ type AccountKeeper interface { // BankKeeper defines the expected interface contract the vesting module requires // for creating vesting accounts with funds. type BankKeeper interface { + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins