Skip to content

Commit

Permalink
chore: only charge coins which are whitelisted (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
troykessler authored May 8, 2024
1 parent 6e0aba8 commit b7d8eb0
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 82 deletions.
2 changes: 2 additions & 0 deletions x/bundles/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package types
import (
"cosmossdk.io/math"
delegationTypes "github.com/KYVENetwork/chain/x/delegation/types"
"github.com/KYVENetwork/chain/x/funders/types"
pooltypes "github.com/KYVENetwork/chain/x/pool/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
Expand Down Expand Up @@ -48,6 +49,7 @@ type DelegationKeeper interface {
}

type FundersKeeper interface {
GetCoinWhitelist(ctx sdk.Context) (whitelist []types.WhitelistCoinEntry)
ChargeFundersOfPool(ctx sdk.Context, poolId uint64, recipient string) (payout sdk.Coins, err error)
}

Expand Down
64 changes: 48 additions & 16 deletions x/funders/keeper/logic_funders.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,37 @@ func (k Keeper) GetTotalActiveFunding(ctx sdk.Context, poolId uint64) (amounts s
return
}

// GetCoinWhitelist gets the coin whitelist from the params of the funding module
func (k Keeper) GetCoinWhitelist(ctx sdk.Context) (whitelist []types.WhitelistCoinEntry) {
params := k.GetParams(ctx)

for _, entry := range params.CoinWhitelist {
whitelist = append(whitelist, *entry)
}

return
}

// GetCoinWhitelistMap gets the coin whitelist as a map with the denom as key for easier lookup.
// WARNING: Don't use this for setter functions since go maps are non-deterministic!
func (k Keeper) GetCoinWhitelistMap(ctx sdk.Context) (whitelist map[string]types.WhitelistCoinEntry) {
whitelist = make(map[string]types.WhitelistCoinEntry)

w := k.GetCoinWhitelist(ctx)
for _, entry := range w {
whitelist[entry.CoinDenom] = entry
}

return
}

// ChargeFundersOfPool charges all funders of a pool with their amount_per_bundle
// If the amount is lower than the amount_per_bundle,
// the max amount is charged and the funder is removed from the active funders list.
// The amount is transferred from the funders to the recipient module account.
// If there are no more active funders, an event is emitted.
func (k Keeper) ChargeFundersOfPool(ctx sdk.Context, poolId uint64, recipient string) (payouts sdk.Coins, err error) {
// If there are no more active funders, an event is emitted. This method only charges
// coins which are whitelisted.
func (k Keeper) ChargeFundersOfPool(ctx sdk.Context, poolId uint64, recipient string) (sdk.Coins, error) {
// Get funding state for pool
fundingState, found := k.GetFundingState(ctx, poolId)
if !found {
Expand All @@ -49,9 +74,12 @@ func (k Keeper) ChargeFundersOfPool(ctx sdk.Context, poolId uint64, recipient st
return sdk.NewCoins(), nil
}

// This is the amount every funding will be charged
whitelist := k.GetCoinWhitelistMap(ctx)
payouts := sdk.NewCoins()

// Charge every active funder and collect payouts
for _, funding := range activeFundings {
payouts = payouts.Add(funding.ChargeOneBundle()...)
payouts = payouts.Add(funding.ChargeOneBundle(whitelist)...)
if funding.Amounts.IsZero() {
fundingState.SetInactive(&funding)
}
Expand All @@ -68,24 +96,28 @@ func (k Keeper) ChargeFundersOfPool(ctx sdk.Context, poolId uint64, recipient st
})
}

// Move funds to pool module account
if !payouts.IsZero() {
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, recipient, payouts); err != nil {
return sdk.NewCoins(), err
}
if payouts.IsZero() {
return payouts, nil
}

return
// Move funds to recipient module
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, recipient, payouts); err != nil {
return sdk.NewCoins(), err
}

return payouts, nil
}

// GetLowestFunding returns the funding with the lowest amount
// Precondition: len(fundings) > 0
func (k Keeper) GetLowestFunding(fundings []types.Funding, whitelist []*types.WhitelistCoinEntry) (lowestFunding *types.Funding, err error) {
func (k Keeper) GetLowestFunding(ctx sdk.Context, fundings []types.Funding) (lowestFunding *types.Funding, err error) {
if len(fundings) == 0 {
return nil, fmt.Errorf("no active fundings")
}

whitelist := k.GetCoinWhitelistMap(ctx)
lowestFundingIndex := 0

for i := range fundings {
if fundings[i].GetScore(whitelist) < fundings[lowestFundingIndex].GetScore(whitelist) {
lowestFundingIndex = i
Expand Down Expand Up @@ -167,9 +199,7 @@ func (k Keeper) ensureFreeSlot(ctx sdk.Context, newFunding *types.Funding, fundi
return nil
}

params := k.GetParams(ctx)

lowestFunding, err := k.GetLowestFunding(activeFundings, params.CoinWhitelist)
lowestFunding, err := k.GetLowestFunding(ctx, activeFundings)
if err != nil {
return err
}
Expand All @@ -179,9 +209,11 @@ func (k Keeper) ensureFreeSlot(ctx sdk.Context, newFunding *types.Funding, fundi
return nil
}

whitelist := k.GetCoinWhitelistMap(ctx)

// Check if lowest funding is lower than new funding based on amount (amount per bundle is ignored)
if newFunding.GetScore(params.CoinWhitelist) < lowestFunding.GetScore(params.CoinWhitelist) {
return errors.Wrapf(errorsTypes.ErrLogic, types.ErrFundsTooLow.Error(), lowestFunding.GetScore(params.CoinWhitelist))
if newFunding.GetScore(whitelist) < lowestFunding.GetScore(whitelist) {
return errors.Wrapf(errorsTypes.ErrLogic, types.ErrFundsTooLow.Error(), lowestFunding.GetScore(whitelist))
}

// Defund lowest funder
Expand Down
37 changes: 18 additions & 19 deletions x/funders/keeper/logic_funders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ var _ = Describe("logic_funders.go", Ordered, func() {

It("Charge funder that has coins which are not in the whitelist", func() {
// ARRANGE
whitelist = []*funderstypes.WhitelistCoinEntry{
s.App().FundersKeeper.SetParams(s.Ctx(), funderstypes.NewParams([]*funderstypes.WhitelistCoinEntry{
{
CoinDenom: globaltypes.Denom,
MinFundingAmount: 10 * i.KYVE,
Expand All @@ -387,25 +387,24 @@ var _ = Describe("logic_funders.go", Ordered, func() {
MinFundingAmountPerBundle: 1 * i.KYVE,
CoinWeight: math.LegacyNewDec(3),
},
}
s.App().FundersKeeper.SetParams(s.Ctx(), funderstypes.NewParams(whitelist, 20))
}, 20))

// ACT
payout, err := s.App().FundersKeeper.ChargeFundersOfPool(s.Ctx(), 0, pooltypes.ModuleName)
Expect(err).NotTo(HaveOccurred())

// ASSERT
Expect(payout.String()).To(Equal(i.ACoins(11 * i.T_KYVE).String()))
Expect(payout).To(BeEmpty())

fundingAlice, foundAlice := s.App().FundersKeeper.GetFunding(s.Ctx(), i.ALICE, 0)
Expect(foundAlice).To(BeTrue())
Expect(fundingAlice.Amounts.String()).To(Equal(i.ACoins(99 * i.T_KYVE).String()))
Expect(fundingAlice.TotalFunded.String()).To(Equal(i.ACoins(1 * i.T_KYVE).String()))
Expect(fundingAlice.Amounts.String()).To(Equal(i.ACoins(100 * i.T_KYVE).String()))
Expect(fundingAlice.TotalFunded).To(BeEmpty())

fundingBob, foundBob := s.App().FundersKeeper.GetFunding(s.Ctx(), i.BOB, 0)
Expect(foundBob).To(BeTrue())
Expect(fundingBob.Amounts.String()).To(Equal(i.ACoins(40 * i.T_KYVE).String()))
Expect(fundingBob.TotalFunded.String()).To(Equal(i.ACoins(10 * i.T_KYVE).String()))
Expect(fundingBob.Amounts.String()).To(Equal(i.ACoins(50 * i.T_KYVE).String()))
Expect(fundingBob.TotalFunded).To(BeEmpty())

fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0)
Expect(fundingState.ActiveFunderAddresses).To(HaveLen(2))
Expand All @@ -414,8 +413,8 @@ var _ = Describe("logic_funders.go", Ordered, func() {

fundersBalance := s.GetCoinsFromModule(funderstypes.ModuleName)
poolBalance := s.GetCoinsFromModule(pooltypes.ModuleName)
Expect(fundersBalance.String()).To(Equal(i.ACoins(139 * i.T_KYVE).String()))
Expect(poolBalance.String()).To(Equal(i.ACoins(11 * i.T_KYVE).String()))
Expect(fundersBalance.String()).To(Equal(i.ACoins(150 * i.T_KYVE).String()))
Expect(poolBalance).To(BeEmpty())
})

It("Charge without fundings", func() {
Expand Down Expand Up @@ -458,7 +457,7 @@ var _ = Describe("logic_funders.go", Ordered, func() {
})

It("Check if the lowest funding is returned correctly with one coin", func() {
whitelist := []*funderstypes.WhitelistCoinEntry{
s.App().FundersKeeper.SetParams(s.Ctx(), funderstypes.NewParams([]*funderstypes.WhitelistCoinEntry{
{
CoinDenom: globaltypes.Denom,
MinFundingAmount: 10 * i.KYVE,
Expand All @@ -477,7 +476,7 @@ var _ = Describe("logic_funders.go", Ordered, func() {
CoinDenom: i.C_DENOM,
CoinWeight: math.LegacyNewDec(3),
},
}
}, 20))

fundings := []funderstypes.Funding{
{
Expand All @@ -500,13 +499,13 @@ var _ = Describe("logic_funders.go", Ordered, func() {
},
}

getLowestFunding, err := s.App().FundersKeeper.GetLowestFunding(fundings, whitelist)
getLowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), fundings)
Expect(err).NotTo(HaveOccurred())
Expect(getLowestFunding.FunderAddress).To(Equal(i.DUMMY[2]))
})

It("Check if the lowest funding is returned correctly with multiple coins", func() {
whitelist := []*funderstypes.WhitelistCoinEntry{
s.App().FundersKeeper.SetParams(s.Ctx(), funderstypes.NewParams([]*funderstypes.WhitelistCoinEntry{
{
CoinDenom: globaltypes.Denom,
MinFundingAmount: 10 * i.KYVE,
Expand All @@ -525,7 +524,7 @@ var _ = Describe("logic_funders.go", Ordered, func() {
CoinDenom: i.C_DENOM,
CoinWeight: math.LegacyNewDec(3),
},
}
}, 20))

fundings := []funderstypes.Funding{
{
Expand All @@ -548,13 +547,13 @@ var _ = Describe("logic_funders.go", Ordered, func() {
},
}

getLowestFunding, err := s.App().FundersKeeper.GetLowestFunding(fundings, whitelist)
getLowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), fundings)
Expect(err).NotTo(HaveOccurred())
Expect(getLowestFunding.FunderAddress).To(Equal(i.DUMMY[1]))
})

It("Check if the lowest funding is returned correctly with coins which are not whitelisted", func() {
whitelist := []*funderstypes.WhitelistCoinEntry{
s.App().FundersKeeper.SetParams(s.Ctx(), funderstypes.NewParams([]*funderstypes.WhitelistCoinEntry{
{
CoinDenom: globaltypes.Denom,
MinFundingAmount: 10 * i.KYVE,
Expand All @@ -569,7 +568,7 @@ var _ = Describe("logic_funders.go", Ordered, func() {
CoinDenom: i.B_DENOM,
CoinWeight: math.LegacyNewDec(2),
},
}
}, 20))

fundings := []funderstypes.Funding{
{
Expand All @@ -592,7 +591,7 @@ var _ = Describe("logic_funders.go", Ordered, func() {
},
}

getLowestFunding, err := s.App().FundersKeeper.GetLowestFunding(fundings, whitelist)
getLowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), fundings)
Expect(err).NotTo(HaveOccurred())
Expect(getLowestFunding.FunderAddress).To(Equal(i.DUMMY[2]))
})
Expand Down
14 changes: 6 additions & 8 deletions x/funders/keeper/msg_server_defund_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ var _ = Describe("msg_server_defund_pool.go", Ordered, func() {

fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0)
activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.BOB))

Expand All @@ -228,7 +228,7 @@ var _ = Describe("msg_server_defund_pool.go", Ordered, func() {
// ASSERT
fundingState, _ = s.App().FundersKeeper.GetFundingState(s.Ctx(), 0)
activeFundings = s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err = s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err = s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))

Expand Down Expand Up @@ -304,7 +304,7 @@ var _ = Describe("msg_server_defund_pool.go", Ordered, func() {

It("Try to partially defund after a coin has been removed from the whitelist", func() {
// ARRANGE
whitelist = []*types.WhitelistCoinEntry{
s.App().FundersKeeper.SetParams(s.Ctx(), types.NewParams([]*types.WhitelistCoinEntry{
{
CoinDenom: globaltypes.Denom,
MinFundingAmount: 10 * i.KYVE,
Expand All @@ -323,8 +323,7 @@ var _ = Describe("msg_server_defund_pool.go", Ordered, func() {
MinFundingAmountPerBundle: 1 * i.KYVE,
CoinWeight: math.LegacyNewDec(3),
},
}
s.App().FundersKeeper.SetParams(s.Ctx(), types.NewParams(whitelist, 20))
}, 20))

// ACT
_, err := s.RunTx(&types.MsgDefundPool{
Expand All @@ -340,7 +339,7 @@ var _ = Describe("msg_server_defund_pool.go", Ordered, func() {

It("Try to fully defund after a coin has been removed from the whitelist", func() {
// ARRANGE
whitelist = []*types.WhitelistCoinEntry{
s.App().FundersKeeper.SetParams(s.Ctx(), types.NewParams([]*types.WhitelistCoinEntry{
{
CoinDenom: globaltypes.Denom,
MinFundingAmount: 10 * i.KYVE,
Expand All @@ -359,8 +358,7 @@ var _ = Describe("msg_server_defund_pool.go", Ordered, func() {
MinFundingAmountPerBundle: 1 * i.KYVE,
CoinWeight: math.LegacyNewDec(3),
},
}
s.App().FundersKeeper.SetParams(s.Ctx(), types.NewParams(whitelist, 20))
}, 20))

// ACT
s.RunTxFundersSuccess(&types.MsgDefundPool{
Expand Down
18 changes: 9 additions & 9 deletions x/funders/keeper/msg_server_fund_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {
Expect(fundingState.ActiveFunderAddresses[0]).To(Equal(i.ALICE))

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))
})
Expand Down Expand Up @@ -222,7 +222,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {
Expect(fundingState.ActiveFunderAddresses[0]).To(Equal(i.ALICE))

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))
})
Expand Down Expand Up @@ -262,7 +262,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {
Expect(fundingState.ActiveFunderAddresses[0]).To(Equal(i.ALICE))

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))
})
Expand Down Expand Up @@ -302,7 +302,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {
Expect(fundingState.ActiveFunderAddresses[0]).To(Equal(i.ALICE))

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))
})
Expand Down Expand Up @@ -384,7 +384,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {
Expect(len(fundingState.ActiveFunderAddresses)).To(Equal(2))

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.BOB))
})
Expand Down Expand Up @@ -420,7 +420,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {
Expect(len(fundingState.ActiveFunderAddresses)).To(Equal(2))

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))
})
Expand Down Expand Up @@ -672,7 +672,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
Expect(activeFundings).To(HaveLen(50))
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))
})
Expand Down Expand Up @@ -718,7 +718,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {

activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
Expect(activeFundings).To(HaveLen(50))
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.BOB))

Expand Down Expand Up @@ -755,7 +755,7 @@ var _ = Describe("msg_server_fund_pool.go", Ordered, func() {
fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0)
activeFundings := s.App().FundersKeeper.GetActiveFundings(s.Ctx(), fundingState)
Expect(activeFundings).To(HaveLen(50))
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(activeFundings, whitelist)
lowestFunding, err := s.App().FundersKeeper.GetLowestFunding(s.Ctx(), activeFundings)
Expect(err).To(BeNil())
Expect(lowestFunding.FunderAddress).To(Equal(i.ALICE))

Expand Down
Loading

0 comments on commit b7d8eb0

Please sign in to comment.