mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 06:59:41 +00:00 
			
		
		
		
	Fix incentive usdx/borrow/supply reward calculation bug (#974)
* extract borrow sync logic into separate func * fix borrow reward calculations Use the normalized borrow as the source shares in reward calculations. * extract supply sync logic into separate func * prepare to fix supply reward calculations * fix deposit reward calculations Use the normalized deposit as the source shares in reward calculations. * extract usdx sync logic into separate func * prepare to fix usdx reward calculations * fix cdp reward calculations Use the normalized cdp debt as the source shares in reward calculations. * fix compile error from messed up partial stage * Fix incentive usdx reward bug (#976) * minor test refactors * fix overpayment bug Init methods should not read params. Add test to cover bug * fix typos
This commit is contained in:
		
							parent
							
								
									6d546d6a96
								
							
						
					
					
						commit
						dc6f5c6c83
					
				@ -104,6 +104,20 @@ func (cdp CDP) GetTotalPrincipal() sdk.Coin {
 | 
				
			|||||||
	return cdp.Principal.Add(cdp.AccumulatedFees)
 | 
						return cdp.Principal.Add(cdp.AccumulatedFees)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetNormalizedPrincipal returns the total cdp principal divided by the interest factor.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Multiplying the normalized principal by the current global factor gives the current debt (ie including all interest, ie a synced cdp).
 | 
				
			||||||
 | 
					// The normalized principal is effectively how big the principal would have been if it had been borrowed at time 0 and not touched since.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// An error is returned if the cdp interest factor is in an invalid state.
 | 
				
			||||||
 | 
					func (cdp CDP) GetNormalizedPrincipal() (sdk.Dec, error) {
 | 
				
			||||||
 | 
						unsyncedDebt := cdp.GetTotalPrincipal().Amount
 | 
				
			||||||
 | 
						if cdp.InterestFactor.LT(sdk.OneDec()) {
 | 
				
			||||||
 | 
							return sdk.Dec{}, fmt.Errorf("interest factor '%s' must be ≥ 1", cdp.InterestFactor)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return unsyncedDebt.ToDec().Quo(cdp.InterestFactor), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CDPs a collection of CDP objects
 | 
					// CDPs a collection of CDP objects
 | 
				
			||||||
type CDPs []CDP
 | 
					type CDPs []CDP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -176,6 +176,68 @@ func (suite *CdpValidationSuite) TestCdpGetTotalPrinciple() {
 | 
				
			|||||||
	suite.Require().Equal(cdp.GetTotalPrincipal(), principal.Add(accumulatedFees))
 | 
						suite.Require().Equal(cdp.GetTotalPrincipal(), principal.Add(accumulatedFees))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *CdpValidationSuite) TestCDPGetNormalizedPrincipal() {
 | 
				
			||||||
 | 
						type expectedErr struct {
 | 
				
			||||||
 | 
							expectPass bool
 | 
				
			||||||
 | 
							contains   string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name        string
 | 
				
			||||||
 | 
							cdp         types.CDP
 | 
				
			||||||
 | 
							expected    sdk.Dec
 | 
				
			||||||
 | 
							expectedErr expectedErr
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "principal + fees is divided by factor correctly",
 | 
				
			||||||
 | 
								cdp: types.CDP{
 | 
				
			||||||
 | 
									Principal:       sdk.NewInt64Coin("usdx", 1e9),
 | 
				
			||||||
 | 
									AccumulatedFees: sdk.NewInt64Coin("usdx", 1e6),
 | 
				
			||||||
 | 
									InterestFactor:  sdk.MustNewDecFromStr("2"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: sdk.MustNewDecFromStr("500500000"),
 | 
				
			||||||
 | 
								expectedErr: expectedErr{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "factor < 1 returns error",
 | 
				
			||||||
 | 
								cdp: types.CDP{
 | 
				
			||||||
 | 
									Principal:       sdk.NewInt64Coin("usdx", 1e9),
 | 
				
			||||||
 | 
									AccumulatedFees: sdk.NewInt64Coin("usdx", 1e6),
 | 
				
			||||||
 | 
									InterestFactor:  sdk.MustNewDecFromStr("0.999999999999999999"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedErr: expectedErr{
 | 
				
			||||||
 | 
									contains: "must be ≥ 1",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "0 factor returns error rather than div by 0 panic",
 | 
				
			||||||
 | 
								cdp: types.CDP{
 | 
				
			||||||
 | 
									Principal:       sdk.NewInt64Coin("usdx", 1e9),
 | 
				
			||||||
 | 
									AccumulatedFees: sdk.NewInt64Coin("usdx", 1e6),
 | 
				
			||||||
 | 
									InterestFactor:  sdk.MustNewDecFromStr("0"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedErr: expectedErr{
 | 
				
			||||||
 | 
									contains: "must be ≥ 1",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							suite.Run(tc.name, func() {
 | 
				
			||||||
 | 
								np, err := tc.cdp.GetNormalizedPrincipal()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tc.expectedErr.expectPass {
 | 
				
			||||||
 | 
									suite.Require().NoError(err, tc.name)
 | 
				
			||||||
 | 
									suite.Equal(tc.expected, np)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									suite.Require().Error(err, tc.name)
 | 
				
			||||||
 | 
									suite.Contains(err.Error(), tc.expectedErr.contains)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCdpValidationSuite(t *testing.T) {
 | 
					func TestCdpValidationSuite(t *testing.T) {
 | 
				
			||||||
	suite.Run(t, new(CdpValidationSuite))
 | 
						suite.Run(t, new(CdpValidationSuite))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,36 @@ func NewBorrow(borrower sdk.AccAddress, amount sdk.Coins, index BorrowInterestFa
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NormalizedBorrow is the borrow amounts divided by the interest factors.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Multiplying the normalized borrow by the current global factors gives the current borrow (ie including all interest, ie a synced borrow).
 | 
				
			||||||
 | 
					// The normalized borrow is effectively how big the borrow would have been if it had been borrowed at time 0 and not touched since.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// An error is returned if the borrow is in an invalid state.
 | 
				
			||||||
 | 
					func (b Borrow) NormalizedBorrow() (sdk.DecCoins, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						normalized := sdk.NewDecCoins()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, coin := range b.Amount {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							factor, found := b.Index.GetInterestFactor(coin.Denom)
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("borrowed amount '%s' missing interest factor", coin.Denom)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if factor.LT(sdk.OneDec()) {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("interest factor '%s' < 1", coin.Denom)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							normalized = normalized.Add(
 | 
				
			||||||
 | 
								sdk.NewDecCoinFromDec(
 | 
				
			||||||
 | 
									coin.Denom,
 | 
				
			||||||
 | 
									coin.Amount.ToDec().Quo(factor),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return normalized, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Validate deposit validation
 | 
					// Validate deposit validation
 | 
				
			||||||
func (b Borrow) Validate() error {
 | 
					func (b Borrow) Validate() error {
 | 
				
			||||||
	if b.Borrower.Empty() {
 | 
						if b.Borrower.Empty() {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										117
									
								
								x/hard/types/borrow_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								x/hard/types/borrow_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					package types_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/hard/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBorrow_NormalizedBorrow(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							borrow    types.Borrow
 | 
				
			||||||
 | 
							expect    sdk.DecCoins
 | 
				
			||||||
 | 
							expectErr string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multiple denoms are calculated correctly",
 | 
				
			||||||
 | 
								borrow: types.Borrow{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("xrpb", 1e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.BorrowInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "xrpb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("1.25"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "bnb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("2.0"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: sdk.NewDecCoins(
 | 
				
			||||||
 | 
									sdk.NewInt64DecCoin("bnb", 50e8),
 | 
				
			||||||
 | 
									sdk.NewInt64DecCoin("xrpb", 8e7),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "empty borrow amount returns empty dec coins",
 | 
				
			||||||
 | 
								borrow: types.Borrow{
 | 
				
			||||||
 | 
									Amount: sdk.Coins{},
 | 
				
			||||||
 | 
									Index:  types.BorrowInterestFactors{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: sdk.DecCoins{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "nil borrow amount returns empty dec coins",
 | 
				
			||||||
 | 
								borrow: types.Borrow{
 | 
				
			||||||
 | 
									Amount: nil,
 | 
				
			||||||
 | 
									Index:  types.BorrowInterestFactors{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: sdk.DecCoins{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "missing indexes return error",
 | 
				
			||||||
 | 
								borrow: types.Borrow{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.BorrowInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "xrpb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("1.25"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "missing interest factor",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid indexes return error",
 | 
				
			||||||
 | 
								borrow: types.Borrow{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.BorrowInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "bnb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("0.999999999999999999"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "< 1",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "zero indexes return error rather than panicking",
 | 
				
			||||||
 | 
								borrow: types.Borrow{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.BorrowInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "bnb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("0"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "< 1",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								nb, err := tc.borrow.NormalizedBorrow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								require.Equal(t, tc.expect, nb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if len(tc.expectErr) > 0 {
 | 
				
			||||||
 | 
									require.Error(t, err)
 | 
				
			||||||
 | 
									require.Contains(t, err.Error(), tc.expectErr)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -23,6 +23,36 @@ func NewDeposit(depositor sdk.AccAddress, amount sdk.Coins, indexes SupplyIntere
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NormalizedDeposit is the deposit amounts divided by the interest factors.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Multiplying the normalized deposit by the current global factors gives the current deposit (ie including all interest, ie a synced deposit).
 | 
				
			||||||
 | 
					// The normalized deposit is effectively how big the deposit would have been if it had been supplied at time 0 and not touched since.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// An error is returned if the deposit is in an invalid state.
 | 
				
			||||||
 | 
					func (b Deposit) NormalizedDeposit() (sdk.DecCoins, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						normalized := sdk.NewDecCoins()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, coin := range b.Amount {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							factor, found := b.Index.GetInterestFactor(coin.Denom)
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("deposited amount '%s' missing interest factor", coin.Denom)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if factor.LT(sdk.OneDec()) {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("interest factor '%s' < 1", coin.Denom)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							normalized = normalized.Add(
 | 
				
			||||||
 | 
								sdk.NewDecCoinFromDec(
 | 
				
			||||||
 | 
									coin.Denom,
 | 
				
			||||||
 | 
									coin.Amount.ToDec().Quo(factor),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return normalized, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Validate deposit validation
 | 
					// Validate deposit validation
 | 
				
			||||||
func (d Deposit) Validate() error {
 | 
					func (d Deposit) Validate() error {
 | 
				
			||||||
	if d.Depositor.Empty() {
 | 
						if d.Depositor.Empty() {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										117
									
								
								x/hard/types/deposit_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								x/hard/types/deposit_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					package types_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/hard/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDeposit_NormalizedDeposit(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							deposit   types.Deposit
 | 
				
			||||||
 | 
							expect    sdk.DecCoins
 | 
				
			||||||
 | 
							expectErr string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multiple denoms are calculated correctly",
 | 
				
			||||||
 | 
								deposit: types.Deposit{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("xrpb", 1e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.SupplyInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "xrpb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("1.25"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "bnb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("2.0"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: sdk.NewDecCoins(
 | 
				
			||||||
 | 
									sdk.NewInt64DecCoin("bnb", 50e8),
 | 
				
			||||||
 | 
									sdk.NewInt64DecCoin("xrpb", 8e7),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "empty deposit amount returns empty dec coins",
 | 
				
			||||||
 | 
								deposit: types.Deposit{
 | 
				
			||||||
 | 
									Amount: sdk.Coins{},
 | 
				
			||||||
 | 
									Index:  types.SupplyInterestFactors{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: sdk.DecCoins{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "nil deposit amount returns empty dec coins",
 | 
				
			||||||
 | 
								deposit: types.Deposit{
 | 
				
			||||||
 | 
									Amount: nil,
 | 
				
			||||||
 | 
									Index:  types.SupplyInterestFactors{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: sdk.DecCoins{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "missing indexes return error",
 | 
				
			||||||
 | 
								deposit: types.Deposit{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.SupplyInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "xrpb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("1.25"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "missing interest factor",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid indexes return error",
 | 
				
			||||||
 | 
								deposit: types.Deposit{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.SupplyInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "bnb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("0.999999999999999999"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "< 1",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "zero indexes return error rather than panicking",
 | 
				
			||||||
 | 
								deposit: types.Deposit{
 | 
				
			||||||
 | 
									Amount: sdk.NewCoins(
 | 
				
			||||||
 | 
										sdk.NewInt64Coin("bnb", 100e8),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Index: types.SupplyInterestFactors{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Denom: "bnb",
 | 
				
			||||||
 | 
											Value: sdk.MustNewDecFromStr("0"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "< 1",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								nb, err := tc.deposit.NormalizedDeposit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								require.Equal(t, tc.expect, nb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if len(tc.expectErr) > 0 {
 | 
				
			||||||
 | 
									require.Error(t, err)
 | 
				
			||||||
 | 
									require.Contains(t, err.Error(), tc.expectErr)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,71 +0,0 @@
 | 
				
			|||||||
package keeper_test
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/stretchr/testify/require"
 | 
					 | 
				
			||||||
	abci "github.com/tendermint/tendermint/abci/types"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/kava-labs/kava/app"
 | 
					 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/testutil"
 | 
					 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestRiskyCDPsAccumulateRewards(t *testing.T) {
 | 
					 | 
				
			||||||
	genesisTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
					 | 
				
			||||||
	_, addrs := app.GeneratePrivKeyAddressPairs(5)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	initialCollateral := c("bnb", 1_000_000_000)
 | 
					 | 
				
			||||||
	user := addrs[0]
 | 
					 | 
				
			||||||
	authBuilder := app.NewAuthGenesisBuilder().
 | 
					 | 
				
			||||||
		WithSimpleAccount(user, cs(initialCollateral))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	collateralType := "bnb-a"
 | 
					 | 
				
			||||||
	rewardsPerSecond := c(types.USDXMintingRewardDenom, 1_000_000)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	incentBuilder := testutil.NewIncentiveGenesisBuilder().
 | 
					 | 
				
			||||||
		WithGenesisTime(genesisTime).
 | 
					 | 
				
			||||||
		WithSimpleUSDXRewardPeriod(collateralType, rewardsPerSecond)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tApp := app.NewTestApp()
 | 
					 | 
				
			||||||
	tApp.InitializeFromGenesisStatesWithTime(
 | 
					 | 
				
			||||||
		genesisTime,
 | 
					 | 
				
			||||||
		authBuilder.BuildMarshalled(),
 | 
					 | 
				
			||||||
		NewPricefeedGenStateMultiFromTime(genesisTime),
 | 
					 | 
				
			||||||
		NewCDPGenStateMulti(),
 | 
					 | 
				
			||||||
		incentBuilder.BuildMarshalled(),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: genesisTime})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Setup cdp state containing one CDP
 | 
					 | 
				
			||||||
	cdpKeeper := tApp.GetCDPKeeper()
 | 
					 | 
				
			||||||
	err := cdpKeeper.AddCdp(ctx, user, initialCollateral, c("usdx", 100_000_000), collateralType)
 | 
					 | 
				
			||||||
	require.NoError(t, err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Skip ahead two blocks to accumulate both interest and usdx reward for the cdp
 | 
					 | 
				
			||||||
	// Two blocks are required because the cdp begin blocker runs before incentive begin blocker.
 | 
					 | 
				
			||||||
	// In the first begin block the cdp is synced, which triggers its claim to sync. But no global rewards have accumulated yet so the sync does nothing.
 | 
					 | 
				
			||||||
	// Global rewards accumulate immediately after during the incentive begin blocker.
 | 
					 | 
				
			||||||
	// Rewards are added to the cdp's claim in the next block when the cdp is synced.
 | 
					 | 
				
			||||||
	_ = tApp.EndBlocker(ctx, abci.RequestEndBlock{})
 | 
					 | 
				
			||||||
	ctx = ctx.WithBlockTime(ctx.BlockTime().Add(10 * time.Minute))
 | 
					 | 
				
			||||||
	_ = tApp.BeginBlocker(ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_ = tApp.EndBlocker(ctx, abci.RequestEndBlock{})
 | 
					 | 
				
			||||||
	ctx = ctx.WithBlockTime(ctx.BlockTime().Add(10 * time.Minute))
 | 
					 | 
				
			||||||
	_ = tApp.BeginBlocker(ctx, abci.RequestBeginBlock{})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// check cdp rewards
 | 
					 | 
				
			||||||
	cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, user, collateralType)
 | 
					 | 
				
			||||||
	require.True(t, found)
 | 
					 | 
				
			||||||
	// This additional sync adds the rewards accumulated at the end of the last begin block.
 | 
					 | 
				
			||||||
	// They weren't added during the begin blocker as the incentive BB runs after the CDP BB.
 | 
					 | 
				
			||||||
	incentiveKeeper := tApp.GetIncentiveKeeper()
 | 
					 | 
				
			||||||
	incentiveKeeper.SynchronizeUSDXMintingReward(ctx, cdp)
 | 
					 | 
				
			||||||
	claim, found := incentiveKeeper.GetUSDXMintingClaim(ctx, user)
 | 
					 | 
				
			||||||
	require.True(t, found)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// rewards are roughly rewardsPerSecond * secondsElapsed (10mins) * num blocks (2)
 | 
					 | 
				
			||||||
	require.Equal(t, c(types.USDXMintingRewardDenom, 1_200_000_557), claim.Reward)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -138,7 +138,7 @@ func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, v
 | 
				
			|||||||
func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
 | 
					func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// BeforeDelegationRemoved runs directly before a delegation is deleted
 | 
					// BeforeDelegationRemoved runs directly before a delegation is deleted. BeforeDelegationSharesModified is run prior to this.
 | 
				
			||||||
func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
 | 
					func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -109,6 +109,8 @@ func NewCDPGenStateMulti() app.GenesisState {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewPricefeedGenStateMultiFromTime(t time.Time) app.GenesisState {
 | 
					func NewPricefeedGenStateMultiFromTime(t time.Time) app.GenesisState {
 | 
				
			||||||
 | 
						expiry := 100 * 365 * 24 * time.Hour // 100 years
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pfGenesis := pricefeed.GenesisState{
 | 
						pfGenesis := pricefeed.GenesisState{
 | 
				
			||||||
		Params: pricefeed.Params{
 | 
							Params: pricefeed.Params{
 | 
				
			||||||
			Markets: []pricefeed.Market{
 | 
								Markets: []pricefeed.Market{
 | 
				
			||||||
@ -125,37 +127,37 @@ func NewPricefeedGenStateMultiFromTime(t time.Time) app.GenesisState {
 | 
				
			|||||||
				MarketID:      "kava:usd",
 | 
									MarketID:      "kava:usd",
 | 
				
			||||||
				OracleAddress: sdk.AccAddress{},
 | 
									OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
				Price:         sdk.MustNewDecFromStr("2.00"),
 | 
									Price:         sdk.MustNewDecFromStr("2.00"),
 | 
				
			||||||
				Expiry:        t.Add(1 * time.Hour),
 | 
									Expiry:        t.Add(expiry),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				MarketID:      "btc:usd",
 | 
									MarketID:      "btc:usd",
 | 
				
			||||||
				OracleAddress: sdk.AccAddress{},
 | 
									OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
				Price:         sdk.MustNewDecFromStr("8000.00"),
 | 
									Price:         sdk.MustNewDecFromStr("8000.00"),
 | 
				
			||||||
				Expiry:        t.Add(1 * time.Hour),
 | 
									Expiry:        t.Add(expiry),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				MarketID:      "xrp:usd",
 | 
									MarketID:      "xrp:usd",
 | 
				
			||||||
				OracleAddress: sdk.AccAddress{},
 | 
									OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
				Price:         sdk.MustNewDecFromStr("0.25"),
 | 
									Price:         sdk.MustNewDecFromStr("0.25"),
 | 
				
			||||||
				Expiry:        t.Add(1 * time.Hour),
 | 
									Expiry:        t.Add(expiry),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				MarketID:      "bnb:usd",
 | 
									MarketID:      "bnb:usd",
 | 
				
			||||||
				OracleAddress: sdk.AccAddress{},
 | 
									OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
				Price:         sdk.MustNewDecFromStr("17.25"),
 | 
									Price:         sdk.MustNewDecFromStr("17.25"),
 | 
				
			||||||
				Expiry:        t.Add(1 * time.Hour),
 | 
									Expiry:        t.Add(expiry),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				MarketID:      "busd:usd",
 | 
									MarketID:      "busd:usd",
 | 
				
			||||||
				OracleAddress: sdk.AccAddress{},
 | 
									OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
				Price:         sdk.OneDec(),
 | 
									Price:         sdk.OneDec(),
 | 
				
			||||||
				Expiry:        t.Add(1 * time.Hour),
 | 
									Expiry:        t.Add(expiry),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				MarketID:      "zzz:usd",
 | 
									MarketID:      "zzz:usd",
 | 
				
			||||||
				OracleAddress: sdk.AccAddress{},
 | 
									OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
				Price:         sdk.MustNewDecFromStr("2.00"),
 | 
									Price:         sdk.MustNewDecFromStr("2.00"),
 | 
				
			||||||
				Expiry:        t.Add(1 * time.Hour),
 | 
									Expiry:        t.Add(expiry),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -186,22 +188,20 @@ func NewStakingGenesisState() app.GenesisState {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewCommitteeGenesisState(members []sdk.AccAddress) app.GenesisState {
 | 
					func NewCommitteeGenesisState(committeeID uint64, members ...sdk.AccAddress) app.GenesisState {
 | 
				
			||||||
	genState := committeetypes.DefaultGenesisState()
 | 
						genState := committeetypes.DefaultGenesisState()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	genState.Committees = committeetypes.Committees{
 | 
						genState.Committees = committeetypes.Committees{
 | 
				
			||||||
		committeetypes.MemberCommittee{
 | 
							committeetypes.NewMemberCommittee(
 | 
				
			||||||
			BaseCommittee: committeetypes.BaseCommittee{
 | 
								committeeID,
 | 
				
			||||||
				ID:               genState.NextProposalID,
 | 
								"This committee is for testing.",
 | 
				
			||||||
				Description:      "This committee is for testing.",
 | 
								members,
 | 
				
			||||||
				Members:          members,
 | 
								[]committeetypes.Permission{committeetypes.GodPermission{}},
 | 
				
			||||||
				Permissions:      []committeetypes.Permission{committeetypes.GodPermission{}},
 | 
								sdk.MustNewDecFromStr("0.666666667"),
 | 
				
			||||||
				VoteThreshold:    d("0.667"),
 | 
								time.Hour*24*7,
 | 
				
			||||||
				ProposalDuration: time.Hour * 24 * 7,
 | 
								committeetypes.FirstPastThePost,
 | 
				
			||||||
				TallyOption:      committeetypes.FirstPastThePost,
 | 
							),
 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	genState.NextProposalID += 1
 | 
					 | 
				
			||||||
	return app.GenesisState{
 | 
						return app.GenesisState{
 | 
				
			||||||
		committeetypes.ModuleName: committeetypes.ModuleCdc.MustMarshalJSON(genState),
 | 
							committeetypes.ModuleName: committeetypes.ModuleCdc.MustMarshalJSON(genState),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -38,8 +38,14 @@ func (k Keeper) AccumulateHardBorrowRewards(ctx sdk.Context, rewardPeriod types.
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// getHardBorrowTotalSourceShares fetches the sum of all source shares for a borrow reward.
 | 
					// getHardBorrowTotalSourceShares fetches the sum of all source shares for a borrow reward.
 | 
				
			||||||
// In the case of hard borrow, this is the total borrowed divided by the borrow interest factor.
 | 
					//
 | 
				
			||||||
// This give the "pre interest" value of the total borrowed.
 | 
					// In the case of hard borrow, this is the total borrowed divided by the borrow interest factor (for a particular denom).
 | 
				
			||||||
 | 
					// This gives the "pre interest" or "normalized" value of the total borrowed. This is an amount, that if it was borrowed when
 | 
				
			||||||
 | 
					// the interest factor was zero (ie at time 0), the current value of it with interest would be equal to the current total borrowed.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The normalized borrow is also used for each individual borrow's source shares amount. Normalized amounts do not change except through
 | 
				
			||||||
 | 
					// user input. This is essential as claims must be synced before any change to a source shares amount. The actual borrowed amounts cannot
 | 
				
			||||||
 | 
					// be used as they increase every block due to interest.
 | 
				
			||||||
func (k Keeper) getHardBorrowTotalSourceShares(ctx sdk.Context, denom string) sdk.Dec {
 | 
					func (k Keeper) getHardBorrowTotalSourceShares(ctx sdk.Context, denom string) sdk.Dec {
 | 
				
			||||||
	totalBorrowedCoins, found := k.hardKeeper.GetBorrowedCoins(ctx)
 | 
						totalBorrowedCoins, found := k.hardKeeper.GetBorrowedCoins(ctx)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
@ -87,8 +93,24 @@ func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Bo
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, coin := range borrow.Amount {
 | 
						// Source shares for hard borrows is their normalized borrow amount
 | 
				
			||||||
		globalRewardIndexes, found := k.GetHardBorrowRewardIndexes(ctx, coin.Denom)
 | 
						normalizedBorrows, err := borrow.NormalizedBorrow()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(fmt.Sprintf("during borrow reward sync, could not get normalized borrow for %s: %s", borrow.Borrower, err.Error()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, normedBorrow := range normalizedBorrows {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							claim = k.synchronizeSingleHardBorrowReward(ctx, claim, normedBorrow.Denom, normedBorrow.Amount)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						k.SetHardLiquidityProviderClaim(ctx, claim)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// synchronizeSingleHardBorrowReward synchronizes a single rewarded borrow denom in a hard claim.
 | 
				
			||||||
 | 
					// It returns the claim without setting in the store.
 | 
				
			||||||
 | 
					// The public methods for accessing and modifying claims are preferred over this one. Direct modification of claims is easy to get wrong.
 | 
				
			||||||
 | 
					func (k Keeper) synchronizeSingleHardBorrowReward(ctx sdk.Context, claim types.HardLiquidityProviderClaim, denom string, sourceShares sdk.Dec) types.HardLiquidityProviderClaim {
 | 
				
			||||||
 | 
						globalRewardIndexes, found := k.GetHardBorrowRewardIndexes(ctx, denom)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
		// The global factor is only not found if
 | 
							// The global factor is only not found if
 | 
				
			||||||
		// - the borrowed denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
 | 
							// - the borrowed denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
 | 
				
			||||||
@ -96,10 +118,10 @@ func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Bo
 | 
				
			|||||||
		// If not found we could either skip this sync, or assume the global factor is zero.
 | 
							// If not found we could either skip this sync, or assume the global factor is zero.
 | 
				
			||||||
		// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
 | 
							// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
 | 
				
			||||||
		// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
 | 
							// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
 | 
				
			||||||
			continue
 | 
							return claim
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		userRewardIndexes, found := claim.BorrowRewardIndexes.Get(coin.Denom)
 | 
						userRewardIndexes, found := claim.BorrowRewardIndexes.Get(denom)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
		// Normally the reward indexes should always be found.
 | 
							// Normally the reward indexes should always be found.
 | 
				
			||||||
		// But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that borrowed denom.
 | 
							// But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that borrowed denom.
 | 
				
			||||||
@ -107,7 +129,7 @@ func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Bo
 | 
				
			|||||||
		userRewardIndexes = types.RewardIndexes{}
 | 
							userRewardIndexes = types.RewardIndexes{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, coin.Amount.ToDec())
 | 
						newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, sourceShares)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
 | 
							// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
 | 
				
			||||||
		// This panics if a global reward factor decreases or disappears between the old and new indexes.
 | 
							// This panics if a global reward factor decreases or disappears between the old and new indexes.
 | 
				
			||||||
@ -115,12 +137,12 @@ func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Bo
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	claim.Reward = claim.Reward.Add(newRewards...)
 | 
						claim.Reward = claim.Reward.Add(newRewards...)
 | 
				
			||||||
		claim.BorrowRewardIndexes = claim.BorrowRewardIndexes.With(coin.Denom, globalRewardIndexes)
 | 
						claim.BorrowRewardIndexes = claim.BorrowRewardIndexes.With(denom, globalRewardIndexes)
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	k.SetHardLiquidityProviderClaim(ctx, claim)
 | 
						return claim
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateHardBorrowIndexDenoms adds any new borrow denoms to the claim's borrow reward index
 | 
					// UpdateHardBorrowIndexDenoms adds or removes reward indexes from a claim to match the denoms in the borrow.
 | 
				
			||||||
func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Borrow) {
 | 
					func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Borrow) {
 | 
				
			||||||
	claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower)
 | 
						claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,19 +5,10 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hardtypes "github.com/kava-labs/kava/x/hard/types"
 | 
					 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// InitializeHardBorrowRewardTests runs unit tests for the keeper.InitializeHardBorrowReward method
 | 
					// InitializeHardBorrowRewardTests runs unit tests for the keeper.InitializeHardBorrowReward method
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// inputs
 | 
					 | 
				
			||||||
// - claim in store if it exists (only claim.BorrowRewardIndexes)
 | 
					 | 
				
			||||||
// - global indexes in store
 | 
					 | 
				
			||||||
// - borrow function arg (only borrow.Amount)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// outputs
 | 
					 | 
				
			||||||
// - sets or creates a claim
 | 
					 | 
				
			||||||
type InitializeHardBorrowRewardTests struct {
 | 
					type InitializeHardBorrowRewardTests struct {
 | 
				
			||||||
	unitTester
 | 
						unitTester
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -41,10 +32,9 @@ func (suite *InitializeHardBorrowRewardTests) TestClaimIndexesAreSetWhenClaimExi
 | 
				
			|||||||
	globalIndexes := nonEmptyMultiRewardIndexes
 | 
						globalIndexes := nonEmptyMultiRewardIndexes
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,10 +46,9 @@ func (suite *InitializeHardBorrowRewardTests) TestClaimIndexesAreSetWhenClaimDoe
 | 
				
			|||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	owner := arbitraryAddress()
 | 
						owner := arbitraryAddress()
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(owner).
 | 
				
			||||||
		Borrower: owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,10 +66,9 @@ func (suite *InitializeHardBorrowRewardTests) TestClaimIndexesAreSetEmptyForMiss
 | 
				
			|||||||
	// This happens when a borrow denom has no rewards associated with it.
 | 
						// This happens when a borrow denom has no rewards associated with it.
 | 
				
			||||||
	expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
 | 
						expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
 | 
				
			||||||
	borrowedDenoms := extractCollateralTypes(expectedIndexes)
 | 
						borrowedDenoms := extractCollateralTypes(expectedIndexes)
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(owner).
 | 
				
			||||||
		Borrower: owner,
 | 
							WithArbitrarySourceShares(borrowedDenoms...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(borrowedDenoms...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -14,14 +14,6 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SynchronizeHardBorrowRewardTests runs unit tests for the keeper.SynchronizeHardBorrowReward method
 | 
					// SynchronizeHardBorrowRewardTests runs unit tests for the keeper.SynchronizeHardBorrowReward method
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// inputs
 | 
					 | 
				
			||||||
// - claim in store (only claim.BorrowRewardIndexes, claim.Reward)
 | 
					 | 
				
			||||||
// - global indexes in store
 | 
					 | 
				
			||||||
// - borrow function arg (only borrow.Amount)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// outputs
 | 
					 | 
				
			||||||
// - sets a claim
 | 
					 | 
				
			||||||
type SynchronizeHardBorrowRewardTests struct {
 | 
					type SynchronizeHardBorrowRewardTests struct {
 | 
				
			||||||
	unitTester
 | 
						unitTester
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -43,10 +35,10 @@ func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUpdatedWhenGlo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
 | 
						globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
					
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(claim.BorrowRewardIndexes)...),
 | 
							WithArbitrarySourceShares(extractCollateralTypes(claim.BorrowRewardIndexes)...).
 | 
				
			||||||
	}
 | 
							Build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,10 +60,9 @@ func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUnchangedWhenG
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(unchangingIndexes)
 | 
						suite.storeGlobalBorrowIndexes(unchangingIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(unchangingIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(unchangingIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -93,10 +84,9 @@ func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUpdatedWhenNew
 | 
				
			|||||||
	globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
 | 
						globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,10 +109,9 @@ func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUpdatedWhenNew
 | 
				
			|||||||
	globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
 | 
						globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -169,10 +158,9 @@ func (suite *SynchronizeHardBorrowRewardTests) TestRewardIsIncrementedWhenGlobal
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithSourceShares("borrowdenom", 1e9).
 | 
				
			||||||
		Amount:   cs(c("borrowdenom", 1e9)),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -232,10 +220,10 @@ func (suite *SynchronizeHardBorrowRewardTests) TestRewardIsIncrementedWhenNewRew
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithSourceShares("rewarded", 1e9).
 | 
				
			||||||
		Amount:   cs(c("rewarded", 1e9), c("newlyrewarded", 1e9)),
 | 
							WithSourceShares("newlyrewarded", 1e9).
 | 
				
			||||||
	}
 | 
							Build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -290,10 +278,9 @@ func (suite *SynchronizeHardBorrowRewardTests) TestRewardIsIncrementedWhenNewRew
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithSourceShares("borrowed", 1e9).
 | 
				
			||||||
		Amount:   cs(c("borrowed", 1e9)),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
						suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -306,6 +293,53 @@ func (suite *SynchronizeHardBorrowRewardTests) TestRewardIsIncrementedWhenNewRew
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BorrowBuilder is a tool for creating a hard borrows.
 | 
				
			||||||
 | 
					// The builder inherits from hard.Borrow, so fields can be accessed directly if a helper method doesn't exist.
 | 
				
			||||||
 | 
					type BorrowBuilder struct {
 | 
				
			||||||
 | 
						hardtypes.Borrow
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewBorrowBuilder creates a BorrowBuilder containing an empty borrow.
 | 
				
			||||||
 | 
					func NewBorrowBuilder(borrower sdk.AccAddress) BorrowBuilder {
 | 
				
			||||||
 | 
						return BorrowBuilder{
 | 
				
			||||||
 | 
							Borrow: hardtypes.Borrow{
 | 
				
			||||||
 | 
								Borrower: borrower,
 | 
				
			||||||
 | 
							}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Build assembles and returns the final borrow.
 | 
				
			||||||
 | 
					func (builder BorrowBuilder) Build() hardtypes.Borrow { return builder.Borrow }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithSourceShares adds a borrow amount and factor such that the source shares for this borrow is equal to specified.
 | 
				
			||||||
 | 
					// With a factor of 1, the borrow amount is the source shares. This picks an arbitrary factor to ensure factors are accounted for in production code.
 | 
				
			||||||
 | 
					func (builder BorrowBuilder) WithSourceShares(denom string, shares int64) BorrowBuilder {
 | 
				
			||||||
 | 
						if !builder.Amount.AmountOf(denom).Equal(sdk.ZeroInt()) {
 | 
				
			||||||
 | 
							panic("adding to amount with existing denom not implemented")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, f := builder.Index.GetInterestFactor(denom); f {
 | 
				
			||||||
 | 
							panic("adding to indexes with existing denom not implemented")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// pick arbitrary factor
 | 
				
			||||||
 | 
						factor := sdk.MustNewDecFromStr("2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate borrow amount that would equal the requested source shares given the above factor.
 | 
				
			||||||
 | 
						amt := sdk.NewInt(shares).Mul(factor.RoundInt())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						builder.Amount = builder.Amount.Add(sdk.NewCoin(denom, amt))
 | 
				
			||||||
 | 
						builder.Index = builder.Index.SetInterestFactor(denom, factor)
 | 
				
			||||||
 | 
						return builder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithArbitrarySourceShares adds arbitrary borrow amounts and indexes for each specified denom.
 | 
				
			||||||
 | 
					func (builder BorrowBuilder) WithArbitrarySourceShares(denoms ...string) BorrowBuilder {
 | 
				
			||||||
 | 
						const arbitraryShares = 1e9
 | 
				
			||||||
 | 
						for _, denom := range denoms {
 | 
				
			||||||
 | 
							builder = builder.WithSourceShares(denom, arbitraryShares)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return builder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCalculateRewards(t *testing.T) {
 | 
					func TestCalculateRewards(t *testing.T) {
 | 
				
			||||||
	type expected struct {
 | 
						type expected struct {
 | 
				
			||||||
		err   error
 | 
							err   error
 | 
				
			||||||
 | 
				
			|||||||
@ -17,8 +17,75 @@ import (
 | 
				
			|||||||
	"github.com/kava-labs/kava/x/incentive/keeper"
 | 
						"github.com/kava-labs/kava/x/incentive/keeper"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/testutil"
 | 
						"github.com/kava-labs/kava/x/incentive/testutil"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/kavadist"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type BorrowIntegrationTests struct {
 | 
				
			||||||
 | 
						testutil.IntegrationTester
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						genesisTime time.Time
 | 
				
			||||||
 | 
						addrs       []sdk.AccAddress
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBorrowIntegration(t *testing.T) {
 | 
				
			||||||
 | 
						suite.Run(t, new(BorrowIntegrationTests))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetupTest is run automatically before each suite test
 | 
				
			||||||
 | 
					func (suite *BorrowIntegrationTests) SetupTest() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, suite.addrs = app.GeneratePrivKeyAddressPairs(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.genesisTime = time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *BorrowIntegrationTests) TestSingleUserAccumulatesRewardsAfterSyncing() {
 | 
				
			||||||
 | 
						userA := suite.addrs[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authBulder := app.NewAuthGenesisBuilder().
 | 
				
			||||||
 | 
							WithSimpleModuleAccount(kavadist.ModuleName, cs(c("hard", 1e18))). // Fill kavadist with enough coins to pay out any reward
 | 
				
			||||||
 | 
							WithSimpleAccount(userA, cs(c("bnb", 1e12)))                       // give the user some coins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						incentBuilder := testutil.NewIncentiveGenesisBuilder().
 | 
				
			||||||
 | 
							WithGenesisTime(suite.genesisTime).
 | 
				
			||||||
 | 
							WithMultipliers(types.Multipliers{
 | 
				
			||||||
 | 
								types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")), // keep payout at 1.0 to make maths easier
 | 
				
			||||||
 | 
							}).
 | 
				
			||||||
 | 
							WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6))) // only borrow rewards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.StartChain(
 | 
				
			||||||
 | 
							suite.genesisTime,
 | 
				
			||||||
 | 
							NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
				
			||||||
 | 
							NewHardGenStateMulti(suite.genesisTime).BuildMarshalled(),
 | 
				
			||||||
 | 
							authBulder.BuildMarshalled(),
 | 
				
			||||||
 | 
							incentBuilder.BuildMarshalled(),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create a borrow (need to first deposit to allow it)
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgDeposit(userA, cs(c("bnb", 1e11))))
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgBorrow(userA, cs(c("bnb", 1e10))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Let time pass to accumulate interest on the borrow
 | 
				
			||||||
 | 
						// Use one long block instead of many to reduce any rounding errors, and speed up tests.
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second) // about 12 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User borrows and repays just to sync their borrow.
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgRepay(userA, cs(c("bnb", 1))))
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgBorrow(userA, cs(c("bnb", 1))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Accumulate more rewards.
 | 
				
			||||||
 | 
						// The user still has the same percentage of all borrows (100%) so their rewards should be the same as in the previous block.
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second) // about 12 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User claims all their rewards
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverIncentiveMsg(types.NewMsgClaimHardReward(userA, "large", nil)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The users has always had 100% of borrows, so they should receive all rewards for the previous two blocks.
 | 
				
			||||||
 | 
						// Total rewards for each block is block duration * rewards per second
 | 
				
			||||||
 | 
						accuracy := 1e-10 // using a very high accuracy to flag future small calculation changes
 | 
				
			||||||
 | 
						suite.BalanceInEpsilon(userA, cs(c("bnb", 1e12-1e11+1e10), c("hard", 2*1e6*1e6)), accuracy)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Test suite used for all keeper tests
 | 
					// Test suite used for all keeper tests
 | 
				
			||||||
type BorrowRewardsTestSuite struct {
 | 
					type BorrowRewardsTestSuite struct {
 | 
				
			||||||
	suite.Suite
 | 
						suite.Suite
 | 
				
			||||||
@ -62,7 +129,7 @@ func (suite *BorrowRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenes
 | 
				
			|||||||
		authBuilder.BuildMarshalled(),
 | 
							authBuilder.BuildMarshalled(),
 | 
				
			||||||
		NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
							NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
				
			||||||
		hardBuilder.BuildMarshalled(),
 | 
							hardBuilder.BuildMarshalled(),
 | 
				
			||||||
		NewCommitteeGenesisState(suite.addrs[:2]),
 | 
							NewCommitteeGenesisState(1, suite.addrs[:2]...),
 | 
				
			||||||
		incentBuilder.BuildMarshalled(),
 | 
							incentBuilder.BuildMarshalled(),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,19 +5,10 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hardtypes "github.com/kava-labs/kava/x/hard/types"
 | 
					 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateHardBorrowIndexDenomsTests runs unit tests for the keeper.UpdateHardBorrowIndexDenoms method
 | 
					// UpdateHardBorrowIndexDenomsTests runs unit tests for the keeper.UpdateHardBorrowIndexDenoms method
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// inputs
 | 
					 | 
				
			||||||
// - claim in store if it exists (only claim.BorrowRewardIndexes)
 | 
					 | 
				
			||||||
// - global indexes in store
 | 
					 | 
				
			||||||
// - borrow function arg (only borrow.Amount)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// outputs
 | 
					 | 
				
			||||||
// - sets a claim
 | 
					 | 
				
			||||||
type UpdateHardBorrowIndexDenomsTests struct {
 | 
					type UpdateHardBorrowIndexDenomsTests struct {
 | 
				
			||||||
	unitTester
 | 
						unitTester
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -38,10 +29,9 @@ func (suite *UpdateHardBorrowIndexDenomsTests) TestClaimIndexesAreRemovedForDeno
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// remove one denom from the indexes already in the borrow
 | 
						// remove one denom from the indexes already in the borrow
 | 
				
			||||||
	expectedIndexes := claim.BorrowRewardIndexes[1:]
 | 
						expectedIndexes := claim.BorrowRewardIndexes[1:]
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(expectedIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(expectedIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
						suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -60,10 +50,9 @@ func (suite *UpdateHardBorrowIndexDenomsTests) TestClaimIndexesAreAddedForNewlyB
 | 
				
			|||||||
	globalIndexes := appendUniqueMultiRewardIndex(claim.BorrowRewardIndexes)
 | 
						globalIndexes := appendUniqueMultiRewardIndex(claim.BorrowRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
						suite.storeGlobalBorrowIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
						suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,10 +72,9 @@ func (suite *UpdateHardBorrowIndexDenomsTests) TestClaimIndexesAreUnchangedWhenB
 | 
				
			|||||||
	// UpdateHardBorrowIndexDenoms should ignore the new values.
 | 
						// UpdateHardBorrowIndexDenoms should ignore the new values.
 | 
				
			||||||
	suite.storeGlobalBorrowIndexes(increaseAllRewardFactors(claim.BorrowRewardIndexes))
 | 
						suite.storeGlobalBorrowIndexes(increaseAllRewardFactors(claim.BorrowRewardIndexes))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(claim.BorrowRewardIndexes)...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(extractCollateralTypes(claim.BorrowRewardIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
						suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,10 +95,9 @@ func (suite *UpdateHardBorrowIndexDenomsTests) TestEmptyClaimIndexesAreAddedForN
 | 
				
			|||||||
	// add a denom to the borrowed amount that is not in the global or claim's indexes
 | 
						// add a denom to the borrowed amount that is not in the global or claim's indexes
 | 
				
			||||||
	expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.BorrowRewardIndexes)
 | 
						expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.BorrowRewardIndexes)
 | 
				
			||||||
	borrowedDenoms := extractCollateralTypes(expectedIndexes)
 | 
						borrowedDenoms := extractCollateralTypes(expectedIndexes)
 | 
				
			||||||
	borrow := hardtypes.Borrow{
 | 
						borrow := NewBorrowBuilder(claim.Owner).
 | 
				
			||||||
		Borrower: claim.Owner,
 | 
							WithArbitrarySourceShares(borrowedDenoms...).
 | 
				
			||||||
		Amount:   arbitraryCoinsWithDenoms(borrowedDenoms...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
						suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,7 @@ func (k Keeper) AccumulateHardSupplyRewards(ctx sdk.Context, rewardPeriod types.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// getHardSupplyTotalSourceShares fetches the sum of all source shares for a supply reward.
 | 
					// getHardSupplyTotalSourceShares fetches the sum of all source shares for a supply reward.
 | 
				
			||||||
// In the case of hard supply, this is the total supplied divided by the supply interest factor.
 | 
					// In the case of hard supply, this is the total supplied divided by the supply interest factor.
 | 
				
			||||||
// This give the "pre interest" value of the total supplied.
 | 
					// This gives the "pre interest" value of the total supplied.
 | 
				
			||||||
func (k Keeper) getHardSupplyTotalSourceShares(ctx sdk.Context, denom string) sdk.Dec {
 | 
					func (k Keeper) getHardSupplyTotalSourceShares(ctx sdk.Context, denom string) sdk.Dec {
 | 
				
			||||||
	totalSuppliedCoins, found := k.hardKeeper.GetSuppliedCoins(ctx)
 | 
						totalSuppliedCoins, found := k.hardKeeper.GetSuppliedCoins(ctx)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
@ -86,8 +86,24 @@ func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.D
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, coin := range deposit.Amount {
 | 
						// Source shares for hard deposits is their normalized deposit amount
 | 
				
			||||||
		globalRewardIndexes, found := k.GetHardSupplyRewardIndexes(ctx, coin.Denom)
 | 
						normalizedDeposit, err := deposit.NormalizedDeposit()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(fmt.Sprintf("during deposit reward sync, could not get normalized deposit for %s: %s", deposit.Depositor, err.Error()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, normedDeposit := range normalizedDeposit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							claim = k.synchronizeSingleHardSupplyReward(ctx, claim, normedDeposit.Denom, normedDeposit.Amount)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						k.SetHardLiquidityProviderClaim(ctx, claim)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// synchronizeSingleHardSupplyReward synchronizes a single rewarded supply denom in a hard claim.
 | 
				
			||||||
 | 
					// It returns the claim without setting in the store.
 | 
				
			||||||
 | 
					// The public methods for accessing and modifying claims are preferred over this one. Direct modification of claims is easy to get wrong.
 | 
				
			||||||
 | 
					func (k Keeper) synchronizeSingleHardSupplyReward(ctx sdk.Context, claim types.HardLiquidityProviderClaim, denom string, sourceShares sdk.Dec) types.HardLiquidityProviderClaim {
 | 
				
			||||||
 | 
						globalRewardIndexes, found := k.GetHardSupplyRewardIndexes(ctx, denom)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
		// The global factor is only not found if
 | 
							// The global factor is only not found if
 | 
				
			||||||
		// - the supply denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
 | 
							// - the supply denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
 | 
				
			||||||
@ -95,10 +111,10 @@ func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.D
 | 
				
			|||||||
		// If not found we could either skip this sync, or assume the global factor is zero.
 | 
							// If not found we could either skip this sync, or assume the global factor is zero.
 | 
				
			||||||
		// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
 | 
							// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
 | 
				
			||||||
		// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
 | 
							// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
 | 
				
			||||||
			continue
 | 
							return claim
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		userRewardIndexes, found := claim.SupplyRewardIndexes.Get(coin.Denom)
 | 
						userRewardIndexes, found := claim.SupplyRewardIndexes.Get(denom)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
		// Normally the reward indexes should always be found.
 | 
							// Normally the reward indexes should always be found.
 | 
				
			||||||
		// But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that supplied denom.
 | 
							// But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that supplied denom.
 | 
				
			||||||
@ -106,7 +122,7 @@ func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.D
 | 
				
			|||||||
		userRewardIndexes = types.RewardIndexes{}
 | 
							userRewardIndexes = types.RewardIndexes{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, coin.Amount.ToDec())
 | 
						newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, sourceShares)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
 | 
							// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
 | 
				
			||||||
		// This panics if a global reward factor decreases or disappears between the old and new indexes.
 | 
							// This panics if a global reward factor decreases or disappears between the old and new indexes.
 | 
				
			||||||
@ -114,9 +130,9 @@ func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.D
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	claim.Reward = claim.Reward.Add(newRewards...)
 | 
						claim.Reward = claim.Reward.Add(newRewards...)
 | 
				
			||||||
		claim.SupplyRewardIndexes = claim.SupplyRewardIndexes.With(coin.Denom, globalRewardIndexes)
 | 
						claim.SupplyRewardIndexes = claim.SupplyRewardIndexes.With(denom, globalRewardIndexes)
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	k.SetHardLiquidityProviderClaim(ctx, claim)
 | 
						return claim
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateHardSupplyIndexDenoms adds any new deposit denoms to the claim's supply reward index
 | 
					// UpdateHardSupplyIndexDenoms adds any new deposit denoms to the claim's supply reward index
 | 
				
			||||||
 | 
				
			|||||||
@ -5,19 +5,10 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hardtypes "github.com/kava-labs/kava/x/hard/types"
 | 
					 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// InitializeHardSupplyRewardTests runs unit tests for the keeper.InitializeHardSupplyReward method
 | 
					// InitializeHardSupplyRewardTests runs unit tests for the keeper.InitializeHardSupplyReward method
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// inputs
 | 
					 | 
				
			||||||
// - claim in store if it exists (only claim.SupplyRewardIndexes)
 | 
					 | 
				
			||||||
// - global indexes in store
 | 
					 | 
				
			||||||
// - deposit function arg (only deposit.Amount)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// outputs
 | 
					 | 
				
			||||||
// - sets or creates a claim
 | 
					 | 
				
			||||||
type InitializeHardSupplyRewardTests struct {
 | 
					type InitializeHardSupplyRewardTests struct {
 | 
				
			||||||
	unitTester
 | 
						unitTester
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -41,10 +32,9 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimExi
 | 
				
			|||||||
	globalIndexes := nonEmptyMultiRewardIndexes
 | 
						globalIndexes := nonEmptyMultiRewardIndexes
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,10 +46,9 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimDoe
 | 
				
			|||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	owner := arbitraryAddress()
 | 
						owner := arbitraryAddress()
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(owner).
 | 
				
			||||||
		Depositor: owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,10 +66,9 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetEmptyForMiss
 | 
				
			|||||||
	// This happens when a deposit denom has no rewards associated with it.
 | 
						// This happens when a deposit denom has no rewards associated with it.
 | 
				
			||||||
	expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
 | 
						expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
 | 
				
			||||||
	depositedDenoms := extractCollateralTypes(expectedIndexes)
 | 
						depositedDenoms := extractCollateralTypes(expectedIndexes)
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(owner).
 | 
				
			||||||
		Depositor: owner,
 | 
							WithArbitrarySourceShares(depositedDenoms...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(depositedDenoms...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ package keeper_test
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hardtypes "github.com/kava-labs/kava/x/hard/types"
 | 
						hardtypes "github.com/kava-labs/kava/x/hard/types"
 | 
				
			||||||
@ -10,14 +11,6 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SynchronizeHardSupplyRewardTests runs unit tests for the keeper.SynchronizeHardSupplyReward method
 | 
					// SynchronizeHardSupplyRewardTests runs unit tests for the keeper.SynchronizeHardSupplyReward method
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// inputs
 | 
					 | 
				
			||||||
// - claim in store (only claim.SupplyRewardIndexes, claim.Reward)
 | 
					 | 
				
			||||||
// - global indexes in store
 | 
					 | 
				
			||||||
// - deposit function arg (only deposit.Amount)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// outputs
 | 
					 | 
				
			||||||
// - sets a claim
 | 
					 | 
				
			||||||
type SynchronizeHardSupplyRewardTests struct {
 | 
					type SynchronizeHardSupplyRewardTests struct {
 | 
				
			||||||
	unitTester
 | 
						unitTester
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -39,10 +32,9 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenGlo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
 | 
						globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(claim.SupplyRewardIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,10 +56,9 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUnchangedWhenG
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(unchangingIndexes)
 | 
						suite.storeGlobalSupplyIndexes(unchangingIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(unchangingIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(unchangingIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -89,10 +80,9 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNew
 | 
				
			|||||||
	globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
 | 
						globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -115,10 +105,9 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNew
 | 
				
			|||||||
	globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
 | 
						globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -165,10 +154,9 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenGlobal
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithSourceShares("depositdenom", 1e9).
 | 
				
			||||||
		Amount:    cs(c("depositdenom", 1e9)),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -228,10 +216,10 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithSourceShares("rewarded", 1e9).
 | 
				
			||||||
		Amount:    cs(c("rewarded", 1e9), c("newlyrewarded", 1e9)),
 | 
							WithSourceShares("newlyrewarded", 1e9).
 | 
				
			||||||
	}
 | 
							Build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -286,10 +274,9 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithSourceShares("deposited", 1e9).
 | 
				
			||||||
		Amount:    cs(c("deposited", 1e9)),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
						suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -301,3 +288,50 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
 | 
				
			|||||||
		syncedClaim.Reward,
 | 
							syncedClaim.Reward,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DepositBuilder is a tool for creating a hard deposit in tests.
 | 
				
			||||||
 | 
					// The builder inherits from hard.Deposit, so fields can be accessed directly if a helper method doesn't exist.
 | 
				
			||||||
 | 
					type DepositBuilder struct {
 | 
				
			||||||
 | 
						hardtypes.Deposit
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewDepositBuilder creates a DepositBuilder containing an empty deposit.
 | 
				
			||||||
 | 
					func NewDepositBuilder(depositor sdk.AccAddress) DepositBuilder {
 | 
				
			||||||
 | 
						return DepositBuilder{
 | 
				
			||||||
 | 
							Deposit: hardtypes.Deposit{
 | 
				
			||||||
 | 
								Depositor: depositor,
 | 
				
			||||||
 | 
							}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Build assembles and returns the final deposit.
 | 
				
			||||||
 | 
					func (builder DepositBuilder) Build() hardtypes.Deposit { return builder.Deposit }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithSourceShares adds a deposit amount and factor such that the source shares for this deposit is equal to specified.
 | 
				
			||||||
 | 
					// With a factor of 1, the deposit amount is the source shares. This picks an arbitrary factor to ensure factors are accounted for in production code.
 | 
				
			||||||
 | 
					func (builder DepositBuilder) WithSourceShares(denom string, shares int64) DepositBuilder {
 | 
				
			||||||
 | 
						if !builder.Amount.AmountOf(denom).Equal(sdk.ZeroInt()) {
 | 
				
			||||||
 | 
							panic("adding to amount with existing denom not implemented")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, f := builder.Index.GetInterestFactor(denom); f {
 | 
				
			||||||
 | 
							panic("adding to indexes with existing denom not implemented")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// pick arbitrary factor
 | 
				
			||||||
 | 
						factor := sdk.MustNewDecFromStr("2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate deposit amount that would equal the requested source shares given the above factor.
 | 
				
			||||||
 | 
						amt := sdk.NewInt(shares).Mul(factor.RoundInt())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						builder.Amount = builder.Amount.Add(sdk.NewCoin(denom, amt))
 | 
				
			||||||
 | 
						builder.Index = builder.Index.SetInterestFactor(denom, factor)
 | 
				
			||||||
 | 
						return builder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithArbitrarySourceShares adds arbitrary deposit amounts and indexes for each specified denom.
 | 
				
			||||||
 | 
					func (builder DepositBuilder) WithArbitrarySourceShares(denoms ...string) DepositBuilder {
 | 
				
			||||||
 | 
						const arbitraryShares = 1e9
 | 
				
			||||||
 | 
						for _, denom := range denoms {
 | 
				
			||||||
 | 
							builder = builder.WithSourceShares(denom, arbitraryShares)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return builder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,8 +17,76 @@ import (
 | 
				
			|||||||
	"github.com/kava-labs/kava/x/incentive/keeper"
 | 
						"github.com/kava-labs/kava/x/incentive/keeper"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/testutil"
 | 
						"github.com/kava-labs/kava/x/incentive/testutil"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/kavadist"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SupplyIntegrationTests struct {
 | 
				
			||||||
 | 
						testutil.IntegrationTester
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						genesisTime time.Time
 | 
				
			||||||
 | 
						addrs       []sdk.AccAddress
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSupplyIntegration(t *testing.T) {
 | 
				
			||||||
 | 
						suite.Run(t, new(SupplyIntegrationTests))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetupTest is run automatically before each suite test
 | 
				
			||||||
 | 
					func (suite *SupplyIntegrationTests) SetupTest() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, suite.addrs = app.GeneratePrivKeyAddressPairs(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.genesisTime = time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *SupplyIntegrationTests) TestSingleUserAccumulatesRewardsAfterSyncing() {
 | 
				
			||||||
 | 
						userA := suite.addrs[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authBulder := app.NewAuthGenesisBuilder().
 | 
				
			||||||
 | 
							WithSimpleModuleAccount(kavadist.ModuleName, cs(c("hard", 1e18))). // Fill kavadist with enough coins to pay out any reward
 | 
				
			||||||
 | 
							WithSimpleAccount(userA, cs(c("bnb", 1e12)))                       // give the user some coins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						incentBuilder := testutil.NewIncentiveGenesisBuilder().
 | 
				
			||||||
 | 
							WithGenesisTime(suite.genesisTime).
 | 
				
			||||||
 | 
							WithMultipliers(types.Multipliers{
 | 
				
			||||||
 | 
								types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")), // keep payout at 1.0 to make maths easier
 | 
				
			||||||
 | 
							}).
 | 
				
			||||||
 | 
							WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6))) // only borrow rewards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.StartChain(
 | 
				
			||||||
 | 
							suite.genesisTime,
 | 
				
			||||||
 | 
							NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
				
			||||||
 | 
							NewHardGenStateMulti(suite.genesisTime).BuildMarshalled(),
 | 
				
			||||||
 | 
							authBulder.BuildMarshalled(),
 | 
				
			||||||
 | 
							incentBuilder.BuildMarshalled(),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create a deposit
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgDeposit(userA, cs(c("bnb", 1e11))))
 | 
				
			||||||
 | 
						// Also create a borrow so interest accumulates on the deposit
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgBorrow(userA, cs(c("bnb", 1e10))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Let time pass to accumulate interest on the deposit
 | 
				
			||||||
 | 
						// Use one long block instead of many to reduce any rounding errors, and speed up tests.
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second) // about 12 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User withdraw and redeposits just to sync their deposit.
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgWithdraw(userA, cs(c("bnb", 1))))
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverHardMsgDeposit(userA, cs(c("bnb", 1))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Accumulate more rewards.
 | 
				
			||||||
 | 
						// The user still has the same percentage of all deposits (100%) so their rewards should be the same as in the previous block.
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second) // about 12 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User claims all their rewards
 | 
				
			||||||
 | 
						suite.NoError(suite.DeliverIncentiveMsg(types.NewMsgClaimHardReward(userA, "large", nil)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The users has always had 100% of deposits, so they should receive all rewards for the previous two blocks.
 | 
				
			||||||
 | 
						// Total rewards for each block is block duration * rewards per second
 | 
				
			||||||
 | 
						accuracy := 1e-10 // using a very high accuracy to flag future small calculation changes
 | 
				
			||||||
 | 
						suite.BalanceInEpsilon(userA, cs(c("bnb", 1e12-1e11+1e10), c("hard", 2*1e6*1e6)), accuracy)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Test suite used for all keeper tests
 | 
					// Test suite used for all keeper tests
 | 
				
			||||||
type SupplyRewardsTestSuite struct {
 | 
					type SupplyRewardsTestSuite struct {
 | 
				
			||||||
	suite.Suite
 | 
						suite.Suite
 | 
				
			||||||
@ -62,7 +130,7 @@ func (suite *SupplyRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenes
 | 
				
			|||||||
		authBuilder.BuildMarshalled(),
 | 
							authBuilder.BuildMarshalled(),
 | 
				
			||||||
		NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
							NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
				
			||||||
		hardBuilder.BuildMarshalled(),
 | 
							hardBuilder.BuildMarshalled(),
 | 
				
			||||||
		NewCommitteeGenesisState(suite.addrs[:2]),
 | 
							NewCommitteeGenesisState(1, suite.addrs[:2]...),
 | 
				
			||||||
		incentBuilder.BuildMarshalled(),
 | 
							incentBuilder.BuildMarshalled(),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,19 +5,10 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hardtypes "github.com/kava-labs/kava/x/hard/types"
 | 
					 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateHardSupplyIndexDenomsTests runs unit tests for the keeper.UpdateHardSupplyIndexDenoms method
 | 
					// UpdateHardSupplyIndexDenomsTests runs unit tests for the keeper.UpdateHardSupplyIndexDenoms method
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// inputs
 | 
					 | 
				
			||||||
// - claim in store if it exists (only claim.SupplyRewardIndexes)
 | 
					 | 
				
			||||||
// - global indexes in store
 | 
					 | 
				
			||||||
// - deposit function arg (only deposit.Amount)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// outputs
 | 
					 | 
				
			||||||
// - sets a claim
 | 
					 | 
				
			||||||
type UpdateHardSupplyIndexDenomsTests struct {
 | 
					type UpdateHardSupplyIndexDenomsTests struct {
 | 
				
			||||||
	unitTester
 | 
						unitTester
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -38,10 +29,9 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreRemovedForDeno
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// remove one denom from the indexes already in the deposit
 | 
						// remove one denom from the indexes already in the deposit
 | 
				
			||||||
	expectedIndexes := claim.SupplyRewardIndexes[1:]
 | 
						expectedIndexes := claim.SupplyRewardIndexes[1:]
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(expectedIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(expectedIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
						suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -60,10 +50,9 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreAddedForNewlyS
 | 
				
			|||||||
	globalIndexes := appendUniqueMultiRewardIndex(claim.SupplyRewardIndexes)
 | 
						globalIndexes := appendUniqueMultiRewardIndex(claim.SupplyRewardIndexes)
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
						suite.storeGlobalSupplyIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
						suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,10 +72,9 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreUnchangedWhenS
 | 
				
			|||||||
	// UpdateHardSupplyIndexDenoms should ignore the new values.
 | 
						// UpdateHardSupplyIndexDenoms should ignore the new values.
 | 
				
			||||||
	suite.storeGlobalSupplyIndexes(increaseAllRewardFactors(claim.SupplyRewardIndexes))
 | 
						suite.storeGlobalSupplyIndexes(increaseAllRewardFactors(claim.SupplyRewardIndexes))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(extractCollateralTypes(claim.SupplyRewardIndexes)...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
						suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,10 +95,9 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestEmptyClaimIndexesAreAddedForN
 | 
				
			|||||||
	// add a denom to the deposited amount that is not in the global or claim's indexes
 | 
						// add a denom to the deposited amount that is not in the global or claim's indexes
 | 
				
			||||||
	expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.SupplyRewardIndexes)
 | 
						expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.SupplyRewardIndexes)
 | 
				
			||||||
	depositedDenoms := extractCollateralTypes(expectedIndexes)
 | 
						depositedDenoms := extractCollateralTypes(expectedIndexes)
 | 
				
			||||||
	deposit := hardtypes.Deposit{
 | 
						deposit := NewDepositBuilder(claim.Owner).
 | 
				
			||||||
		Depositor: claim.Owner,
 | 
							WithArbitrarySourceShares(depositedDenoms...).
 | 
				
			||||||
		Amount:    arbitraryCoinsWithDenoms(depositedDenoms...),
 | 
							Build()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
						suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -41,7 +41,7 @@ func (k Keeper) AccumulateUSDXMintingRewards(ctx sdk.Context, rewardPeriod types
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// getUSDXTotalSourceShares fetches the sum of all source shares for a usdx minting reward.
 | 
					// getUSDXTotalSourceShares fetches the sum of all source shares for a usdx minting reward.
 | 
				
			||||||
// In the case of usdx minting, this is the total debt from all cdps of a particular type, divided by the cdp interest factor.
 | 
					// In the case of usdx minting, this is the total debt from all cdps of a particular type, divided by the cdp interest factor.
 | 
				
			||||||
// This give the "pre interest" value of the total debt.
 | 
					// This gives the "pre interest" value of the total debt.
 | 
				
			||||||
func (k Keeper) getUSDXTotalSourceShares(ctx sdk.Context, collateralType string) sdk.Dec {
 | 
					func (k Keeper) getUSDXTotalSourceShares(ctx sdk.Context, collateralType string) sdk.Dec {
 | 
				
			||||||
	totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, collateralType, cdptypes.DefaultStableDenom)
 | 
						totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, collateralType, cdptypes.DefaultStableDenom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -59,15 +59,11 @@ func (k Keeper) getUSDXTotalSourceShares(ctx sdk.Context, collateralType string)
 | 
				
			|||||||
// accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor,
 | 
					// accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor,
 | 
				
			||||||
// any unclaimed rewards are preserved, but no new rewards are added.
 | 
					// any unclaimed rewards are preserved, but no new rewards are added.
 | 
				
			||||||
func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) {
 | 
					func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) {
 | 
				
			||||||
	_, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type)
 | 
					 | 
				
			||||||
	if !found {
 | 
					 | 
				
			||||||
		// this collateral type is not incentivized, do nothing
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
 | 
						claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
 | 
				
			||||||
	if !found { // this is the owner's first usdx minting reward claim
 | 
						if !found { // this is the owner's first usdx minting reward claim
 | 
				
			||||||
		claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{})
 | 
							claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
 | 
						globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
		globalRewardFactor = sdk.ZeroDec()
 | 
							globalRewardFactor = sdk.ZeroDec()
 | 
				
			||||||
@ -81,7 +77,27 @@ func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) {
 | 
				
			|||||||
// this should be called before a cdp is modified.
 | 
					// this should be called before a cdp is modified.
 | 
				
			||||||
func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) {
 | 
					func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
 | 
						claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sourceShares, err := cdp.GetNormalizedPrincipal()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(fmt.Sprintf("during usdx reward sync, could not get normalized principal for %s: %s", cdp.Owner, err.Error()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						claim = k.synchronizeSingleUSDXMintingReward(ctx, claim, cdp.Type, sourceShares)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						k.SetUSDXMintingClaim(ctx, claim)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// synchronizeSingleUSDXMintingReward synchronizes a single rewarded cdp collateral type in a usdx minting claim.
 | 
				
			||||||
 | 
					// It returns the claim without setting in the store.
 | 
				
			||||||
 | 
					// The public methods for accessing and modifying claims are preferred over this one. Direct modification of claims is easy to get wrong.
 | 
				
			||||||
 | 
					func (k Keeper) synchronizeSingleUSDXMintingReward(ctx sdk.Context, claim types.USDXMintingClaim, ctype string, sourceShares sdk.Dec) types.USDXMintingClaim {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, ctype)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
		// The global factor is only not found if
 | 
							// The global factor is only not found if
 | 
				
			||||||
		// - the cdp collateral type has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
 | 
							// - the cdp collateral type has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
 | 
				
			||||||
@ -89,18 +105,10 @@ func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP)
 | 
				
			|||||||
		// If not found we could either skip this sync, or assume the global factor is zero.
 | 
							// If not found we could either skip this sync, or assume the global factor is zero.
 | 
				
			||||||
		// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
 | 
							// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
 | 
				
			||||||
		// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
 | 
							// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
 | 
				
			||||||
		return
 | 
							return claim
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
 | 
					 | 
				
			||||||
	if !found {
 | 
					 | 
				
			||||||
		claim = types.NewUSDXMintingClaim(
 | 
					 | 
				
			||||||
			cdp.Owner,
 | 
					 | 
				
			||||||
			sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()),
 | 
					 | 
				
			||||||
			types.RewardIndexes{},
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	userRewardFactor, found := claim.RewardIndexes.Get(cdp.Type)
 | 
						userRewardFactor, found := claim.RewardIndexes.Get(ctype)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
		// Normally the factor should always be found, as it is added when the cdp is created in InitializeUSDXMintingClaim.
 | 
							// Normally the factor should always be found, as it is added when the cdp is created in InitializeUSDXMintingClaim.
 | 
				
			||||||
		// However if a cdp type is not rewarded then becomes rewarded (ie a reward period is added to params), existing cdps will not have the factor in their claims.
 | 
							// However if a cdp type is not rewarded then becomes rewarded (ie a reward period is added to params), existing cdps will not have the factor in their claims.
 | 
				
			||||||
@ -108,7 +116,7 @@ func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP)
 | 
				
			|||||||
		userRewardFactor = sdk.ZeroDec()
 | 
							userRewardFactor = sdk.ZeroDec()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	newRewardsAmount, err := k.CalculateSingleReward(userRewardFactor, globalRewardFactor, cdp.GetTotalPrincipal().Amount.ToDec())
 | 
						newRewardsAmount, err := k.CalculateSingleReward(userRewardFactor, globalRewardFactor, sourceShares)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
 | 
							// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
 | 
				
			||||||
		// This panics if a global reward factor decreases or disappears between the old and new indexes.
 | 
							// This panics if a global reward factor decreases or disappears between the old and new indexes.
 | 
				
			||||||
@ -117,9 +125,9 @@ func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP)
 | 
				
			|||||||
	newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount)
 | 
						newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	claim.Reward = claim.Reward.Add(newRewardsCoin)
 | 
						claim.Reward = claim.Reward.Add(newRewardsCoin)
 | 
				
			||||||
	claim.RewardIndexes = claim.RewardIndexes.With(cdp.Type, globalRewardFactor)
 | 
						claim.RewardIndexes = claim.RewardIndexes.With(ctype, globalRewardFactor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.SetUSDXMintingClaim(ctx, claim)
 | 
						return claim
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SimulateUSDXMintingSynchronization calculates a user's outstanding USDX minting rewards by simulating reward synchronization
 | 
					// SimulateUSDXMintingSynchronization calculates a user's outstanding USDX minting rewards by simulating reward synchronization
 | 
				
			||||||
 | 
				
			|||||||
@ -5,16 +5,229 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
						paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
	abci "github.com/tendermint/tendermint/abci/types"
 | 
						abci "github.com/tendermint/tendermint/abci/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/kava-labs/kava/app"
 | 
						"github.com/kava-labs/kava/app"
 | 
				
			||||||
	cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
 | 
						cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
 | 
				
			||||||
	cdptypes "github.com/kava-labs/kava/x/cdp/types"
 | 
						cdptypes "github.com/kava-labs/kava/x/cdp/types"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/incentive"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/keeper"
 | 
						"github.com/kava-labs/kava/x/incentive/keeper"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/testutil"
 | 
						"github.com/kava-labs/kava/x/incentive/testutil"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/kavadist"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type USDXIntegrationTests struct {
 | 
				
			||||||
 | 
						testutil.IntegrationTester
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						genesisTime time.Time
 | 
				
			||||||
 | 
						addrs       []sdk.AccAddress
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestUSDXIntegration(t *testing.T) {
 | 
				
			||||||
 | 
						suite.Run(t, new(USDXIntegrationTests))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetupTest is run automatically before each suite test
 | 
				
			||||||
 | 
					func (suite *USDXIntegrationTests) SetupTest() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, suite.addrs = app.GeneratePrivKeyAddressPairs(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.genesisTime = time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *USDXIntegrationTests) ProposeAndVoteOnNewRewardPeriods(committeeID uint64, voter sdk.AccAddress, newPeriods types.RewardPeriods) {
 | 
				
			||||||
 | 
						suite.ProposeAndVoteOnNewParams(
 | 
				
			||||||
 | 
							voter,
 | 
				
			||||||
 | 
							committeeID,
 | 
				
			||||||
 | 
							[]paramtypes.ParamChange{{
 | 
				
			||||||
 | 
								Subspace: incentive.ModuleName,
 | 
				
			||||||
 | 
								Key:      string(incentive.KeyUSDXMintingRewardPeriods),
 | 
				
			||||||
 | 
								Value:    string(incentive.ModuleCdc.MustMarshalJSON(newPeriods)),
 | 
				
			||||||
 | 
							}})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *USDXIntegrationTests) TestSingleUserAccumulatesRewardsAfterSyncing() {
 | 
				
			||||||
 | 
						userA := suite.addrs[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authBulder := app.NewAuthGenesisBuilder().
 | 
				
			||||||
 | 
							WithSimpleModuleAccount(kavadist.ModuleName, cs(c(types.USDXMintingRewardDenom, 1e18))). // Fill kavadist with enough coins to pay out any reward
 | 
				
			||||||
 | 
							WithSimpleAccount(userA, cs(c("bnb", 1e12)))                                             // give the user some coins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						incentBuilder := testutil.NewIncentiveGenesisBuilder().
 | 
				
			||||||
 | 
							WithGenesisTime(suite.genesisTime).
 | 
				
			||||||
 | 
							WithMultipliers(types.Multipliers{
 | 
				
			||||||
 | 
								types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")), // keep payout at 1.0 to make maths easier
 | 
				
			||||||
 | 
							}).
 | 
				
			||||||
 | 
							WithSimpleUSDXRewardPeriod("bnb-a", c(types.USDXMintingRewardDenom, 1e6))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.StartChain(
 | 
				
			||||||
 | 
							suite.genesisTime,
 | 
				
			||||||
 | 
							NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
				
			||||||
 | 
							NewCDPGenStateMulti(),
 | 
				
			||||||
 | 
							authBulder.BuildMarshalled(),
 | 
				
			||||||
 | 
							incentBuilder.BuildMarshalled(),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User creates a CDP to begin earning rewards.
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverMsgCreateCDP(userA, c("bnb", 1e10), c(cdptypes.DefaultStableDenom, 1e9), "bnb-a"),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Let time pass to accumulate interest on the deposit
 | 
				
			||||||
 | 
						// Use one long block instead of many to reduce any rounding errors, and speed up tests.
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second) // about 12 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User repays and borrows just to sync their CDP
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverCDPMsgRepay(userA, "bnb-a", c(cdptypes.DefaultStableDenom, 1)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverCDPMsgBorrow(userA, "bnb-a", c(cdptypes.DefaultStableDenom, 1)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Accumulate more rewards.
 | 
				
			||||||
 | 
						// The user still has the same percentage of all CDP debt (100%) so their rewards should be the same as in the previous block.
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second) // about 12 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User claims all their rewards
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverIncentiveMsg(types.NewMsgClaimUSDXMintingReward(userA, "large")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The users has always had 100% of cdp debt, so they should receive all rewards for the previous two blocks.
 | 
				
			||||||
 | 
						// Total rewards for each block is block duration * rewards per second
 | 
				
			||||||
 | 
						accuracy := 1e-18 // using a very high accuracy to flag future small calculation changes
 | 
				
			||||||
 | 
						suite.BalanceInEpsilon(userA, cs(c("bnb", 1e12-1e10), c(cdptypes.DefaultStableDenom, 1e9), c(types.USDXMintingRewardDenom, 2*1e6*1e6)), accuracy)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *USDXIntegrationTests) TestSingleUserAccumulatesRewardsWithoutSyncing() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user := suite.addrs[0]
 | 
				
			||||||
 | 
						initialCollateral := c("bnb", 1e9)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authBuilder := app.NewAuthGenesisBuilder().
 | 
				
			||||||
 | 
							WithSimpleModuleAccount(kavadist.ModuleName, cs(c(types.USDXMintingRewardDenom, 1e18))). // Fill kavadist with enough coins to pay out any reward
 | 
				
			||||||
 | 
							WithSimpleAccount(user, cs(initialCollateral))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						collateralType := "bnb-a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						incentBuilder := testutil.NewIncentiveGenesisBuilder().
 | 
				
			||||||
 | 
							WithGenesisTime(suite.genesisTime).
 | 
				
			||||||
 | 
							WithMultipliers(types.Multipliers{
 | 
				
			||||||
 | 
								types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")), // keep payout at 1.0 to make maths easier
 | 
				
			||||||
 | 
							}).
 | 
				
			||||||
 | 
							WithSimpleUSDXRewardPeriod(collateralType, c(types.USDXMintingRewardDenom, 1e6))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.StartChain(
 | 
				
			||||||
 | 
							suite.genesisTime,
 | 
				
			||||||
 | 
							authBuilder.BuildMarshalled(),
 | 
				
			||||||
 | 
							NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
				
			||||||
 | 
							NewCDPGenStateMulti(),
 | 
				
			||||||
 | 
							incentBuilder.BuildMarshalled(),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup cdp state containing one CDP
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverMsgCreateCDP(user, initialCollateral, c("usdx", 1e8), collateralType),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Skip ahead a few blocks blocks to accumulate both interest and usdx reward for the cdp
 | 
				
			||||||
 | 
						// Don't sync the CDP between the blocks
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second) // about 12 days
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second)
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverIncentiveMsg(types.NewMsgClaimUSDXMintingReward(user, "large")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The users has always had 100% of cdp debt, so they should receive all rewards for the previous two blocks.
 | 
				
			||||||
 | 
						// Total rewards for each block is block duration * rewards per second
 | 
				
			||||||
 | 
						accuracy := 1e-18 // using a very high accuracy to flag future small calculation changes
 | 
				
			||||||
 | 
						suite.BalanceInEpsilon(user, cs(c(cdptypes.DefaultStableDenom, 1e8), c(types.USDXMintingRewardDenom, 3*1e6*1e6)), accuracy)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *USDXIntegrationTests) TestReinstatingRewardParamsDoesNotTriggerOverPayments() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userA := suite.addrs[0]
 | 
				
			||||||
 | 
						userB := suite.addrs[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authBuilder := app.NewAuthGenesisBuilder().
 | 
				
			||||||
 | 
							WithSimpleModuleAccount(kavadist.ModuleName, cs(c(types.USDXMintingRewardDenom, 1e18))). // Fill kavadist with enough coins to pay out any reward
 | 
				
			||||||
 | 
							WithSimpleAccount(userA, cs(c("bnb", 1e10))).
 | 
				
			||||||
 | 
							WithSimpleAccount(userB, cs(c("bnb", 1e10)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						incentBuilder := testutil.NewIncentiveGenesisBuilder().
 | 
				
			||||||
 | 
							WithGenesisTime(suite.genesisTime).
 | 
				
			||||||
 | 
							WithMultipliers(types.Multipliers{
 | 
				
			||||||
 | 
								types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")), // keep payout at 1.0 to make maths easier
 | 
				
			||||||
 | 
							}).
 | 
				
			||||||
 | 
							WithSimpleUSDXRewardPeriod("bnb-a", c(types.USDXMintingRewardDenom, 1e6))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.StartChain(
 | 
				
			||||||
 | 
							suite.genesisTime,
 | 
				
			||||||
 | 
							authBuilder.BuildMarshalled(),
 | 
				
			||||||
 | 
							NewPricefeedGenStateMultiFromTime(suite.genesisTime),
 | 
				
			||||||
 | 
							NewCDPGenStateMulti(),
 | 
				
			||||||
 | 
							incentBuilder.BuildMarshalled(),
 | 
				
			||||||
 | 
							NewCommitteeGenesisState(0, userA), // create a committtee to change params
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Accumulate some CDP rewards, requires creating a cdp so the total borrowed isn't 0.
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverMsgCreateCDP(userA, c("bnb", 1e10), c("usdx", 1e9), "bnb-a"),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1e6 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Remove the USDX reward period
 | 
				
			||||||
 | 
						suite.ProposeAndVoteOnNewRewardPeriods(0, userA, types.RewardPeriods{})
 | 
				
			||||||
 | 
						// next block so proposal is enacted
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create a CDP when there is no reward periods. In a previous version the claim object would not be created, leading to the bug.
 | 
				
			||||||
 | 
						// Withdraw the same amount of usdx as the first cdp currently has. This make the reward maths easier, as rewards will be split 50:50 between each cdp.
 | 
				
			||||||
 | 
						firstCDP, f := suite.App.GetCDPKeeper().GetCdpByOwnerAndCollateralType(suite.Ctx, userA, "bnb-a")
 | 
				
			||||||
 | 
						suite.True(f)
 | 
				
			||||||
 | 
						firstCDPTotalPrincipal := firstCDP.GetTotalPrincipal()
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverMsgCreateCDP(userB, c("bnb", 1e10), firstCDPTotalPrincipal, "bnb-a"),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add back the reward period
 | 
				
			||||||
 | 
						suite.ProposeAndVoteOnNewRewardPeriods(0, userA,
 | 
				
			||||||
 | 
							types.RewardPeriods{types.NewRewardPeriod(
 | 
				
			||||||
 | 
								true,
 | 
				
			||||||
 | 
								"bnb-a",
 | 
				
			||||||
 | 
								suite.Ctx.BlockTime(), // start accumulating again from this block
 | 
				
			||||||
 | 
								suite.genesisTime.Add(365*24*time.Hour),
 | 
				
			||||||
 | 
								c(types.USDXMintingRewardDenom, 1e6),
 | 
				
			||||||
 | 
							)},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						// next block so proposal is enacted
 | 
				
			||||||
 | 
						suite.NextBlockAfter(1 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Sync the cdp and claim by borrowing a bit
 | 
				
			||||||
 | 
						// In a previous version this would create the cdp with incorrect indexes, leading to overpayment.
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverCDPMsgBorrow(userB, "bnb-a", c(cdptypes.DefaultStableDenom, 1)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Claim rewards
 | 
				
			||||||
 | 
						suite.NoError(
 | 
				
			||||||
 | 
							suite.DeliverIncentiveMsg(types.NewMsgClaimUSDXMintingReward(userB, "large")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The cdp had half the total borrows for a 1s block. So should earn half the rewards for that block
 | 
				
			||||||
 | 
						suite.BalanceInEpsilon(
 | 
				
			||||||
 | 
							userB,
 | 
				
			||||||
 | 
							cs(firstCDPTotalPrincipal.Add(c(cdptypes.DefaultStableDenom, 1)), c(types.USDXMintingRewardDenom, 0.5*1e6)),
 | 
				
			||||||
 | 
							1e-18, // using very high accuracy to catch small changes to the calculations
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Test suite used for all keeper tests
 | 
					// Test suite used for all keeper tests
 | 
				
			||||||
type USDXRewardsTestSuite struct {
 | 
					type USDXRewardsTestSuite struct {
 | 
				
			||||||
	suite.Suite
 | 
						suite.Suite
 | 
				
			||||||
 | 
				
			|||||||
@ -35,9 +35,6 @@ func TestInitializeUSDXMintingClaims(t *testing.T) {
 | 
				
			|||||||
func (suite *InitializeUSDXMintingClaimTests) TestClaimIndexIsSetWhenClaimDoesNotExist() {
 | 
					func (suite *InitializeUSDXMintingClaimTests) TestClaimIndexIsSetWhenClaimDoesNotExist() {
 | 
				
			||||||
	collateralType := "bnb-a"
 | 
						collateralType := "bnb-a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	subspace := paramsWithSingleUSDXRewardPeriod(collateralType)
 | 
					 | 
				
			||||||
	suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cdp := NewCDPBuilder(arbitraryAddress(), collateralType).Build()
 | 
						cdp := NewCDPBuilder(arbitraryAddress(), collateralType).Build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	globalIndexes := types.RewardIndexes{{
 | 
						globalIndexes := types.RewardIndexes{{
 | 
				
			||||||
@ -56,9 +53,6 @@ func (suite *InitializeUSDXMintingClaimTests) TestClaimIndexIsSetWhenClaimDoesNo
 | 
				
			|||||||
func (suite *InitializeUSDXMintingClaimTests) TestClaimIndexIsSetWhenClaimExists() {
 | 
					func (suite *InitializeUSDXMintingClaimTests) TestClaimIndexIsSetWhenClaimExists() {
 | 
				
			||||||
	collateralType := "bnb-a"
 | 
						collateralType := "bnb-a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	subspace := paramsWithSingleUSDXRewardPeriod(collateralType)
 | 
					 | 
				
			||||||
	suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	claim := types.USDXMintingClaim{
 | 
						claim := types.USDXMintingClaim{
 | 
				
			||||||
		BaseClaim: types.BaseClaim{
 | 
							BaseClaim: types.BaseClaim{
 | 
				
			||||||
			Owner: arbitraryAddress(),
 | 
								Owner: arbitraryAddress(),
 | 
				
			||||||
@ -106,7 +100,7 @@ func (suite *SynchronizeUSDXMintingRewardTests) TestRewardUnchangedWhenGlobalInd
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	suite.storeGlobalUSDXIndexes(unchangingRewardIndexes)
 | 
						suite.storeGlobalUSDXIndexes(unchangingRewardIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cdp := NewCDPBuilder(claim.Owner, collateralType).WithPrincipal(i(1e12)).Build()
 | 
						cdp := NewCDPBuilder(claim.Owner, collateralType).WithSourceShares(1e12).Build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
 | 
						suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -139,7 +133,7 @@ func (suite *SynchronizeUSDXMintingRewardTests) TestRewardIsIncrementedWhenGloba
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	suite.storeGlobalUSDXIndexes(globalIndexes)
 | 
						suite.storeGlobalUSDXIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cdp := NewCDPBuilder(claim.Owner, collateralType).WithPrincipal(i(1e12)).Build()
 | 
						cdp := NewCDPBuilder(claim.Owner, collateralType).WithSourceShares(1e12).Build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
 | 
						suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -148,28 +142,6 @@ func (suite *SynchronizeUSDXMintingRewardTests) TestRewardIsIncrementedWhenGloba
 | 
				
			|||||||
	suite.Equal(c(types.USDXMintingRewardDenom, 1e11), syncedClaim.Reward)
 | 
						suite.Equal(c(types.USDXMintingRewardDenom, 1e11), syncedClaim.Reward)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *SynchronizeUSDXMintingRewardTests) TestRewardIsIncrementedWhenNewRewardAddedAndClaimDoesNotExit() {
 | 
					 | 
				
			||||||
	collateralType := "bnb-a"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	globalIndexes := types.RewardIndexes{
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			CollateralType: collateralType,
 | 
					 | 
				
			||||||
			RewardFactor:   d("0.2"),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	suite.storeGlobalUSDXIndexes(globalIndexes)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cdp := NewCDPBuilder(arbitraryAddress(), collateralType).WithPrincipal(i(1e12)).Build()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, cdp.Owner)
 | 
					 | 
				
			||||||
	// The global index was not around when this cdp was created as it was not stored in a claim.
 | 
					 | 
				
			||||||
	// Therefore it must have been added via params after.
 | 
					 | 
				
			||||||
	// To include rewards since the params were updated, the old index should be assumed to be 0.
 | 
					 | 
				
			||||||
	// reward is ( new index - old index ) * cdp.TotalPrincipal
 | 
					 | 
				
			||||||
	suite.Equal(c(types.USDXMintingRewardDenom, 2e11), syncedClaim.Reward)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
func (suite *SynchronizeUSDXMintingRewardTests) TestClaimIndexIsUpdatedWhenGlobalIndexIncreased() {
 | 
					func (suite *SynchronizeUSDXMintingRewardTests) TestClaimIndexIsUpdatedWhenGlobalIndexIncreased() {
 | 
				
			||||||
	claimsRewardIndexes := nonEmptyRewardIndexes
 | 
						claimsRewardIndexes := nonEmptyRewardIndexes
 | 
				
			||||||
	collateralType := extractFirstCollateralType(claimsRewardIndexes)
 | 
						collateralType := extractFirstCollateralType(claimsRewardIndexes)
 | 
				
			||||||
@ -248,7 +220,7 @@ func (suite *SynchronizeUSDXMintingRewardTests) TestClaimIsUnchangedWhenGlobalFa
 | 
				
			|||||||
	// don't store any reward indexes
 | 
						// don't store any reward indexes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// create a cdp with collateral type that doesn't exist in the claim's indexes, and does not have a corresponding global factor
 | 
						// create a cdp with collateral type that doesn't exist in the claim's indexes, and does not have a corresponding global factor
 | 
				
			||||||
	cdp := NewCDPBuilder(claim.Owner, "unrewardedcollateral").WithPrincipal(i(1e12)).Build()
 | 
						cdp := NewCDPBuilder(claim.Owner, "unrewardedcollateral").WithSourceShares(1e12).Build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
 | 
						suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -257,12 +229,15 @@ func (suite *SynchronizeUSDXMintingRewardTests) TestClaimIsUnchangedWhenGlobalFa
 | 
				
			|||||||
	suite.Equal(claim.Reward, syncedClaim.Reward)
 | 
						suite.Equal(claim.Reward, syncedClaim.Reward)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type cdpBuilder struct {
 | 
					// CDPBuilder is a tool for creating a CDP in tests.
 | 
				
			||||||
 | 
					// The builder inherits from cdp.CDP, so fields can be accessed directly if a helper method doesn't exist.
 | 
				
			||||||
 | 
					type CDPBuilder struct {
 | 
				
			||||||
	cdptypes.CDP
 | 
						cdptypes.CDP
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewCDPBuilder(owner sdk.AccAddress, collateralType string) cdpBuilder {
 | 
					// NewCDPBuilder creates a CdpBuilder containing a CDP with owner and collateral type set.
 | 
				
			||||||
	return cdpBuilder{
 | 
					func NewCDPBuilder(owner sdk.AccAddress, collateralType string) CDPBuilder {
 | 
				
			||||||
 | 
						return CDPBuilder{
 | 
				
			||||||
		CDP: cdptypes.CDP{
 | 
							CDP: cdptypes.CDP{
 | 
				
			||||||
			Owner: owner,
 | 
								Owner: owner,
 | 
				
			||||||
			Type:  collateralType,
 | 
								Type:  collateralType,
 | 
				
			||||||
@ -270,12 +245,36 @@ func NewCDPBuilder(owner sdk.AccAddress, collateralType string) cdpBuilder {
 | 
				
			|||||||
			// Set them to the default denom, but with 0 amount.
 | 
								// Set them to the default denom, but with 0 amount.
 | 
				
			||||||
			Principal:       c(cdptypes.DefaultStableDenom, 0),
 | 
								Principal:       c(cdptypes.DefaultStableDenom, 0),
 | 
				
			||||||
			AccumulatedFees: c(cdptypes.DefaultStableDenom, 0),
 | 
								AccumulatedFees: c(cdptypes.DefaultStableDenom, 0),
 | 
				
			||||||
 | 
								// zero value of sdk.Dec causes nil pointer panics
 | 
				
			||||||
 | 
								InterestFactor: sdk.OneDec(),
 | 
				
			||||||
		}}
 | 
							}}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (builder cdpBuilder) Build() cdptypes.CDP { return builder.CDP }
 | 
					// Build assembles and returns the final deposit.
 | 
				
			||||||
 | 
					func (builder CDPBuilder) Build() cdptypes.CDP { return builder.CDP }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (builder cdpBuilder) WithPrincipal(principal sdk.Int) cdpBuilder {
 | 
					// WithSourceShares adds a principal amount and interest factor such that the source shares for this CDP is equal to specified.
 | 
				
			||||||
 | 
					// With a factor of 1, the total principal is the source shares. This picks an arbitrary factor to ensure factors are accounted for in production code.
 | 
				
			||||||
 | 
					func (builder CDPBuilder) WithSourceShares(shares int64) CDPBuilder {
 | 
				
			||||||
 | 
						if !builder.GetTotalPrincipal().Amount.Equal(sdk.ZeroInt()) {
 | 
				
			||||||
 | 
							panic("setting source shares on cdp with existing principal or fees not implemented")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !(builder.InterestFactor.IsNil() || builder.InterestFactor.Equal(sdk.OneDec())) {
 | 
				
			||||||
 | 
							panic("setting source shares on cdp with existing interest factor not implemented")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// pick arbitrary interest factor
 | 
				
			||||||
 | 
						factor := sdk.NewInt(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate deposit amount that would equal the requested source shares given the above factor.
 | 
				
			||||||
 | 
						principal := sdk.NewInt(shares).Mul(factor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						builder.Principal = sdk.NewCoin(cdptypes.DefaultStableDenom, principal)
 | 
				
			||||||
 | 
						builder.InterestFactor = factor.ToDec()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return builder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (builder CDPBuilder) WithPrincipal(principal sdk.Int) CDPBuilder {
 | 
				
			||||||
	builder.Principal = sdk.NewCoin(cdptypes.DefaultStableDenom, principal)
 | 
						builder.Principal = sdk.NewCoin(cdptypes.DefaultStableDenom, principal)
 | 
				
			||||||
	return builder
 | 
						return builder
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -291,18 +290,6 @@ var nonEmptyRewardIndexes = types.RewardIndexes{
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func paramsWithSingleUSDXRewardPeriod(collateralType string) types.ParamSubspace {
 | 
					 | 
				
			||||||
	return &fakeParamSubspace{
 | 
					 | 
				
			||||||
		params: types.Params{
 | 
					 | 
				
			||||||
			USDXMintingRewardPeriods: types.RewardPeriods{
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					CollateralType: collateralType,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func extractFirstCollateralType(indexes types.RewardIndexes) string {
 | 
					func extractFirstCollateralType(indexes types.RewardIndexes) string {
 | 
				
			||||||
	if len(indexes) == 0 {
 | 
						if len(indexes) == 0 {
 | 
				
			||||||
		panic("cannot extract a collateral type from 0 length RewardIndexes")
 | 
							panic("cannot extract a collateral type from 0 length RewardIndexes")
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ import (
 | 
				
			|||||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
	authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
 | 
						authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
 | 
				
			||||||
	"github.com/cosmos/cosmos-sdk/x/auth/vesting"
 | 
						"github.com/cosmos/cosmos-sdk/x/auth/vesting"
 | 
				
			||||||
 | 
						paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
 | 
				
			||||||
	"github.com/cosmos/cosmos-sdk/x/staking"
 | 
						"github.com/cosmos/cosmos-sdk/x/staking"
 | 
				
			||||||
	supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
 | 
						supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
@ -16,6 +17,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/kava-labs/kava/app"
 | 
						"github.com/kava-labs/kava/app"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/cdp"
 | 
						"github.com/kava-labs/kava/x/cdp"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/committee"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/hard"
 | 
						"github.com/kava-labs/kava/x/hard"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive"
 | 
						"github.com/kava-labs/kava/x/incentive"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/swap"
 | 
						"github.com/kava-labs/kava/x/swap"
 | 
				
			||||||
@ -27,6 +29,22 @@ type IntegrationTester struct {
 | 
				
			|||||||
	Ctx sdk.Context
 | 
						Ctx sdk.Context
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) SetupSuite() {
 | 
				
			||||||
 | 
						config := sdk.GetConfig()
 | 
				
			||||||
 | 
						app.SetBech32AddressPrefixes(config)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) StartChain(genesisTime time.Time, genesisStates ...app.GenesisState) {
 | 
				
			||||||
 | 
						suite.App = app.NewTestApp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.App.InitializeFromGenesisStatesWithTime(
 | 
				
			||||||
 | 
							genesisTime,
 | 
				
			||||||
 | 
							genesisStates...,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Ctx = suite.App.NewContext(false, abci.Header{Height: 1, Time: genesisTime})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *IntegrationTester) NextBlockAt(blockTime time.Time) {
 | 
					func (suite *IntegrationTester) NextBlockAt(blockTime time.Time) {
 | 
				
			||||||
	if !suite.Ctx.BlockTime().Before(blockTime) {
 | 
						if !suite.Ctx.BlockTime().Before(blockTime) {
 | 
				
			||||||
		panic(fmt.Sprintf("new block time %s must be after current %s", blockTime, suite.Ctx.BlockTime()))
 | 
							panic(fmt.Sprintf("new block time %s must be after current %s", blockTime, suite.Ctx.BlockTime()))
 | 
				
			||||||
@ -87,14 +105,26 @@ func (suite *IntegrationTester) DeliverSwapMsgDeposit(depositor sdk.AccAddress,
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *IntegrationTester) DeliverHardMsgDeposit(depositor sdk.AccAddress, deposit sdk.Coins) error {
 | 
					func (suite *IntegrationTester) DeliverHardMsgDeposit(owner sdk.AccAddress, deposit sdk.Coins) error {
 | 
				
			||||||
	msg := hard.NewMsgDeposit(depositor, deposit)
 | 
						msg := hard.NewMsgDeposit(owner, deposit)
 | 
				
			||||||
	_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
 | 
						_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *IntegrationTester) DeliverHardMsgBorrow(depositor sdk.AccAddress, borrow sdk.Coins) error {
 | 
					func (suite *IntegrationTester) DeliverHardMsgBorrow(owner sdk.AccAddress, borrow sdk.Coins) error {
 | 
				
			||||||
	msg := hard.NewMsgBorrow(depositor, borrow)
 | 
						msg := hard.NewMsgBorrow(owner, borrow)
 | 
				
			||||||
 | 
						_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) DeliverHardMsgRepay(owner sdk.AccAddress, repay sdk.Coins) error {
 | 
				
			||||||
 | 
						msg := hard.NewMsgRepay(owner, owner, repay)
 | 
				
			||||||
 | 
						_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) DeliverHardMsgWithdraw(owner sdk.AccAddress, withdraw sdk.Coins) error {
 | 
				
			||||||
 | 
						msg := hard.NewMsgRepay(owner, owner, withdraw)
 | 
				
			||||||
	_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
 | 
						_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -105,6 +135,42 @@ func (suite *IntegrationTester) DeliverMsgCreateCDP(owner sdk.AccAddress, collat
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) DeliverCDPMsgRepay(owner sdk.AccAddress, collateralType string, payment sdk.Coin) error {
 | 
				
			||||||
 | 
						msg := cdp.NewMsgRepayDebt(owner, collateralType, payment)
 | 
				
			||||||
 | 
						_, err := cdp.NewHandler(suite.App.GetCDPKeeper())(suite.Ctx, msg)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) DeliverCDPMsgBorrow(owner sdk.AccAddress, collateralType string, draw sdk.Coin) error {
 | 
				
			||||||
 | 
						msg := cdp.NewMsgDrawDebt(owner, collateralType, draw)
 | 
				
			||||||
 | 
						_, err := cdp.NewHandler(suite.App.GetCDPKeeper())(suite.Ctx, msg)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) ProposeAndVoteOnNewParams(voter sdk.AccAddress, committeeID uint64, changes []paramtypes.ParamChange) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						propose := committee.NewMsgSubmitProposal(
 | 
				
			||||||
 | 
							paramtypes.NewParameterChangeProposal(
 | 
				
			||||||
 | 
								"test title",
 | 
				
			||||||
 | 
								"test description",
 | 
				
			||||||
 | 
								changes,
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							voter,
 | 
				
			||||||
 | 
							committeeID,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handleMsg := committee.NewHandler(suite.App.GetCommitteeKeeper())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := handleMsg(suite.Ctx, propose)
 | 
				
			||||||
 | 
						suite.NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						proposalID := committee.Uint64FromBytes(res.Data)
 | 
				
			||||||
 | 
						vote := committee.NewMsgVote(voter, proposalID, committee.Yes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = handleMsg(suite.Ctx, vote)
 | 
				
			||||||
 | 
						suite.NoError(err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *IntegrationTester) GetAccount(addr sdk.AccAddress) authexported.Account {
 | 
					func (suite *IntegrationTester) GetAccount(addr sdk.AccAddress) authexported.Account {
 | 
				
			||||||
	ak := suite.App.GetAccountKeeper()
 | 
						ak := suite.App.GetAccountKeeper()
 | 
				
			||||||
	return ak.GetAccount(suite.Ctx, addr)
 | 
						return ak.GetAccount(suite.Ctx, addr)
 | 
				
			||||||
@ -134,6 +200,20 @@ func (suite *IntegrationTester) BalanceEquals(address sdk.AccAddress, expected s
 | 
				
			|||||||
	suite.Equalf(expected, acc.GetCoins(), "expected account balance to equal coins %s, but got %s", expected, acc.GetCoins())
 | 
						suite.Equalf(expected, acc.GetCoins(), "expected account balance to equal coins %s, but got %s", expected, acc.GetCoins())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *IntegrationTester) BalanceInEpsilon(address sdk.AccAddress, expected sdk.Coins, epsilon float64) {
 | 
				
			||||||
 | 
						actual := suite.GetBalance(address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						allDenoms := expected.Add(actual...)
 | 
				
			||||||
 | 
						for _, coin := range allDenoms {
 | 
				
			||||||
 | 
							suite.InEpsilonf(
 | 
				
			||||||
 | 
								expected.AmountOf(coin.Denom).Int64(),
 | 
				
			||||||
 | 
								actual.AmountOf(coin.Denom).Int64(),
 | 
				
			||||||
 | 
								epsilon,
 | 
				
			||||||
 | 
								"expected balance to be within %f%% of coins %s, but got %s", epsilon*100, expected, actual,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *IntegrationTester) VestingPeriodsEqual(address sdk.AccAddress, expectedPeriods vesting.Periods) {
 | 
					func (suite *IntegrationTester) VestingPeriodsEqual(address sdk.AccAddress, expectedPeriods vesting.Periods) {
 | 
				
			||||||
	acc := suite.App.GetAccountKeeper().GetAccount(suite.Ctx, address)
 | 
						acc := suite.App.GetAccountKeeper().GetAccount(suite.Ctx, address)
 | 
				
			||||||
	suite.Require().NotNil(acc, "expected vesting account not to be nil")
 | 
						suite.Require().NotNil(acc, "expected vesting account not to be nil")
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user