mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +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 | ||||
| *.out | ||||
| cover.html | ||||
| 
 | ||||
| # Exclude build files | ||||
| vendor | ||||
|  | ||||
							
								
								
									
										52
									
								
								app/app.go
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								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 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -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
									
								
							
							
						
						
									
										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
 | ||||
| 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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										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
 | ||||
| // 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
 | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
							
								
								
									
										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" | ||||
| 
 | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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" | ||||
| ) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Robert Pirtle
						Robert Pirtle