mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +00:00
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:
parent
a7f3b7732b
commit
7c58fb5303
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
# Output of the go coverage tool
|
# Output of the go coverage tool
|
||||||
*.out
|
*.out
|
||||||
|
cover.html
|
||||||
|
|
||||||
# Exclude build files
|
# Exclude build files
|
||||||
vendor
|
vendor
|
||||||
|
52
app/app.go
52
app/app.go
@ -508,20 +508,6 @@ func NewApp(
|
|||||||
|
|
||||||
app.evmutilKeeper.SetEvmKeeper(app.evmKeeper)
|
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(
|
app.transferKeeper = ibctransferkeeper.NewKeeper(
|
||||||
appCodec,
|
appCodec,
|
||||||
keys[ibctransfertypes.StoreKey],
|
keys[ibctransfertypes.StoreKey],
|
||||||
@ -620,6 +606,23 @@ func NewApp(
|
|||||||
&savingsKeeper,
|
&savingsKeeper,
|
||||||
communitytypes.ModuleAccountName,
|
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(
|
app.kavamintKeeper = kavamintkeeper.NewKeeper(
|
||||||
appCodec,
|
appCodec,
|
||||||
keys[kavaminttypes.StoreKey],
|
keys[kavaminttypes.StoreKey],
|
||||||
@ -694,6 +697,7 @@ func NewApp(
|
|||||||
AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)).
|
AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)).
|
||||||
AddRoute(kavadisttypes.RouterKey, kavadist.NewCommunityPoolMultiSpendProposalHandler(app.kavadistKeeper)).
|
AddRoute(kavadisttypes.RouterKey, kavadist.NewCommunityPoolMultiSpendProposalHandler(app.kavadistKeeper)).
|
||||||
AddRoute(earntypes.RouterKey, earn.NewCommunityPoolProposalHandler(app.earnKeeper)).
|
AddRoute(earntypes.RouterKey, earn.NewCommunityPoolProposalHandler(app.earnKeeper)).
|
||||||
|
AddRoute(communitytypes.RouterKey, community.NewCommunityPoolProposalHandler(app.communityKeeper)).
|
||||||
AddRoute(committeetypes.RouterKey, committee.NewProposalHandler(app.committeeKeeper))
|
AddRoute(committeetypes.RouterKey, committee.NewProposalHandler(app.committeeKeeper))
|
||||||
app.govKeeper = govkeeper.NewKeeper(
|
app.govKeeper = govkeeper.NewKeeper(
|
||||||
appCodec,
|
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
|
// loadBlockedMaccAddrs returns a map indicating the blocked status of each module account address
|
||||||
func (app *App) loadBlockedMaccAddrs() map[string]bool {
|
func (app *App) loadBlockedMaccAddrs() map[string]bool {
|
||||||
modAccAddrs := app.ModuleAccountAddrs()
|
modAccAddrs := app.ModuleAccountAddrs()
|
||||||
kavadistMaccAddr := app.accountKeeper.GetModuleAddress(kavadisttypes.ModuleName)
|
allowedMaccs := map[string]bool{
|
||||||
earnMaccAddr := app.accountKeeper.GetModuleAddress(earntypes.ModuleName)
|
// kavadist
|
||||||
liquidMaccAddr := app.accountKeeper.GetModuleAddress(liquidtypes.ModuleName)
|
app.accountKeeper.GetModuleAddress(kavadisttypes.ModuleName).String(): true,
|
||||||
kavadistFundMaccAddr := app.accountKeeper.GetModuleAddress(kavadisttypes.FundModuleAccount)
|
// 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 {
|
for addr := range modAccAddrs {
|
||||||
// Set the kavadist and earn module account address as unblocked
|
// Set allowed module accounts as unblocked
|
||||||
if addr == kavadistMaccAddr.String() || addr == earnMaccAddr.String() || addr == liquidMaccAddr.String() || addr == kavadistFundMaccAddr.String() {
|
if allowedMaccs[addr] {
|
||||||
modAccAddrs[addr] = false
|
modAccAddrs[addr] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
"github.com/kava-labs/kava/x/community"
|
"github.com/kava-labs/kava/x/community"
|
||||||
"github.com/kava-labs/kava/x/community/testutil"
|
"github.com/kava-labs/kava/x/community/testutil"
|
||||||
"github.com/kava-labs/kava/x/community/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type genesisTestSuite struct {
|
type genesisTestSuite struct {
|
||||||
@ -32,12 +31,8 @@ func (suite *genesisTestSuite) TestInitGenesis() {
|
|||||||
community.InitGenesis(suite.Ctx, suite.Keeper, accountKeeper)
|
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.
|
// 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)
|
suite.NotNil(acc)
|
||||||
_, ok := acc.(authtypes.ModuleAccountI)
|
_, ok := acc.(authtypes.ModuleAccountI)
|
||||||
suite.True(ok)
|
suite.True(ok)
|
||||||
|
24
x/community/handler.go
Normal file
24
x/community/handler.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,11 +12,12 @@ import (
|
|||||||
// Keeper of the community store
|
// Keeper of the community store
|
||||||
type Keeper struct {
|
type Keeper struct {
|
||||||
bankKeeper types.BankKeeper
|
bankKeeper types.BankKeeper
|
||||||
|
hardKeeper types.HardKeeper
|
||||||
moduleAddress sdk.AccAddress
|
moduleAddress sdk.AccAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeeper creates a new community Keeper instance
|
// 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
|
// ensure community module account is set
|
||||||
addr := ak.GetModuleAddress(types.ModuleAccountName)
|
addr := ak.GetModuleAddress(types.ModuleAccountName)
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
@ -25,6 +26,7 @@ func NewKeeper(ak types.AccountKeeper, bk types.BankKeeper) Keeper {
|
|||||||
|
|
||||||
return Keeper{
|
return Keeper{
|
||||||
bankKeeper: bk,
|
bankKeeper: bk,
|
||||||
|
hardKeeper: hk,
|
||||||
moduleAddress: addr,
|
moduleAddress: addr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
x/community/keeper/proposal_handler.go
Normal file
17
x/community/keeper/proposal_handler.go
Normal 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)
|
||||||
|
}
|
312
x/community/keeper/proposal_handler_test.go
Normal file
312
x/community/keeper/proposal_handler_test.go
Normal 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...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -109,7 +109,7 @@ func (am AppModule) Route() sdk.Route { return sdk.Route{} }
|
|||||||
|
|
||||||
// QuerierRoute module querier route name
|
// QuerierRoute module querier route name
|
||||||
// Deprecated: unused but necessary to fulfill AppModule interface
|
// 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.
|
// LegacyQuerierHandler returns no sdk.Querier.
|
||||||
// Deprecated: unused but necessary to fulfill AppModule interface
|
// Deprecated: unused but necessary to fulfill AppModule interface
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"github.com/kava-labs/kava/app"
|
||||||
"github.com/kava-labs/kava/x/community/keeper"
|
"github.com/kava-labs/kava/x/community/keeper"
|
||||||
|
"github.com/kava-labs/kava/x/community/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test suite used for all community tests
|
// Test suite used for all community tests
|
||||||
@ -17,6 +18,8 @@ type Suite struct {
|
|||||||
App app.TestApp
|
App app.TestApp
|
||||||
Ctx sdk.Context
|
Ctx sdk.Context
|
||||||
Keeper keeper.Keeper
|
Keeper keeper.Keeper
|
||||||
|
|
||||||
|
MaccAddress sdk.AccAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default state used by each test
|
// The default state used by each test
|
||||||
@ -30,6 +33,10 @@ func (suite *Suite) SetupTest() {
|
|||||||
suite.App = tApp
|
suite.App = tApp
|
||||||
suite.Ctx = ctx
|
suite.Ctx = ctx
|
||||||
suite.Keeper = tApp.GetCommunityKeeper()
|
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.
|
// CreateFundedAccount creates a random account and mints `coins` to it.
|
||||||
|
60
x/community/testutil/pricefeed_genesis_builder.go
Normal file
60
x/community/testutil/pricefeed_genesis_builder.go
Normal 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
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/types/msgservice"
|
"github.com/cosmos/cosmos-sdk/types/msgservice"
|
||||||
|
|
||||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
||||||
|
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
@ -13,6 +14,8 @@ import (
|
|||||||
// RegisterLegacyAminoCodec registers all the necessary types and interfaces for the module.
|
// RegisterLegacyAminoCodec registers all the necessary types and interfaces for the module.
|
||||||
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
||||||
cdc.RegisterConcrete(&MsgFundCommunityPool{}, "community/MsgFundCommunityPool", nil)
|
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,
|
// RegisterInterfaces registers proto messages under their interfaces for unmarshalling,
|
||||||
@ -21,6 +24,10 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
|
|||||||
registry.RegisterImplementations((*sdk.Msg)(nil),
|
registry.RegisterImplementations((*sdk.Msg)(nil),
|
||||||
&MsgFundCommunityPool{},
|
&MsgFundCommunityPool{},
|
||||||
)
|
)
|
||||||
|
registry.RegisterImplementations((*govtypes.Content)(nil),
|
||||||
|
&CommunityPoolLendDepositProposal{},
|
||||||
|
&CommunityPoolLendWithdrawProposal{},
|
||||||
|
)
|
||||||
|
|
||||||
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
|
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
|
||||||
}
|
}
|
||||||
|
@ -18,3 +18,9 @@ type BankKeeper interface {
|
|||||||
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
||||||
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
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
|
||||||
|
}
|
||||||
|
@ -7,6 +7,9 @@ const (
|
|||||||
// ModuleAccountName is the name of the module's account
|
// ModuleAccountName is the name of the module's account
|
||||||
ModuleAccountName = ModuleName
|
ModuleAccountName = ModuleName
|
||||||
|
|
||||||
|
// RouterKey is the top-level router key for the module
|
||||||
|
RouterKey = ModuleName
|
||||||
|
|
||||||
// Query endpoints supported by community
|
// Query endpoints supported by community
|
||||||
QueryBalance = "balance"
|
QueryBalance = "balance"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user