mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-10-31 18:27:26 +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
	 Robert Pirtle
						Robert Pirtle