From 7c58fb53034c251fa2b13a88cba8411eb527b6c3 Mon Sep 17 00:00:00 2001 From: Robert Pirtle Date: Mon, 12 Dec 2022 16:38:27 -0800 Subject: [PATCH] Add x/community Lend proposals (#1425) * implement & register x/community lend proposals * register proposals in x/community codec * allow x/community macc to receive funds * init lend from genesis in proposal tests * test CommunityLendWithdrawProposal * helpful comment on x/community keeper deps * use RouteKey in module.go --- .gitignore | 1 + app/app.go | 52 +-- x/community/genesis_test.go | 7 +- x/community/handler.go | 24 ++ x/community/keeper/keeper.go | 4 +- x/community/keeper/proposal_handler.go | 17 + x/community/keeper/proposal_handler_test.go | 312 ++++++++++++++++++ x/community/module.go | 2 +- x/community/testutil/main.go | 7 + .../testutil/pricefeed_genesis_builder.go | 60 ++++ x/community/types/codec.go | 7 + x/community/types/expected_keepers.go | 6 + x/community/types/keys.go | 3 + 13 files changed, 474 insertions(+), 28 deletions(-) create mode 100644 x/community/handler.go create mode 100644 x/community/keeper/proposal_handler.go create mode 100644 x/community/keeper/proposal_handler_test.go create mode 100644 x/community/testutil/pricefeed_genesis_builder.go diff --git a/.gitignore b/.gitignore index 2c641600..017d95a6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Output of the go coverage tool *.out +cover.html # Exclude build files vendor diff --git a/app/app.go b/app/app.go index 60ddc608..aaed2a77 100644 --- a/app/app.go +++ b/app/app.go @@ -508,20 +508,6 @@ func NewApp( app.evmutilKeeper.SetEvmKeeper(app.evmKeeper) - app.communityKeeper = communitykeeper.NewKeeper( - app.accountKeeper, - app.bankKeeper, - ) - app.kavadistKeeper = kavadistkeeper.NewKeeper( - appCodec, - keys[kavadisttypes.StoreKey], - kavadistSubspace, - app.bankKeeper, - app.accountKeeper, - app.communityKeeper, - app.loadBlockedMaccAddrs(), - ) - app.transferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], @@ -620,6 +606,23 @@ func NewApp( &savingsKeeper, communitytypes.ModuleAccountName, ) + + // x/community's deposit/withdraw to lend proposals depend on hard keeper. + app.communityKeeper = communitykeeper.NewKeeper( + app.accountKeeper, + app.bankKeeper, + hardKeeper, + ) + app.kavadistKeeper = kavadistkeeper.NewKeeper( + appCodec, + keys[kavadisttypes.StoreKey], + kavadistSubspace, + app.bankKeeper, + app.accountKeeper, + app.communityKeeper, + app.loadBlockedMaccAddrs(), + ) + app.kavamintKeeper = kavamintkeeper.NewKeeper( appCodec, keys[kavaminttypes.StoreKey], @@ -694,6 +697,7 @@ func NewApp( AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)). AddRoute(kavadisttypes.RouterKey, kavadist.NewCommunityPoolMultiSpendProposalHandler(app.kavadistKeeper)). AddRoute(earntypes.RouterKey, earn.NewCommunityPoolProposalHandler(app.earnKeeper)). + AddRoute(communitytypes.RouterKey, community.NewCommunityPoolProposalHandler(app.communityKeeper)). AddRoute(committeetypes.RouterKey, committee.NewProposalHandler(app.committeeKeeper)) app.govKeeper = govkeeper.NewKeeper( appCodec, @@ -1046,14 +1050,22 @@ func (app *App) RegisterTendermintService(clientCtx client.Context) { // loadBlockedMaccAddrs returns a map indicating the blocked status of each module account address func (app *App) loadBlockedMaccAddrs() map[string]bool { modAccAddrs := app.ModuleAccountAddrs() - kavadistMaccAddr := app.accountKeeper.GetModuleAddress(kavadisttypes.ModuleName) - earnMaccAddr := app.accountKeeper.GetModuleAddress(earntypes.ModuleName) - liquidMaccAddr := app.accountKeeper.GetModuleAddress(liquidtypes.ModuleName) - kavadistFundMaccAddr := app.accountKeeper.GetModuleAddress(kavadisttypes.FundModuleAccount) + allowedMaccs := map[string]bool{ + // kavadist + app.accountKeeper.GetModuleAddress(kavadisttypes.ModuleName).String(): true, + // earn + app.accountKeeper.GetModuleAddress(earntypes.ModuleName).String(): true, + // liquid + app.accountKeeper.GetModuleAddress(liquidtypes.ModuleName).String(): true, + // kavadist fund + app.accountKeeper.GetModuleAddress(kavadisttypes.FundModuleAccount).String(): true, + // community + app.accountKeeper.GetModuleAddress(communitytypes.ModuleAccountName).String(): true, + } for addr := range modAccAddrs { - // Set the kavadist and earn module account address as unblocked - if addr == kavadistMaccAddr.String() || addr == earnMaccAddr.String() || addr == liquidMaccAddr.String() || addr == kavadistFundMaccAddr.String() { + // Set allowed module accounts as unblocked + if allowedMaccs[addr] { modAccAddrs[addr] = false } } diff --git a/x/community/genesis_test.go b/x/community/genesis_test.go index 758a8cba..cf45cd3a 100644 --- a/x/community/genesis_test.go +++ b/x/community/genesis_test.go @@ -8,7 +8,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/kava-labs/kava/x/community" "github.com/kava-labs/kava/x/community/testutil" - "github.com/kava-labs/kava/x/community/types" ) type genesisTestSuite struct { @@ -32,12 +31,8 @@ func (suite *genesisTestSuite) TestInitGenesis() { community.InitGenesis(suite.Ctx, suite.Keeper, accountKeeper) }) - communityPoolAddress := accountKeeper.GetModuleAddress(types.ModuleAccountName) - // hello, greppers! - suite.Equal("kava17d2wax0zhjrrecvaszuyxdf5wcu5a0p4qlx3t5", communityPoolAddress.String()) - // check for module account this way b/c GetModuleAccount creates if not existing. - acc := accountKeeper.GetAccount(suite.Ctx, communityPoolAddress) + acc := accountKeeper.GetAccount(suite.Ctx, suite.MaccAddress) suite.NotNil(acc) _, ok := acc.(authtypes.ModuleAccountI) suite.True(ok) diff --git a/x/community/handler.go b/x/community/handler.go new file mode 100644 index 00000000..cd498440 --- /dev/null +++ b/x/community/handler.go @@ -0,0 +1,24 @@ +package community + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/types" +) + +// NewCommunityPoolProposalHandler handles x/community proposals. +func NewCommunityPoolProposalHandler(k keeper.Keeper) govtypes.Handler { + return func(ctx sdk.Context, content govtypes.Content) error { + switch c := content.(type) { + case *types.CommunityPoolLendDepositProposal: + return keeper.HandleCommunityPoolLendDepositProposal(ctx, k, c) + case *types.CommunityPoolLendWithdrawProposal: + return keeper.HandleCommunityPoolLendWithdrawProposal(ctx, k, c) + default: + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized community proposal content type: %T", c) + } + } +} diff --git a/x/community/keeper/keeper.go b/x/community/keeper/keeper.go index 8293d840..d83ce418 100644 --- a/x/community/keeper/keeper.go +++ b/x/community/keeper/keeper.go @@ -12,11 +12,12 @@ import ( // Keeper of the community store type Keeper struct { bankKeeper types.BankKeeper + hardKeeper types.HardKeeper moduleAddress sdk.AccAddress } // NewKeeper creates a new community Keeper instance -func NewKeeper(ak types.AccountKeeper, bk types.BankKeeper) Keeper { +func NewKeeper(ak types.AccountKeeper, bk types.BankKeeper, hk types.HardKeeper) Keeper { // ensure community module account is set addr := ak.GetModuleAddress(types.ModuleAccountName) if addr == nil { @@ -25,6 +26,7 @@ func NewKeeper(ak types.AccountKeeper, bk types.BankKeeper) Keeper { return Keeper{ bankKeeper: bk, + hardKeeper: hk, moduleAddress: addr, } } diff --git a/x/community/keeper/proposal_handler.go b/x/community/keeper/proposal_handler.go new file mode 100644 index 00000000..68470d56 --- /dev/null +++ b/x/community/keeper/proposal_handler.go @@ -0,0 +1,17 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/community/types" +) + +// HandleCommunityPoolLendDepositProposal is a handler for executing a passed community pool lend deposit proposal. +func HandleCommunityPoolLendDepositProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolLendDepositProposal) error { + return k.hardKeeper.Deposit(ctx, k.moduleAddress, p.Amount) +} + +// HandleCommunityPoolLendWithdrawProposal is a handler for executing a passed community pool lend withdraw proposal. +func HandleCommunityPoolLendWithdrawProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolLendWithdrawProposal) error { + return k.hardKeeper.Withdraw(ctx, k.moduleAddress, p.Amount) +} diff --git a/x/community/keeper/proposal_handler_test.go b/x/community/keeper/proposal_handler_test.go new file mode 100644 index 00000000..b6444b22 --- /dev/null +++ b/x/community/keeper/proposal_handler_test.go @@ -0,0 +1,312 @@ +package keeper_test + +import ( + "fmt" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + abcitypes "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/testutil" + "github.com/kava-labs/kava/x/community/types" + hardkeeper "github.com/kava-labs/kava/x/hard/keeper" + hardtypes "github.com/kava-labs/kava/x/hard/types" + pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types" +) + +const chainID = "kavatest_2221-1" + +func ukava(amt int64) sdk.Coins { + return sdk.NewCoins(sdk.NewInt64Coin("ukava", amt)) +} +func usdx(amt int64) sdk.Coins { + return sdk.NewCoins(sdk.NewInt64Coin("usdx", amt)) +} +func otherdenom(amt int64) sdk.Coins { + return sdk.NewCoins(sdk.NewInt64Coin("other-denom", amt)) +} + +type proposalTestSuite struct { + suite.Suite + + App app.TestApp + Ctx sdk.Context + Keeper keeper.Keeper + MaccAddress sdk.AccAddress + + hardKeeper hardkeeper.Keeper +} + +func TestProposalTestSuite(t *testing.T) { + suite.Run(t, new(proposalTestSuite)) +} + +func (suite *proposalTestSuite) SetupTest() { + app.SetSDKConfig() + + genTime := tmtime.Now() + + hardGS, pricefeedGS := testutil.NewLendGenesisBuilder(). + WithMarket("ukava", "kava:usd", sdk.OneDec()). + WithMarket("usdx", "usdx:usd", sdk.OneDec()). + Build() + + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, tmproto.Header{ + Height: 1, + Time: genTime, + ChainID: chainID, + }) + + tApp.InitializeFromGenesisStatesWithTimeAndChainID( + genTime, chainID, + app.GenesisState{hardtypes.ModuleName: tApp.AppCodec().MustMarshalJSON(&hardGS)}, + app.GenesisState{pricefeedtypes.ModuleName: tApp.AppCodec().MustMarshalJSON(&pricefeedGS)}, + ) + + suite.App = tApp + suite.Ctx = ctx + suite.Keeper = tApp.GetCommunityKeeper() + suite.MaccAddress = tApp.GetAccountKeeper().GetModuleAddress(types.ModuleAccountName) + suite.hardKeeper = suite.App.GetHardKeeper() + + // give the community pool some funds + // ukava + err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleAccountName, ukava(1e10)) + suite.NoError(err) + + // usdx + err = suite.App.FundModuleAccount(suite.Ctx, types.ModuleAccountName, usdx(1e10)) + suite.NoError(err) + + // other-denom + err = suite.App.FundModuleAccount(suite.Ctx, types.ModuleAccountName, otherdenom(1e10)) + suite.NoError(err) +} + +func (suite *proposalTestSuite) NextBlock() { + newTime := suite.Ctx.BlockTime().Add(6 * time.Second) + newHeight := suite.Ctx.BlockHeight() + 1 + + suite.App.EndBlocker(suite.Ctx, abcitypes.RequestEndBlock{}) + suite.Ctx = suite.Ctx.WithBlockTime(newTime).WithBlockHeight(newHeight).WithChainID(chainID) + suite.App.BeginBlocker(suite.Ctx, abcitypes.RequestBeginBlock{}) +} + +func (suite *proposalTestSuite) TestCommunityLendDepositProposal() { + testCases := []struct { + name string + proposals []*types.CommunityPoolLendDepositProposal + expectedErr string + expectedDeposits []sdk.Coins + }{ + { + name: "valid - one proposal, one denom", + proposals: []*types.CommunityPoolLendDepositProposal{ + {Amount: ukava(1e8)}, + }, + expectedErr: "", + expectedDeposits: []sdk.Coins{ukava(1e8)}, + }, + { + name: "valid - one proposal, multiple denoms", + proposals: []*types.CommunityPoolLendDepositProposal{ + {Amount: ukava(1e8).Add(usdx(1e8)...)}, + }, + expectedErr: "", + expectedDeposits: []sdk.Coins{ukava(1e8).Add(usdx(1e8)...)}, + }, + { + name: "valid - multiple proposals, same denom", + proposals: []*types.CommunityPoolLendDepositProposal{ + {Amount: ukava(1e8)}, + {Amount: ukava(1e9)}, + }, + expectedErr: "", + expectedDeposits: []sdk.Coins{ukava(1e8 + 1e9)}, + }, + { + name: "valid - multiple proposals, different denoms", + proposals: []*types.CommunityPoolLendDepositProposal{ + {Amount: ukava(1e8)}, + {Amount: usdx(1e8)}, + }, + expectedErr: "", + expectedDeposits: []sdk.Coins{ukava(1e8).Add(usdx(1e8)...)}, + }, + { + name: "invalid - insufficient balance", + proposals: []*types.CommunityPoolLendDepositProposal{ + { + Description: "more coins than i have!", + Amount: ukava(1e11), + }, + }, + expectedErr: "insufficient funds", + expectedDeposits: []sdk.Coins{}, + }, + { + name: "invalid - invalid lend deposit (unsupported denom)", + proposals: []*types.CommunityPoolLendDepositProposal{ + {Amount: otherdenom(1e9)}, + }, + expectedErr: "invalid deposit denom", + expectedDeposits: []sdk.Coins{}, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + for _, proposal := range tc.proposals { + err := keeper.HandleCommunityPoolLendDepositProposal(suite.Ctx, suite.Keeper, proposal) + if tc.expectedErr == "" { + suite.NoError(err) + } else { + suite.ErrorContains(err, tc.expectedErr) + } + } + + deposits := suite.hardKeeper.GetDepositsByUser(suite.Ctx, suite.MaccAddress) + suite.Len(deposits, len(tc.expectedDeposits), "expected a deposit to lend") + for _, amt := range tc.expectedDeposits { + suite.Equal(amt, deposits[0].Amount, "expected amount to match") + } + }) + } +} + +func (suite *proposalTestSuite) TestCommunityLendWithdrawProposal() { + testCases := []struct { + name string + initialDeposit sdk.Coins + proposals []*types.CommunityPoolLendWithdrawProposal + expectedErr string + expectedWithdrawal sdk.Coins + }{ + { + // in the week it would take a proposal to pass, the position would have grown + // to withdraw the entire position, it'd be safest to set a very high withdraw + name: "valid - requesting withdrawal of more than total will withdraw all", + initialDeposit: ukava(1e9), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: ukava(1e12)}, + }, + expectedErr: "", + expectedWithdrawal: ukava(1e9), + }, + { + name: "valid - single proposal, single denom, full withdrawal", + initialDeposit: ukava(1e9), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: ukava(1e9)}, + }, + expectedErr: "", + expectedWithdrawal: ukava(1e9), + }, + { + name: "valid - single proposal, multiple denoms, full withdrawal", + initialDeposit: ukava(1e9).Add(usdx(1e9)...), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: ukava(1e9).Add(usdx(1e9)...)}, + }, + expectedErr: "", + expectedWithdrawal: ukava(1e9).Add(usdx(1e9)...), + }, + { + name: "valid - single proposal, partial withdrawal", + initialDeposit: ukava(1e9).Add(usdx(1e9)...), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: ukava(1e8).Add(usdx(1e9)...)}, + }, + expectedErr: "", + expectedWithdrawal: ukava(1e8).Add(usdx(1e9)...), + }, + { + name: "valid - multiple proposals, full withdrawal", + initialDeposit: ukava(1e9).Add(usdx(1e9)...), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: ukava(1e9)}, + {Amount: usdx(1e9)}, + }, + expectedErr: "", + expectedWithdrawal: ukava(1e9).Add(usdx(1e9)...), + }, + { + name: "valid - multiple proposals, partial withdrawal", + initialDeposit: ukava(1e9).Add(usdx(1e9)...), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: ukava(1e8)}, + {Amount: usdx(1e8)}, + }, + expectedErr: "", + expectedWithdrawal: ukava(1e8).Add(usdx(1e8)...), + }, + { + name: "invalid - nonexistent position, has no deposits", + initialDeposit: sdk.NewCoins(), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: ukava(1e8)}, + }, + expectedErr: "deposit not found", + expectedWithdrawal: sdk.NewCoins(), + }, + { + name: "invalid - nonexistent position, has deposits of different denom", + initialDeposit: ukava(1e8), + proposals: []*types.CommunityPoolLendWithdrawProposal{ + {Amount: usdx(1e8)}, + }, + expectedErr: "no coins of this type deposited", + expectedWithdrawal: sdk.NewCoins(), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + // setup initial deposit + if !tc.initialDeposit.IsZero() { + deposit := types.NewCommunityPoolLendDepositProposal("initial deposit", "has coins", tc.initialDeposit) + err := keeper.HandleCommunityPoolLendDepositProposal(suite.Ctx, suite.Keeper, deposit) + suite.NoError(err, "unexpected error while seeding lend deposit") + } + + beforeBalance := suite.Keeper.GetModuleAccountBalance(suite.Ctx) + + // run the proposals + for i, proposal := range tc.proposals { + fmt.Println("submitting proposal ", i, " ", suite.Ctx.ChainID()) + err := keeper.HandleCommunityPoolLendWithdrawProposal(suite.Ctx, suite.Keeper, proposal) + if tc.expectedErr == "" { + suite.NoError(err) + } else { + suite.ErrorContains(err, tc.expectedErr) + } + + suite.NextBlock() + } + + // expect funds to be removed from hard deposit + expectedRemaining := tc.initialDeposit.Sub(tc.expectedWithdrawal) + deposits := suite.hardKeeper.GetDepositsByUser(suite.Ctx, suite.MaccAddress) + if expectedRemaining.IsZero() { + suite.Len(deposits, 0, "expected all deposits to be withdrawn") + } else { + suite.Len(deposits, 1, "expected user to have remaining deposit") + suite.Equal(expectedRemaining, deposits[0].Amount) + } + + // expect funds to be distributed back to community pool + suite.App.CheckBalance(suite.T(), suite.Ctx, suite.MaccAddress, beforeBalance.Add(tc.expectedWithdrawal...)) + }) + } +} diff --git a/x/community/module.go b/x/community/module.go index 7cc9fe25..f017e435 100644 --- a/x/community/module.go +++ b/x/community/module.go @@ -109,7 +109,7 @@ func (am AppModule) Route() sdk.Route { return sdk.Route{} } // QuerierRoute module querier route name // Deprecated: unused but necessary to fulfill AppModule interface -func (AppModule) QuerierRoute() string { return "" } +func (AppModule) QuerierRoute() string { return types.RouterKey } // LegacyQuerierHandler returns no sdk.Querier. // Deprecated: unused but necessary to fulfill AppModule interface diff --git a/x/community/testutil/main.go b/x/community/testutil/main.go index 8469d8ed..c9ec69fd 100644 --- a/x/community/testutil/main.go +++ b/x/community/testutil/main.go @@ -9,6 +9,7 @@ import ( "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/types" ) // Test suite used for all community tests @@ -17,6 +18,8 @@ type Suite struct { App app.TestApp Ctx sdk.Context Keeper keeper.Keeper + + MaccAddress sdk.AccAddress } // The default state used by each test @@ -30,6 +33,10 @@ func (suite *Suite) SetupTest() { suite.App = tApp suite.Ctx = ctx suite.Keeper = tApp.GetCommunityKeeper() + communityPoolAddress := tApp.GetAccountKeeper().GetModuleAddress(types.ModuleAccountName) + // hello, greppers! + suite.Equal("kava17d2wax0zhjrrecvaszuyxdf5wcu5a0p4qlx3t5", communityPoolAddress.String()) + suite.MaccAddress = communityPoolAddress } // CreateFundedAccount creates a random account and mints `coins` to it. diff --git a/x/community/testutil/pricefeed_genesis_builder.go b/x/community/testutil/pricefeed_genesis_builder.go new file mode 100644 index 00000000..3a43c76c --- /dev/null +++ b/x/community/testutil/pricefeed_genesis_builder.go @@ -0,0 +1,60 @@ +package testutil + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + hardtypes "github.com/kava-labs/kava/x/hard/types" + pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types" +) + +// lendGenesisBuilder builds the Hard and Pricefeed genesis states for setting up Kava Lend +type lendGenesisBuilder struct { + hardMarkets []hardtypes.MoneyMarket + pfMarkets []pricefeedtypes.Market + prices []pricefeedtypes.PostedPrice +} + +func NewLendGenesisBuilder() lendGenesisBuilder { + return lendGenesisBuilder{} +} + +func (b lendGenesisBuilder) Build() (hardtypes.GenesisState, pricefeedtypes.GenesisState) { + hardGS := hardtypes.DefaultGenesisState() + hardGS.Params.MoneyMarkets = b.hardMarkets + + pricefeedGS := pricefeedtypes.DefaultGenesisState() + pricefeedGS.Params.Markets = b.pfMarkets + pricefeedGS.PostedPrices = b.prices + return hardGS, pricefeedGS +} + +func (b lendGenesisBuilder) WithMarket(denom, spotMarketId string, price sdk.Dec) lendGenesisBuilder { + // add hard money market + b.hardMarkets = append(b.hardMarkets, + hardtypes.NewMoneyMarket( + denom, + hardtypes.NewBorrowLimit(false, sdk.NewDec(1e15), sdk.MustNewDecFromStr("0.6")), + spotMarketId, + sdk.NewInt(1e6), + hardtypes.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), + sdk.MustNewDecFromStr("0.05"), + sdk.ZeroDec(), + ), + ) + + // add pricefeed + b.pfMarkets = append(b.pfMarkets, + pricefeedtypes.Market{MarketID: spotMarketId, BaseAsset: denom, QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + ) + b.prices = append(b.prices, + pricefeedtypes.PostedPrice{ + MarketID: spotMarketId, + OracleAddress: sdk.AccAddress{}, + Price: price, + Expiry: time.Now().Add(100 * time.Hour), + }, + ) + + return b +} diff --git a/x/community/types/codec.go b/x/community/types/codec.go index 838c7779..9913d187 100644 --- a/x/community/types/codec.go +++ b/x/community/types/codec.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -13,6 +14,8 @@ import ( // RegisterLegacyAminoCodec registers all the necessary types and interfaces for the module. func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgFundCommunityPool{}, "community/MsgFundCommunityPool", nil) + cdc.RegisterConcrete(&CommunityPoolLendDepositProposal{}, "kava/CommunityPoolLendDepositProposal", nil) + cdc.RegisterConcrete(&CommunityPoolLendWithdrawProposal{}, "kava/CommunityPoolLendWithdrawProposal", nil) } // RegisterInterfaces registers proto messages under their interfaces for unmarshalling, @@ -21,6 +24,10 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { registry.RegisterImplementations((*sdk.Msg)(nil), &MsgFundCommunityPool{}, ) + registry.RegisterImplementations((*govtypes.Content)(nil), + &CommunityPoolLendDepositProposal{}, + &CommunityPoolLendWithdrawProposal{}, + ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/community/types/expected_keepers.go b/x/community/types/expected_keepers.go index 5300772d..0745c755 100644 --- a/x/community/types/expected_keepers.go +++ b/x/community/types/expected_keepers.go @@ -18,3 +18,9 @@ type BankKeeper interface { SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error } + +// HardKeeper defines the contract needed to be fulfilled for Kava Lend dependencies. +type HardKeeper interface { + Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error + Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error +} diff --git a/x/community/types/keys.go b/x/community/types/keys.go index 9a0d7c8f..3108c220 100644 --- a/x/community/types/keys.go +++ b/x/community/types/keys.go @@ -7,6 +7,9 @@ const ( // ModuleAccountName is the name of the module's account ModuleAccountName = ModuleName + // RouterKey is the top-level router key for the module + RouterKey = ModuleName + // Query endpoints supported by community QueryBalance = "balance" )