diff --git a/app/app.go b/app/app.go
index 7c0bf157..9843e066 100644
--- a/app/app.go
+++ b/app/app.go
@@ -125,6 +125,9 @@ import (
kavadistclient "github.com/kava-labs/kava/x/kavadist/client"
kavadistkeeper "github.com/kava-labs/kava/x/kavadist/keeper"
kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
+ "github.com/kava-labs/kava/x/liquid"
+ liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper"
+ liquidtypes "github.com/kava-labs/kava/x/liquid/types"
pricefeed "github.com/kava-labs/kava/x/pricefeed"
pricefeedkeeper "github.com/kava-labs/kava/x/pricefeed/keeper"
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
@@ -190,6 +193,7 @@ var (
savings.AppModuleBasic{},
validatorvesting.AppModuleBasic{},
evmutil.AppModuleBasic{},
+ liquid.AppModuleBasic{},
earn.AppModuleBasic{},
)
@@ -215,12 +219,12 @@ var (
cdptypes.LiquidatorMacc: {authtypes.Minter, authtypes.Burner},
hardtypes.ModuleAccountName: {authtypes.Minter},
savingstypes.ModuleAccountName: nil,
- earntypes.ModuleName: nil,
+ liquidtypes.ModuleAccountName: {authtypes.Minter, authtypes.Burner},
+ earntypes.ModuleAccountName: nil,
}
)
// Verify app interface at compile time
-// var _ simapp.App = (*App)(nil) // TODO
var _ servertypes.Application = (*App)(nil)
// Options bundles several configuration params for an App.
@@ -285,6 +289,7 @@ type App struct {
committeeKeeper committeekeeper.Keeper
incentiveKeeper incentivekeeper.Keeper
savingsKeeper savingskeeper.Keeper
+ liquidKeeper liquidkeeper.Keeper
earnKeeper earnkeeper.Keeper
// make scoped keepers public for test purposes
@@ -586,6 +591,12 @@ func NewApp(
app.accountKeeper,
app.bankKeeper,
)
+ app.liquidKeeper = liquidkeeper.NewDefaultKeeper(
+ appCodec,
+ app.accountKeeper,
+ app.bankKeeper,
+ &app.stakingKeeper,
+ )
app.incentiveKeeper = incentivekeeper.NewKeeper(
appCodec,
keys[incentivetypes.StoreKey],
@@ -691,6 +702,7 @@ func NewApp(
incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.bankKeeper, app.cdpKeeper),
evmutil.NewAppModule(app.evmutilKeeper, app.bankKeeper),
savings.NewAppModule(app.savingsKeeper, app.accountKeeper, app.bankKeeper),
+ liquid.NewAppModule(app.liquidKeeper),
earn.NewAppModule(app.earnKeeper, app.accountKeeper, app.bankKeeper),
)
@@ -738,6 +750,7 @@ func NewApp(
authz.ModuleName,
evmutiltypes.ModuleName,
savingstypes.ModuleName,
+ liquidtypes.ModuleName,
earntypes.ModuleName,
)
@@ -777,6 +790,7 @@ func NewApp(
authz.ModuleName,
evmutiltypes.ModuleName,
savingstypes.ModuleName,
+ liquidtypes.ModuleName,
earntypes.ModuleName,
)
@@ -816,6 +830,7 @@ func NewApp(
paramstypes.ModuleName,
upgradetypes.ModuleName,
validatorvestingtypes.ModuleName,
+ liquidtypes.ModuleName,
)
app.mm.RegisterInvariants(&app.crisisKeeper)
@@ -979,10 +994,11 @@ func (app *App) loadBlockedMaccAddrs() map[string]bool {
modAccAddrs := app.ModuleAccountAddrs()
kavadistMaccAddr := app.accountKeeper.GetModuleAddress(kavadisttypes.ModuleName)
earnMaccAddr := app.accountKeeper.GetModuleAddress(earntypes.ModuleName)
+ liquidMaccAddr := app.accountKeeper.GetModuleAddress(liquidtypes.ModuleName)
for addr := range modAccAddrs {
// Set the kavadist and earn module account address as unblocked
- if addr == kavadistMaccAddr.String() || addr == earnMaccAddr.String() {
+ if addr == kavadistMaccAddr.String() || addr == earnMaccAddr.String() || addr == liquidMaccAddr.String() {
modAccAddrs[addr] = false
}
}
diff --git a/app/test_common.go b/app/test_common.go
index 084ae182..ccb5455a 100644
--- a/app/test_common.go
+++ b/app/test_common.go
@@ -40,6 +40,7 @@ import (
incentivekeeper "github.com/kava-labs/kava/x/incentive/keeper"
issuancekeeper "github.com/kava-labs/kava/x/issuance/keeper"
kavadistkeeper "github.com/kava-labs/kava/x/kavadist/keeper"
+ liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper"
pricefeedkeeper "github.com/kava-labs/kava/x/pricefeed/keeper"
savingskeeper "github.com/kava-labs/kava/x/savings/keeper"
swapkeeper "github.com/kava-labs/kava/x/swap/keeper"
@@ -109,6 +110,7 @@ func (tApp TestApp) GetEvmutilKeeper() evmutilkeeper.Keeper { return tApp.ev
func (tApp TestApp) GetEvmKeeper() *evmkeeper.Keeper { return tApp.evmKeeper }
func (tApp TestApp) GetSavingsKeeper() savingskeeper.Keeper { return tApp.savingsKeeper }
func (tApp TestApp) GetFeeMarketKeeper() feemarketkeeper.Keeper { return tApp.feeMarketKeeper }
+func (tApp TestApp) GetLiquidKeeper() liquidkeeper.Keeper { return tApp.liquidKeeper }
func (tApp TestApp) GetEarnKeeper() earnkeeper.Keeper { return tApp.earnKeeper }
// LegacyAmino returns the app's amino codec.
diff --git a/contrib/devnet/init-new-chain.sh b/contrib/devnet/init-new-chain.sh
index 20674282..58e29d20 100755
--- a/contrib/devnet/init-new-chain.sh
+++ b/contrib/devnet/init-new-chain.sh
@@ -35,6 +35,10 @@ sed -in-place='' 's/enable = false/enable = true/g' $DATA/config/app.toml
# Set evm tracer to json
sed -in-place='' 's/tracer = ""/tracer = "json"/g' $DATA/config/app.toml
+# Enable full error trace to be returned on tx failure
+sed -in-place='' '/iavl-cache-size/a\
+trace = true' $DATA/config/app.toml
+
# Set client chain id
sed -in-place='' 's/chain-id = ""/chain-id = "kavalocalnet_8888-1"/g' $DATA/config/client.toml
diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md
index 3a7d4561..5ccc403c 100644
--- a/docs/core/proto-docs.md
+++ b/docs/core/proto-docs.md
@@ -385,6 +385,14 @@
- [Query](#kava.kavadist.v1beta1.Query)
+- [kava/liquid/v1beta1/tx.proto](#kava/liquid/v1beta1/tx.proto)
+ - [MsgBurnDerivative](#kava.liquid.v1beta1.MsgBurnDerivative)
+ - [MsgBurnDerivativeResponse](#kava.liquid.v1beta1.MsgBurnDerivativeResponse)
+ - [MsgMintDerivative](#kava.liquid.v1beta1.MsgMintDerivative)
+ - [MsgMintDerivativeResponse](#kava.liquid.v1beta1.MsgMintDerivativeResponse)
+
+ - [Msg](#kava.liquid.v1beta1.Msg)
+
- [kava/pricefeed/v1beta1/store.proto](#kava/pricefeed/v1beta1/store.proto)
- [CurrentPrice](#kava.pricefeed.v1beta1.CurrentPrice)
- [Market](#kava.pricefeed.v1beta1.Market)
@@ -5384,6 +5392,97 @@ Query defines the gRPC querier service.
+
+
Top
+
+## kava/liquid/v1beta1/tx.proto
+
+
+
+
+
+### MsgBurnDerivative
+MsgBurnDerivative defines the Msg/BurnDerivative request type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `sender` | [string](#string) | | sender is the owner of the derivatives to be converted |
+| `validator` | [string](#string) | | validator is the validator of the derivatives to be converted |
+| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | amount is the quantity of derivatives to be converted |
+
+
+
+
+
+
+
+
+### MsgBurnDerivativeResponse
+MsgBurnDerivativeResponse defines the Msg/BurnDerivative response type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `received` | [string](#string) | | received is the number of delegation shares sent to the sender |
+
+
+
+
+
+
+
+
+### MsgMintDerivative
+MsgMintDerivative defines the Msg/MintDerivative request type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `sender` | [string](#string) | | sender is the owner of the delegation to be converted |
+| `validator` | [string](#string) | | validator is the validator of the delegation to be converted |
+| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | amount is the quantity of staked assets to be converted |
+
+
+
+
+
+
+
+
+### MsgMintDerivativeResponse
+MsgMintDerivativeResponse defines the Msg/MintDerivative response type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `received` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | received is the amount of staking derivative minted and sent to the sender |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Msg
+Msg defines the liquid Msg service.
+
+| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
+| ----------- | ------------ | ------------- | ------------| ------- | -------- |
+| `MintDerivative` | [MsgMintDerivative](#kava.liquid.v1beta1.MsgMintDerivative) | [MsgMintDerivativeResponse](#kava.liquid.v1beta1.MsgMintDerivativeResponse) | MintDerivative defines a method for converting a delegation into staking deriviatives. | |
+| `BurnDerivative` | [MsgBurnDerivative](#kava.liquid.v1beta1.MsgBurnDerivative) | [MsgBurnDerivativeResponse](#kava.liquid.v1beta1.MsgBurnDerivativeResponse) | BurnDerivative defines a method for converting staking deriviatives into a delegation. | |
+
+
+
+
+
Top
diff --git a/proto/kava/liquid/v1beta1/tx.proto b/proto/kava/liquid/v1beta1/tx.proto
new file mode 100644
index 00000000..da6925d2
--- /dev/null
+++ b/proto/kava/liquid/v1beta1/tx.proto
@@ -0,0 +1,54 @@
+syntax = "proto3";
+package kava.liquid.v1beta1;
+
+import "gogoproto/gogo.proto";
+import "cosmos/base/v1beta1/coin.proto";
+import "cosmos_proto/cosmos.proto";
+
+option go_package = "github.com/kava-labs/kava/x/liquid/types";
+
+// Msg defines the liquid Msg service.
+service Msg {
+
+ // MintDerivative defines a method for converting a delegation into staking deriviatives.
+ rpc MintDerivative(MsgMintDerivative) returns (MsgMintDerivativeResponse);
+
+ // BurnDerivative defines a method for converting staking deriviatives into a delegation.
+ rpc BurnDerivative(MsgBurnDerivative) returns (MsgBurnDerivativeResponse);
+}
+
+// MsgMintDerivative defines the Msg/MintDerivative request type.
+message MsgMintDerivative {
+ // sender is the owner of the delegation to be converted
+ string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
+ // validator is the validator of the delegation to be converted
+ string validator = 2;
+ // amount is the quantity of staked assets to be converted
+ cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false];
+}
+
+// MsgMintDerivativeResponse defines the Msg/MintDerivative response type.
+message MsgMintDerivativeResponse {
+ // received is the amount of staking derivative minted and sent to the sender
+ cosmos.base.v1beta1.Coin received = 1 [(gogoproto.nullable) = false];
+}
+
+// MsgBurnDerivative defines the Msg/BurnDerivative request type.
+message MsgBurnDerivative {
+ // sender is the owner of the derivatives to be converted
+ string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
+ // validator is the validator of the derivatives to be converted
+ string validator = 2;
+ // amount is the quantity of derivatives to be converted
+ cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false];
+}
+
+// MsgBurnDerivativeResponse defines the Msg/BurnDerivative response type.
+message MsgBurnDerivativeResponse {
+ // received is the number of delegation shares sent to the sender
+ string received = 1 [
+ (cosmos_proto.scalar) = "cosmos.Dec",
+ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
+ (gogoproto.nullable) = false
+ ];
+}
\ No newline at end of file
diff --git a/x/liquid/client/cli/query.go b/x/liquid/client/cli/query.go
new file mode 100644
index 00000000..70054c2a
--- /dev/null
+++ b/x/liquid/client/cli/query.go
@@ -0,0 +1,31 @@
+package cli
+
+import (
+ "github.com/spf13/cobra"
+
+ "github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/client/flags"
+
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+// GetQueryCmd returns the cli query commands for this module
+func GetQueryCmd() *cobra.Command {
+ liquidQueryCmd := &cobra.Command{
+ Use: types.ModuleName,
+ Short: "Querying commands for the liquid module",
+ DisableFlagParsing: true,
+ SuggestionsMinimumDistance: 2,
+ RunE: client.ValidateCmd,
+ }
+
+ cmds := []*cobra.Command{}
+
+ for _, cmd := range cmds {
+ flags.AddQueryFlagsToCmd(cmd)
+ }
+
+ liquidQueryCmd.AddCommand(cmds...)
+
+ return liquidQueryCmd
+}
diff --git a/x/liquid/client/cli/tx.go b/x/liquid/client/cli/tx.go
new file mode 100644
index 00000000..6ef2043f
--- /dev/null
+++ b/x/liquid/client/cli/tx.go
@@ -0,0 +1,122 @@
+package cli
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/client/flags"
+ "github.com/cosmos/cosmos-sdk/client/tx"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/cosmos/cosmos-sdk/version"
+
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+// GetTxCmd returns the transaction commands for this module
+func GetTxCmd() *cobra.Command {
+ liquidTxCmd := &cobra.Command{
+ Use: types.ModuleName,
+ Short: "liquid transactions subcommands",
+ DisableFlagParsing: true,
+ SuggestionsMinimumDistance: 2,
+ RunE: client.ValidateCmd,
+ }
+
+ cmds := []*cobra.Command{
+ getCmdMintDerivative(),
+ getCmdBurnDerivative(),
+ }
+
+ for _, cmd := range cmds {
+ flags.AddTxFlagsToCmd(cmd)
+ }
+
+ liquidTxCmd.AddCommand(cmds...)
+
+ return liquidTxCmd
+}
+
+func getCmdMintDerivative() *cobra.Command {
+ return &cobra.Command{
+ Use: "mint [validator-addr] [amount]",
+ Short: "mints staking derivative from a delegation",
+ Long: "Mint removes a portion of a user's staking delegation and issues them validator specific staking derivative tokens.",
+ Args: cobra.ExactArgs(2),
+ Example: fmt.Sprintf(
+ `%s tx %s mint kavavaloper16lnfpgn6llvn4fstg5nfrljj6aaxyee9z59jqd 10000000ukava --from `, version.AppName, types.ModuleName,
+ ),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ valAddr, err := sdk.ValAddressFromBech32(args[0])
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
+ }
+
+ coin, err := sdk.ParseCoinNormalized(args[1])
+ if err != nil {
+ return err
+ }
+
+ msg := types.NewMsgMintDerivative(clientCtx.GetFromAddress(), valAddr, coin)
+ if err := msg.ValidateBasic(); err != nil {
+ return err
+ }
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
+ },
+ }
+}
+
+func getCmdBurnDerivative() *cobra.Command {
+ return &cobra.Command{
+ Use: "burn [amount]",
+ Short: "burns staking derivative to redeem a delegation",
+ Long: "Burn removes some staking derivative from a user's account and converts it back to a staking delegation.",
+ Example: fmt.Sprintf(
+ `%s tx %s burn 10000000bkava-kavavaloper16lnfpgn6llvn4fstg5nfrljj6aaxyee9z59jqd --from `, version.AppName, types.ModuleName,
+ ),
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ amount, err := sdk.ParseCoinNormalized(args[0])
+ if err != nil {
+ return err
+ }
+
+ valAddr, err := parseLiquidStakingTokenDenom(amount.Denom)
+ if err != nil {
+ return sdkerrors.Wrap(types.ErrInvalidDenom, err.Error())
+ }
+
+ msg := types.NewMsgBurnDerivative(clientCtx.GetFromAddress(), valAddr, amount)
+ if err := msg.ValidateBasic(); err != nil {
+ return err
+ }
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
+ },
+ }
+}
+
+// parseLiquidStakingTokenDenom extracts a validator address from a derivative denom.
+func parseLiquidStakingTokenDenom(denom string) (sdk.ValAddress, error) {
+ elements := strings.Split(denom, types.DenomSeparator)
+ if len(elements) != 2 {
+ return nil, fmt.Errorf("cannot parse denom %s", denom)
+ }
+ addr, err := sdk.ValAddressFromBech32(elements[1])
+ if err != nil {
+ return nil, err
+ }
+ return addr, nil
+}
diff --git a/x/liquid/keeper/derivative.go b/x/liquid/keeper/derivative.go
new file mode 100644
index 00000000..cac2b74b
--- /dev/null
+++ b/x/liquid/keeper/derivative.go
@@ -0,0 +1,118 @@
+package keeper
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+// MintDerivative removes a user's staking delegation and mints them equivalent staking derivative coins.
+//
+// The input staking token amount is used to calculate shares in the user's delegation, which are transferred to a delegation owned by the module.
+// Derivative coins are them minted and transferred to the user.
+func (k Keeper) MintDerivative(ctx sdk.Context, delegatorAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) (sdk.Coin, error) {
+ bondDenom := k.stakingKeeper.BondDenom(ctx)
+ if amount.Denom != bondDenom {
+ return sdk.Coin{}, sdkerrors.Wrapf(types.ErrInvalidDenom, "expected %s", bondDenom)
+ }
+
+ derivativeAmount, shares, err := k.CalculateDerivativeSharesFromTokens(ctx, delegatorAddr, valAddr, amount.Amount)
+ if err != nil {
+ return sdk.Coin{}, err
+ }
+
+ // Fetching the module account will create it if it doesn't exist.
+ // This is necessary as otherwise TransferDelegation will create a normal account.
+ modAcc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
+ if _, err := k.TransferDelegation(ctx, valAddr, delegatorAddr, modAcc.GetAddress(), shares); err != nil {
+ return sdk.Coin{}, err
+ }
+
+ liquidTokenDenom := k.GetLiquidStakingTokenDenom(valAddr)
+ liquidToken := sdk.NewCoin(liquidTokenDenom, derivativeAmount)
+ if err = k.mintCoins(ctx, delegatorAddr, sdk.NewCoins(liquidToken)); err != nil {
+ return sdk.Coin{}, err
+ }
+
+ ctx.EventManager().EmitEvent(
+ sdk.NewEvent(
+ types.EventTypeMintDerivative,
+ sdk.NewAttribute(types.AttributeKeyDelegator, delegatorAddr.String()),
+ sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()),
+ sdk.NewAttribute(sdk.AttributeKeyAmount, liquidToken.String()),
+ sdk.NewAttribute(types.AttributeKeySharesTransferred, shares.String()),
+ ),
+ )
+
+ return liquidToken, nil
+}
+
+// CalculateDerivativeSharesFromTokens converts a staking token amount into its equivalent delegation shares, and staking derivative amount.
+// This combines the code for calculating the shares to be transferred, and the derivative coins to be minted.
+func (k Keeper) CalculateDerivativeSharesFromTokens(ctx sdk.Context, delegator sdk.AccAddress, validator sdk.ValAddress, tokens sdk.Int) (sdk.Int, sdk.Dec, error) {
+ if !tokens.IsPositive() {
+ return sdk.Int{}, sdk.Dec{}, sdkerrors.Wrap(types.ErrUntransferableShares, "token amount must be positive")
+ }
+ shares, err := k.stakingKeeper.ValidateUnbondAmount(ctx, delegator, validator, tokens)
+ if err != nil {
+ return sdk.Int{}, sdk.Dec{}, err
+ }
+ return shares.TruncateInt(), shares, nil
+}
+
+// BurnDerivative burns an user's staking derivative coins and returns them an equivalent staking delegation.
+//
+// The derivative coins are burned, and an equivalent number of shares in the module's staking delegation are transferred back to the user.
+func (k Keeper) BurnDerivative(ctx sdk.Context, delegatorAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) (sdk.Dec, error) {
+
+ if amount.Denom != k.GetLiquidStakingTokenDenom(valAddr) {
+ return sdk.Dec{}, sdkerrors.Wrap(types.ErrInvalidDenom, "derivative denom does not match validator")
+ }
+
+ if err := k.burnCoins(ctx, delegatorAddr, sdk.NewCoins(amount)); err != nil {
+ return sdk.Dec{}, err
+ }
+
+ modAcc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
+ shares := amount.Amount.ToDec()
+ receivedShares, err := k.TransferDelegation(ctx, valAddr, modAcc.GetAddress(), delegatorAddr, shares)
+ if err != nil {
+ return sdk.Dec{}, err
+ }
+
+ ctx.EventManager().EmitEvent(
+ sdk.NewEvent(
+ types.EventTypeBurnDerivative,
+ sdk.NewAttribute(types.AttributeKeyDelegator, delegatorAddr.String()),
+ sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()),
+ sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()),
+ sdk.NewAttribute(types.AttributeKeySharesTransferred, shares.String()),
+ ),
+ )
+ return receivedShares, nil
+}
+
+func (k Keeper) GetLiquidStakingTokenDenom(valAddr sdk.ValAddress) string {
+ return types.GetLiquidStakingTokenDenom(k.derivativeDenom, valAddr)
+}
+
+func (k Keeper) mintCoins(ctx sdk.Context, receiver sdk.AccAddress, amount sdk.Coins) error {
+ if err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, amount); err != nil {
+ return err
+ }
+ if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, receiver, amount); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (k Keeper) burnCoins(ctx sdk.Context, sender sdk.AccAddress, amount sdk.Coins) error {
+ if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleAccountName, amount); err != nil {
+ return err
+ }
+ if err := k.bankKeeper.BurnCoins(ctx, types.ModuleAccountName, amount); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/x/liquid/keeper/derivative_test.go b/x/liquid/keeper/derivative_test.go
new file mode 100644
index 00000000..f389b722
--- /dev/null
+++ b/x/liquid/keeper/derivative_test.go
@@ -0,0 +1,316 @@
+package keeper_test
+
+import (
+ "fmt"
+
+ 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/cosmos/cosmos-sdk/x/staking"
+ stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
+
+ "github.com/kava-labs/kava/app"
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+func (suite *KeeperTestSuite) TestBurnDerivative() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(5)
+ valAccAddr, user := addrs[0], addrs[1]
+ valAddr := sdk.ValAddress(valAccAddr)
+
+ liquidDenom := suite.Keeper.GetLiquidStakingTokenDenom(valAddr)
+
+ testCases := []struct {
+ name string
+ balance sdk.Coin
+ moduleDelegation sdk.Int
+ burnAmount sdk.Coin
+ expectedErr error
+ }{
+ {
+ name: "user can burn their entire balance",
+ balance: c(liquidDenom, 1e9),
+ moduleDelegation: i(1e9),
+ burnAmount: c(liquidDenom, 1e9),
+ },
+ {
+ name: "user can burn minimum derivative unit",
+ balance: c(liquidDenom, 1e9),
+ moduleDelegation: i(1e9),
+ burnAmount: c(liquidDenom, 1),
+ },
+ {
+ name: "error when denom cannot be parsed",
+ balance: c(liquidDenom, 1e9),
+ moduleDelegation: i(1e9),
+ burnAmount: c(fmt.Sprintf("ckava-%s", valAddr), 1e6),
+ expectedErr: types.ErrInvalidDenom,
+ },
+ {
+ name: "error when burn amount is 0",
+ balance: c(liquidDenom, 1e9),
+ moduleDelegation: i(1e9),
+ burnAmount: c(liquidDenom, 0),
+ expectedErr: types.ErrUntransferableShares,
+ },
+ {
+ name: "error when user doesn't have enough funds",
+ balance: c("ukava", 10),
+ moduleDelegation: i(1e9),
+ burnAmount: c(liquidDenom, 1e9),
+ expectedErr: sdkerrors.ErrInsufficientFunds,
+ },
+ {
+ name: "error when backing delegation isn't large enough",
+ balance: c(liquidDenom, 1e9),
+ moduleDelegation: i(999_999_999),
+ burnAmount: c(liquidDenom, 1e9),
+ expectedErr: stakingtypes.ErrNotEnoughDelegationShares,
+ },
+ }
+
+ for _, tc := range testCases {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+
+ suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(i(1e6)))
+ suite.CreateAccountWithAddress(user, sdk.NewCoins(tc.balance))
+ suite.AddCoinsToModule(types.ModuleAccountName, suite.NewBondCoins(tc.moduleDelegation))
+
+ // create delegation from module account to back the derivatives
+ moduleAccAddress := authtypes.NewModuleAddress(types.ModuleAccountName)
+ suite.CreateNewUnbondedValidator(valAddr, i(1e6))
+ suite.CreateDelegation(valAddr, moduleAccAddress, tc.moduleDelegation)
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+ modBalance := suite.BankKeeper.GetAllBalances(suite.Ctx, moduleAccAddress)
+
+ _, err := suite.Keeper.BurnDerivative(suite.Ctx, user, valAddr, tc.burnAmount)
+
+ suite.Require().ErrorIs(err, tc.expectedErr)
+ if tc.expectedErr != nil {
+ // if an error is expected, state should be reverted so don't need to test state is unchanged
+ return
+ }
+
+ suite.AccountBalanceEqual(user, sdk.NewCoins(tc.balance.Sub(tc.burnAmount)))
+ suite.AccountBalanceEqual(moduleAccAddress, modBalance) // ensure derivatives are burned, and not in module account
+
+ sharesTransferred := tc.burnAmount.Amount.ToDec()
+ suite.DelegationSharesEqual(valAddr, user, sharesTransferred)
+ suite.DelegationSharesEqual(valAddr, moduleAccAddress, tc.moduleDelegation.ToDec().Sub(sharesTransferred))
+
+ suite.EventsContains(suite.Ctx.EventManager().Events(), sdk.NewEvent(
+ types.EventTypeBurnDerivative,
+ sdk.NewAttribute(types.AttributeKeyDelegator, user.String()),
+ sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()),
+ sdk.NewAttribute(sdk.AttributeKeyAmount, tc.burnAmount.String()),
+ sdk.NewAttribute(types.AttributeKeySharesTransferred, sharesTransferred.String()),
+ ))
+ })
+ }
+}
+
+func (suite *KeeperTestSuite) TestCalculateShares() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(5)
+ valAccAddr, delegator := addrs[0], addrs[1]
+ valAddr := sdk.ValAddress(valAccAddr)
+
+ type returns struct {
+ derivatives sdk.Int
+ shares sdk.Dec
+ err error
+ }
+ type validator struct {
+ tokens sdk.Int
+ delegatorShares sdk.Dec
+ }
+ testCases := []struct {
+ name string
+ validator *validator
+ delegation sdk.Dec
+ transfer sdk.Int
+ expected returns
+ }{
+ {
+ name: "error when validator not found",
+ validator: nil,
+ delegation: d("1000000000"),
+ transfer: i(500e6),
+ expected: returns{
+ err: stakingtypes.ErrNoValidatorFound,
+ },
+ },
+ {
+ name: "error when delegation not found",
+ validator: &validator{i(1e9), d("1000000000")},
+ delegation: sdk.Dec{},
+ transfer: i(500e6),
+ expected: returns{
+ err: stakingtypes.ErrNoDelegation,
+ },
+ },
+ {
+ name: "error when transfer < 0",
+ validator: &validator{i(10), d("10")},
+ delegation: d("10"),
+ transfer: i(-1),
+ expected: returns{
+ err: types.ErrUntransferableShares,
+ },
+ },
+ { // disallow zero transfers
+ name: "error when transfer = 0",
+ validator: &validator{i(10), d("10")},
+ delegation: d("10"),
+ transfer: i(0),
+ expected: returns{
+ err: types.ErrUntransferableShares,
+ },
+ },
+ {
+ name: "error when transfer > delegated shares",
+ validator: &validator{i(10), d("10")},
+ delegation: d("10"),
+ transfer: i(11),
+ expected: returns{
+ err: sdkerrors.ErrInvalidRequest,
+ },
+ },
+ {
+ name: "error when validator has no tokens",
+ validator: &validator{i(0), d("10")},
+ delegation: d("10"),
+ transfer: i(5),
+ expected: returns{
+ err: stakingtypes.ErrInsufficientShares,
+ },
+ },
+ {
+ name: "shares and derivatives are truncated",
+ validator: &validator{i(3), d("4")},
+ delegation: d("4"),
+ transfer: i(2),
+ expected: returns{
+ derivatives: i(2), // truncated down
+ shares: d("2.666666666666666666"), // 2/3 * 4 not rounded to ...667
+ },
+ },
+ {
+ name: "error if calculated shares > shares in delegation",
+ validator: &validator{i(3), d("4")},
+ delegation: d("2.666666666666666665"), // one less than 2/3 * 4
+ transfer: i(2),
+ expected: returns{
+ err: sdkerrors.ErrInvalidRequest,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+
+ if tc.validator != nil {
+ suite.StakingKeeper.SetValidator(suite.Ctx, stakingtypes.Validator{
+ OperatorAddress: valAddr.String(),
+ Tokens: tc.validator.tokens,
+ DelegatorShares: tc.validator.delegatorShares,
+ })
+ }
+ if !tc.delegation.IsNil() {
+ suite.StakingKeeper.SetDelegation(suite.Ctx, stakingtypes.Delegation{
+ DelegatorAddress: delegator.String(),
+ ValidatorAddress: valAddr.String(),
+ Shares: tc.delegation,
+ })
+ }
+
+ derivatives, shares, err := suite.Keeper.CalculateDerivativeSharesFromTokens(suite.Ctx, delegator, valAddr, tc.transfer)
+ if tc.expected.err != nil {
+ suite.ErrorIs(err, tc.expected.err)
+ } else {
+ suite.NoError(err)
+ suite.Equal(tc.expected.derivatives, derivatives, "expected '%s' got '%s'", tc.expected.derivatives, derivatives)
+ suite.Equal(tc.expected.shares, shares)
+ }
+ })
+ }
+}
+
+func (suite *KeeperTestSuite) TestMintDerivative() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(5)
+ valAccAddr, delegator := addrs[0], addrs[1]
+ valAddr := sdk.ValAddress(valAccAddr)
+ moduleAccAddress := authtypes.NewModuleAddress(types.ModuleAccountName)
+
+ initialBalance := i(1e9)
+ vestedBalance := i(500e6)
+
+ testCases := []struct {
+ name string
+ amount sdk.Coin
+ expectedDerivatives sdk.Int
+ expectedSharesRemaining sdk.Dec
+ expectedSharesAdded sdk.Dec
+ expectedErr error
+ }{
+ {
+ name: "derivative is minted",
+ amount: suite.NewBondCoin(vestedBalance),
+ expectedDerivatives: i(500e6),
+ expectedSharesRemaining: d("500000000.0"),
+ expectedSharesAdded: d("500000000.0"),
+ },
+ {
+ name: "error when the input denom isn't correct",
+ amount: sdk.NewCoin("invalid", i(1000)),
+ expectedErr: types.ErrInvalidDenom,
+ },
+ {
+ name: "error when shares cannot be calculated",
+ amount: suite.NewBondCoin(initialBalance.Mul(i(100))),
+ expectedErr: sdkerrors.ErrInvalidRequest,
+ },
+ {
+ name: "error when shares cannot be transferred",
+ amount: suite.NewBondCoin(initialBalance), // trying to move vesting coins will fail in `TransferShares`
+ expectedErr: sdkerrors.ErrInsufficientFunds,
+ },
+ }
+
+ for _, tc := range testCases {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+
+ suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(initialBalance))
+ suite.CreateVestingAccountWithAddress(delegator, suite.NewBondCoins(initialBalance), suite.NewBondCoins(vestedBalance))
+
+ suite.CreateNewUnbondedValidator(valAddr, initialBalance)
+ suite.CreateDelegation(valAddr, delegator, initialBalance)
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ _, err := suite.Keeper.MintDerivative(suite.Ctx, delegator, valAddr, tc.amount)
+
+ suite.Require().ErrorIs(err, tc.expectedErr)
+ if tc.expectedErr != nil {
+ // if an error is expected, state should be reverted so don't need to test state is unchanged
+ return
+ }
+
+ derivative := sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("bkava-%s", valAddr), tc.expectedDerivatives))
+ suite.AccountBalanceEqual(delegator, derivative)
+
+ suite.DelegationSharesEqual(valAddr, delegator, tc.expectedSharesRemaining)
+ suite.DelegationSharesEqual(valAddr, moduleAccAddress, tc.expectedSharesAdded)
+
+ sharesTransferred := initialBalance.ToDec().Sub(tc.expectedSharesRemaining)
+ suite.EventsContains(suite.Ctx.EventManager().Events(), sdk.NewEvent(
+ types.EventTypeMintDerivative,
+ sdk.NewAttribute(types.AttributeKeyDelegator, delegator.String()),
+ sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()),
+ sdk.NewAttribute(sdk.AttributeKeyAmount, derivative.String()),
+ sdk.NewAttribute(types.AttributeKeySharesTransferred, sharesTransferred.String()),
+ ))
+ })
+ }
+}
diff --git a/x/liquid/keeper/keeper.go b/x/liquid/keeper/keeper.go
new file mode 100644
index 00000000..0756f38c
--- /dev/null
+++ b/x/liquid/keeper/keeper.go
@@ -0,0 +1,52 @@
+package keeper
+
+import (
+ "fmt"
+
+ "github.com/cosmos/cosmos-sdk/codec"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/tendermint/tendermint/libs/log"
+
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+// Keeper struct for the liquid module.
+type Keeper struct {
+ cdc codec.Codec
+
+ accountKeeper types.AccountKeeper
+ bankKeeper types.BankKeeper
+ stakingKeeper types.StakingKeeper
+
+ derivativeDenom string
+}
+
+// NewKeeper returns a new keeper for the liquid module.
+func NewKeeper(
+ cdc codec.Codec,
+ ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper,
+ derivativeDenom string,
+) Keeper {
+
+ return Keeper{
+ cdc: cdc,
+ accountKeeper: ak,
+ bankKeeper: bk,
+ stakingKeeper: sk,
+ derivativeDenom: derivativeDenom,
+ }
+}
+
+// NewDefaultKeeper returns a new keeper for the liquid module with default values.
+func NewDefaultKeeper(
+ cdc codec.Codec,
+ ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper,
+) Keeper {
+
+ return NewKeeper(cdc, ak, bk, sk, types.DefaultDerivativeDenom)
+}
+
+// Logger returns a module-specific logger.
+func (k Keeper) Logger(ctx sdk.Context) log.Logger {
+ return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
+}
diff --git a/x/liquid/keeper/keeper_test.go b/x/liquid/keeper/keeper_test.go
new file mode 100644
index 00000000..37a16a37
--- /dev/null
+++ b/x/liquid/keeper/keeper_test.go
@@ -0,0 +1,238 @@
+package keeper_test
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
+ "github.com/cosmos/cosmos-sdk/simapp"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
+ bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
+ stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
+ stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
+ "github.com/stretchr/testify/suite"
+ abci "github.com/tendermint/tendermint/abci/types"
+ tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
+ tmtime "github.com/tendermint/tendermint/types/time"
+
+ "github.com/kava-labs/kava/app"
+ "github.com/kava-labs/kava/x/liquid/keeper"
+)
+
+// Test suite used for all keeper tests
+type KeeperTestSuite struct {
+ suite.Suite
+ App app.TestApp
+ Ctx sdk.Context
+ Keeper keeper.Keeper
+ BankKeeper bankkeeper.Keeper
+ StakingKeeper stakingkeeper.Keeper
+}
+
+// The default state used by each test
+func (suite *KeeperTestSuite) SetupTest() {
+ tApp := app.NewTestApp()
+ ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
+
+ tApp.InitializeFromGenesisStates()
+
+ suite.App = tApp
+ suite.Ctx = ctx
+ suite.Keeper = tApp.GetLiquidKeeper()
+ suite.StakingKeeper = tApp.GetStakingKeeper()
+ suite.BankKeeper = tApp.GetBankKeeper()
+}
+
+// CreateAccount creates a new account (with a fixed address) from the provided balance.
+func (suite *KeeperTestSuite) CreateAccount(initialBalance sdk.Coins) authtypes.AccountI {
+ _, addrs := app.GeneratePrivKeyAddressPairs(1)
+
+ return suite.CreateAccountWithAddress(addrs[0], initialBalance)
+}
+
+// CreateAccount creates a new account from the provided balance and address
+func (suite *KeeperTestSuite) CreateAccountWithAddress(addr sdk.AccAddress, initialBalance sdk.Coins) authtypes.AccountI {
+ ak := suite.App.GetAccountKeeper()
+
+ acc := ak.NewAccountWithAddress(suite.Ctx, addr)
+ ak.SetAccount(suite.Ctx, acc)
+
+ err := simapp.FundAccount(suite.BankKeeper, suite.Ctx, acc.GetAddress(), initialBalance)
+ suite.Require().NoError(err)
+
+ return acc
+}
+
+// CreateVestingAccount creates a new vesting account. `vestingBalance` should be a fraction of `initialBalance`.
+func (suite *KeeperTestSuite) CreateVestingAccountWithAddress(addr sdk.AccAddress, initialBalance sdk.Coins, vestingBalance sdk.Coins) authtypes.AccountI {
+ if vestingBalance.IsAnyGT(initialBalance) {
+ panic("vesting balance must be less than initial balance")
+ }
+ acc := suite.CreateAccountWithAddress(addr, initialBalance)
+ bacc := acc.(*authtypes.BaseAccount)
+
+ periods := vestingtypes.Periods{
+ vestingtypes.Period{
+ Length: 31556952,
+ Amount: vestingBalance,
+ },
+ }
+ vacc := vestingtypes.NewPeriodicVestingAccount(bacc, vestingBalance, suite.Ctx.BlockTime().Unix(), periods)
+ suite.App.GetAccountKeeper().SetAccount(suite.Ctx, vacc)
+ return vacc
+}
+
+// AddCoinsToModule adds coins to the a module account, creating it if it doesn't exist.
+func (suite *KeeperTestSuite) AddCoinsToModule(module string, amount sdk.Coins) {
+ err := simapp.FundModuleAccount(suite.BankKeeper, suite.Ctx, module, amount)
+ suite.Require().NoError(err)
+}
+
+// AccountBalanceEqual checks if an account has the specified coins.
+func (suite *KeeperTestSuite) AccountBalanceEqual(addr sdk.AccAddress, coins sdk.Coins) {
+ balance := suite.BankKeeper.GetAllBalances(suite.Ctx, addr)
+ suite.Equalf(coins, balance, "expected account balance to equal coins %s, but got %s", coins, balance)
+}
+
+func (suite *KeeperTestSuite) deliverMsgCreateValidator(ctx sdk.Context, address sdk.ValAddress, selfDelegation sdk.Coin) error {
+ msg, err := stakingtypes.NewMsgCreateValidator(
+ address,
+ ed25519.GenPrivKey().PubKey(),
+ selfDelegation,
+ stakingtypes.Description{},
+ stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
+ sdk.NewInt(1e6),
+ )
+ if err != nil {
+ return err
+ }
+
+ msgServer := stakingkeeper.NewMsgServerImpl(suite.StakingKeeper)
+ _, err = msgServer.CreateValidator(sdk.WrapSDKContext(suite.Ctx), msg)
+ return err
+}
+
+// NewBondCoin creates a Coin with the current staking denom.
+func (suite *KeeperTestSuite) NewBondCoin(amount sdk.Int) sdk.Coin {
+ stakingDenom := suite.StakingKeeper.BondDenom(suite.Ctx)
+ return sdk.NewCoin(stakingDenom, amount)
+}
+
+// NewBondCoins creates Coins with the current staking denom.
+func (suite *KeeperTestSuite) NewBondCoins(amount sdk.Int) sdk.Coins {
+ return sdk.NewCoins(suite.NewBondCoin(amount))
+}
+
+// CreateNewUnbondedValidator creates a new validator in the staking module.
+// New validators are unbonded until the end blocker is run.
+func (suite *KeeperTestSuite) CreateNewUnbondedValidator(addr sdk.ValAddress, selfDelegation sdk.Int) stakingtypes.Validator {
+ // Create a validator
+ err := suite.deliverMsgCreateValidator(suite.Ctx, addr, suite.NewBondCoin(selfDelegation))
+ suite.Require().NoError(err)
+
+ // New validators are created in an unbonded state. Note if the end blocker is run later this validator could become bonded.
+
+ validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, addr)
+ suite.Require().True(found)
+ return validator
+}
+
+// SlashValidator burns tokens staked in a validator. new_tokens = old_tokens * (1-slashFraction)
+func (suite *KeeperTestSuite) SlashValidator(addr sdk.ValAddress, slashFraction sdk.Dec) {
+ validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, addr)
+ suite.Require().True(found)
+ consAddr, err := validator.GetConsAddr()
+ suite.Require().NoError(err)
+
+ // Assume infraction was at current height. Note unbonding delegations and redelegations are only slashed if created after
+ // the infraction height so none will be slashed.
+ infractionHeight := suite.Ctx.BlockHeight()
+
+ power := suite.StakingKeeper.TokensToConsensusPower(suite.Ctx, validator.GetTokens())
+
+ suite.StakingKeeper.Slash(suite.Ctx, consAddr, infractionHeight, power, slashFraction)
+}
+
+// CreateDelegation delegates tokens to a validator.
+func (suite *KeeperTestSuite) CreateDelegation(valAddr sdk.ValAddress, delegator sdk.AccAddress, amount sdk.Int) sdk.Dec {
+ stakingDenom := suite.StakingKeeper.BondDenom(suite.Ctx)
+ msg := stakingtypes.NewMsgDelegate(
+ delegator,
+ valAddr,
+ sdk.NewCoin(stakingDenom, amount),
+ )
+
+ msgServer := stakingkeeper.NewMsgServerImpl(suite.StakingKeeper)
+ _, err := msgServer.Delegate(sdk.WrapSDKContext(suite.Ctx), msg)
+ suite.Require().NoError(err)
+
+ del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr)
+ suite.Require().True(found)
+ return del.Shares
+}
+
+// CreateRedelegation undelegates tokens from one validator and delegates to another.
+func (suite *KeeperTestSuite) CreateRedelegation(delegator sdk.AccAddress, fromValidator, toValidator sdk.ValAddress, amount sdk.Int) {
+ stakingDenom := suite.StakingKeeper.BondDenom(suite.Ctx)
+ msg := stakingtypes.NewMsgBeginRedelegate(
+ delegator,
+ fromValidator,
+ toValidator,
+ sdk.NewCoin(stakingDenom, amount),
+ )
+
+ msgServer := stakingkeeper.NewMsgServerImpl(suite.StakingKeeper)
+ _, err := msgServer.BeginRedelegate(sdk.WrapSDKContext(suite.Ctx), msg)
+ suite.Require().NoError(err)
+}
+
+// DelegationSharesEqual checks if a delegation has the specified shares.
+// It expects delegations with zero shares to not be stored in state.
+func (suite *KeeperTestSuite) DelegationSharesEqual(valAddr sdk.ValAddress, delegator sdk.AccAddress, shares sdk.Dec) bool {
+ del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr)
+
+ if shares.IsZero() {
+ return suite.Falsef(found, "expected delegator to not be found, got %s shares", del.Shares)
+ } else {
+ res := suite.True(found, "expected delegator to be found")
+ return res && suite.Truef(shares.Equal(del.Shares), "expected %s delegator shares but got %s", shares, del.Shares)
+ }
+}
+
+// EventsContains asserts that the expected event is in the provided events
+func (suite *KeeperTestSuite) EventsContains(events sdk.Events, expectedEvent sdk.Event) {
+ foundMatch := false
+ for _, event := range events {
+ if event.Type == expectedEvent.Type {
+ if reflect.DeepEqual(attrsToMap(expectedEvent.Attributes), attrsToMap(event.Attributes)) {
+ foundMatch = true
+ }
+ }
+ }
+
+ suite.True(foundMatch, fmt.Sprintf("event of type %s not found or did not match", expectedEvent.Type))
+}
+
+// EventsDoNotContainType asserts that the provided events do contain an event of a certain type.
+func (suite *KeeperTestSuite) EventsDoNotContainType(events sdk.Events, eventType string) {
+ for _, event := range events {
+ suite.Falsef(event.Type == eventType, "found unexpected event %s", eventType)
+ }
+}
+
+func attrsToMap(attrs []abci.EventAttribute) []sdk.Attribute {
+ out := []sdk.Attribute{}
+
+ for _, attr := range attrs {
+ out = append(out, sdk.NewAttribute(string(attr.Key), string(attr.Value)))
+ }
+
+ return out
+}
+
+func TestKeeperTestSuite(t *testing.T) {
+ suite.Run(t, new(KeeperTestSuite))
+}
diff --git a/x/liquid/keeper/msg_server.go b/x/liquid/keeper/msg_server.go
new file mode 100644
index 00000000..b09e6b4b
--- /dev/null
+++ b/x/liquid/keeper/msg_server.go
@@ -0,0 +1,84 @@
+package keeper
+
+import (
+ "context"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+type msgServer struct {
+ keeper Keeper
+}
+
+// NewMsgServerImpl returns an implementation of the liquid MsgServer interface
+// for the provided Keeper.
+func NewMsgServerImpl(keeper Keeper) types.MsgServer {
+ return &msgServer{keeper: keeper}
+}
+
+var _ types.MsgServer = msgServer{}
+
+// MintDerivative handles MintDerivative msgs.
+func (k msgServer) MintDerivative(goCtx context.Context, msg *types.MsgMintDerivative) (*types.MsgMintDerivativeResponse, error) {
+ ctx := sdk.UnwrapSDKContext(goCtx)
+
+ validator, err := sdk.ValAddressFromBech32(msg.Validator)
+ if err != nil {
+ return nil, err
+ }
+
+ sender, err := sdk.AccAddressFromBech32(msg.Sender)
+ if err != nil {
+ return nil, err
+ }
+
+ mintedDerivative, err := k.keeper.MintDerivative(ctx, sender, validator, msg.Amount)
+ if err != nil {
+ return nil, err
+ }
+
+ ctx.EventManager().EmitEvent(
+ sdk.NewEvent(
+ sdk.EventTypeMessage,
+ sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
+ sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender),
+ ),
+ )
+
+ return &types.MsgMintDerivativeResponse{
+ Received: mintedDerivative,
+ }, nil
+}
+
+// BurnDerivative handles BurnDerivative msgs.
+func (k msgServer) BurnDerivative(goCtx context.Context, msg *types.MsgBurnDerivative) (*types.MsgBurnDerivativeResponse, error) {
+ ctx := sdk.UnwrapSDKContext(goCtx)
+
+ sender, err := sdk.AccAddressFromBech32(msg.Sender)
+ if err != nil {
+ return nil, err
+ }
+
+ validator, err := sdk.ValAddressFromBech32(msg.Validator)
+ if err != nil {
+ return nil, err
+ }
+
+ sharesReceived, err := k.keeper.BurnDerivative(ctx, sender, validator, msg.Amount)
+ if err != nil {
+ return nil, err
+ }
+
+ ctx.EventManager().EmitEvent(
+ sdk.NewEvent(
+ sdk.EventTypeMessage,
+ sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
+ sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender),
+ ),
+ )
+ return &types.MsgBurnDerivativeResponse{
+ Received: sharesReceived,
+ }, nil
+}
diff --git a/x/liquid/keeper/staking.go b/x/liquid/keeper/staking.go
new file mode 100644
index 00000000..8c809518
--- /dev/null
+++ b/x/liquid/keeper/staking.go
@@ -0,0 +1,109 @@
+package keeper
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
+
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+// TransferDelegation moves some delegation shares between addresses, while keeping the same validator.
+//
+// Internally shares are unbonded, tokens moved then bonded again. This limits only vested tokens from being transferred.
+// The sending delegation must not have any active redelegations.
+// A validator cannot reduce self delegated shares below its min self delegation.
+// Attempting to transfer zero shares will error.
+func (k Keeper) TransferDelegation(ctx sdk.Context, valAddr sdk.ValAddress, fromDelegator, toDelegator sdk.AccAddress, shares sdk.Dec) (sdk.Dec, error) {
+ // Redelegations link a delegation to it's previous validator so slashes are propagated to the new validator.
+ // If the delegation is transferred to a new owner, the redelegation object must be updated.
+ // For expediency all transfers with redelegations are blocked.
+ if k.stakingKeeper.HasReceivingRedelegation(ctx, fromDelegator, valAddr) {
+ return sdk.Dec{}, types.ErrRedelegationsNotCompleted
+ }
+
+ if shares.IsNil() || shares.LT(sdk.ZeroDec()) {
+ return sdk.Dec{}, sdkerrors.Wrap(types.ErrUntransferableShares, "nil or negative shares")
+ }
+ if shares.Equal(sdk.ZeroDec()) {
+ // Block 0 transfers to reduce edge cases.
+ return sdk.Dec{}, sdkerrors.Wrap(types.ErrUntransferableShares, "zero shares")
+ }
+
+ fromDelegation, found := k.stakingKeeper.GetDelegation(ctx, fromDelegator, valAddr)
+ if !found {
+ return sdk.Dec{}, types.ErrNoDelegatorForAddress
+ }
+ validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
+ if !found {
+ return sdk.Dec{}, types.ErrNoValidatorFound
+ }
+ // Prevent validators from reducing their self delegation below the min.
+ isValidatorOperator := fromDelegator.Equals(valAddr)
+ if isValidatorOperator {
+ if isBelowMinSelfDelegation(validator, fromDelegation.Shares.Sub(shares)) {
+ return sdk.Dec{}, types.ErrSelfDelegationBelowMinimum
+ }
+ }
+
+ returnAmount, err := k.fastUndelegate(ctx, valAddr, fromDelegator, shares)
+ if err != nil {
+ return sdk.Dec{}, err
+ }
+ returnCoins := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), returnAmount))
+
+ if err := k.bankKeeper.SendCoins(ctx, fromDelegator, toDelegator, returnCoins); err != nil {
+ return sdk.Dec{}, err
+ }
+ receivedShares, err := k.delegateFromAccount(ctx, valAddr, toDelegator, returnAmount)
+ if err != nil {
+ return sdk.Dec{}, err
+ }
+
+ return receivedShares, nil
+}
+
+// isBelowMinSelfDelegation check if the supplied shares, converted to tokens, are under the validator's min_self_delegation.
+func isBelowMinSelfDelegation(validator stakingtypes.ValidatorI, shares sdk.Dec) bool {
+ return validator.TokensFromShares(shares).TruncateInt().LT(validator.GetMinSelfDelegation())
+}
+
+// fastUndelegate undelegates shares from a validator skipping the unbonding period and not creating any unbonding delegations.
+func (k Keeper) fastUndelegate(ctx sdk.Context, valAddr sdk.ValAddress, delegator sdk.AccAddress, shares sdk.Dec) (sdk.Int, error) {
+ validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
+ if !found {
+ return sdk.Int{}, types.ErrNoDelegatorForAddress
+ }
+
+ returnAmount, err := k.stakingKeeper.Unbond(ctx, delegator, valAddr, shares)
+ if err != nil {
+ return sdk.Int{}, err
+ }
+ returnCoins := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), returnAmount))
+
+ // transfer the validator tokens to the not bonded pool
+ if validator.IsBonded() {
+ if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, returnCoins); err != nil {
+ panic(err)
+ }
+ }
+
+ if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(ctx, stakingtypes.NotBondedPoolName, delegator, returnCoins); err != nil {
+ return sdk.Int{}, err
+ }
+ return returnAmount, nil
+}
+
+// delegateFromAccount delegates to a validator from an account (vs redelegating from an existing delegation)
+func (k Keeper) delegateFromAccount(ctx sdk.Context, valAddr sdk.ValAddress, delegator sdk.AccAddress, amount sdk.Int) (sdk.Dec, error) {
+ validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
+ if !found {
+ return sdk.Dec{}, types.ErrNoValidatorFound
+ }
+ // source tokens are from an account, so subtractAccount true and tokenSrc unbonded
+ newShares, err := k.stakingKeeper.Delegate(ctx, delegator, amount, stakingtypes.Unbonded, validator, true)
+ if err != nil {
+ return sdk.Dec{}, err
+ }
+ return newShares, nil
+}
diff --git a/x/liquid/keeper/staking_test.go b/x/liquid/keeper/staking_test.go
new file mode 100644
index 00000000..8ab495ee
--- /dev/null
+++ b/x/liquid/keeper/staking_test.go
@@ -0,0 +1,378 @@
+package keeper_test
+
+import (
+ "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
+ 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/cosmos/cosmos-sdk/x/staking"
+ stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
+ stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
+
+ "github.com/kava-labs/kava/app"
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+var (
+ // d is an alias for sdk.MustNewDecFromStr
+ d = sdk.MustNewDecFromStr
+ // i is an alias for sdk.NewInt
+ i = sdk.NewInt
+ // c is an alias for sdk.NewInt64Coin
+ c = sdk.NewInt64Coin
+)
+
+func (suite *KeeperTestSuite) TestTransferDelegation_ValidatorStates() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(3)
+ valAccAddr, fromDelegator, toDelegator := addrs[0], addrs[1], addrs[2]
+ valAddr := sdk.ValAddress(valAccAddr)
+
+ initialBalance := i(1e9)
+
+ notBondedModAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName)
+ bondedModAddr := authtypes.NewModuleAddress(stakingtypes.BondedPoolName)
+
+ testCases := []struct {
+ name string
+ createValidator func() (delegatorShares sdk.Dec, err error)
+ }{
+ {
+ name: "bonded validator",
+ createValidator: func() (sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, initialBalance)
+ delegatorShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+
+ // Run end blocker to update validator state to bonded.
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return delegatorShares, nil
+ },
+ },
+ {
+ name: "unbonded validator",
+ createValidator: func() (sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, initialBalance)
+ delegatorShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+
+ // Don't run end blocker, new validators are by default unbonded.
+ return delegatorShares, nil
+ },
+ },
+ {
+ name: "ubonding (jailed) validator",
+ createValidator: func() (sdk.Dec, error) {
+ val := suite.CreateNewUnbondedValidator(valAddr, initialBalance)
+ delegatorShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+
+ // Run end blocker to update validator state to bonded.
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ // Jail and run end blocker to transition validator to unbonding.
+ consAddr, err := val.GetConsAddr()
+ if err != nil {
+ return sdk.Dec{}, err
+ }
+ suite.StakingKeeper.Jail(suite.Ctx, consAddr)
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return delegatorShares, nil
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+
+ suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(i(1e9)))
+ suite.CreateAccountWithAddress(fromDelegator, suite.NewBondCoins(i(1e9)))
+
+ fromDelegationShares, err := tc.createValidator()
+ suite.Require().NoError(err)
+
+ validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr)
+ suite.Require().True(found)
+ notBondedBalance := suite.BankKeeper.GetAllBalances(suite.Ctx, notBondedModAddr)
+ bondedBalance := suite.BankKeeper.GetAllBalances(suite.Ctx, bondedModAddr)
+
+ shares := d("1000")
+
+ _, err = suite.Keeper.TransferDelegation(suite.Ctx, valAddr, fromDelegator, toDelegator, shares)
+ suite.Require().NoError(err)
+
+ // Transferring a delegation should move shares, and leave the validator and pool balances the same.
+
+ suite.DelegationSharesEqual(valAddr, fromDelegator, fromDelegationShares.Sub(shares))
+ suite.DelegationSharesEqual(valAddr, toDelegator, shares) // also creates new delegation
+
+ validatorAfter, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr)
+ suite.Require().True(found)
+ suite.Equal(validator.GetTokens(), validatorAfter.GetTokens())
+ suite.Equal(validator.GetDelegatorShares(), validatorAfter.GetDelegatorShares())
+ suite.Equal(validator.GetStatus(), validatorAfter.GetStatus())
+
+ suite.AccountBalanceEqual(notBondedModAddr, notBondedBalance)
+ suite.AccountBalanceEqual(bondedModAddr, bondedBalance)
+ })
+ }
+}
+
+func (suite *KeeperTestSuite) TestTransferDelegation_Shares() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(5)
+ valAccAddr, fromDelegator, toDelegator := addrs[0], addrs[1], addrs[2]
+ valAddr := sdk.ValAddress(valAccAddr)
+
+ initialBalance := i(1e12)
+
+ testCases := []struct {
+ name string
+ createDelegations func() (fromDelegatorShares, toDelegatorShares sdk.Dec, err error)
+ shares sdk.Dec
+ expectReceived sdk.Dec
+ expectedErr error
+ }{
+ {
+ name: "negative shares cannot be transferred",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ // Run end blocker to update validator state to bonded.
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return fromDelegationShares, sdk.ZeroDec(), nil
+ },
+ shares: d("-1.0"),
+ expectedErr: types.ErrUntransferableShares,
+ },
+ {
+ name: "nil shares cannot be transferred",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return fromDelegationShares, sdk.ZeroDec(), nil
+ },
+ shares: sdk.Dec{},
+ expectedErr: types.ErrUntransferableShares,
+ },
+ {
+ name: "0 shares cannot be transferred",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ toDelegationShares := suite.CreateDelegation(valAddr, toDelegator, i(2e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return fromDelegationShares, toDelegationShares, nil
+ },
+ shares: sdk.ZeroDec(),
+ expectedErr: types.ErrUntransferableShares,
+ },
+ {
+ name: "all shares can be transferred",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ toDelegationShares := suite.CreateDelegation(valAddr, toDelegator, i(2e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return fromDelegationShares, toDelegationShares, nil
+ },
+ shares: d("1000000000.0"),
+ expectReceived: d("1000000000.0"),
+ },
+ {
+ name: "excess shares cannot be transferred",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return fromDelegationShares, sdk.ZeroDec(), nil
+ },
+ shares: d("1000000000.000000000000000001"),
+ expectedErr: stakingtypes.ErrNotEnoughDelegationShares,
+ },
+ {
+ name: "shares can be transferred to a non existent delegation",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return fromDelegationShares, sdk.ZeroDec(), nil
+ },
+ shares: d("500000000.0"),
+ expectReceived: d("500000000.0"),
+ },
+ {
+ name: "shares cannot be transferred from a non existent delegation",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ return sdk.ZeroDec(), sdk.ZeroDec(), nil
+ },
+ shares: d("500000000.0"),
+ expectedErr: types.ErrNoDelegatorForAddress,
+ },
+ {
+ name: "slashed validator shares can be transferred",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ suite.SlashValidator(valAddr, d("0.05"))
+
+ return fromDelegationShares, sdk.ZeroDec(), nil
+ },
+ shares: d("500000000.0"),
+ expectReceived: d("500000000.0"),
+ },
+ {
+ name: "zero shares received when transfer < 1 token",
+ createDelegations: func() (sdk.Dec, sdk.Dec, error) {
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(1e9))
+ toDelegationShares := suite.CreateDelegation(valAddr, toDelegator, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+ // make 1 share worth more than 1 token
+ suite.SlashValidator(valAddr, d("0.05"))
+
+ return fromDelegationShares, toDelegationShares, nil
+ },
+ shares: d("1.0"), // send 1 share (truncates to zero tokens)
+ expectReceived: d("0.0"),
+ },
+ }
+ for _, tc := range testCases {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+
+ suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(initialBalance))
+ suite.CreateAccountWithAddress(fromDelegator, suite.NewBondCoins(initialBalance))
+ suite.CreateAccountWithAddress(toDelegator, suite.NewBondCoins(initialBalance))
+
+ fromDelegationShares, toDelegationShares, err := tc.createDelegations()
+ suite.Require().NoError(err)
+ validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr)
+ suite.Require().True(found)
+
+ _, err = suite.Keeper.TransferDelegation(suite.Ctx, valAddr, fromDelegator, toDelegator, tc.shares)
+
+ if tc.expectedErr != nil {
+ suite.ErrorIs(err, tc.expectedErr)
+ return
+ }
+
+ suite.NoError(err)
+ suite.DelegationSharesEqual(valAddr, fromDelegator, fromDelegationShares.Sub(tc.shares))
+ suite.DelegationSharesEqual(valAddr, toDelegator, toDelegationShares.Add(tc.expectReceived))
+
+ validatorAfter, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr)
+ suite.Require().True(found)
+ // total tokens should not change
+ suite.Equal(validator.GetTokens(), validatorAfter.GetTokens())
+ // but total shares can differ
+ suite.Equal(
+ validator.GetDelegatorShares().Sub(tc.shares).Add(tc.expectReceived),
+ validatorAfter.GetDelegatorShares(),
+ )
+ })
+ }
+}
+
+func (suite *KeeperTestSuite) TestTransferDelegation_RedelegationsForbidden() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(4)
+ val1AccAddr, val2AccAddr, fromDelegator, toDelegator := addrs[0], addrs[1], addrs[2], addrs[3]
+ val1Addr := sdk.ValAddress(val1AccAddr)
+ val2Addr := sdk.ValAddress(val2AccAddr)
+
+ initialBalance := i(1e12)
+
+ suite.CreateAccountWithAddress(val1AccAddr, suite.NewBondCoins(initialBalance))
+ suite.CreateAccountWithAddress(val2AccAddr, suite.NewBondCoins(initialBalance))
+ suite.CreateAccountWithAddress(fromDelegator, suite.NewBondCoins(initialBalance))
+
+ // create bonded validator 1 with a delegation
+ suite.CreateNewUnbondedValidator(val1Addr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(val1Addr, fromDelegator, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ // create validator 2 and redelegate to it
+ suite.CreateNewUnbondedValidator(val2Addr, i(1e9))
+ suite.CreateRedelegation(fromDelegator, val1Addr, val2Addr, i(1e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ _, err := suite.Keeper.TransferDelegation(suite.Ctx, val2Addr, fromDelegator, toDelegator, fromDelegationShares)
+ suite.ErrorIs(err, types.ErrRedelegationsNotCompleted)
+ suite.DelegationSharesEqual(val2Addr, fromDelegator, fromDelegationShares)
+ suite.DelegationSharesEqual(val2Addr, toDelegator, sdk.ZeroDec())
+}
+
+func (suite *KeeperTestSuite) TestTransferDelegation_CompliesWithMinSelfDelegation() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(4)
+ valAccAddr, toDelegator := addrs[0], addrs[1]
+ valAddr := sdk.ValAddress(valAccAddr)
+
+ suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(i(1e12)))
+
+ // create bonded validator with minimum delegated
+ minSelfDelegation := i(1e9)
+ delegation := suite.NewBondCoin(i(1e9))
+ msg, err := stakingtypes.NewMsgCreateValidator(
+ valAddr,
+ ed25519.GenPrivKey().PubKey(),
+ delegation,
+ stakingtypes.Description{},
+ stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
+ minSelfDelegation,
+ )
+ suite.Require().NoError(err)
+
+ msgServer := stakingkeeper.NewMsgServerImpl(suite.StakingKeeper)
+ _, err = msgServer.CreateValidator(sdk.WrapSDKContext(suite.Ctx), msg)
+ suite.Require().NoError(err)
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ _, err = suite.Keeper.TransferDelegation(suite.Ctx, valAddr, valAccAddr, toDelegator, d("0.000000000000000001"))
+ suite.ErrorIs(err, types.ErrSelfDelegationBelowMinimum)
+ suite.DelegationSharesEqual(valAddr, valAccAddr, delegation.Amount.ToDec())
+}
+
+func (suite *KeeperTestSuite) TestTransferDelegation_CanTransferVested() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(4)
+ valAccAddr, fromDelegator, toDelegator := addrs[0], addrs[1], addrs[2]
+ valAddr := sdk.ValAddress(valAccAddr)
+
+ suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(i(1e9)))
+ suite.CreateVestingAccountWithAddress(fromDelegator, suite.NewBondCoins(i(2e9)), suite.NewBondCoins(i(1e9)))
+
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ fromDelegationShares := suite.CreateDelegation(valAddr, fromDelegator, i(2e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ shares := d("1000000000.0")
+ _, err := suite.Keeper.TransferDelegation(suite.Ctx, valAddr, fromDelegator, toDelegator, shares)
+ suite.NoError(err)
+ suite.DelegationSharesEqual(valAddr, fromDelegator, fromDelegationShares.Sub(shares))
+ suite.DelegationSharesEqual(valAddr, toDelegator, shares)
+}
+
+func (suite *KeeperTestSuite) TestTransferDelegation_CannotTransferVesting() {
+ _, addrs := app.GeneratePrivKeyAddressPairs(4)
+ valAccAddr, fromDelegator, toDelegator := addrs[0], addrs[1], addrs[2]
+ valAddr := sdk.ValAddress(valAccAddr)
+
+ suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(i(1e9)))
+ suite.CreateVestingAccountWithAddress(fromDelegator, suite.NewBondCoins(i(2e9)), suite.NewBondCoins(i(1e9)))
+
+ suite.CreateNewUnbondedValidator(valAddr, i(1e9))
+ suite.CreateDelegation(valAddr, fromDelegator, i(2e9))
+ staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
+
+ _, err := suite.Keeper.TransferDelegation(suite.Ctx, valAddr, fromDelegator, toDelegator, d("1000000001.0"))
+ suite.ErrorIs(err, sdkerrors.ErrInsufficientFunds)
+}
diff --git a/x/liquid/module.go b/x/liquid/module.go
new file mode 100644
index 00000000..a3756120
--- /dev/null
+++ b/x/liquid/module.go
@@ -0,0 +1,138 @@
+package liquid
+
+import (
+ "encoding/json"
+
+ "github.com/gorilla/mux"
+ "github.com/grpc-ecosystem/grpc-gateway/runtime"
+ "github.com/spf13/cobra"
+
+ "github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/codec"
+ codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/cosmos/cosmos-sdk/types/module"
+
+ abci "github.com/tendermint/tendermint/abci/types"
+
+ "github.com/kava-labs/kava/x/liquid/client/cli"
+ "github.com/kava-labs/kava/x/liquid/keeper"
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+var (
+ _ module.AppModule = AppModule{}
+ _ module.AppModuleBasic = AppModuleBasic{}
+)
+
+// AppModuleBasic app module basics object
+type AppModuleBasic struct{}
+
+// Name get module name
+func (AppModuleBasic) Name() string {
+ return types.ModuleName
+}
+
+// RegisterLegacyAminoCodec register module codec
+func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
+ types.RegisterLegacyAminoCodec(cdc)
+}
+
+// DefaultGenesis default genesis state
+func (AppModuleBasic) DefaultGenesis(_ codec.JSONCodec) json.RawMessage {
+ return []byte("{}")
+}
+
+// ValidateGenesis module validate genesis
+func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConfig, _ json.RawMessage) error {
+ return nil
+}
+
+// RegisterInterfaces implements InterfaceModule.RegisterInterfaces
+func (a AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
+ types.RegisterInterfaces(registry)
+}
+
+// RegisterRESTRoutes registers REST routes for the module.
+func (a AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
+
+// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module.
+func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {}
+
+// GetTxCmd returns the root tx command for the module.
+func (AppModuleBasic) GetTxCmd() *cobra.Command {
+ return cli.GetTxCmd()
+}
+
+// GetQueryCmd returns no root query command for the module.
+func (AppModuleBasic) GetQueryCmd() *cobra.Command {
+ return cli.GetQueryCmd()
+}
+
+//____________________________________________________________________________
+
+// AppModule app module type
+type AppModule struct {
+ AppModuleBasic
+
+ keeper keeper.Keeper
+}
+
+// NewAppModule creates a new AppModule object
+func NewAppModule(keeper keeper.Keeper) AppModule {
+ return AppModule{
+ AppModuleBasic: AppModuleBasic{},
+ keeper: keeper,
+ }
+}
+
+// Name module name
+func (am AppModule) Name() string {
+ return am.AppModuleBasic.Name()
+}
+
+// RegisterInvariants register module invariants
+func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
+
+// Route module message route name
+func (am AppModule) Route() sdk.Route {
+ return sdk.Route{}
+}
+
+// QuerierRoute module querier route name
+func (AppModule) QuerierRoute() string {
+ return ""
+}
+
+// LegacyQuerierHandler returns no sdk.Querier.
+func (am AppModule) LegacyQuerierHandler(_ *codec.LegacyAmino) sdk.Querier {
+ return nil
+}
+
+// ConsensusVersion implements AppModule/ConsensusVersion.
+func (AppModule) ConsensusVersion() uint64 {
+ return 1
+}
+
+// RegisterServices registers module services.
+func (am AppModule) RegisterServices(cfg module.Configurator) {
+ types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
+}
+
+// InitGenesis module init-genesis
+func (am AppModule) InitGenesis(_ sdk.Context, _ codec.JSONCodec, _ json.RawMessage) []abci.ValidatorUpdate {
+ return []abci.ValidatorUpdate{}
+}
+
+// ExportGenesis module export genesis
+func (am AppModule) ExportGenesis(_ sdk.Context, cdc codec.JSONCodec) json.RawMessage {
+ return am.DefaultGenesis(cdc)
+}
+
+// BeginBlock module begin-block
+func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
+
+// EndBlock module end-block
+func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
+ return []abci.ValidatorUpdate{}
+}
diff --git a/x/liquid/types/codec.go b/x/liquid/types/codec.go
new file mode 100644
index 00000000..9a6a1599
--- /dev/null
+++ b/x/liquid/types/codec.go
@@ -0,0 +1,38 @@
+package types
+
+import (
+ "github.com/cosmos/cosmos-sdk/codec"
+ "github.com/cosmos/cosmos-sdk/codec/types"
+ "github.com/cosmos/cosmos-sdk/types/msgservice"
+
+ cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// RegisterLegacyAminoCodec registers all the necessary types and interfaces for the module.
+func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
+ cdc.RegisterConcrete(&MsgMintDerivative{}, "liquid/MsgMintDerivative", nil)
+ cdc.RegisterConcrete(&MsgBurnDerivative{}, "liquid/MsgBurnDerivative", nil)
+}
+
+// RegisterInterfaces registers proto messages under their interfaces for unmarshalling,
+// in addition to registering the msg service for handling tx msgs.
+func RegisterInterfaces(registry types.InterfaceRegistry) {
+ registry.RegisterImplementations((*sdk.Msg)(nil),
+ &MsgMintDerivative{},
+ &MsgBurnDerivative{},
+ )
+
+ msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
+}
+
+var (
+ amino = codec.NewLegacyAmino()
+ ModuleCdc = codec.NewAminoCodec(amino)
+)
+
+func init() {
+ RegisterLegacyAminoCodec(amino)
+ cryptocodec.RegisterCrypto(amino)
+}
diff --git a/x/liquid/types/common_test.go b/x/liquid/types/common_test.go
new file mode 100644
index 00000000..e88e1d5e
--- /dev/null
+++ b/x/liquid/types/common_test.go
@@ -0,0 +1,13 @@
+package types_test
+
+import (
+ "os"
+ "testing"
+
+ "github.com/kava-labs/kava/app"
+)
+
+func TestMain(m *testing.M) {
+ app.SetSDKConfig()
+ os.Exit(m.Run())
+}
diff --git a/x/liquid/types/errors.go b/x/liquid/types/errors.go
new file mode 100644
index 00000000..e4d6652a
--- /dev/null
+++ b/x/liquid/types/errors.go
@@ -0,0 +1,15 @@
+package types
+
+import (
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+)
+
+var (
+ ErrNoValidatorFound = sdkerrors.New(ModuleName, 2, "validator does not exist")
+ ErrNoDelegatorForAddress = sdkerrors.New(ModuleName, 3, "delegator does not contain delegation")
+ ErrInvalidDenom = sdkerrors.New(ModuleName, 4, "invalid denom")
+ ErrNotEnoughDelegationShares = sdkerrors.New(ModuleName, 5, "not enough delegation shares")
+ ErrRedelegationsNotCompleted = sdkerrors.New(ModuleName, 6, "active redelegations cannot be transferred")
+ ErrUntransferableShares = sdkerrors.New(ModuleName, 7, "shares cannot be transferred")
+ ErrSelfDelegationBelowMinimum = sdkerrors.Register(ModuleName, 8, "validator's self delegation must be greater than their minimum self delegation")
+)
diff --git a/x/liquid/types/events.go b/x/liquid/types/events.go
new file mode 100644
index 00000000..27658719
--- /dev/null
+++ b/x/liquid/types/events.go
@@ -0,0 +1,11 @@
+package types
+
+const (
+ EventTypeMintDerivative = "mint_derivative"
+ EventTypeBurnDerivative = "burn_derivative"
+
+ AttributeValueCategory = ModuleName
+ AttributeKeyDelegator = "delegator"
+ AttributeKeyValidator = "validator"
+ AttributeKeySharesTransferred = "shares_transferred"
+)
diff --git a/x/liquid/types/expected_keepers.go b/x/liquid/types/expected_keepers.go
new file mode 100644
index 00000000..c3d9ca8a
--- /dev/null
+++ b/x/liquid/types/expected_keepers.go
@@ -0,0 +1,46 @@
+package types
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ "github.com/cosmos/cosmos-sdk/x/staking/types"
+ stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
+)
+
+// BankKeeper defines the expected bank keeper
+type BankKeeper interface {
+ SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
+ SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
+ SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
+ SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
+
+ MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
+ BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
+ UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
+}
+
+// AccountKeeper defines the expected keeper interface for interacting with account
+type AccountKeeper interface {
+ GetModuleAccount(ctx sdk.Context, name string) authtypes.ModuleAccountI
+}
+
+// StakingKeeper defines the expected keeper interface for interacting with staking
+type StakingKeeper interface {
+ BondDenom(ctx sdk.Context) (res string)
+
+ GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool)
+ GetDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (delegation types.Delegation, found bool)
+ HasReceivingRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) bool
+
+ ValidateUnbondAmount(
+ ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt sdk.Int,
+ ) (shares sdk.Dec, err error)
+
+ Delegate(
+ ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Int, tokenSrc stakingtypes.BondStatus,
+ validator stakingtypes.Validator, subtractAccount bool,
+ ) (newShares sdk.Dec, err error)
+ Unbond(
+ ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec,
+ ) (amount sdk.Int, err error)
+}
diff --git a/x/liquid/types/key.go b/x/liquid/types/key.go
new file mode 100644
index 00000000..ba3d9822
--- /dev/null
+++ b/x/liquid/types/key.go
@@ -0,0 +1,26 @@
+package types
+
+import (
+ "fmt"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+const (
+ // ModuleName The name that will be used throughout the module
+ ModuleName = "liquid"
+
+ // RouterKey Top level router key
+ RouterKey = ModuleName
+
+ // ModuleAccountName is the module account's name
+ ModuleAccountName = ModuleName
+
+ DefaultDerivativeDenom = "bkava"
+
+ DenomSeparator = "-"
+)
+
+func GetLiquidStakingTokenDenom(bondDenom string, valAddr sdk.ValAddress) string {
+ return fmt.Sprintf("%s%s%s", bondDenom, DenomSeparator, valAddr.String())
+}
diff --git a/x/liquid/types/msg.go b/x/liquid/types/msg.go
new file mode 100644
index 00000000..8b37ac22
--- /dev/null
+++ b/x/liquid/types/msg.go
@@ -0,0 +1,120 @@
+package types
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
+)
+
+const (
+ // TypeMsgMintDerivative represents the type string for MsgMintDerivative
+ TypeMsgMintDerivative = "mint_derivative"
+ // TypeMsgBurnDerivative represents the type string for MsgBurnDerivative
+ TypeMsgBurnDerivative = "burn_derivative"
+)
+
+// ensure Msg interface compliance at compile time
+var (
+ _ sdk.Msg = &MsgMintDerivative{}
+ _ legacytx.LegacyMsg = &MsgMintDerivative{}
+ _ sdk.Msg = &MsgBurnDerivative{}
+ _ legacytx.LegacyMsg = &MsgBurnDerivative{}
+)
+
+// NewMsgMintDerivative returns a new MsgMintDerivative
+func NewMsgMintDerivative(sender sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) MsgMintDerivative {
+ return MsgMintDerivative{
+ Sender: sender.String(),
+ Validator: validator.String(),
+ Amount: amount,
+ }
+}
+
+// Route return the message type used for routing the message.
+func (msg MsgMintDerivative) Route() string { return RouterKey }
+
+// Type returns a human-readable string for the message, intended for utilization within tags.
+func (msg MsgMintDerivative) Type() string { return TypeMsgMintDerivative }
+
+// ValidateBasic does a simple validation check that doesn't require access to any other information.
+func (msg MsgMintDerivative) ValidateBasic() error {
+ _, err := sdk.AccAddressFromBech32(msg.Sender)
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
+ }
+
+ _, err = sdk.ValAddressFromBech32(msg.Validator)
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
+ }
+
+ if msg.Amount.IsNil() || !msg.Amount.IsValid() || msg.Amount.IsZero() {
+ return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "'%s'", msg.Amount)
+ }
+
+ return nil
+}
+
+// GetSignBytes gets the canonical byte representation of the Msg.
+func (msg MsgMintDerivative) GetSignBytes() []byte {
+ bz := ModuleCdc.MustMarshalJSON(&msg)
+ return sdk.MustSortJSON(bz)
+}
+
+// GetSigners returns the addresses of signers that must sign.
+func (msg MsgMintDerivative) GetSigners() []sdk.AccAddress {
+ sender, err := sdk.AccAddressFromBech32(msg.Sender)
+ if err != nil {
+ panic(err)
+ }
+ return []sdk.AccAddress{sender}
+}
+
+// NewMsgBurnDerivative returns a new MsgBurnDerivative
+func NewMsgBurnDerivative(sender sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) MsgBurnDerivative {
+ return MsgBurnDerivative{
+ Sender: sender.String(),
+ Validator: validator.String(),
+ Amount: amount,
+ }
+}
+
+// Route return the message type used for routing the message.
+func (msg MsgBurnDerivative) Route() string { return RouterKey }
+
+// Type returns a human-readable string for the message, intended for utilization within tags.
+func (msg MsgBurnDerivative) Type() string { return TypeMsgBurnDerivative }
+
+// ValidateBasic does a simple validation check that doesn't require access to any other information.
+func (msg MsgBurnDerivative) ValidateBasic() error {
+ _, err := sdk.AccAddressFromBech32(msg.Sender)
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
+ }
+
+ _, err = sdk.ValAddressFromBech32(msg.Validator)
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
+ }
+
+ if msg.Amount.IsNil() || !msg.Amount.IsValid() || msg.Amount.IsZero() {
+ return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "'%s'", msg.Amount)
+ }
+
+ return nil
+}
+
+// GetSignBytes gets the canonical byte representation of the Msg.
+func (msg MsgBurnDerivative) GetSignBytes() []byte {
+ bz := ModuleCdc.MustMarshalJSON(&msg)
+ return sdk.MustSortJSON(bz)
+}
+
+// GetSigners returns the addresses of signers that must sign.
+func (msg MsgBurnDerivative) GetSigners() []sdk.AccAddress {
+ sender, err := sdk.AccAddressFromBech32(msg.Sender)
+ if err != nil {
+ panic(err)
+ }
+ return []sdk.AccAddress{sender}
+}
diff --git a/x/liquid/types/msg_test.go b/x/liquid/types/msg_test.go
new file mode 100644
index 00000000..1ca3cda8
--- /dev/null
+++ b/x/liquid/types/msg_test.go
@@ -0,0 +1,163 @@
+package types_test
+
+import (
+ fmt "fmt"
+ "testing"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/kava-labs/kava/x/liquid/types"
+)
+
+func TestMsgMintDerivative_Signing(t *testing.T) {
+ address := mustAccAddressFromBech32("kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d")
+ validatorAddress := mustValAddressFromBech32("kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42")
+
+ msg := types.NewMsgMintDerivative(
+ address,
+ validatorAddress,
+ sdk.NewCoin("ukava", sdk.NewInt(1e9)),
+ )
+
+ // checking for the "type" field ensures the msg is registered on the amino codec
+ signBytes := []byte(
+ `{"type":"liquid/MsgMintDerivative","value":{"amount":{"amount":"1000000000","denom":"ukava"},"sender":"kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d","validator":"kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42"}}`,
+ )
+
+ assert.Equal(t, []sdk.AccAddress{address}, msg.GetSigners())
+ assert.Equal(t, signBytes, msg.GetSignBytes())
+}
+
+func TestMsgBurnDerivative_Signing(t *testing.T) {
+ address := mustAccAddressFromBech32("kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d")
+ validatorAddress := mustValAddressFromBech32("kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42")
+
+ msg := types.NewMsgBurnDerivative(
+ address,
+ validatorAddress,
+ sdk.NewCoin("bkava-kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42", sdk.NewInt(1e9)),
+ )
+
+ // checking for the "type" field ensures the msg is registered on the amino codec
+ signBytes := []byte(
+ `{"type":"liquid/MsgBurnDerivative","value":{"amount":{"amount":"1000000000","denom":"bkava-kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42"},"sender":"kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d","validator":"kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42"}}`,
+ )
+
+ assert.Equal(t, []sdk.AccAddress{address}, msg.GetSigners())
+ assert.Equal(t, signBytes, msg.GetSignBytes())
+}
+
+func TestMsg_Validate(t *testing.T) {
+ validAddress := mustAccAddressFromBech32("kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d")
+ validValidatorAddress := mustValAddressFromBech32("kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42")
+ validCoin := sdk.NewInt64Coin("ukava", 1e9)
+
+ type msgArgs struct {
+ sender string
+ validator string
+ amount sdk.Coin
+ }
+ tests := []struct {
+ name string
+ msgArgs msgArgs
+ expectedErr error
+ }{
+ {
+ name: "normal is valid",
+ msgArgs: msgArgs{
+ sender: validAddress.String(),
+ validator: validValidatorAddress.String(),
+ amount: validCoin,
+ },
+ },
+ {
+ name: "invalid sender",
+ msgArgs: msgArgs{
+ sender: "invalid",
+ validator: validValidatorAddress.String(),
+ amount: validCoin,
+ },
+ expectedErr: sdkerrors.ErrInvalidAddress,
+ },
+ {
+ name: "invalid short sender",
+ msgArgs: msgArgs{
+ sender: "kava1uexte6", // encoded zero length address
+ validator: validValidatorAddress.String(),
+ amount: validCoin,
+ },
+ expectedErr: sdkerrors.ErrInvalidAddress,
+ },
+ {
+ name: "invalid validator",
+ msgArgs: msgArgs{
+ sender: validAddress.String(),
+ validator: "invalid",
+ amount: validCoin,
+ },
+ expectedErr: sdkerrors.ErrInvalidAddress,
+ },
+ {
+ name: "invalid nil coin",
+ msgArgs: msgArgs{
+ sender: validAddress.String(),
+ validator: validValidatorAddress.String(),
+ amount: sdk.Coin{},
+ },
+ expectedErr: sdkerrors.ErrInvalidCoins,
+ },
+ {
+ name: "invalid zero coin",
+ msgArgs: msgArgs{
+ sender: validAddress.String(),
+ validator: validValidatorAddress.String(),
+ amount: sdk.NewInt64Coin("ukava", 0),
+ },
+ expectedErr: sdkerrors.ErrInvalidCoins,
+ },
+ }
+
+ for _, tc := range tests {
+ msgs := []sdk.Msg{
+ &types.MsgMintDerivative{
+ Sender: tc.msgArgs.sender,
+ Validator: tc.msgArgs.validator,
+ Amount: tc.msgArgs.amount,
+ },
+ &types.MsgBurnDerivative{
+ Sender: tc.msgArgs.sender,
+ Validator: tc.msgArgs.validator,
+ Amount: tc.msgArgs.amount,
+ },
+ }
+ for _, msg := range msgs {
+ t.Run(fmt.Sprintf("%s/%T", tc.name, msg), func(t *testing.T) {
+ err := msg.ValidateBasic()
+ if tc.expectedErr == nil {
+ require.NoError(t, err)
+ } else {
+ require.ErrorIs(t, err, tc.expectedErr, "expected error '%s' not found in actual '%s'", tc.expectedErr, err)
+ }
+ })
+ }
+ }
+}
+
+func mustAccAddressFromBech32(address string) sdk.AccAddress {
+ addr, err := sdk.AccAddressFromBech32(address)
+ if err != nil {
+ panic(err)
+ }
+ return addr
+}
+
+func mustValAddressFromBech32(address string) sdk.ValAddress {
+ addr, err := sdk.ValAddressFromBech32(address)
+ if err != nil {
+ panic(err)
+ }
+ return addr
+}
diff --git a/x/liquid/types/tx.pb.go b/x/liquid/types/tx.pb.go
new file mode 100644
index 00000000..42619710
--- /dev/null
+++ b/x/liquid/types/tx.pb.go
@@ -0,0 +1,1188 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: kava/liquid/v1beta1/tx.proto
+
+package types
+
+import (
+ context "context"
+ fmt "fmt"
+ _ "github.com/cosmos/cosmos-proto"
+ github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
+ types "github.com/cosmos/cosmos-sdk/types"
+ _ "github.com/gogo/protobuf/gogoproto"
+ grpc1 "github.com/gogo/protobuf/grpc"
+ proto "github.com/gogo/protobuf/proto"
+ 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
+
+// MsgMintDerivative defines the Msg/MintDerivative request type.
+type MsgMintDerivative struct {
+ // sender is the owner of the delegation to be converted
+ Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"`
+ // validator is the validator of the delegation to be converted
+ Validator string `protobuf:"bytes,2,opt,name=validator,proto3" json:"validator,omitempty"`
+ // amount is the quantity of staked assets to be converted
+ Amount types.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount"`
+}
+
+func (m *MsgMintDerivative) Reset() { *m = MsgMintDerivative{} }
+func (m *MsgMintDerivative) String() string { return proto.CompactTextString(m) }
+func (*MsgMintDerivative) ProtoMessage() {}
+func (*MsgMintDerivative) Descriptor() ([]byte, []int) {
+ return fileDescriptor_738981106e50f269, []int{0}
+}
+func (m *MsgMintDerivative) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *MsgMintDerivative) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_MsgMintDerivative.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 *MsgMintDerivative) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MsgMintDerivative.Merge(m, src)
+}
+func (m *MsgMintDerivative) XXX_Size() int {
+ return m.Size()
+}
+func (m *MsgMintDerivative) XXX_DiscardUnknown() {
+ xxx_messageInfo_MsgMintDerivative.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MsgMintDerivative proto.InternalMessageInfo
+
+func (m *MsgMintDerivative) GetSender() string {
+ if m != nil {
+ return m.Sender
+ }
+ return ""
+}
+
+func (m *MsgMintDerivative) GetValidator() string {
+ if m != nil {
+ return m.Validator
+ }
+ return ""
+}
+
+func (m *MsgMintDerivative) GetAmount() types.Coin {
+ if m != nil {
+ return m.Amount
+ }
+ return types.Coin{}
+}
+
+// MsgMintDerivativeResponse defines the Msg/MintDerivative response type.
+type MsgMintDerivativeResponse struct {
+ // received is the amount of staking derivative minted and sent to the sender
+ Received types.Coin `protobuf:"bytes,1,opt,name=received,proto3" json:"received"`
+}
+
+func (m *MsgMintDerivativeResponse) Reset() { *m = MsgMintDerivativeResponse{} }
+func (m *MsgMintDerivativeResponse) String() string { return proto.CompactTextString(m) }
+func (*MsgMintDerivativeResponse) ProtoMessage() {}
+func (*MsgMintDerivativeResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_738981106e50f269, []int{1}
+}
+func (m *MsgMintDerivativeResponse) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *MsgMintDerivativeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_MsgMintDerivativeResponse.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 *MsgMintDerivativeResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MsgMintDerivativeResponse.Merge(m, src)
+}
+func (m *MsgMintDerivativeResponse) XXX_Size() int {
+ return m.Size()
+}
+func (m *MsgMintDerivativeResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_MsgMintDerivativeResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MsgMintDerivativeResponse proto.InternalMessageInfo
+
+func (m *MsgMintDerivativeResponse) GetReceived() types.Coin {
+ if m != nil {
+ return m.Received
+ }
+ return types.Coin{}
+}
+
+// MsgBurnDerivative defines the Msg/BurnDerivative request type.
+type MsgBurnDerivative struct {
+ // sender is the owner of the derivatives to be converted
+ Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"`
+ // validator is the validator of the derivatives to be converted
+ Validator string `protobuf:"bytes,2,opt,name=validator,proto3" json:"validator,omitempty"`
+ // amount is the quantity of derivatives to be converted
+ Amount types.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount"`
+}
+
+func (m *MsgBurnDerivative) Reset() { *m = MsgBurnDerivative{} }
+func (m *MsgBurnDerivative) String() string { return proto.CompactTextString(m) }
+func (*MsgBurnDerivative) ProtoMessage() {}
+func (*MsgBurnDerivative) Descriptor() ([]byte, []int) {
+ return fileDescriptor_738981106e50f269, []int{2}
+}
+func (m *MsgBurnDerivative) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *MsgBurnDerivative) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_MsgBurnDerivative.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 *MsgBurnDerivative) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MsgBurnDerivative.Merge(m, src)
+}
+func (m *MsgBurnDerivative) XXX_Size() int {
+ return m.Size()
+}
+func (m *MsgBurnDerivative) XXX_DiscardUnknown() {
+ xxx_messageInfo_MsgBurnDerivative.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MsgBurnDerivative proto.InternalMessageInfo
+
+func (m *MsgBurnDerivative) GetSender() string {
+ if m != nil {
+ return m.Sender
+ }
+ return ""
+}
+
+func (m *MsgBurnDerivative) GetValidator() string {
+ if m != nil {
+ return m.Validator
+ }
+ return ""
+}
+
+func (m *MsgBurnDerivative) GetAmount() types.Coin {
+ if m != nil {
+ return m.Amount
+ }
+ return types.Coin{}
+}
+
+// MsgBurnDerivativeResponse defines the Msg/BurnDerivative response type.
+type MsgBurnDerivativeResponse struct {
+ // received is the number of delegation shares sent to the sender
+ Received github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=received,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"received"`
+}
+
+func (m *MsgBurnDerivativeResponse) Reset() { *m = MsgBurnDerivativeResponse{} }
+func (m *MsgBurnDerivativeResponse) String() string { return proto.CompactTextString(m) }
+func (*MsgBurnDerivativeResponse) ProtoMessage() {}
+func (*MsgBurnDerivativeResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_738981106e50f269, []int{3}
+}
+func (m *MsgBurnDerivativeResponse) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *MsgBurnDerivativeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_MsgBurnDerivativeResponse.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 *MsgBurnDerivativeResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MsgBurnDerivativeResponse.Merge(m, src)
+}
+func (m *MsgBurnDerivativeResponse) XXX_Size() int {
+ return m.Size()
+}
+func (m *MsgBurnDerivativeResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_MsgBurnDerivativeResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MsgBurnDerivativeResponse proto.InternalMessageInfo
+
+func init() {
+ proto.RegisterType((*MsgMintDerivative)(nil), "kava.liquid.v1beta1.MsgMintDerivative")
+ proto.RegisterType((*MsgMintDerivativeResponse)(nil), "kava.liquid.v1beta1.MsgMintDerivativeResponse")
+ proto.RegisterType((*MsgBurnDerivative)(nil), "kava.liquid.v1beta1.MsgBurnDerivative")
+ proto.RegisterType((*MsgBurnDerivativeResponse)(nil), "kava.liquid.v1beta1.MsgBurnDerivativeResponse")
+}
+
+func init() { proto.RegisterFile("kava/liquid/v1beta1/tx.proto", fileDescriptor_738981106e50f269) }
+
+var fileDescriptor_738981106e50f269 = []byte{
+ // 421 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x53, 0xbd, 0xae, 0xda, 0x30,
+ 0x18, 0x8d, 0x7b, 0x2b, 0x54, 0x5c, 0xe9, 0x4a, 0x4d, 0xef, 0x10, 0xd0, 0x55, 0x40, 0x0c, 0x88,
+ 0x25, 0x4e, 0xa1, 0x43, 0x87, 0x76, 0x69, 0xca, 0xca, 0x92, 0x2e, 0xa8, 0x4b, 0xe5, 0x24, 0x56,
+ 0xb0, 0x00, 0x9b, 0xda, 0x4e, 0x44, 0xdf, 0xa2, 0x0f, 0xd0, 0xc7, 0xe0, 0x21, 0x18, 0x11, 0x53,
+ 0xdb, 0x01, 0x55, 0xf0, 0x22, 0x55, 0x12, 0x13, 0xca, 0x9f, 0xc4, 0x78, 0xa7, 0x38, 0x3e, 0xe7,
+ 0x7c, 0x3e, 0xe7, 0xfb, 0x6c, 0xf8, 0x38, 0xc6, 0x29, 0x76, 0x27, 0xf4, 0x5b, 0x42, 0x23, 0x37,
+ 0xed, 0x06, 0x44, 0xe1, 0xae, 0xab, 0xe6, 0x68, 0x26, 0xb8, 0xe2, 0xe6, 0xeb, 0x0c, 0x45, 0x05,
+ 0x8a, 0x34, 0x5a, 0x7f, 0x88, 0x79, 0xcc, 0x73, 0xdc, 0xcd, 0x56, 0x05, 0xb5, 0x6e, 0x87, 0x5c,
+ 0x4e, 0xb9, 0x74, 0x03, 0x2c, 0x49, 0x59, 0x28, 0xe4, 0x94, 0x69, 0xbc, 0x56, 0xe0, 0x5f, 0x0b,
+ 0x61, 0xf1, 0x53, 0x40, 0xad, 0x9f, 0x00, 0xbe, 0x1a, 0xc8, 0x78, 0x40, 0x99, 0xea, 0x13, 0x41,
+ 0x53, 0xac, 0x68, 0x4a, 0xcc, 0x37, 0xb0, 0x22, 0x09, 0x8b, 0x88, 0xb0, 0x40, 0x13, 0x74, 0xaa,
+ 0x9e, 0xb5, 0x5e, 0x38, 0x0f, 0x5a, 0xf7, 0x31, 0x8a, 0x04, 0x91, 0xf2, 0xb3, 0x12, 0x94, 0xc5,
+ 0xbe, 0xe6, 0x99, 0x8f, 0xb0, 0x9a, 0xe2, 0x09, 0x8d, 0xb0, 0xe2, 0xc2, 0x7a, 0x96, 0x89, 0xfc,
+ 0xc3, 0x86, 0xf9, 0x0e, 0x56, 0xf0, 0x94, 0x27, 0x4c, 0x59, 0x77, 0x4d, 0xd0, 0x79, 0xd9, 0xab,
+ 0x21, 0x5d, 0x2c, 0x73, 0xbc, 0x0f, 0x87, 0x3e, 0x71, 0xca, 0xbc, 0xe7, 0xcb, 0x4d, 0xc3, 0xf0,
+ 0x35, 0xbd, 0x35, 0x84, 0xb5, 0x33, 0x77, 0x3e, 0x91, 0x33, 0xce, 0x24, 0x31, 0xdf, 0xc3, 0x17,
+ 0x82, 0x84, 0x84, 0xa6, 0x24, 0xca, 0x7d, 0xde, 0x50, 0xb7, 0x14, 0xec, 0x83, 0x7b, 0x89, 0x60,
+ 0x4f, 0x31, 0x78, 0x92, 0x07, 0x3f, 0x76, 0x57, 0x06, 0x1f, 0x9e, 0x04, 0xaf, 0x7a, 0x1f, 0x32,
+ 0xf1, 0x9f, 0x4d, 0xa3, 0x1d, 0x53, 0x35, 0x4a, 0x02, 0x14, 0xf2, 0xa9, 0x9e, 0xb3, 0xfe, 0x38,
+ 0x32, 0x1a, 0xbb, 0xea, 0xfb, 0x8c, 0x48, 0xd4, 0x27, 0xe1, 0x7a, 0xe1, 0x40, 0x6d, 0xa4, 0x4f,
+ 0xc2, 0x43, 0x57, 0x7a, 0xbf, 0x01, 0xbc, 0x1b, 0xc8, 0xd8, 0x1c, 0xc1, 0xfb, 0x93, 0x2b, 0xd1,
+ 0x46, 0x17, 0xee, 0x23, 0x3a, 0x1b, 0x4e, 0x1d, 0xdd, 0xc6, 0x2b, 0xb3, 0x8c, 0xe0, 0xfd, 0xc9,
+ 0x0c, 0xae, 0x9e, 0x74, 0xcc, 0xbb, 0x7e, 0xd2, 0xe5, 0xae, 0x79, 0xde, 0x72, 0x6b, 0x83, 0xd5,
+ 0xd6, 0x06, 0x7f, 0xb7, 0x36, 0xf8, 0xb1, 0xb3, 0x8d, 0xd5, 0xce, 0x36, 0x7e, 0xed, 0x6c, 0xe3,
+ 0x4b, 0xe7, 0xbf, 0xae, 0x65, 0x35, 0x9d, 0x09, 0x0e, 0x64, 0xbe, 0x72, 0xe7, 0xfb, 0xf7, 0x99,
+ 0xf7, 0x2e, 0xa8, 0xe4, 0xaf, 0xe6, 0xed, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x25, 0x6e,
+ 0x57, 0xbb, 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 {
+ // MintDerivative defines a method for converting a delegation into staking deriviatives.
+ MintDerivative(ctx context.Context, in *MsgMintDerivative, opts ...grpc.CallOption) (*MsgMintDerivativeResponse, error)
+ // BurnDerivative defines a method for converting staking deriviatives into a delegation.
+ BurnDerivative(ctx context.Context, in *MsgBurnDerivative, opts ...grpc.CallOption) (*MsgBurnDerivativeResponse, error)
+}
+
+type msgClient struct {
+ cc grpc1.ClientConn
+}
+
+func NewMsgClient(cc grpc1.ClientConn) MsgClient {
+ return &msgClient{cc}
+}
+
+func (c *msgClient) MintDerivative(ctx context.Context, in *MsgMintDerivative, opts ...grpc.CallOption) (*MsgMintDerivativeResponse, error) {
+ out := new(MsgMintDerivativeResponse)
+ err := c.cc.Invoke(ctx, "/kava.liquid.v1beta1.Msg/MintDerivative", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *msgClient) BurnDerivative(ctx context.Context, in *MsgBurnDerivative, opts ...grpc.CallOption) (*MsgBurnDerivativeResponse, error) {
+ out := new(MsgBurnDerivativeResponse)
+ err := c.cc.Invoke(ctx, "/kava.liquid.v1beta1.Msg/BurnDerivative", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// MsgServer is the server API for Msg service.
+type MsgServer interface {
+ // MintDerivative defines a method for converting a delegation into staking deriviatives.
+ MintDerivative(context.Context, *MsgMintDerivative) (*MsgMintDerivativeResponse, error)
+ // BurnDerivative defines a method for converting staking deriviatives into a delegation.
+ BurnDerivative(context.Context, *MsgBurnDerivative) (*MsgBurnDerivativeResponse, error)
+}
+
+// UnimplementedMsgServer can be embedded to have forward compatible implementations.
+type UnimplementedMsgServer struct {
+}
+
+func (*UnimplementedMsgServer) MintDerivative(ctx context.Context, req *MsgMintDerivative) (*MsgMintDerivativeResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method MintDerivative not implemented")
+}
+func (*UnimplementedMsgServer) BurnDerivative(ctx context.Context, req *MsgBurnDerivative) (*MsgBurnDerivativeResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method BurnDerivative not implemented")
+}
+
+func RegisterMsgServer(s grpc1.Server, srv MsgServer) {
+ s.RegisterService(&_Msg_serviceDesc, srv)
+}
+
+func _Msg_MintDerivative_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(MsgMintDerivative)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(MsgServer).MintDerivative(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/kava.liquid.v1beta1.Msg/MintDerivative",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(MsgServer).MintDerivative(ctx, req.(*MsgMintDerivative))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _Msg_BurnDerivative_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(MsgBurnDerivative)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(MsgServer).BurnDerivative(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/kava.liquid.v1beta1.Msg/BurnDerivative",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(MsgServer).BurnDerivative(ctx, req.(*MsgBurnDerivative))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+var _Msg_serviceDesc = grpc.ServiceDesc{
+ ServiceName: "kava.liquid.v1beta1.Msg",
+ HandlerType: (*MsgServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "MintDerivative",
+ Handler: _Msg_MintDerivative_Handler,
+ },
+ {
+ MethodName: "BurnDerivative",
+ Handler: _Msg_BurnDerivative_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "kava/liquid/v1beta1/tx.proto",
+}
+
+func (m *MsgMintDerivative) 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 *MsgMintDerivative) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MsgMintDerivative) 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.Validator) > 0 {
+ i -= len(m.Validator)
+ copy(dAtA[i:], m.Validator)
+ i = encodeVarintTx(dAtA, i, uint64(len(m.Validator)))
+ i--
+ dAtA[i] = 0x12
+ }
+ if len(m.Sender) > 0 {
+ i -= len(m.Sender)
+ copy(dAtA[i:], m.Sender)
+ i = encodeVarintTx(dAtA, i, uint64(len(m.Sender)))
+ i--
+ dAtA[i] = 0xa
+ }
+ return len(dAtA) - i, nil
+}
+
+func (m *MsgMintDerivativeResponse) 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 *MsgMintDerivativeResponse) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MsgMintDerivativeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ {
+ size, err := m.Received.MarshalToSizedBuffer(dAtA[:i])
+ if err != nil {
+ return 0, err
+ }
+ i -= size
+ i = encodeVarintTx(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0xa
+ return len(dAtA) - i, nil
+}
+
+func (m *MsgBurnDerivative) 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 *MsgBurnDerivative) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MsgBurnDerivative) 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.Validator) > 0 {
+ i -= len(m.Validator)
+ copy(dAtA[i:], m.Validator)
+ i = encodeVarintTx(dAtA, i, uint64(len(m.Validator)))
+ i--
+ dAtA[i] = 0x12
+ }
+ if len(m.Sender) > 0 {
+ i -= len(m.Sender)
+ copy(dAtA[i:], m.Sender)
+ i = encodeVarintTx(dAtA, i, uint64(len(m.Sender)))
+ i--
+ dAtA[i] = 0xa
+ }
+ return len(dAtA) - i, nil
+}
+
+func (m *MsgBurnDerivativeResponse) 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 *MsgBurnDerivativeResponse) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MsgBurnDerivativeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ {
+ size := m.Received.Size()
+ i -= size
+ if _, err := m.Received.MarshalTo(dAtA[i:]); err != nil {
+ return 0, err
+ }
+ i = encodeVarintTx(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0xa
+ 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 *MsgMintDerivative) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = len(m.Sender)
+ if l > 0 {
+ n += 1 + l + sovTx(uint64(l))
+ }
+ l = len(m.Validator)
+ if l > 0 {
+ n += 1 + l + sovTx(uint64(l))
+ }
+ l = m.Amount.Size()
+ n += 1 + l + sovTx(uint64(l))
+ return n
+}
+
+func (m *MsgMintDerivativeResponse) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = m.Received.Size()
+ n += 1 + l + sovTx(uint64(l))
+ return n
+}
+
+func (m *MsgBurnDerivative) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = len(m.Sender)
+ if l > 0 {
+ n += 1 + l + sovTx(uint64(l))
+ }
+ l = len(m.Validator)
+ if l > 0 {
+ n += 1 + l + sovTx(uint64(l))
+ }
+ l = m.Amount.Size()
+ n += 1 + l + sovTx(uint64(l))
+ return n
+}
+
+func (m *MsgBurnDerivativeResponse) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = m.Received.Size()
+ n += 1 + l + sovTx(uint64(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 *MsgMintDerivative) 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: MsgMintDerivative: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: MsgMintDerivative: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Sender", 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.Sender = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Validator", 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.Validator = 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 *MsgMintDerivativeResponse) 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: MsgMintDerivativeResponse: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: MsgMintDerivativeResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Received", 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.Received.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 *MsgBurnDerivative) 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: MsgBurnDerivative: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: MsgBurnDerivative: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Sender", 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.Sender = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Validator", 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.Validator = 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 *MsgBurnDerivativeResponse) 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: MsgBurnDerivativeResponse: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: MsgBurnDerivativeResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Received", 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
+ }
+ if err := m.Received.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 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")
+)