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
This commit is contained in:
Robert Pirtle 2022-12-12 16:38:27 -08:00 committed by GitHub
parent a7f3b7732b
commit 7c58fb5303
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 474 additions and 28 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
# Output of the go coverage tool
*.out
cover.html
# Exclude build files
vendor

View File

@ -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
}
}

View File

@ -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)

24
x/community/handler.go Normal file
View File

@ -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)
}
}
}

View File

@ -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,
}
}

View File

@ -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)
}

View File

@ -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...))
})
}
}

View File

@ -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

View File

@ -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.

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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"
)