mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-10-31 07:47:27 +00:00 
			
		
		
		
	Add Combined Earn and Liquid msgs (#1305)
* add new msg type definitions * add msg methods and tests * add module and keeper skeleton * add deposit and withdraw methods (no delegation) * untested depsit/withdraw with delegation methods * add cli cmds * fix cli argument parsing * add tests for delegate/undelegate msgs * emit un/delegate events * add godoc comments
This commit is contained in:
		
							parent
							
								
									314f733cb8
								
							
						
					
					
						commit
						9519690324
					
				
							
								
								
									
										14
									
								
								app/app.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								app/app.go
									
									
									
									
									
								
							| @ -131,6 +131,9 @@ import ( | ||||
| 	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" | ||||
| 	"github.com/kava-labs/kava/x/router" | ||||
| 	routerkeeper "github.com/kava-labs/kava/x/router/keeper" | ||||
| 	routertypes "github.com/kava-labs/kava/x/router/types" | ||||
| 	savings "github.com/kava-labs/kava/x/savings" | ||||
| 	savingskeeper "github.com/kava-labs/kava/x/savings/keeper" | ||||
| 	savingstypes "github.com/kava-labs/kava/x/savings/types" | ||||
| @ -195,6 +198,7 @@ var ( | ||||
| 		evmutil.AppModuleBasic{}, | ||||
| 		liquid.AppModuleBasic{}, | ||||
| 		earn.AppModuleBasic{}, | ||||
| 		router.AppModuleBasic{}, | ||||
| 	) | ||||
| 
 | ||||
| 	// module account permissions
 | ||||
| @ -291,6 +295,7 @@ type App struct { | ||||
| 	savingsKeeper    savingskeeper.Keeper | ||||
| 	liquidKeeper     liquidkeeper.Keeper | ||||
| 	earnKeeper       earnkeeper.Keeper | ||||
| 	routerKeeper     routerkeeper.Keeper | ||||
| 
 | ||||
| 	// make scoped keepers public for test purposes
 | ||||
| 	ScopedIBCKeeper      capabilitykeeper.ScopedKeeper | ||||
| @ -624,6 +629,11 @@ func NewApp( | ||||
| 		nil, | ||||
| 		&earnKeeper, | ||||
| 	) | ||||
| 	app.routerKeeper = routerkeeper.NewKeeper( | ||||
| 		&app.earnKeeper, | ||||
| 		app.liquidKeeper, | ||||
| 		&app.stakingKeeper, | ||||
| 	) | ||||
| 
 | ||||
| 	// create committee keeper with router
 | ||||
| 	committeeGovRouter := govtypes.NewRouter() | ||||
| @ -718,6 +728,7 @@ func NewApp( | ||||
| 		savings.NewAppModule(app.savingsKeeper, app.accountKeeper, app.bankKeeper), | ||||
| 		liquid.NewAppModule(app.liquidKeeper), | ||||
| 		earn.NewAppModule(app.earnKeeper, app.accountKeeper, app.bankKeeper), | ||||
| 		router.NewAppModule(app.routerKeeper), | ||||
| 	) | ||||
| 
 | ||||
| 	// Warning: Some begin blockers must run before others. Ensure the dependencies are understood before modifying this list.
 | ||||
| @ -766,6 +777,7 @@ func NewApp( | ||||
| 		savingstypes.ModuleName, | ||||
| 		liquidtypes.ModuleName, | ||||
| 		earntypes.ModuleName, | ||||
| 		routertypes.ModuleName, | ||||
| 	) | ||||
| 
 | ||||
| 	// Warning: Some end blockers must run before others. Ensure the dependencies are understood before modifying this list.
 | ||||
| @ -806,6 +818,7 @@ func NewApp( | ||||
| 		savingstypes.ModuleName, | ||||
| 		liquidtypes.ModuleName, | ||||
| 		earntypes.ModuleName, | ||||
| 		routertypes.ModuleName, | ||||
| 	) | ||||
| 
 | ||||
| 	// Warning: Some init genesis methods must run before others. Ensure the dependencies are understood before modifying this list
 | ||||
| @ -845,6 +858,7 @@ func NewApp( | ||||
| 		upgradetypes.ModuleName, | ||||
| 		validatorvestingtypes.ModuleName, | ||||
| 		liquidtypes.ModuleName, | ||||
| 		routertypes.ModuleName, | ||||
| 	) | ||||
| 
 | ||||
| 	app.mm.RegisterInvariants(&app.crisisKeeper) | ||||
|  | ||||
| @ -42,6 +42,7 @@ import ( | ||||
| 	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" | ||||
| 	routerkeeper "github.com/kava-labs/kava/x/router/keeper" | ||||
| 	savingskeeper "github.com/kava-labs/kava/x/savings/keeper" | ||||
| 	swapkeeper "github.com/kava-labs/kava/x/swap/keeper" | ||||
| ) | ||||
| @ -112,6 +113,7 @@ func (tApp TestApp) GetSavingsKeeper() savingskeeper.Keeper     { return tApp.sa | ||||
| 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 } | ||||
| func (tApp TestApp) GetRouterKeeper() routerkeeper.Keeper       { return tApp.routerKeeper } | ||||
| 
 | ||||
| func (tApp TestApp) GetStoreKey(s string) sdk.StoreKey { return tApp.keys[s] } | ||||
| 
 | ||||
|  | ||||
| @ -2,7 +2,8 @@ | ||||
| set -e | ||||
| 
 | ||||
| validatorMnemonic="equip town gesture square tomorrow volume nephew minute witness beef rich gadget actress egg sing secret pole winter alarm law today check violin uncover" | ||||
| # kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c | ||||
| #        kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c | ||||
| # kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 | ||||
| 
 | ||||
| faucetMnemonic="crash sort dwarf disease change advice attract clump avoid mobile clump right junior axis book fresh mask tube front require until face effort vault" | ||||
| # kava1adkm6svtzjsxxvg7g6rshg6kj9qwej8gwqadqd | ||||
| @ -92,5 +93,14 @@ jq '.app_state.evm.params.chain_config.merge_fork_block = null' $DATA/config/gen | ||||
| jq '.app_state.earn.params.allowed_vaults =  [ | ||||
|     { | ||||
|         denom: "usdx", | ||||
|         vault_strategy: 1, | ||||
|         strategies: ["STRATEGY_TYPE_HARD"], | ||||
|     }, | ||||
|     { | ||||
|         denom: "bkava", | ||||
|         strategies: ["STRATEGY_TYPE_SAVINGS"], | ||||
|     }]' $DATA/config/genesis.json | sponge $DATA/config/genesis.json | ||||
| 
 | ||||
| jq '.app_state.savings.params.supported_denoms = ["bkava-kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0"]' $DATA/config/genesis.json | sponge $DATA/config/genesis.json | ||||
| 
 | ||||
| 
 | ||||
| $BINARY config broadcast-mode block | ||||
| @ -430,6 +430,18 @@ | ||||
|    | ||||
|     - [Msg](#kava.pricefeed.v1beta1.Msg) | ||||
|    | ||||
| - [kava/router/v1beta1/tx.proto](#kava/router/v1beta1/tx.proto) | ||||
|     - [MsgDelegateMintDeposit](#kava.router.v1beta1.MsgDelegateMintDeposit) | ||||
|     - [MsgDelegateMintDepositResponse](#kava.router.v1beta1.MsgDelegateMintDepositResponse) | ||||
|     - [MsgMintDeposit](#kava.router.v1beta1.MsgMintDeposit) | ||||
|     - [MsgMintDepositResponse](#kava.router.v1beta1.MsgMintDepositResponse) | ||||
|     - [MsgWithdrawBurn](#kava.router.v1beta1.MsgWithdrawBurn) | ||||
|     - [MsgWithdrawBurnResponse](#kava.router.v1beta1.MsgWithdrawBurnResponse) | ||||
|     - [MsgWithdrawBurnUndelegate](#kava.router.v1beta1.MsgWithdrawBurnUndelegate) | ||||
|     - [MsgWithdrawBurnUndelegateResponse](#kava.router.v1beta1.MsgWithdrawBurnUndelegateResponse) | ||||
|    | ||||
|     - [Msg](#kava.router.v1beta1.Msg) | ||||
|    | ||||
| - [kava/savings/v1beta1/store.proto](#kava/savings/v1beta1/store.proto) | ||||
|     - [Deposit](#kava.savings.v1beta1.Deposit) | ||||
|     - [Params](#kava.savings.v1beta1.Params) | ||||
| @ -5957,6 +5969,145 @@ Msg defines the pricefeed Msg service. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava/router/v1beta1/tx.proto"></a> | ||||
| <p align="right"><a href="#top">Top</a></p> | ||||
| 
 | ||||
| ## kava/router/v1beta1/tx.proto | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgDelegateMintDeposit"></a> | ||||
| 
 | ||||
| ### MsgDelegateMintDeposit | ||||
| MsgDelegateMintDeposit delegates tokens to a validator, then converts them into staking derivatives, | ||||
| then deposits to an earn vault. | ||||
| 
 | ||||
| 
 | ||||
| | Field | Type | Label | Description | | ||||
| | ----- | ---- | ----- | ----------- | | ||||
| | `depositor` | [string](#string) |  | depositor represents the owner of the tokens to delegate | | ||||
| | `validator` | [string](#string) |  | validator is the address of the validator to delegate to | | ||||
| | `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) |  | amount is the tokens to delegate | | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgDelegateMintDepositResponse"></a> | ||||
| 
 | ||||
| ### MsgDelegateMintDepositResponse | ||||
| MsgDelegateMintDepositResponse defines the Msg/MsgDelegateMintDeposit response type. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgMintDeposit"></a> | ||||
| 
 | ||||
| ### MsgMintDeposit | ||||
| MsgMintDeposit converts a delegation into staking derivatives and deposits it all into an earn vault. | ||||
| 
 | ||||
| 
 | ||||
| | Field | Type | Label | Description | | ||||
| | ----- | ---- | ----- | ----------- | | ||||
| | `depositor` | [string](#string) |  | depositor represents the owner of the delegation to convert | | ||||
| | `validator` | [string](#string) |  | validator is the validator for the depositor's delegation | | ||||
| | `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) |  | amount is the delegation balance to convert | | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgMintDepositResponse"></a> | ||||
| 
 | ||||
| ### MsgMintDepositResponse | ||||
| MsgMintDepositResponse defines the Msg/MsgMintDeposit response type. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgWithdrawBurn"></a> | ||||
| 
 | ||||
| ### MsgWithdrawBurn | ||||
| MsgWithdrawBurn removes staking derivatives from an earn vault and converts them back to a staking delegation. | ||||
| 
 | ||||
| 
 | ||||
| | Field | Type | Label | Description | | ||||
| | ----- | ---- | ----- | ----------- | | ||||
| | `from` | [string](#string) |  | from is the owner of the earn vault to withdraw from | | ||||
| | `validator` | [string](#string) |  | validator is the address to select the derivative denom to withdraw | | ||||
| | `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) |  | amount is the staked token equivalent to withdraw | | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgWithdrawBurnResponse"></a> | ||||
| 
 | ||||
| ### MsgWithdrawBurnResponse | ||||
| MsgWithdrawBurnResponse defines the Msg/MsgWithdrawBurn response type. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgWithdrawBurnUndelegate"></a> | ||||
| 
 | ||||
| ### MsgWithdrawBurnUndelegate | ||||
| MsgWithdrawBurnUndelegate removes staking derivatives from an earn vault, converts them to a staking delegation, | ||||
| then undelegates them from their validator. | ||||
| 
 | ||||
| 
 | ||||
| | Field | Type | Label | Description | | ||||
| | ----- | ---- | ----- | ----------- | | ||||
| | `from` | [string](#string) |  | from is the owner of the earn vault to withdraw from | | ||||
| | `validator` | [string](#string) |  | validator is the address to select the derivative denom to withdraw | | ||||
| | `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) |  | amount is the staked token equivalent to withdraw | | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.MsgWithdrawBurnUndelegateResponse"></a> | ||||
| 
 | ||||
| ### MsgWithdrawBurnUndelegateResponse | ||||
| MsgWithdrawBurnUndelegateResponse defines the Msg/MsgWithdrawBurnUndelegate response type. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  <!-- end messages --> | ||||
| 
 | ||||
|  <!-- end enums --> | ||||
| 
 | ||||
|  <!-- end HasExtensions --> | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava.router.v1beta1.Msg"></a> | ||||
| 
 | ||||
| ### Msg | ||||
| Msg defines the router Msg service. | ||||
| 
 | ||||
| | Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | | ||||
| | ----------- | ------------ | ------------- | ------------| ------- | -------- | | ||||
| | `MintDeposit` | [MsgMintDeposit](#kava.router.v1beta1.MsgMintDeposit) | [MsgMintDepositResponse](#kava.router.v1beta1.MsgMintDepositResponse) | MintDeposit converts a delegation into staking derivatives and deposits it all into an earn vault. | | | ||||
| | `DelegateMintDeposit` | [MsgDelegateMintDeposit](#kava.router.v1beta1.MsgDelegateMintDeposit) | [MsgDelegateMintDepositResponse](#kava.router.v1beta1.MsgDelegateMintDepositResponse) | DelegateMintDeposit delegates tokens to a validator, then converts them into staking derivatives, then deposits to an earn vault. | | | ||||
| | `WithdrawBurn` | [MsgWithdrawBurn](#kava.router.v1beta1.MsgWithdrawBurn) | [MsgWithdrawBurnResponse](#kava.router.v1beta1.MsgWithdrawBurnResponse) | WithdrawBurn removes staking derivatives from an earn vault and converts them back to a staking delegation. | | | ||||
| | `WithdrawBurnUndelegate` | [MsgWithdrawBurnUndelegate](#kava.router.v1beta1.MsgWithdrawBurnUndelegate) | [MsgWithdrawBurnUndelegateResponse](#kava.router.v1beta1.MsgWithdrawBurnUndelegateResponse) | WithdrawBurnUndelegate removes staking derivatives from an earn vault, converts them to a staking delegation, then undelegates them from their validator. | | | ||||
| 
 | ||||
|  <!-- end services --> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <a name="kava/savings/v1beta1/store.proto"></a> | ||||
| <p align="right"><a href="#top">Top</a></p> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										76
									
								
								proto/kava/router/v1beta1/tx.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								proto/kava/router/v1beta1/tx.proto
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| syntax = "proto3"; | ||||
| package kava.router.v1beta1; | ||||
| 
 | ||||
| import "cosmos_proto/cosmos.proto"; | ||||
| import "cosmos/base/v1beta1/coin.proto"; | ||||
| import "gogoproto/gogo.proto"; | ||||
| 
 | ||||
| option go_package                      = "github.com/kava-labs/kava/x/router/types"; | ||||
| option (gogoproto.goproto_getters_all) = false; | ||||
| 
 | ||||
| // Msg defines the router Msg service. | ||||
| service Msg { | ||||
|   // MintDeposit converts a delegation into staking derivatives and deposits it all into an earn vault. | ||||
|   rpc MintDeposit(MsgMintDeposit) returns (MsgMintDepositResponse); | ||||
| 
 | ||||
|   // DelegateMintDeposit delegates tokens to a validator, then converts them into staking derivatives, | ||||
|   // then deposits to an earn vault. | ||||
|   rpc DelegateMintDeposit(MsgDelegateMintDeposit) returns (MsgDelegateMintDepositResponse); | ||||
| 
 | ||||
|   // WithdrawBurn removes staking derivatives from an earn vault and converts them back to a staking delegation. | ||||
|   rpc WithdrawBurn(MsgWithdrawBurn) returns (MsgWithdrawBurnResponse); | ||||
| 
 | ||||
|   // WithdrawBurnUndelegate removes staking derivatives from an earn vault, converts them to a staking delegation, | ||||
|   // then undelegates them from their validator. | ||||
|   rpc WithdrawBurnUndelegate(MsgWithdrawBurnUndelegate) returns (MsgWithdrawBurnUndelegateResponse); | ||||
| } | ||||
| 
 | ||||
| // MsgMintDeposit converts a delegation into staking derivatives and deposits it all into an earn vault. | ||||
| message MsgMintDeposit { | ||||
|   // depositor represents the owner of the delegation to convert | ||||
|   string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; | ||||
|   // validator is the validator for the depositor's delegation | ||||
|   string validator = 2; | ||||
|   // amount is the delegation balance to convert | ||||
|   cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false]; | ||||
| } | ||||
| // MsgMintDepositResponse defines the Msg/MsgMintDeposit response type. | ||||
| message MsgMintDepositResponse {} | ||||
| 
 | ||||
| // MsgDelegateMintDeposit delegates tokens to a validator, then converts them into staking derivatives, | ||||
| // then deposits to an earn vault. | ||||
| message MsgDelegateMintDeposit { | ||||
|   // depositor represents the owner of the tokens to delegate | ||||
|   string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; | ||||
|   // validator is the address of the validator to delegate to | ||||
|   string validator = 2; | ||||
|   // amount is the tokens to delegate | ||||
|   cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false]; | ||||
| } | ||||
| // MsgDelegateMintDepositResponse defines the Msg/MsgDelegateMintDeposit response type. | ||||
| message MsgDelegateMintDepositResponse {} | ||||
| 
 | ||||
| // MsgWithdrawBurn removes staking derivatives from an earn vault and converts them back to a staking delegation. | ||||
| message MsgWithdrawBurn { | ||||
|   // from is the owner of the earn vault to withdraw from | ||||
|   string from = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; | ||||
|   // validator is the address to select the derivative denom to withdraw | ||||
|   string validator = 2; | ||||
|   // amount is the staked token equivalent to withdraw | ||||
|   cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false]; | ||||
| } | ||||
| // MsgWithdrawBurnResponse defines the Msg/MsgWithdrawBurn response type. | ||||
| message MsgWithdrawBurnResponse {} | ||||
| 
 | ||||
| // MsgWithdrawBurnUndelegate removes staking derivatives from an earn vault, converts them to a staking delegation, | ||||
| // then undelegates them from their validator. | ||||
| message MsgWithdrawBurnUndelegate { | ||||
|   // from is the owner of the earn vault to withdraw from | ||||
|   string from = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; | ||||
|   // validator is the address to select the derivative denom to withdraw | ||||
|   string validator = 2; | ||||
|   // amount is the staked token equivalent to withdraw | ||||
|   cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false]; | ||||
| } | ||||
| // MsgWithdrawBurnUndelegateResponse defines the Msg/MsgWithdrawBurnUndelegate response type. | ||||
| message MsgWithdrawBurnUndelegateResponse {} | ||||
| @ -155,3 +155,21 @@ func (k Keeper) burnCoins(ctx sdk.Context, sender sdk.AccAddress, amount sdk.Coi | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // DerivativeFromTokens calculates the approximate amount of derivative coins that would be minted for a given amount of staking tokens.
 | ||||
| func (k Keeper) DerivativeFromTokens(ctx sdk.Context, valAddr sdk.ValAddress, tokens sdk.Coin) (sdk.Coin, error) { | ||||
| 	bondDenom := k.stakingKeeper.BondDenom(ctx) | ||||
| 	if tokens.Denom != bondDenom { | ||||
| 		return sdk.Coin{}, sdkerrors.Wrapf(types.ErrInvalidDenom, "'%s' does not match staking denom '%s'", tokens.Denom, bondDenom) | ||||
| 	} | ||||
| 
 | ||||
| 	// Use GetModuleAddress instead of GetModuleAccount to avoid creating a module account if it doesn't exist.
 | ||||
| 	modAddress := k.accountKeeper.GetModuleAddress(types.ModuleAccountName) | ||||
| 	derivative, _, err := k.CalculateDerivativeSharesFromTokens(ctx, modAddress, valAddr, tokens.Amount) | ||||
| 	if err != nil { | ||||
| 		return sdk.Coin{}, err | ||||
| 	} | ||||
| 	liquidTokenDenom := k.GetLiquidStakingTokenDenom(valAddr) | ||||
| 	liquidToken := sdk.NewCoin(liquidTokenDenom, derivative) | ||||
| 	return liquidToken, nil | ||||
| } | ||||
|  | ||||
| @ -469,3 +469,27 @@ func (suite *KeeperTestSuite) TestGetStakedTokensForDerivatives() { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestDerivativeFromTokens() { | ||||
| 	_, addrs := app.GeneratePrivKeyAddressPairs(1) | ||||
| 	valAccAddr := addrs[0] | ||||
| 	valAddr := sdk.ValAddress(valAccAddr) | ||||
| 	moduleAccAddress := authtypes.NewModuleAddress(types.ModuleAccountName) | ||||
| 
 | ||||
| 	initialBalance := i(1e9) | ||||
| 
 | ||||
| 	suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(initialBalance)) | ||||
| 	suite.AddCoinsToModule(types.ModuleAccountName, suite.NewBondCoins(initialBalance)) | ||||
| 
 | ||||
| 	suite.CreateNewUnbondedValidator(valAddr, initialBalance) | ||||
| 	suite.CreateDelegation(valAddr, moduleAccAddress, initialBalance) | ||||
| 	staking.EndBlocker(suite.Ctx, suite.StakingKeeper) | ||||
| 
 | ||||
| 	_, err := suite.Keeper.DerivativeFromTokens(suite.Ctx, valAddr, sdk.NewCoin("invalid", initialBalance)) | ||||
| 	suite.ErrorIs(err, types.ErrInvalidDenom) | ||||
| 
 | ||||
| 	derivatives, err := suite.Keeper.DerivativeFromTokens(suite.Ctx, valAddr, suite.NewBondCoin(initialBalance)) | ||||
| 	suite.NoError(err) | ||||
| 	expected := sdk.NewCoin(fmt.Sprintf("bkava-%s", valAddr), initialBalance) | ||||
| 	suite.Equal(expected, derivatives) | ||||
| } | ||||
|  | ||||
| @ -21,6 +21,7 @@ type BankKeeper interface { | ||||
| 
 | ||||
| // AccountKeeper defines the expected keeper interface for interacting with account
 | ||||
| type AccountKeeper interface { | ||||
| 	GetModuleAddress(moduleName string) sdk.AccAddress | ||||
| 	GetModuleAccount(ctx sdk.Context, name string) authtypes.ModuleAccountI | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										174
									
								
								x/router/client/cli/tx.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								x/router/client/cli/tx.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| package cli | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"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/router/types" | ||||
| ) | ||||
| 
 | ||||
| // GetTxCmd returns the transaction commands for this module
 | ||||
| func GetTxCmd() *cobra.Command { | ||||
| 	liquidTxCmd := &cobra.Command{ | ||||
| 		Use:                        types.ModuleName, | ||||
| 		Short:                      "router transactions subcommands", | ||||
| 		DisableFlagParsing:         true, | ||||
| 		SuggestionsMinimumDistance: 2, | ||||
| 		RunE:                       client.ValidateCmd, | ||||
| 	} | ||||
| 
 | ||||
| 	cmds := []*cobra.Command{ | ||||
| 		getCmdMintDeposit(), | ||||
| 		getCmdDelegateMintDeposit(), | ||||
| 		getCmdWithdrawBurn(), | ||||
| 		getCmdWithdrawBurnUndelegate(), | ||||
| 	} | ||||
| 
 | ||||
| 	for _, cmd := range cmds { | ||||
| 		flags.AddTxFlagsToCmd(cmd) | ||||
| 	} | ||||
| 
 | ||||
| 	liquidTxCmd.AddCommand(cmds...) | ||||
| 
 | ||||
| 	return liquidTxCmd | ||||
| } | ||||
| 
 | ||||
| func getCmdMintDeposit() *cobra.Command { | ||||
| 	return &cobra.Command{ | ||||
| 		Use:   "mint-deposit [validator-addr] [amount]", | ||||
| 		Short: "mints staking derivative from a delegation and deposits them to earn", | ||||
| 		Args:  cobra.ExactArgs(2), | ||||
| 		Example: fmt.Sprintf( | ||||
| 			`%s tx %s mint-deposit kavavaloper16lnfpgn6llvn4fstg5nfrljj6aaxyee9z59jqd 10000000ukava --from <key>`, 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.NewMsgMintDeposit(clientCtx.GetFromAddress(), valAddr, coin) | ||||
| 			if err := msg.ValidateBasic(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getCmdDelegateMintDeposit() *cobra.Command { | ||||
| 	return &cobra.Command{ | ||||
| 		Use:   "delegate-mint-deposit [validator-addr] [amount]", | ||||
| 		Short: "delegates tokens, mints staking derivative from a them, and deposits them to earn", | ||||
| 		Args:  cobra.ExactArgs(2), | ||||
| 		Example: fmt.Sprintf( | ||||
| 			`%s tx %s delegate-mint-deposit kavavaloper16lnfpgn6llvn4fstg5nfrljj6aaxyee9z59jqd 10000000ukava --from <key>`, 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.NewMsgDelegateMintDeposit(clientCtx.GetFromAddress(), valAddr, coin) | ||||
| 			if err := msg.ValidateBasic(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getCmdWithdrawBurn() *cobra.Command { | ||||
| 	return &cobra.Command{ | ||||
| 		Use:   "withdraw-burn [validator-addr] [amount]", | ||||
| 		Short: "withdraws staking derivatives from earn and burns them to redeem a delegation", | ||||
| 		Example: fmt.Sprintf( | ||||
| 			`%s tx %s withdraw-burn kavavaloper16lnfpgn6llvn4fstg5nfrljj6aaxyee9z59jqd 10000000ukava --from <key>`, version.AppName, types.ModuleName, | ||||
| 		), | ||||
| 		Args: cobra.ExactArgs(2), | ||||
| 		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()) | ||||
| 			} | ||||
| 
 | ||||
| 			amount, err := sdk.ParseCoinNormalized(args[1]) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 
 | ||||
| 			msg := types.NewMsgWithdrawBurn(clientCtx.GetFromAddress(), valAddr, amount) | ||||
| 			if err := msg.ValidateBasic(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getCmdWithdrawBurnUndelegate() *cobra.Command { | ||||
| 	return &cobra.Command{ | ||||
| 		Use:   "withdraw-burn-undelegate [validator-addr] [amount]", | ||||
| 		Short: "withdraws staking derivatives from earn, burns them to redeem a delegation, then unstakes the delegation", | ||||
| 		Example: fmt.Sprintf( | ||||
| 			`%s tx %s withdraw-burn-undelegate kavavaloper16lnfpgn6llvn4fstg5nfrljj6aaxyee9z59jqd 10000000ukava --from <key>`, version.AppName, types.ModuleName, | ||||
| 		), | ||||
| 		Args: cobra.ExactArgs(2), | ||||
| 		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()) | ||||
| 			} | ||||
| 
 | ||||
| 			amount, err := sdk.ParseCoinNormalized(args[1]) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 
 | ||||
| 			msg := types.NewMsgWithdrawBurnUndelegate(clientCtx.GetFromAddress(), valAddr, amount) | ||||
| 			if err := msg.ValidateBasic(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										26
									
								
								x/router/keeper/keeper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								x/router/keeper/keeper.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/kava-labs/kava/x/router/types" | ||||
| ) | ||||
| 
 | ||||
| // Keeper is the keeper for the module
 | ||||
| type Keeper struct { | ||||
| 	earnKeeper    types.EarnKeeper | ||||
| 	liquidKeeper  types.LiquidKeeper | ||||
| 	stakingKeeper types.StakingKeeper | ||||
| } | ||||
| 
 | ||||
| // NewKeeper creates a new keeper
 | ||||
| func NewKeeper( | ||||
| 	earnKeeper types.EarnKeeper, | ||||
| 	liquidKeeper types.LiquidKeeper, | ||||
| 	stakingKeeper types.StakingKeeper, | ||||
| ) Keeper { | ||||
| 
 | ||||
| 	return Keeper{ | ||||
| 		earnKeeper:    earnKeeper, | ||||
| 		liquidKeeper:  liquidKeeper, | ||||
| 		stakingKeeper: stakingKeeper, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										201
									
								
								x/router/keeper/msg_server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								x/router/keeper/msg_server.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||||
| 	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||||
| 
 | ||||
| 	earntypes "github.com/kava-labs/kava/x/earn/types" | ||||
| 	"github.com/kava-labs/kava/x/router/types" | ||||
| ) | ||||
| 
 | ||||
| type msgServer struct { | ||||
| 	keeper Keeper | ||||
| } | ||||
| 
 | ||||
| // NewMsgServerImpl returns an implementation of the module's MsgServer interface
 | ||||
| // for the provided Keeper.
 | ||||
| func NewMsgServerImpl(keeper Keeper) types.MsgServer { | ||||
| 	return &msgServer{keeper: keeper} | ||||
| } | ||||
| 
 | ||||
| var _ types.MsgServer = msgServer{} | ||||
| 
 | ||||
| // MintDeposit converts a delegation into staking derivatives and deposits it all into an earn vault
 | ||||
| func (m msgServer) MintDeposit(goCtx context.Context, msg *types.MsgMintDeposit) (*types.MsgMintDepositResponse, error) { | ||||
| 	ctx := sdk.UnwrapSDKContext(goCtx) | ||||
| 
 | ||||
| 	depositor, err := sdk.AccAddressFromBech32(msg.Depositor) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	val, err := sdk.ValAddressFromBech32(msg.Validator) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	derivative, err := m.keeper.liquidKeeper.MintDerivative(ctx, depositor, val, msg.Amount) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	err = m.keeper.earnKeeper.Deposit(ctx, depositor, derivative, earntypes.STRATEGY_TYPE_SAVINGS) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.EventManager().EmitEvent( | ||||
| 		sdk.NewEvent( | ||||
| 			sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, depositor.String()), | ||||
| 		), | ||||
| 	) | ||||
| 	return &types.MsgMintDepositResponse{}, nil | ||||
| } | ||||
| 
 | ||||
| // DelegateMintDeposit delegates tokens to a validator, then converts them into staking derivatives,
 | ||||
| // then deposits to an earn vault.
 | ||||
| func (m msgServer) DelegateMintDeposit(goCtx context.Context, msg *types.MsgDelegateMintDeposit) (*types.MsgDelegateMintDepositResponse, error) { | ||||
| 	ctx := sdk.UnwrapSDKContext(goCtx) | ||||
| 
 | ||||
| 	depositor, err := sdk.AccAddressFromBech32(msg.Depositor) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	valAddr, err := sdk.ValAddressFromBech32(msg.Validator) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	validator, found := m.keeper.stakingKeeper.GetValidator(ctx, valAddr) | ||||
| 	if !found { | ||||
| 		return nil, stakingtypes.ErrNoValidatorFound | ||||
| 	} | ||||
| 	bondDenom := m.keeper.stakingKeeper.BondDenom(ctx) | ||||
| 	if msg.Amount.Denom != bondDenom { | ||||
| 		return nil, sdkerrors.Wrapf( | ||||
| 			sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, | ||||
| 		) | ||||
| 	} | ||||
| 	newShares, err := m.keeper.stakingKeeper.Delegate(ctx, depositor, msg.Amount.Amount, stakingtypes.Unbonded, validator, true) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	derivativeMinted, err := m.keeper.liquidKeeper.MintDerivative(ctx, depositor, valAddr, msg.Amount) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	err = m.keeper.earnKeeper.Deposit(ctx, depositor, derivativeMinted, earntypes.STRATEGY_TYPE_SAVINGS) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.EventManager().EmitEvents(sdk.Events{ | ||||
| 		sdk.NewEvent( | ||||
| 			stakingtypes.EventTypeDelegate, | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyValidator, valAddr.String()), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyNewShares, newShares.String()), | ||||
| 		), | ||||
| 		sdk.NewEvent( | ||||
| 			sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, depositor.String()), | ||||
| 		), | ||||
| 	}) | ||||
| 
 | ||||
| 	return &types.MsgDelegateMintDepositResponse{}, nil | ||||
| } | ||||
| 
 | ||||
| // WithdrawBurn removes staking derivatives from an earn vault and converts them back to a staking delegation.
 | ||||
| func (m msgServer) WithdrawBurn(goCtx context.Context, msg *types.MsgWithdrawBurn) (*types.MsgWithdrawBurnResponse, error) { | ||||
| 	ctx := sdk.UnwrapSDKContext(goCtx) | ||||
| 
 | ||||
| 	depositor, err := sdk.AccAddressFromBech32(msg.From) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	val, err := sdk.ValAddressFromBech32(msg.Validator) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	tokenAmount, err := m.keeper.liquidKeeper.DerivativeFromTokens(ctx, val, msg.Amount) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	err = m.keeper.earnKeeper.Withdraw(ctx, depositor, tokenAmount, earntypes.STRATEGY_TYPE_SAVINGS) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = m.keeper.liquidKeeper.BurnDerivative(ctx, depositor, val, tokenAmount) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.EventManager().EmitEvent( | ||||
| 		sdk.NewEvent( | ||||
| 			sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, depositor.String()), | ||||
| 		), | ||||
| 	) | ||||
| 
 | ||||
| 	return &types.MsgWithdrawBurnResponse{}, nil | ||||
| } | ||||
| 
 | ||||
| // WithdrawBurnUndelegate removes staking derivatives from an earn vault, converts them to a staking delegation,
 | ||||
| // then undelegates them from their validator.
 | ||||
| func (m msgServer) WithdrawBurnUndelegate(goCtx context.Context, msg *types.MsgWithdrawBurnUndelegate) (*types.MsgWithdrawBurnUndelegateResponse, error) { | ||||
| 	ctx := sdk.UnwrapSDKContext(goCtx) | ||||
| 
 | ||||
| 	depositor, err := sdk.AccAddressFromBech32(msg.From) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	val, err := sdk.ValAddressFromBech32(msg.Validator) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	tokenAmount, err := m.keeper.liquidKeeper.DerivativeFromTokens(ctx, val, msg.Amount) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	err = m.keeper.earnKeeper.Withdraw(ctx, depositor, tokenAmount, earntypes.STRATEGY_TYPE_SAVINGS) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	sharesReturned, err := m.keeper.liquidKeeper.BurnDerivative(ctx, depositor, val, tokenAmount) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	completionTime, err := m.keeper.stakingKeeper.Undelegate(ctx, depositor, val, sharesReturned) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.EventManager().EmitEvents(sdk.Events{ | ||||
| 		sdk.NewEvent( | ||||
| 			stakingtypes.EventTypeUnbond, | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyValidator, val.String()), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), | ||||
| 		), | ||||
| 		sdk.NewEvent( | ||||
| 			sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, depositor.String()), | ||||
| 		), | ||||
| 	}) | ||||
| 	return &types.MsgWithdrawBurnUndelegateResponse{}, nil | ||||
| } | ||||
							
								
								
									
										321
									
								
								x/router/keeper/msg_server_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								x/router/keeper/msg_server_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,321 @@ | ||||
| package keeper_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/cosmos/cosmos-sdk/x/staking" | ||||
| 	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||||
| 	"github.com/stretchr/testify/suite" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/app" | ||||
| 	earntypes "github.com/kava-labs/kava/x/earn/types" | ||||
| 	"github.com/kava-labs/kava/x/router/keeper" | ||||
| 	"github.com/kava-labs/kava/x/router/testutil" | ||||
| 	"github.com/kava-labs/kava/x/router/types" | ||||
| ) | ||||
| 
 | ||||
| type msgServerTestSuite struct { | ||||
| 	testutil.Suite | ||||
| 
 | ||||
| 	msgServer types.MsgServer | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) SetupTest() { | ||||
| 	suite.Suite.SetupTest() | ||||
| 
 | ||||
| 	suite.msgServer = keeper.NewMsgServerImpl(suite.Keeper) | ||||
| } | ||||
| 
 | ||||
| func TestMsgServerTestSuite(t *testing.T) { | ||||
| 	suite.Run(t, new(msgServerTestSuite)) | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) TestMintDeposit_Events() { | ||||
| 	user, valAddr, delegation := suite.setupValidatorAndDelegation() | ||||
| 	suite.setupEarnForDeposits(valAddr) | ||||
| 
 | ||||
| 	msg := types.NewMsgMintDeposit( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		suite.NewBondCoin(delegation), | ||||
| 	) | ||||
| 	_, err := suite.msgServer.MintDeposit(sdk.WrapSDKContext(suite.Ctx), msg) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	suite.EventsContains(suite.Ctx.EventManager().Events(), | ||||
| 		sdk.NewEvent(sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, user.String()), | ||||
| 		), | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) TestDelegateMintDeposit_Events() { | ||||
| 	user, valAddr, balance := suite.setupValidator() | ||||
| 	suite.setupEarnForDeposits(valAddr) | ||||
| 
 | ||||
| 	msg := types.NewMsgDelegateMintDeposit( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		suite.NewBondCoin(balance), | ||||
| 	) | ||||
| 	_, err := suite.msgServer.DelegateMintDeposit(sdk.WrapSDKContext(suite.Ctx), msg) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	suite.EventsContains(suite.Ctx.EventManager().Events(), | ||||
| 		sdk.NewEvent(sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, user.String()), | ||||
| 		), | ||||
| 	) | ||||
| 	expectedShares := msg.Amount.Amount.ToDec() // no slashes so shares equal staked tokens
 | ||||
| 	suite.EventsContains(suite.Ctx.EventManager().Events(), | ||||
| 		sdk.NewEvent( | ||||
| 			stakingtypes.EventTypeDelegate, | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyValidator, msg.Validator), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyNewShares, expectedShares.String()), | ||||
| 		), | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) TestWithdrawBurn_Events() { | ||||
| 	user, valAddr, delegated := suite.setupDerivatives() | ||||
| 	// clear events from setup
 | ||||
| 	suite.Ctx = suite.Ctx.WithEventManager(sdk.NewEventManager()) | ||||
| 
 | ||||
| 	msg := types.NewMsgWithdrawBurn( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		// in this setup where the validator is not slashed, the derivative amount is equal to the staked balance
 | ||||
| 		suite.NewBondCoin(delegated.Amount), | ||||
| 	) | ||||
| 	_, err := suite.msgServer.WithdrawBurn(sdk.WrapSDKContext(suite.Ctx), msg) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	suite.EventsContains(suite.Ctx.EventManager().Events(), | ||||
| 		sdk.NewEvent(sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, user.String()), | ||||
| 		), | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) TestWithdrawBurnUndelegate_Events() { | ||||
| 	user, valAddr, delegated := suite.setupDerivatives() | ||||
| 	// clear events from setup
 | ||||
| 	suite.Ctx = suite.Ctx.WithEventManager(sdk.NewEventManager()) | ||||
| 
 | ||||
| 	msg := types.NewMsgWithdrawBurnUndelegate( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		// in this setup where the validator is not slashed, the derivative amount is equal to the staked balance
 | ||||
| 		suite.NewBondCoin(delegated.Amount), | ||||
| 	) | ||||
| 	_, err := suite.msgServer.WithdrawBurnUndelegate(sdk.WrapSDKContext(suite.Ctx), msg) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	suite.EventsContains(suite.Ctx.EventManager().Events(), | ||||
| 		sdk.NewEvent(sdk.EventTypeMessage, | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeySender, user.String()), | ||||
| 		), | ||||
| 	) | ||||
| 	unbondingTime := suite.StakingKeeper.UnbondingTime(suite.Ctx) | ||||
| 	completionTime := suite.Ctx.BlockTime().Add(unbondingTime) | ||||
| 	suite.EventsContains(suite.Ctx.EventManager().Events(), | ||||
| 		sdk.NewEvent( | ||||
| 			stakingtypes.EventTypeUnbond, | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyValidator, msg.Validator), | ||||
| 			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), | ||||
| 			sdk.NewAttribute(stakingtypes.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), | ||||
| 		), | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) TestMintDepositAndWithdrawBurn_TransferEntireBalance() { | ||||
| 	_, addrs := app.GeneratePrivKeyAddressPairs(5) | ||||
| 	valAccAddr, user := addrs[0], addrs[1] | ||||
| 	valAddr := sdk.ValAddress(valAccAddr) | ||||
| 
 | ||||
| 	derivativeDenom := suite.setupEarnForDeposits(valAddr) | ||||
| 
 | ||||
| 	suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(sdk.NewInt(1e9))) | ||||
| 	vesting := sdk.NewInt(1000) | ||||
| 	suite.CreateVestingAccountWithAddress(user, | ||||
| 		suite.NewBondCoins(sdk.NewInt(1e9).Add(vesting)), | ||||
| 		suite.NewBondCoins(vesting), | ||||
| 	) | ||||
| 
 | ||||
| 	// Create a slashed validator, where the delegator owns fractional tokens.
 | ||||
| 	suite.CreateNewUnbondedValidator(valAddr, sdk.NewInt(1e9)) | ||||
| 	suite.CreateDelegation(valAddr, user, sdk.NewInt(1e9)) | ||||
| 	staking.EndBlocker(suite.Ctx, suite.StakingKeeper) | ||||
| 	suite.SlashValidator(valAddr, sdk.MustNewDecFromStr("0.666666666666666667")) | ||||
| 
 | ||||
| 	// Query the full staked balance and convert it all to derivatives
 | ||||
| 	// user technically 333_333_333.333333333333333333 staked tokens without rounding
 | ||||
| 	delegation := suite.QueryStaking_Delegation(valAddr, user) | ||||
| 	suite.Equal(sdk.NewInt(333_333_333), delegation.Balance.Amount) | ||||
| 
 | ||||
| 	msgDeposit := types.NewMsgMintDeposit( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		delegation.Balance, | ||||
| 	) | ||||
| 	_, err := suite.msgServer.MintDeposit(sdk.WrapSDKContext(suite.Ctx), msgDeposit) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// There should be no extractable balance left in delegation
 | ||||
| 	suite.DelegationBalanceLessThan(valAddr, user, sdk.NewInt(1)) | ||||
| 	// All derivative coins should be deposited to earn
 | ||||
| 	suite.AccountBalanceOfEqual(user, derivativeDenom, sdk.ZeroInt()) | ||||
| 	// Earn vault has all minted derivatives
 | ||||
| 	suite.VaultAccountValueEqual(user, sdk.NewInt64Coin(derivativeDenom, 999_999_998)) // 2 lost in conversion
 | ||||
| 
 | ||||
| 	// Query the full kava balance of the earn deposit and convert all to a delegation
 | ||||
| 	deposit := suite.QueryEarn_VaultValue(user, "bkava") | ||||
| 	suite.Equal(suite.NewBondCoins(sdk.NewInt(333_333_332)), deposit.Value) // 1 lost due to lost shares
 | ||||
| 
 | ||||
| 	msgWithdraw := types.NewMsgWithdrawBurn( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		deposit.Value[0], | ||||
| 	) | ||||
| 	_, err = suite.msgServer.WithdrawBurn(sdk.WrapSDKContext(suite.Ctx), msgWithdraw) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// There should be no earn deposit left (earn removes dust amounts)
 | ||||
| 	suite.VaultAccountSharesEqual(user, nil) | ||||
| 	// All derivative coins should be converted to a delegation
 | ||||
| 	suite.AccountBalanceOfEqual(user, derivativeDenom, sdk.ZeroInt()) | ||||
| 	// The user should get back most of their original deposited balance
 | ||||
| 	suite.DelegationBalanceInDeltaBelow(valAddr, user, sdk.NewInt(333_333_332), sdk.NewInt(2)) | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) TestDelegateMintDepositAndWithdrawBurnUndelegate_TransferEntireBalance() { | ||||
| 	_, addrs := app.GeneratePrivKeyAddressPairs(5) | ||||
| 	valAccAddr, user := addrs[0], addrs[1] | ||||
| 	valAddr := sdk.ValAddress(valAccAddr) | ||||
| 
 | ||||
| 	derivativeDenom := suite.setupEarnForDeposits(valAddr) | ||||
| 
 | ||||
| 	valBalance := sdk.NewInt(1e9) | ||||
| 	suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(valBalance)) | ||||
| 
 | ||||
| 	// Create a slashed validator, where a future delegator will own fractional tokens.
 | ||||
| 	suite.CreateNewUnbondedValidator(valAddr, valBalance) | ||||
| 	staking.EndBlocker(suite.Ctx, suite.StakingKeeper) | ||||
| 	suite.SlashValidator(valAddr, sdk.MustNewDecFromStr("0.4")) // tokens remaining 600_000_000
 | ||||
| 
 | ||||
| 	userBalance := sdk.NewInt(100e6) | ||||
| 	vesting := sdk.NewInt(1000) | ||||
| 	suite.CreateVestingAccountWithAddress(user, | ||||
| 		suite.NewBondCoins(userBalance.Add(vesting)), | ||||
| 		suite.NewBondCoins(vesting), | ||||
| 	) | ||||
| 
 | ||||
| 	// Query the full vested balance and convert it all to derivatives
 | ||||
| 	balance := suite.QueryBank_SpendableBalance(user) | ||||
| 	suite.Equal(suite.NewBondCoins(userBalance), balance) | ||||
| 
 | ||||
| 	// When delegation is created it will have 166_666_666.666666666666666666 shares
 | ||||
| 	// newShares = validatorShares * newTokens/validatorTokens, truncated to 18 decimals
 | ||||
| 	msgDeposit := types.NewMsgDelegateMintDeposit( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		balance[0], | ||||
| 	) | ||||
| 	_, err := suite.msgServer.DelegateMintDeposit(sdk.WrapSDKContext(suite.Ctx), msgDeposit) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// All spendable balance should be withdrawn
 | ||||
| 	suite.AccountSpendableBalanceEqual(user, nil) | ||||
| 	// Since shares are newly created, the exact amount should be converted to derivatives, leaving none behind
 | ||||
| 	suite.DelegationSharesEqual(valAddr, user, sdk.ZeroDec()) | ||||
| 	// All derivative coins should be deposited to earn
 | ||||
| 	suite.AccountBalanceOfEqual(user, derivativeDenom, sdk.ZeroInt()) | ||||
| 
 | ||||
| 	suite.VaultAccountValueEqual(user, sdk.NewInt64Coin(derivativeDenom, 166_666_666)) | ||||
| 
 | ||||
| 	// Query the full kava balance of the earn deposit and convert all to a delegation
 | ||||
| 	deposit := suite.QueryEarn_VaultValue(user, "bkava") | ||||
| 	suite.Equal(suite.NewBondCoins(sdk.NewInt(99_999_999)), deposit.Value) // 1 lost due to truncating shares to derivatives
 | ||||
| 
 | ||||
| 	msgWithdraw := types.NewMsgWithdrawBurnUndelegate( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		deposit.Value[0], | ||||
| 	) | ||||
| 	_, err = suite.msgServer.WithdrawBurnUndelegate(sdk.WrapSDKContext(suite.Ctx), msgWithdraw) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	// There should be no earn deposit left (earn removes dust amounts)
 | ||||
| 	suite.VaultAccountSharesEqual(user, nil) | ||||
| 	// All derivative coins should be converted to a delegation
 | ||||
| 	suite.AccountBalanceOfEqual(user, derivativeDenom, sdk.ZeroInt()) | ||||
| 	// There should be zero shares left because undelegate removes all created by burn.
 | ||||
| 	suite.AccountBalanceOfEqual(user, derivativeDenom, sdk.ZeroInt()) | ||||
| 	// User should have most of their original balance back in an unbonding delegation
 | ||||
| 	suite.UnbondingDelegationInDeltaBelow(valAddr, user, userBalance, sdk.NewInt(2)) | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) setupValidator() (sdk.AccAddress, sdk.ValAddress, sdk.Int) { | ||||
| 	_, addrs := app.GeneratePrivKeyAddressPairs(5) | ||||
| 	valAccAddr, user := addrs[0], addrs[1] | ||||
| 	valAddr := sdk.ValAddress(valAccAddr) | ||||
| 
 | ||||
| 	balance := sdk.NewInt(1e9) | ||||
| 
 | ||||
| 	suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(balance)) | ||||
| 	suite.CreateAccountWithAddress(user, suite.NewBondCoins(balance)) | ||||
| 
 | ||||
| 	suite.CreateNewUnbondedValidator(valAddr, balance) | ||||
| 	staking.EndBlocker(suite.Ctx, suite.StakingKeeper) | ||||
| 	return user, valAddr, balance | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) setupValidatorAndDelegation() (sdk.AccAddress, sdk.ValAddress, sdk.Int) { | ||||
| 	_, addrs := app.GeneratePrivKeyAddressPairs(5) | ||||
| 	valAccAddr, user := addrs[0], addrs[1] | ||||
| 	valAddr := sdk.ValAddress(valAccAddr) | ||||
| 
 | ||||
| 	balance := sdk.NewInt(1e9) | ||||
| 
 | ||||
| 	suite.CreateAccountWithAddress(valAccAddr, suite.NewBondCoins(balance)) | ||||
| 	suite.CreateAccountWithAddress(user, suite.NewBondCoins(balance)) | ||||
| 
 | ||||
| 	suite.CreateNewUnbondedValidator(valAddr, balance) | ||||
| 	suite.CreateDelegation(valAddr, user, balance) | ||||
| 	staking.EndBlocker(suite.Ctx, suite.StakingKeeper) | ||||
| 	return user, valAddr, balance | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) setupEarnForDeposits(valAddr sdk.ValAddress) string { | ||||
| 	suite.CreateVault("bkava", earntypes.StrategyTypes{earntypes.STRATEGY_TYPE_SAVINGS}, false, nil) | ||||
| 	derivativeDenom := fmt.Sprintf("bkava-%s", valAddr) | ||||
| 	suite.SetSavingsSupportedDenoms([]string{derivativeDenom}) | ||||
| 	return derivativeDenom | ||||
| } | ||||
| 
 | ||||
| func (suite *msgServerTestSuite) setupDerivatives() (sdk.AccAddress, sdk.ValAddress, sdk.Coin) { | ||||
| 	user, valAddr, delegation := suite.setupValidatorAndDelegation() | ||||
| 	suite.setupEarnForDeposits(valAddr) | ||||
| 
 | ||||
| 	msg := types.NewMsgMintDeposit( | ||||
| 		user, | ||||
| 		valAddr, | ||||
| 		suite.NewBondCoin(delegation), | ||||
| 	) | ||||
| 	_, err := suite.msgServer.MintDeposit(sdk.WrapSDKContext(suite.Ctx), msg) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	derivativeDenom := fmt.Sprintf("bkava-%s", valAddr) | ||||
| 	derivatives, err := suite.EarnKeeper.GetVaultAccountValue(suite.Ctx, derivativeDenom, user) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	return user, valAddr, derivatives | ||||
| } | ||||
							
								
								
									
										137
									
								
								x/router/module.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								x/router/module.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,137 @@ | ||||
| package router | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 
 | ||||
| 	"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" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/grpc-ecosystem/grpc-gateway/runtime" | ||||
| 	"github.com/spf13/cobra" | ||||
| 	abci "github.com/tendermint/tendermint/abci/types" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/router/client/cli" | ||||
| 	"github.com/kava-labs/kava/x/router/keeper" | ||||
| 	"github.com/kava-labs/kava/x/router/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(_ client.Context, _ *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 nil | ||||
| } | ||||
| 
 | ||||
| //____________________________________________________________________________
 | ||||
| 
 | ||||
| // 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{} | ||||
| } | ||||
							
								
								
									
										361
									
								
								x/router/testutil/suite.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								x/router/testutil/suite.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,361 @@ | ||||
| package testutil | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 
 | ||||
| 	"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" | ||||
| 	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" | ||||
| 	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" | ||||
| 	earnkeeper "github.com/kava-labs/kava/x/earn/keeper" | ||||
| 	earntypes "github.com/kava-labs/kava/x/earn/types" | ||||
| 	"github.com/kava-labs/kava/x/router/keeper" | ||||
| 	savingstypes "github.com/kava-labs/kava/x/savings/types" | ||||
| ) | ||||
| 
 | ||||
| // Test suite used for all keeper tests
 | ||||
| type Suite struct { | ||||
| 	suite.Suite | ||||
| 	App           app.TestApp | ||||
| 	Ctx           sdk.Context | ||||
| 	Keeper        keeper.Keeper | ||||
| 	BankKeeper    bankkeeper.Keeper | ||||
| 	StakingKeeper stakingkeeper.Keeper | ||||
| 	EarnKeeper    earnkeeper.Keeper | ||||
| } | ||||
| 
 | ||||
| // The default state used by each test
 | ||||
| func (suite *Suite) 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.GetRouterKeeper() | ||||
| 	suite.StakingKeeper = tApp.GetStakingKeeper() | ||||
| 	suite.BankKeeper = tApp.GetBankKeeper() | ||||
| 	suite.EarnKeeper = tApp.GetEarnKeeper() | ||||
| } | ||||
| 
 | ||||
| // CreateAccount creates a new account from the provided balance and address
 | ||||
| func (suite *Suite) 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 *Suite) 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 *Suite) 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 *Suite) 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) | ||||
| } | ||||
| 
 | ||||
| // AccountBalanceOfEqual checks if an account has the specified amount of one denom.
 | ||||
| func (suite *Suite) AccountBalanceOfEqual(addr sdk.AccAddress, denom string, amount sdk.Int) { | ||||
| 	balance := suite.BankKeeper.GetBalance(suite.Ctx, addr, denom).Amount | ||||
| 	suite.Equalf(amount, balance, "expected account balance to have %[1]s%[2]s, but got %[3]s%[2]s", amount, denom, balance) | ||||
| } | ||||
| 
 | ||||
| // AccountSpendableBalanceEqual checks if an account has the specified coins unlocked.
 | ||||
| func (suite *Suite) AccountSpendableBalanceEqual(addr sdk.AccAddress, amount sdk.Coins) { | ||||
| 	balance := suite.BankKeeper.SpendableCoins(suite.Ctx, addr) | ||||
| 	suite.Equalf(amount, balance, "expected account spendable balance to equal coins %s, but got %s", amount, balance) | ||||
| } | ||||
| 
 | ||||
| func (suite *Suite) QueryBank_SpendableBalance(user sdk.AccAddress) sdk.Coins { | ||||
| 	res, err := suite.BankKeeper.SpendableBalances( | ||||
| 		sdk.WrapSDKContext(suite.Ctx), | ||||
| 		&banktypes.QuerySpendableBalancesRequest{ | ||||
| 			Address: user.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	suite.Require().NoError(err) | ||||
| 	return *&res.Balances | ||||
| } | ||||
| 
 | ||||
| func (suite *Suite) 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 *Suite) 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 *Suite) 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 *Suite) 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 *Suite) 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 *Suite) 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 | ||||
| } | ||||
| 
 | ||||
| // DelegationSharesEqual checks if a delegation has the specified shares.
 | ||||
| // It expects delegations with zero shares to not be stored in state.
 | ||||
| func (suite *Suite) 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) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // DelegationBalanceLessThan checks if a delegation's staked token balance is less the specified amount.
 | ||||
| // It treats not found delegations as having zero shares.
 | ||||
| func (suite *Suite) DelegationBalanceLessThan(valAddr sdk.ValAddress, delegator sdk.AccAddress, max sdk.Int) bool { | ||||
| 	shares := sdk.ZeroDec() | ||||
| 	del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr) | ||||
| 	if found { | ||||
| 		shares = del.Shares | ||||
| 	} | ||||
| 
 | ||||
| 	val, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr) | ||||
| 	suite.Require().Truef(found, "expected validator to be found") | ||||
| 
 | ||||
| 	tokens := val.TokensFromShares(shares).TruncateInt() | ||||
| 
 | ||||
| 	return suite.Truef(tokens.LT(max), "expected delegation balance to be less than %s, got %s", max, tokens) | ||||
| } | ||||
| 
 | ||||
| // DelegationBalanceInDeltaBelow checks if a delegation's staked token balance is between `expected` and `expected - delta` inclusive.
 | ||||
| // It treats not found delegations as having zero shares.
 | ||||
| func (suite *Suite) DelegationBalanceInDeltaBelow(valAddr sdk.ValAddress, delegator sdk.AccAddress, expected, delta sdk.Int) bool { | ||||
| 	shares := sdk.ZeroDec() | ||||
| 	del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr) | ||||
| 	if found { | ||||
| 		shares = del.Shares | ||||
| 	} | ||||
| 
 | ||||
| 	val, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr) | ||||
| 	suite.Require().Truef(found, "expected validator to be found") | ||||
| 
 | ||||
| 	tokens := val.TokensFromShares(shares).TruncateInt() | ||||
| 
 | ||||
| 	lte := suite.Truef(tokens.LTE(expected), "expected delegation balance to be less than or equal to %s, got %s", expected, tokens) | ||||
| 	gte := suite.Truef(tokens.GTE(expected.Sub(delta)), "expected delegation balance to be greater than or equal to %s, got %s", expected.Sub(delta), tokens) | ||||
| 	return lte && gte | ||||
| } | ||||
| 
 | ||||
| // UnbondingDelegationInDeltaBelow checks if the total balance in an unbonding delegation is between `expected` and `expected - delta` inclusive.
 | ||||
| func (suite *Suite) UnbondingDelegationInDeltaBelow(valAddr sdk.ValAddress, delegator sdk.AccAddress, expected, delta sdk.Int) bool { | ||||
| 	tokens := sdk.ZeroInt() | ||||
| 	ubd, found := suite.StakingKeeper.GetUnbondingDelegation(suite.Ctx, delegator, valAddr) | ||||
| 	if found { | ||||
| 		for _, entry := range ubd.Entries { | ||||
| 			tokens = tokens.Add(entry.Balance) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	lte := suite.Truef(tokens.LTE(expected), "expected unbonding delegation balance to be less than or equal to %s, got %s", expected, tokens) | ||||
| 	gte := suite.Truef(tokens.GTE(expected.Sub(delta)), "expected unbonding delegation balance to be greater than or equal to %s, got %s", expected.Sub(delta), tokens) | ||||
| 	return lte && gte | ||||
| } | ||||
| 
 | ||||
| func (suite *Suite) QueryStaking_Delegation(valAddr sdk.ValAddress, delegator sdk.AccAddress) stakingtypes.DelegationResponse { | ||||
| 	stakingQuery := stakingkeeper.Querier{Keeper: suite.StakingKeeper} | ||||
| 	res, err := stakingQuery.Delegation( | ||||
| 		sdk.WrapSDKContext(suite.Ctx), | ||||
| 		&stakingtypes.QueryDelegationRequest{ | ||||
| 			DelegatorAddr: delegator.String(), | ||||
| 			ValidatorAddr: valAddr.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	suite.Require().NoError(err) | ||||
| 	return *res.DelegationResponse | ||||
| } | ||||
| 
 | ||||
| // EventsContains asserts that the expected event is in the provided events
 | ||||
| func (suite *Suite) 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)) | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
| } | ||||
| 
 | ||||
| // CreateVault adds a new earn vault to the earn keeper parameters
 | ||||
| func (suite *Suite) CreateVault( | ||||
| 	vaultDenom string, | ||||
| 	vaultStrategies earntypes.StrategyTypes, | ||||
| 	isPrivateVault bool, | ||||
| 	allowedDepositors []sdk.AccAddress, | ||||
| ) { | ||||
| 	vault := earntypes.NewAllowedVault(vaultDenom, vaultStrategies, isPrivateVault, allowedDepositors) | ||||
| 
 | ||||
| 	allowedVaults := suite.EarnKeeper.GetAllowedVaults(suite.Ctx) | ||||
| 	allowedVaults = append(allowedVaults, vault) | ||||
| 
 | ||||
| 	params := earntypes.NewParams(allowedVaults) | ||||
| 
 | ||||
| 	suite.EarnKeeper.SetParams( | ||||
| 		suite.Ctx, | ||||
| 		params, | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| // SetSavingsSupportedDenoms overwrites the list of supported denoms in the savings module params.
 | ||||
| func (suite *Suite) SetSavingsSupportedDenoms(denoms []string) { | ||||
| 	sk := suite.App.GetSavingsKeeper() | ||||
| 	sk.SetParams(suite.Ctx, savingstypes.NewParams(denoms)) | ||||
| } | ||||
| 
 | ||||
| // VaultAccountValueEqual asserts that the vault account value matches the provided coin amount.
 | ||||
| func (suite *Suite) VaultAccountValueEqual(acc sdk.AccAddress, coin sdk.Coin) { | ||||
| 
 | ||||
| 	accVaultBal, err := suite.EarnKeeper.GetVaultAccountValue(suite.Ctx, coin.Denom, acc) | ||||
| 	suite.Require().NoError(err) | ||||
| 
 | ||||
| 	suite.Require().Truef( | ||||
| 		coin.Equal(accVaultBal), | ||||
| 		"expected account vault balance to equal %s, but got %s", | ||||
| 		coin, accVaultBal, | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| // VaultAccountSharesEqual asserts that the vault account shares match the provided values.
 | ||||
| func (suite *Suite) VaultAccountSharesEqual(acc sdk.AccAddress, shares earntypes.VaultShares) { // TODO
 | ||||
| 
 | ||||
| 	accVaultShares, found := suite.EarnKeeper.GetVaultAccountShares(suite.Ctx, acc) | ||||
| 	if !found { | ||||
| 		suite.Empty(shares) | ||||
| 	} else { | ||||
| 		suite.Equal(shares, accVaultShares) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *Suite) QueryEarn_VaultValue(depositor sdk.AccAddress, vaultDenom string) earntypes.DepositResponse { | ||||
| 	earnQuery := earnkeeper.NewQueryServerImpl(suite.EarnKeeper) | ||||
| 	res, err := earnQuery.Deposits( | ||||
| 		sdk.WrapSDKContext(suite.Ctx), | ||||
| 		&earntypes.QueryDepositsRequest{ | ||||
| 			Depositor: depositor.String(), | ||||
| 			Denom:     vaultDenom, | ||||
| 		}, | ||||
| 	) | ||||
| 	suite.Require().NoError(err) | ||||
| 	suite.Require().Equalf(1, len(res.Deposits), "while earn supports one vault per denom, deposits response should be length 1") | ||||
| 	return res.Deposits[0] | ||||
| } | ||||
							
								
								
									
										42
									
								
								x/router/types/codec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								x/router/types/codec.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/cosmos/cosmos-sdk/codec" | ||||
| 	"github.com/cosmos/cosmos-sdk/codec/types" | ||||
| 
 | ||||
| 	cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/cosmos/cosmos-sdk/types/msgservice" | ||||
| ) | ||||
| 
 | ||||
| // RegisterLegacyAminoCodec registers all the necessary types and interfaces for the module.
 | ||||
| func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { | ||||
| 	cdc.RegisterConcrete(&MsgMintDeposit{}, "router/MsgMintDeposit", nil) | ||||
| 	cdc.RegisterConcrete(&MsgDelegateMintDeposit{}, "router/MsgDelegateMintDeposit", nil) | ||||
| 	cdc.RegisterConcrete(&MsgWithdrawBurn{}, "router/MsgWithdrawBurn", nil) | ||||
| 	cdc.RegisterConcrete(&MsgWithdrawBurnUndelegate{}, "router/MsgWithdrawBurnUndelegate", 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), | ||||
| 		&MsgMintDeposit{}, | ||||
| 		&MsgDelegateMintDeposit{}, | ||||
| 		&MsgWithdrawBurn{}, | ||||
| 		&MsgWithdrawBurnUndelegate{}, | ||||
| 	) | ||||
| 
 | ||||
| 	msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	amino = codec.NewLegacyAmino() | ||||
| 	// ModuleCdc represents the legacy amino codec for the module
 | ||||
| 	ModuleCdc = codec.NewAminoCodec(amino) | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	RegisterLegacyAminoCodec(amino) | ||||
| 	cryptocodec.RegisterCrypto(amino) | ||||
| } | ||||
							
								
								
									
										13
									
								
								x/router/types/common_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								x/router/types/common_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -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()) | ||||
| } | ||||
							
								
								
									
										34
									
								
								x/router/types/expected_keepers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								x/router/types/expected_keepers.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||||
| 
 | ||||
| 	earntypes "github.com/kava-labs/kava/x/earn/types" | ||||
| ) | ||||
| 
 | ||||
| type StakingKeeper interface { | ||||
| 	BondDenom(ctx sdk.Context) (res string) | ||||
| 	GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) | ||||
| 
 | ||||
| 	Delegate( | ||||
| 		ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Int, tokenSrc stakingtypes.BondStatus, | ||||
| 		validator stakingtypes.Validator, subtractAccount bool, | ||||
| 	) (newShares sdk.Dec, err error) | ||||
| 	Undelegate( | ||||
| 		ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec, | ||||
| 	) (time.Time, error) | ||||
| } | ||||
| 
 | ||||
| type LiquidKeeper interface { | ||||
| 	DerivativeFromTokens(ctx sdk.Context, valAddr sdk.ValAddress, amount sdk.Coin) (sdk.Coin, error) | ||||
| 	MintDerivative(ctx sdk.Context, delegatorAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) (sdk.Coin, error) | ||||
| 	BurnDerivative(ctx sdk.Context, delegatorAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) (sdk.Dec, error) | ||||
| } | ||||
| 
 | ||||
| type EarnKeeper interface { | ||||
| 	Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.Coin, depositStrategy earntypes.StrategyType) error | ||||
| 	Withdraw(ctx sdk.Context, from sdk.AccAddress, wantAmount sdk.Coin, withdrawStrategy earntypes.StrategyType) error | ||||
| } | ||||
							
								
								
									
										9
									
								
								x/router/types/keys.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								x/router/types/keys.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| package types | ||||
| 
 | ||||
| const ( | ||||
| 	// ModuleName name that will be used throughout the module
 | ||||
| 	ModuleName = "router" | ||||
| 
 | ||||
| 	// RouterKey top level router key
 | ||||
| 	RouterKey = ModuleName | ||||
| ) | ||||
							
								
								
									
										201
									
								
								x/router/types/msg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								x/router/types/msg.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| 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 ( | ||||
| 	// TypeMsgMintDeposit defines the type for MsgMintDeposit
 | ||||
| 	TypeMsgMintDeposit = "mint_deposit" | ||||
| 	// TypeMsgDelegateMintDeposit defines the type for MsgDelegateMintDeposit
 | ||||
| 	TypeMsgDelegateMintDeposit = "delegate_mint_deposit" | ||||
| 	// TypeMsgWithdrawBurn defines the type for MsgWithdrawBurn
 | ||||
| 	TypeMsgWithdrawBurn = "withdraw_burn" | ||||
| 	// TypeMsgWithdrawBurnUndelegate defines the type for MsgWithdrawBurnUndelegate
 | ||||
| 	TypeMsgWithdrawBurnUndelegate = "withdraw_burn_undelegate" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	_ sdk.Msg            = &MsgMintDeposit{} | ||||
| 	_ legacytx.LegacyMsg = &MsgMintDeposit{} | ||||
| 	_ sdk.Msg            = &MsgDelegateMintDeposit{} | ||||
| 	_ legacytx.LegacyMsg = &MsgDelegateMintDeposit{} | ||||
| 	_ sdk.Msg            = &MsgWithdrawBurn{} | ||||
| 	_ legacytx.LegacyMsg = &MsgWithdrawBurn{} | ||||
| 	_ sdk.Msg            = &MsgWithdrawBurnUndelegate{} | ||||
| 	_ legacytx.LegacyMsg = &MsgWithdrawBurnUndelegate{} | ||||
| ) | ||||
| 
 | ||||
| // NewMsgMintDeposit returns a new MsgMintDeposit.
 | ||||
| func NewMsgMintDeposit(depositor sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) *MsgMintDeposit { | ||||
| 	return &MsgMintDeposit{ | ||||
| 		Depositor: depositor.String(), | ||||
| 		Validator: validator.String(), | ||||
| 		Amount:    amount, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Route return the message type used for routing the message.
 | ||||
| func (msg MsgMintDeposit) Route() string { return RouterKey } | ||||
| 
 | ||||
| // Type returns a human-readable string for the message, intended for utilization within tags.
 | ||||
| func (msg MsgMintDeposit) Type() string { return TypeMsgMintDeposit } | ||||
| 
 | ||||
| // ValidateBasic does a simple validation check that doesn't require access to any other information.
 | ||||
| func (msg MsgMintDeposit) ValidateBasic() error { | ||||
| 	if _, err := sdk.AccAddressFromBech32(msg.Depositor); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid depositor address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := sdk.ValAddressFromBech32(msg.Validator); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid validator address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	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 MsgMintDeposit) GetSignBytes() []byte { | ||||
| 	bz := ModuleCdc.MustMarshalJSON(&msg) | ||||
| 	return sdk.MustSortJSON(bz) | ||||
| } | ||||
| 
 | ||||
| // GetSigners returns the addresses of signers that must sign.
 | ||||
| func (msg MsgMintDeposit) GetSigners() []sdk.AccAddress { | ||||
| 	depositor, _ := sdk.AccAddressFromBech32(msg.Depositor) | ||||
| 	return []sdk.AccAddress{depositor} | ||||
| } | ||||
| 
 | ||||
| // NewMsgDelegateMintDeposit returns a new MsgDelegateMintDeposit.
 | ||||
| func NewMsgDelegateMintDeposit(depositor sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) *MsgDelegateMintDeposit { | ||||
| 	return &MsgDelegateMintDeposit{ | ||||
| 		Depositor: depositor.String(), | ||||
| 		Validator: validator.String(), | ||||
| 		Amount:    amount, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Route return the message type used for routing the message.
 | ||||
| func (msg MsgDelegateMintDeposit) Route() string { return RouterKey } | ||||
| 
 | ||||
| // Type returns a human-readable string for the message, intended for utilization within tags.
 | ||||
| func (msg MsgDelegateMintDeposit) Type() string { return TypeMsgDelegateMintDeposit } | ||||
| 
 | ||||
| // ValidateBasic does a simple validation check that doesn't require access to any other information.
 | ||||
| func (msg MsgDelegateMintDeposit) ValidateBasic() error { | ||||
| 	if _, err := sdk.AccAddressFromBech32(msg.Depositor); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid depositor address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := sdk.ValAddressFromBech32(msg.Validator); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid validator address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	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 MsgDelegateMintDeposit) GetSignBytes() []byte { | ||||
| 	bz := ModuleCdc.MustMarshalJSON(&msg) | ||||
| 	return sdk.MustSortJSON(bz) | ||||
| } | ||||
| 
 | ||||
| // GetSigners returns the addresses of signers that must sign.
 | ||||
| func (msg MsgDelegateMintDeposit) GetSigners() []sdk.AccAddress { | ||||
| 	depositor, _ := sdk.AccAddressFromBech32(msg.Depositor) | ||||
| 	return []sdk.AccAddress{depositor} | ||||
| } | ||||
| 
 | ||||
| // NewMsgWithdrawBurn returns a new MsgWithdrawBurn.
 | ||||
| func NewMsgWithdrawBurn(from sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) *MsgWithdrawBurn { | ||||
| 	return &MsgWithdrawBurn{ | ||||
| 		From:      from.String(), | ||||
| 		Validator: validator.String(), | ||||
| 		Amount:    amount, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Route return the message type used for routing the message.
 | ||||
| func (msg MsgWithdrawBurn) Route() string { return RouterKey } | ||||
| 
 | ||||
| // Type returns a human-readable string for the message, intended for utilization within tags.
 | ||||
| func (msg MsgWithdrawBurn) Type() string { return TypeMsgWithdrawBurn } | ||||
| 
 | ||||
| // ValidateBasic does a simple validation check that doesn't require access to any other information.
 | ||||
| func (msg MsgWithdrawBurn) ValidateBasic() error { | ||||
| 	if _, err := sdk.AccAddressFromBech32(msg.From); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid from address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := sdk.ValAddressFromBech32(msg.Validator); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid validator address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	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 MsgWithdrawBurn) GetSignBytes() []byte { | ||||
| 	bz := ModuleCdc.MustMarshalJSON(&msg) | ||||
| 	return sdk.MustSortJSON(bz) | ||||
| } | ||||
| 
 | ||||
| // GetSigners returns the addresses of signers that must sign.
 | ||||
| func (msg MsgWithdrawBurn) GetSigners() []sdk.AccAddress { | ||||
| 	from, _ := sdk.AccAddressFromBech32(msg.From) | ||||
| 	return []sdk.AccAddress{from} | ||||
| } | ||||
| 
 | ||||
| // NewMsgWithdrawBurnUndelegate returns a new MsgWithdrawBurnUndelegate.
 | ||||
| func NewMsgWithdrawBurnUndelegate(from sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) *MsgWithdrawBurnUndelegate { | ||||
| 	return &MsgWithdrawBurnUndelegate{ | ||||
| 		From:      from.String(), | ||||
| 		Validator: validator.String(), | ||||
| 		Amount:    amount, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Route return the message type used for routing the message.
 | ||||
| func (msg MsgWithdrawBurnUndelegate) Route() string { return RouterKey } | ||||
| 
 | ||||
| // Type returns a human-readable string for the message, intended for utilization within tags.
 | ||||
| func (msg MsgWithdrawBurnUndelegate) Type() string { return TypeMsgWithdrawBurnUndelegate } | ||||
| 
 | ||||
| // ValidateBasic does a simple validation check that doesn't require access to any other information.
 | ||||
| func (msg MsgWithdrawBurnUndelegate) ValidateBasic() error { | ||||
| 	if _, err := sdk.AccAddressFromBech32(msg.From); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid from address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := sdk.ValAddressFromBech32(msg.Validator); err != nil { | ||||
| 		return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid validator address: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	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 MsgWithdrawBurnUndelegate) GetSignBytes() []byte { | ||||
| 	bz := ModuleCdc.MustMarshalJSON(&msg) | ||||
| 	return sdk.MustSortJSON(bz) | ||||
| } | ||||
| 
 | ||||
| // GetSigners returns the addresses of signers that must sign.
 | ||||
| func (msg MsgWithdrawBurnUndelegate) GetSigners() []sdk.AccAddress { | ||||
| 	from, _ := sdk.AccAddressFromBech32(msg.From) | ||||
| 	return []sdk.AccAddress{from} | ||||
| } | ||||
							
								
								
									
										207
									
								
								x/router/types/msg_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								x/router/types/msg_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,207 @@ | ||||
| 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/router/types" | ||||
| ) | ||||
| 
 | ||||
| func TestMsgMintDeposit_Signing(t *testing.T) { | ||||
| 	address := mustAccAddressFromBech32("kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d") | ||||
| 	validatorAddress := mustValAddressFromBech32("kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42") | ||||
| 
 | ||||
| 	msg := types.NewMsgMintDeposit( | ||||
| 		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":"router/MsgMintDeposit","value":{"amount":{"amount":"1000000000","denom":"ukava"},"depositor":"kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d","validator":"kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42"}}`, | ||||
| 	) | ||||
| 
 | ||||
| 	assert.Equal(t, []sdk.AccAddress{address}, msg.GetSigners()) | ||||
| 	assert.Equal(t, signBytes, msg.GetSignBytes()) | ||||
| } | ||||
| 
 | ||||
| func TestMsgDelegateMintDeposit_Signing(t *testing.T) { | ||||
| 	address := mustAccAddressFromBech32("kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d") | ||||
| 	validatorAddress := mustValAddressFromBech32("kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42") | ||||
| 
 | ||||
| 	msg := types.NewMsgDelegateMintDeposit( | ||||
| 		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":"router/MsgDelegateMintDeposit","value":{"amount":{"amount":"1000000000","denom":"ukava"},"depositor":"kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d","validator":"kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42"}}`, | ||||
| 	) | ||||
| 
 | ||||
| 	assert.Equal(t, []sdk.AccAddress{address}, msg.GetSigners()) | ||||
| 	assert.Equal(t, signBytes, msg.GetSignBytes()) | ||||
| } | ||||
| 
 | ||||
| func TestMsgWithdrawBurn_Signing(t *testing.T) { | ||||
| 	address := mustAccAddressFromBech32("kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d") | ||||
| 	validatorAddress := mustValAddressFromBech32("kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42") | ||||
| 
 | ||||
| 	msg := types.NewMsgWithdrawBurn( | ||||
| 		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":"router/MsgWithdrawBurn","value":{"amount":{"amount":"1000000000","denom":"ukava"},"from":"kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d","validator":"kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42"}}`, | ||||
| 	) | ||||
| 
 | ||||
| 	assert.Equal(t, []sdk.AccAddress{address}, msg.GetSigners()) | ||||
| 	assert.Equal(t, signBytes, msg.GetSignBytes()) | ||||
| } | ||||
| 
 | ||||
| func TestMsgWithdrawBurnUndelegate_Signing(t *testing.T) { | ||||
| 	address := mustAccAddressFromBech32("kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d") | ||||
| 	validatorAddress := mustValAddressFromBech32("kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42") | ||||
| 
 | ||||
| 	msg := types.NewMsgWithdrawBurnUndelegate( | ||||
| 		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":"router/MsgWithdrawBurnUndelegate","value":{"amount":{"amount":"1000000000","denom":"ukava"},"from":"kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d","validator":"kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42"}}`, | ||||
| 	) | ||||
| 
 | ||||
| 	assert.Equal(t, []sdk.AccAddress{address}, msg.GetSigners()) | ||||
| 	assert.Equal(t, signBytes, msg.GetSignBytes()) | ||||
| } | ||||
| 
 | ||||
| func TestMsg_Validate(t *testing.T) { | ||||
| 	validAddress := "kava1gepm4nwzz40gtpur93alv9f9wm5ht4l0hzzw9d" | ||||
| 	validValidatorAddress := "kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42" | ||||
| 	validCoin := sdk.NewInt64Coin("ukava", 1e9) | ||||
| 
 | ||||
| 	type msgArgs struct { | ||||
| 		depositor string | ||||
| 		validator string | ||||
| 		amount    sdk.Coin | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		name        string | ||||
| 		msgArgs     msgArgs | ||||
| 		expectedErr error | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "normal multiplier is valid", | ||||
| 			msgArgs: msgArgs{ | ||||
| 				depositor: validAddress, | ||||
| 				validator: validValidatorAddress, | ||||
| 				amount:    validCoin, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid depositor", | ||||
| 			msgArgs: msgArgs{ | ||||
| 				depositor: "invalid", | ||||
| 				validator: validValidatorAddress, | ||||
| 				amount:    validCoin, | ||||
| 			}, | ||||
| 			expectedErr: sdkerrors.ErrInvalidAddress, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "empty depositor", | ||||
| 			msgArgs: msgArgs{ | ||||
| 				depositor: "", | ||||
| 				validator: validValidatorAddress, | ||||
| 				amount:    validCoin, | ||||
| 			}, | ||||
| 			expectedErr: sdkerrors.ErrInvalidAddress, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid validator", | ||||
| 			msgArgs: msgArgs{ | ||||
| 				depositor: validAddress, | ||||
| 				validator: "invalid", | ||||
| 				amount:    validCoin, | ||||
| 			}, | ||||
| 			expectedErr: sdkerrors.ErrInvalidAddress, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "nil coin", | ||||
| 			msgArgs: msgArgs{ | ||||
| 				depositor: validAddress, | ||||
| 				validator: validValidatorAddress, | ||||
| 				amount:    sdk.Coin{}, | ||||
| 			}, | ||||
| 			expectedErr: sdkerrors.ErrInvalidCoins, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "zero coin", | ||||
| 			msgArgs: msgArgs{ | ||||
| 				depositor: validAddress, | ||||
| 				validator: validValidatorAddress, | ||||
| 				amount:    sdk.NewCoin("ukava", sdk.ZeroInt()), | ||||
| 			}, | ||||
| 			expectedErr: sdkerrors.ErrInvalidCoins, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "negative coin", | ||||
| 			msgArgs: msgArgs{ | ||||
| 				depositor: validAddress, | ||||
| 				validator: validValidatorAddress, | ||||
| 				amount:    sdk.Coin{Denom: "ukava", Amount: sdk.NewInt(-1)}, | ||||
| 			}, | ||||
| 			expectedErr: sdkerrors.ErrInvalidCoins, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			msgMintDeposit := types.MsgMintDeposit{tc.msgArgs.depositor, tc.msgArgs.validator, tc.msgArgs.amount} | ||||
| 			msgDelegateMintDeposit := types.MsgDelegateMintDeposit{tc.msgArgs.depositor, tc.msgArgs.validator, tc.msgArgs.amount} | ||||
| 
 | ||||
| 			msgWithdrawBurn := types.MsgWithdrawBurn{tc.msgArgs.depositor, tc.msgArgs.validator, tc.msgArgs.amount} | ||||
| 			msgWithdrawBurnUndelegate := types.MsgWithdrawBurnUndelegate{tc.msgArgs.depositor, tc.msgArgs.validator, tc.msgArgs.amount} | ||||
| 
 | ||||
| 			msgs := []sdk.Msg{&msgMintDeposit, &msgDelegateMintDeposit, &msgWithdrawBurn, &msgWithdrawBurnUndelegate} | ||||
| 			for _, msg := range msgs { | ||||
| 				t.Run(fmt.Sprintf("%T", 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 | ||||
| } | ||||
							
								
								
									
										1882
									
								
								x/router/types/tx.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1882
									
								
								x/router/types/tx.pb.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Ruaridh
						Ruaridh