Skip to content

Commit 6e0d988

Browse files
committed
Merge branch 'main' into mbreithecker/liquid-staking
2 parents 2db71a6 + c530a95 commit 6e0d988

4 files changed

Lines changed: 101 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414

1515
An '!' indicates a state machine breaking change.
1616

17+
## Unreleased
18+
19+
### Improvements
20+
21+
- ! [#266](https://github.com/KYVENetwork/chain/pull/266) Improve storage cost payout.
22+
1723
## [v2.1.0](https://github.com/KYVENetwork/chain/releases/tag/v2.1.0) - 2025-04-29
1824

1925
### Improvements

x/bundles/keeper/keeper_suite_inflation_splitting_test.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,12 +1512,21 @@ var _ = Describe("inflation splitting", Ordered, func() {
15121512
// for kyve coin (7410 - (7410 * 0.01) - _((100 * 0.5) / (3 * 1))_) * 0.1 + _((100 * 0.5) / (3 * 1))_
15131513
// for acoin (10_000 - (10_000 * 0.01) - _((100 * 0.5) / (3 * 1))_) * 0.1 + _((100 * 0.5) / (3 * 1))_
15141514
// for bcoin coins (20_000 - (20_000 * 0.01) - _((100 * 0.5) / (3 * 2))_) * 0.1 + _((100 * 0.5) / (3 * 2))_
1515-
Expect(s.App().StakersKeeper.GetOutstandingCommissionRewards(s.Ctx(), i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(748), i.ACoin(1004), i.BCoin(1987)).String()))
1515+
1516+
// STORAGE COST UPDATE: with $KYVE being used first for storage cost the kyve amount is higher for the uploader
1517+
// commission rewards and the amount of the other coins are lower because the uploader reward includes the storage
1518+
// cost (if kyve is used first the contribution of the remaining coins will be lower)
1519+
// VALUES BEFORE: 1004acoin,1987bcoin,748tkyve
1520+
Expect(s.App().StakersKeeper.GetOutstandingCommissionRewards(s.Ctx(), i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(778), i.ACoin(990), i.BCoin(1980)).String()))
15161521
// assert uploader self delegation rewards (here we round up since the result of delegation rewards is the remainder minus the truncated commission rewards)
15171522
// for kyve coin (7410 - (7410 * 0.01) - _((100 * 0.5) / (3 * 1))_) * (1 - 0.1)
15181523
// for acoin (10_000 - (10_000 * 0.01) - _((100 * 0.5) / (3 * 1))_) * (1 - 0.1)
15191524
// for bcoin (20_000 - (20_000 * 0.01) - _((100 * 0.5) / (3 * 2))_) * (1 - 0.1)
1520-
Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(6588), i.ACoin(8896), i.BCoin(17813)).String()))
1525+
1526+
// STORAGE COST UPDATE: with $KYVE being used first for storage cost the delegators receive less kyve and more
1527+
// of the remaining coins
1528+
// VALUES BEFORE: 8896acoin,17813bcoin,6588tkyve
1529+
Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(6558), i.ACoin(8910), i.BCoin(17820)).String()))
15211530

15221531
fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0)
15231532

@@ -1622,12 +1631,21 @@ var _ = Describe("inflation splitting", Ordered, func() {
16221631
// for kyve coin (24720 - (24720 * 0.01) - _((100 * 0.5) / (3 * 1))_) * 0.1 + _((100 * 0.5) / (3 * 1))_
16231632
// for acoin (10_000 - (10_000 * 0.01) - _((100 * 0.5) / (3 * 1))_) * 0.1 + _((100 * 0.5) / (3 * 1))_
16241633
// for bcoin coins (20_000 - (20_000 * 0.01) - _((100 * 0.5) / (3 * 2))_) * 0.1 + _((100 * 0.5) / (3 * 2))_
1625-
Expect(s.App().StakersKeeper.GetOutstandingCommissionRewards(s.Ctx(), i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(2461), i.ACoin(1004), i.BCoin(1987)).String()))
1634+
1635+
// STORAGE COST UPDATE: with $KYVE being used first for storage cost the kyve amount is higher for the uploader
1636+
// commission rewards and the amount of the other coins are lower because the uploader reward includes the storage
1637+
// cost (if kyve is used first the contribution of the remaining coins will be lower)
1638+
// VALUES BEFORE: 1004acoin,1987bcoin,2461tkyve
1639+
Expect(s.App().StakersKeeper.GetOutstandingCommissionRewards(s.Ctx(), i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(2492), i.ACoin(990), i.BCoin(1980)).String()))
16261640
// assert uploader self delegation rewards (here we round up since the result of delegation rewards is the remainder minus the truncated commission rewards)
16271641
// for kyve coin (24720 - (24720 * 0.01) - _((100 * 0.5) / (3 * 1))_) * (1 - 0.1)
16281642
// for acoin (10_000 - (10_000 * 0.01) - _((100 * 0.5) / (3 * 1))_) * (1 - 0.1)
16291643
// for bcoin (20_000 - (20_000 * 0.01) - _((100 * 0.5) / (3 * 2))_) * (1 - 0.1)
1630-
Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(22012), i.ACoin(8896), i.BCoin(17813)).String()))
1644+
1645+
// STORAGE COST UPDATE: with $KYVE being used first for storage cost the delegators receive less kyve and more
1646+
// of the remaining coins
1647+
// VALUES BEFORE: 8896acoin,17813bcoin,22012tkyve
1648+
Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(21981), i.ACoin(8910), i.BCoin(17820)).String()))
16311649

16321650
fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0)
16331651

x/bundles/keeper/keeper_suite_valid_bundles_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,10 +1957,19 @@ var _ = Describe("valid bundles", Ordered, func() {
19571957
// (amount_per_bundle - treasury_reward - storage_cost) * uploader_commission + storage_cost
19581958
// storage_cost = 1MB * storage_price / coin_length * coin_price
19591959
// (amount_per_bundle - (amount_per_bundle * 0.01) - _((1048576 * 0.000000006288 * 10**coin_decimals) / (4 * coin_weight))_) * 0.1 + _((1048576 * 0.000000006288) / (4 * coin_weight))_
1960-
Expect(s.App().StakersKeeper.GetOutstandingCommissionRewards(s.Ctx(), poolAccountUploader.Staker).String()).To(Equal(sdk.NewCoins(i.KYVECoin(125_973), i.ACoin(99_143), i.BCoin(116_661_015_771_428_571), i.CCoin(100_765)).String()))
1960+
1961+
// STORAGE COST UPDATE: with $KYVE being used first for storage cost the kyve amount is higher for the uploader
1962+
// commission rewards and the amount of the other coins are lower because the uploader reward includes the storage
1963+
// cost (if kyve is used first the contribution of the remaining coins will be lower)
1964+
// VALUES BEFORE: 99_143acoin,116_661_015_771_428_571bcoin,100_765ccoin,125_973tkyve
1965+
Expect(s.App().StakersKeeper.GetOutstandingCommissionRewards(s.Ctx(), poolAccountUploader.Staker).String()).To(Equal(sdk.NewCoins(i.KYVECoin(206_892), i.ACoin(99_000), i.BCoin(99_000_163_885_714_285), i.CCoin(99_000)).String()))
19611966
// assert uploader self delegation rewards (here we round up since the result of delegation rewards is the remainder minus the truncated commission rewards)
19621967
// (amount_per_bundle - (amount_per_bundle * 0.01) - _((29970208 * 0.000000006288 * 1**coin_decimals) / (4 * coin_weight))_) * (1 - 0.1)
1963-
Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(864_027), i.ACoin(890_857), i.BCoin(873_338_984_228_571_429), i.CCoin(889_235)).String()))
1968+
1969+
// STORAGE COST UPDATE: with $KYVE being used first for storage cost the delegators receive less kyve and more
1970+
// of the remaining coins
1971+
// VALUES BEFORE: 890_857acoin,873_338_984_228_571_429bcoin,889_235ccoin,864_027tkyve
1972+
Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0).String()).To(Equal(sdk.NewCoins(i.KYVECoin(783_108), i.ACoin(891_000), i.BCoin(890_999_836_114_285_715), i.CCoin(891_000)).String()))
19641973

19651974
fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0)
19661975

x/bundles/keeper/logic_bundles.go

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -257,45 +257,80 @@ func (k Keeper) calculatePayouts(ctx sdk.Context, poolId uint64, totalPayout sdk
257257
return
258258
}
259259

260-
// subtract storage cost from remaining total payout. We split the storage cost between all coins and charge
261-
// the amount per coin, the idea is that every coin should contribute the same USD value to the total storage
262-
// reward. This is done by defining the storage cost as USD / byte and the coin weights as USD / coin denom.
260+
// subtract storage cost from remaining total payout. We first try to cover the storage cost with the
261+
// native $KYVE coin, if that is not enough the remaining USD value is split equally between the remaining
262+
// coins.
263263
//
264264
// If there is not enough of a coin available to cover the storage reward per coin we simply charge what is left,
265265
// so there can be the case that the storageRewards are less than what we actually wanted to pay out. This is
266266
// acceptable because this case is very rare, usually the minFundingAmount ensures that there are always enough
267267
// funds left of each coin, and in the case there are not enough the coins are removed and therefore for the
268268
// next bundle we split between the other remaining coins.
269269
whitelist := k.fundersKeeper.GetCoinWhitelistMap(ctx)
270-
// wantedStorageRewards are the amounts based on the current storage cost we want to pay out, this can be more
271-
// than we have available in totalPayout
272-
wantedStorageRewards := sdk.NewCoins()
273-
// storageCostPerCoin is the storage cost in $USD for each coin. This implies that each coin contributes the same
274-
// amount of value to the storage rewards
275-
storageCostPerCoin := k.GetStorageCost(ctx, bundleProposal.StorageProviderId).MulInt64(int64(bundleProposal.DataSize)).QuoInt64(int64(totalPayout.Len()))
276-
for _, coin := range totalPayout {
277-
weight := whitelist[coin.Denom].CoinWeight
278-
if weight.IsZero() {
279-
continue
270+
storageCost := k.GetStorageCost(ctx, bundleProposal.StorageProviderId).MulInt64(int64(bundleProposal.DataSize))
271+
272+
kyveWeight := whitelist[globalTypes.Denom].CoinWeight
273+
kyveCurrencyUnit := math.LegacyNewDec(10).Power(uint64(whitelist[globalTypes.Denom].CoinDecimals))
274+
275+
if found, _ := totalPayout.Find(globalTypes.Denom); found && !kyveWeight.IsZero() {
276+
kyveAmount := sdk.NewCoins(sdk.NewCoin(globalTypes.Denom, storageCost.Mul(kyveCurrencyUnit).Quo(kyveWeight).TruncateInt()))
277+
bundleReward.UploaderStorageCost = totalPayout.Min(kyveAmount)
278+
totalPayout = totalPayout.Sub(bundleReward.UploaderStorageCost...)
279+
if totalPayout.IsZero() {
280+
return
280281
}
281282

282-
// currencyUnit is the amount of base denoms of the currency
283-
currencyUnit := math.LegacyNewDec(10).Power(uint64(whitelist[coin.Denom].CoinDecimals))
284-
// amount is the value of storageCostPerCoin in the base denomination of the currency. We calculate this
285-
// by multiplying first with the amount of base denoms of the currency and then divide this by the $USD
286-
// value per currency unit which is the weight.
287-
amount := storageCostPerCoin.Mul(currencyUnit).Quo(weight).TruncateInt()
288-
wantedStorageRewards = wantedStorageRewards.Add(sdk.NewCoin(coin.Denom, amount))
283+
// calculate how much of the storage reward was initially paid with native
284+
// kyve and give the remainder to the other coins
285+
storageCostPaid := bundleReward.UploaderStorageCost.AmountOf(globalTypes.Denom)
286+
storageCostPaidUsd := math.LegacyNewDec(storageCostPaid.Int64()).Mul(kyveWeight).Quo(kyveCurrencyUnit)
287+
288+
if storageCost.GTE(storageCostPaidUsd) {
289+
storageCost = storageCost.Sub(storageCostPaidUsd)
290+
} else {
291+
storageCost = math.LegacyZeroDec()
292+
}
289293
}
290294

291-
// we take the min here since there can be the case where we want to charge more coins for the storage
292-
// reward than we have left in the total payout
293-
bundleReward.UploaderStorageCost = totalPayout.Min(wantedStorageRewards)
295+
kyveCoinFound, kyveCoin := totalPayout.Find(globalTypes.Denom)
296+
remainingCoins := totalPayout
297+
if kyveCoinFound {
298+
remainingCoins = totalPayout.Sub(kyveCoin)
299+
}
300+
301+
if !storageCost.IsZero() && int64(remainingCoins.Len()) > 0 {
302+
// wantedStorageRewards are the amounts based on the current storage cost we want to pay out, this can be more
303+
// than we have available in totalPayout
304+
wantedStorageRewards := sdk.NewCoins()
305+
// storageCostPerCoin is the storage cost in $USD for each coin. This implies that each coin contributes the same
306+
// amount of value to the storage rewards
307+
storageCostPerCoin := storageCost.QuoInt64(int64(remainingCoins.Len()))
308+
for _, coin := range remainingCoins {
309+
weight := whitelist[coin.Denom].CoinWeight
310+
// skip the native kyve denom since we already subtracted it above
311+
if coin.Denom == globalTypes.Denom || weight.IsZero() {
312+
continue
313+
}
294314

295-
// the remaining total payout is split between the uploader and his delegators.
296-
totalPayout = totalPayout.Sub(bundleReward.UploaderStorageCost...)
297-
if totalPayout.IsZero() {
298-
return
315+
// currencyUnit is the amount of base denoms of the currency
316+
currencyUnit := math.LegacyNewDec(10).Power(uint64(whitelist[coin.Denom].CoinDecimals))
317+
// amount is the value of storageCostPerCoin in the base denomination of the currency. We calculate this
318+
// by multiplying first with the amount of base denoms of the currency and then divide this by the $USD
319+
// value per currency unit which is the weight.
320+
amount := storageCostPerCoin.Mul(currencyUnit).Quo(weight).TruncateInt()
321+
wantedStorageRewards = wantedStorageRewards.Add(sdk.NewCoin(coin.Denom, amount))
322+
}
323+
324+
// we take the min here since there can be the case where we want to charge more coins for the storage
325+
// reward than we have left in the total payout
326+
multiCoinStorageCostReward := totalPayout.Min(wantedStorageRewards)
327+
bundleReward.UploaderStorageCost = bundleReward.UploaderStorageCost.Add(multiCoinStorageCostReward...)
328+
329+
// the remaining total payout is split between the uploader and his delegators.
330+
totalPayout = totalPayout.Sub(multiCoinStorageCostReward...)
331+
if totalPayout.IsZero() {
332+
return
333+
}
299334
}
300335

301336
commission := k.stakerKeeper.GetValidatorPoolCommission(ctx, bundleProposal.Uploader, poolId)

0 commit comments

Comments
 (0)