diff --git a/proto/lavanet/lava/subscription/adjustment.proto b/proto/lavanet/lava/subscription/adjustment.proto new file mode 100644 index 0000000000..d3645220b8 --- /dev/null +++ b/proto/lavanet/lava/subscription/adjustment.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package lavanet.lava.subscription; + +option go_package = "github.com/lavanet/lava/x/subscription/types"; + +message Adjustment { + string index = 1; + uint64 adjustedUsage = 2; + uint64 totalUsage = 3; +} + diff --git a/proto/lavanet/lava/subscription/genesis.proto b/proto/lavanet/lava/subscription/genesis.proto index dd1a469825..0b04484171 100644 --- a/proto/lavanet/lava/subscription/genesis.proto +++ b/proto/lavanet/lava/subscription/genesis.proto @@ -3,6 +3,7 @@ package lavanet.lava.subscription; import "gogoproto/gogo.proto"; import "lavanet/lava/subscription/params.proto"; +import "lavanet/lava/subscription/adjustment.proto"; import "lavanet/lava/fixationstore/fixation.proto"; import "lavanet/lava/timerstore/timer.proto"; // this line is used by starport scaffolding # genesis/proto/import @@ -16,5 +17,6 @@ message GenesisState { lavanet.lava.timerstore.GenesisState subsTS = 3 [(gogoproto.nullable) = false]; lavanet.lava.fixationstore.GenesisState cuTrackerFS = 4 [(gogoproto.nullable) = false]; lavanet.lava.timerstore.GenesisState cuTrackerTS = 5 [(gogoproto.nullable) = false]; + repeated Adjustment adjustments = 6 [(gogoproto.nullable) = false]; // this line is used by starport scaffolding # genesis/proto/state } diff --git a/protocol/chainlib/common_test_utils.go b/protocol/chainlib/common_test_utils.go index c73f79043e..1b76c1ec32 100644 --- a/protocol/chainlib/common_test_utils.go +++ b/protocol/chainlib/common_test_utils.go @@ -211,7 +211,7 @@ func SetupForTests(t *testing.T, numOfProviders int, specID string, getToTopMost var stake int64 = 50000000000 // subscribe consumer - testcommon.BuySubscription(t, ts.Ctx, *ts.Keepers, *ts.Servers, ts.Consumer, ts.Plan.Index) + testcommon.BuySubscription(ts.Ctx, *ts.Keepers, *ts.Servers, ts.Consumer, ts.Plan.Index) // stake providers for _, provider := range ts.Providers { diff --git a/testutil/common/common.go b/testutil/common/common.go index b3b7c6a381..ccd7619643 100644 --- a/testutil/common/common.go +++ b/testutil/common/common.go @@ -33,7 +33,7 @@ func StakeAccount(t *testing.T, ctx context.Context, keepers testkeeper.Keepers, require.Nil(t, err) } -func BuySubscription(t *testing.T, ctx context.Context, keepers testkeeper.Keepers, servers testkeeper.Servers, acc sigs.Account, plan string) { +func BuySubscription(ctx context.Context, keepers testkeeper.Keepers, servers testkeeper.Servers, acc sigs.Account, plan string) { servers.SubscriptionServer.Buy(ctx, &subscriptiontypes.MsgBuy{Creator: acc.Addr.String(), Consumer: acc.Addr.String(), Index: plan, Duration: 1}) } diff --git a/testutil/common/tester.go b/testutil/common/tester.go index 54e95ae285..e21e1372e0 100644 --- a/testutil/common/tester.go +++ b/testutil/common/tester.go @@ -45,9 +45,10 @@ type Tester struct { } const ( - PROVIDER string = "provider" - CONSUMER string = "consumer" - VALIDATOR string = "validator" + PROVIDER string = "provider_" + CONSUMER string = "consumer_" + VALIDATOR string = "validator_" + DEVELOPER string = "developer_" ) func NewTester(t *testing.T) *Tester { @@ -160,13 +161,16 @@ func (ts *Tester) StakeProviderExtra( // if necessary, generate mock endpoints if endpoints == nil { - apiInterface := spec.ApiCollections[0].CollectionData.ApiInterface + apiInterfaces := []string{} + for _, apiCollection := range spec.ApiCollections { + apiInterfaces = append(apiInterfaces, apiCollection.CollectionData.ApiInterface) + } geolocations := planstypes.GetGeolocationsFromUint(geoloc) for _, geo := range geolocations { endpoint := epochstoragetypes.Endpoint{ IPPORT: "123", - ApiInterfaces: []string{apiInterface}, + ApiInterfaces: apiInterfaces, Geolocation: int32(geo), } endpoints = append(endpoints, endpoint) @@ -174,7 +178,7 @@ func (ts *Tester) StakeProviderExtra( } stake := sdk.NewCoin(ts.TokenDenom(), sdk.NewInt(amount)) - _, err := ts.TxPairingStakeProvider(addr, spec.Name, stake, endpoints, geoloc, moniker) + _, err := ts.TxPairingStakeProvider(addr, spec.Index, stake, endpoints, geoloc, moniker) return err } @@ -960,12 +964,76 @@ func (ts *Tester) AdvanceMonthsFrom(from time.Time, months int) *Tester { return ts } +func (ts *Tester) BondDenom() string { + return ts.Keepers.StakingKeeper.BondDenom(sdk.UnwrapSDKContext(ts.Ctx)) +} + // AdvanceMonth advanced blocks by given months, like AdvanceMonthsFrom, // starting from the current block's timestamp func (ts *Tester) AdvanceMonths(months int) *Tester { return ts.AdvanceMonthsFrom(ts.BlockTime(), months) } +func (ts *Tester) SetupForTests(getToTopMostPath string, specId string, validators int, subscriptions int, projectsInSubscription int, providers int) error { + var balance int64 = 100000000000 + + start := len(ts.Accounts(VALIDATOR)) + for i := 0; i < validators; i++ { + acc, _ := ts.AddAccount(VALIDATOR, start+i, balance) + ts.TxCreateValidator(acc, math.NewInt(balance)) + } + + sdkContext := sdk.UnwrapSDKContext(ts.Ctx) + spec, err := testkeeper.GetASpec(specId, getToTopMostPath, &sdkContext, &ts.Keepers.Spec) + if err != nil { + return err + } + ts.AddSpec(spec.Index, spec) + ts.Keepers.Spec.SetSpec(sdk.UnwrapSDKContext(ts.Ctx), spec) + start = len(ts.Accounts(CONSUMER)) + for i := 0; i < subscriptions; i++ { + // setup consumer + consumerAcc, consumerAddress := ts.AddAccount(CONSUMER, start+i, balance) + ts.AddPlan("free", CreateMockPlan()) + ts.AddPolicy("mock", CreateMockPolicy()) + plan := ts.plans["free"] + // subscribe consumer + BuySubscription(ts.Ctx, *ts.Keepers, *ts.Servers, consumerAcc, plan.Index) + // create projects: + _, pd2both := ts.AddAccount(DEVELOPER, start+i, 10000) + keys_1_admin_dev := []projectstypes.ProjectKey{ + projectstypes.NewProjectKey(pd2both). + AddType(projectstypes.ProjectKey_ADMIN). + AddType(projectstypes.ProjectKey_DEVELOPER), + } + policy := ts.Policy("mock") + pd := projectstypes.ProjectData{ + Name: "proj", + Enabled: true, + ProjectKeys: keys_1_admin_dev, + Policy: &policy, + } + ts.AddProjectData("projdata", pd) + err = ts.Keepers.Projects.CreateProject(ts.Ctx, consumerAddress, pd, plan) + if err != nil { + return err + } + } + // setup providers + start = len(ts.Accounts(PROVIDER)) + for i := 0; i < providers; i++ { + _, addr := ts.AddAccount(PROVIDER, start+i, balance) + err := ts.StakeProviderExtra(addr, spec, spec.MinStakeProvider.Amount.Int64(), nil, 1, "prov"+strconv.Itoa(start+i)) + if err != nil { + return err + } + } + + // advance for the staking to be valid + ts.AdvanceEpoch() + return nil +} + var sessionID uint64 func (ts *Tester) SendRelay(provider string, clientAcc sigs.Account, chainIDs []string, cuSum uint64) pairingtypes.MsgRelayPayment { diff --git a/x/dualstaking/keeper/delegate.go b/x/dualstaking/keeper/delegate.go index 377886c31d..b568578e33 100644 --- a/x/dualstaking/keeper/delegate.go +++ b/x/dualstaking/keeper/delegate.go @@ -603,7 +603,7 @@ func (k Keeper) VerifyDelegatorBalance(ctx sdk.Context, delAddr sdk.AccAddress) for _, d := range delegations { v, found := k.stakingKeeper.GetValidator(ctx, d.GetValidatorAddr()) if found { - sumValidatorDelegations = sumValidatorDelegations.Add(v.TokensFromSharesRoundUp(d.Shares).TruncateInt()) + sumValidatorDelegations = sumValidatorDelegations.Add(v.TokensFromSharesRoundUp(d.Shares).Ceil().TruncateInt()) } } diff --git a/x/dualstaking/keeper/hooks.go b/x/dualstaking/keeper/hooks.go index 72b77c6a9c..ec1b2de0c4 100644 --- a/x/dualstaking/keeper/hooks.go +++ b/x/dualstaking/keeper/hooks.go @@ -154,7 +154,7 @@ func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, if err != nil { return nil } - amount := validator.TokensFromSharesRoundUp(delegation.Shares).TruncateInt() + amount := validator.TokensFromSharesRoundUp(delegation.Shares).Ceil().TruncateInt() err = h.k.UnbondUniformProviders(ctx, delAddr.String(), sdk.NewCoin(h.k.stakingKeeper.BondDenom(ctx), amount)) if err != nil { return utils.LavaFormatError("delegation removed hook failed", err, diff --git a/x/dualstaking/keeper/hooks_test.go b/x/dualstaking/keeper/hooks_test.go index 80b6165b43..2de18f8cd6 100644 --- a/x/dualstaking/keeper/hooks_test.go +++ b/x/dualstaking/keeper/hooks_test.go @@ -1,9 +1,7 @@ package keeper_test import ( - "fmt" "math/rand" - "strconv" "testing" "time" @@ -415,7 +413,7 @@ func TestValidatorAndProvidersSlash(t *testing.T) { // verify once again that the delegator's delegations balance is preserved diff, err = ts.Keepers.Dualstaking.VerifyDelegatorBalance(ts.Ctx, delegatorAcc.Addr) require.Nil(t, err) - require.True(t, diff.IsZero()) + require.Equal(t, sdk.OneInt(), diff) } // TestCancelUnbond checks that the providers-validators delegations balance is preserved when @@ -489,11 +487,6 @@ func TestHooksRandomDelegations(t *testing.T) { delegator = prevDelegator } _, err := ts.TxDualstakingDelegate(delegator, provider, ts.spec.Index, sdk.NewCoin(ts.TokenDenom(), sdk.NewInt(int64(d)))) - if err == nil { - fmt.Printf("%v: delegated %v\n", strconv.Itoa(i), strconv.Itoa(d)) - } else { - fmt.Printf("%v: failed delegating %v. err: %s\n", strconv.Itoa(i), strconv.Itoa(d), err.Error()) - } require.Nil(t, err) _, found := ts.Keepers.StakingKeeper.GetDelegation(ts.Ctx, delegatorAcc.Addr, sdk.ValAddress(validatorAcc.Addr)) diff --git a/x/pairing/keeper/epoch_payments.go b/x/pairing/keeper/epoch_payments.go index f172a1e938..050fbba64c 100644 --- a/x/pairing/keeper/epoch_payments.go +++ b/x/pairing/keeper/epoch_payments.go @@ -67,7 +67,7 @@ func (k Keeper) GetAllEpochPayments(ctx sdk.Context) (list []types.EpochPayments // Function to remove epochPayments objects from deleted epochs (older than the chain's memory) func (k Keeper) RemoveOldEpochPayment(ctx sdk.Context) { for _, epoch := range k.epochStorageKeeper.GetDeletedEpochs(ctx) { - k.RemoveAllEpochPaymentsForBlock(ctx, epoch) + k.RemoveAllEpochPaymentsForBlockAppendAdjustments(ctx, epoch) } } @@ -116,7 +116,7 @@ func (k Keeper) AddEpochPayment(ctx sdk.Context, chainID string, epoch uint64, p } // Function to remove all epochPayments objects from a specific epoch -func (k Keeper) RemoveAllEpochPaymentsForBlock(ctx sdk.Context, blockForDelete uint64) { +func (k Keeper) RemoveAllEpochPaymentsForBlockAppendAdjustments(ctx sdk.Context, blockForDelete uint64) { // get the epochPayments object of blockForDelete epochPayments, found, key := k.GetEpochPaymentsFromBlock(ctx, blockForDelete) if !found { @@ -124,18 +124,25 @@ func (k Keeper) RemoveAllEpochPaymentsForBlock(ctx sdk.Context, blockForDelete u } // TODO: update Qos in providerQosFS. new consumers (cluster.subUsage = 0) get default QoS (what is default?) - + consumerUsage := map[string]uint64{} + type couplingConsumerProvider struct { + consumer string + provider string + } + // we are keeping the iteration keys to keep determinism when going over the map + iterationOrder := []couplingConsumerProvider{} + couplingUsage := map[couplingConsumerProvider]uint64{} // go over the epochPayments object's providerPaymentStorageKeys userPaymentsStorageKeys := epochPayments.GetProviderPaymentStorageKeys() for _, userPaymentStorageKey := range userPaymentsStorageKeys { // get the providerPaymentStorage object - userPaymentStorage, found := k.GetProviderPaymentStorage(ctx, userPaymentStorageKey) + providerPaymentStorage, found := k.GetProviderPaymentStorage(ctx, userPaymentStorageKey) if !found { continue } // go over the providerPaymentStorage object's uniquePaymentStorageClientProviderKeys - uniquePaymentStoragesCliProKeys := userPaymentStorage.GetUniquePaymentStorageClientProviderKeys() + uniquePaymentStoragesCliProKeys := providerPaymentStorage.GetUniquePaymentStorageClientProviderKeys() for _, uniquePaymentStorageKey := range uniquePaymentStoragesCliProKeys { // get the uniquePaymentStorageClientProvider object uniquePaymentStorage, found := k.GetUniquePaymentStorageClientProvider(ctx, uniquePaymentStorageKey) @@ -157,12 +164,28 @@ func (k Keeper) RemoveAllEpochPaymentsForBlock(ctx sdk.Context, blockForDelete u // delete the uniquePaymentStorageClientProvider object k.RemoveUniquePaymentStorageClientProvider(ctx, uniquePaymentStorage.Index) + consumer := k.GetConsumerFromUniquePayment(&uniquePaymentStorage) + + provider, err := k.GetProviderFromProviderPaymentStorage(&providerPaymentStorage) + if err != nil { + utils.LavaFormatError("failed getting provider from payment storage", err) + continue + } + coupling := couplingConsumerProvider{consumer: consumer, provider: provider} + if _, ok := couplingUsage[coupling]; !ok { + // only add it if it doesn't exist + iterationOrder = append(iterationOrder, coupling) + } + consumerUsage[consumer] += uniquePaymentStorage.UsedCU + couplingUsage[coupling] += uniquePaymentStorage.UsedCU } // after we're done deleting the uniquePaymentStorageClientProvider objects, delete the providerPaymentStorage object - k.RemoveProviderPaymentStorage(ctx, userPaymentStorage.Index) + k.RemoveProviderPaymentStorage(ctx, providerPaymentStorage.Index) + } + for _, coupling := range iterationOrder { + k.subscriptionKeeper.AppendAdjustment(ctx, coupling.consumer, coupling.provider, consumerUsage[coupling.consumer], couplingUsage[coupling]) } - // after we're done deleting the providerPaymentStorage objects, delete the epochPayments object k.RemoveEpochPayments(ctx, key) } diff --git a/x/pairing/keeper/msg_server_relay_payment_test.go b/x/pairing/keeper/msg_server_relay_payment_test.go index 041b6cd2de..9754e6a35a 100644 --- a/x/pairing/keeper/msg_server_relay_payment_test.go +++ b/x/pairing/keeper/msg_server_relay_payment_test.go @@ -596,7 +596,7 @@ func TestBadgeValidation(t *testing.T) { ts.AdvanceBlock() // remove past payments to avoid double spending (first error payment succeeded) - ts.Keepers.Pairing.RemoveAllEpochPaymentsForBlock(ts.Ctx, tt.epoch) + ts.Keepers.Pairing.RemoveAllEpochPaymentsForBlockAppendAdjustments(ts.Ctx, tt.epoch) } badge := types.CreateBadge(badgeCuAllocation, tt.epoch, tt.badgeAddress, tt.lavaChainID, []byte{}) diff --git a/x/pairing/keeper/provider_payment_storage.go b/x/pairing/keeper/provider_payment_storage.go index 486322e08e..1774347efb 100644 --- a/x/pairing/keeper/provider_payment_storage.go +++ b/x/pairing/keeper/provider_payment_storage.go @@ -3,6 +3,7 @@ package keeper import ( "fmt" "strconv" + "strings" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -64,6 +65,16 @@ func (k Keeper) GetAllProviderPaymentStorage(ctx sdk.Context) (list []types.Prov return } +func (k Keeper) GetProviderFromProviderPaymentStorage(providerPaymentStorage *types.ProviderPaymentStorage) (string, error) { + index := providerPaymentStorage.Index + // index consists of chain_epoch_providerAddress + lastIndex := strings.LastIndex(index, "_") + if lastIndex != -1 { + return index[lastIndex+1:], nil + } + return "", fmt.Errorf("invalid provider payment storage key %s", index) +} + // Function to get a providerPaymentStorage object's key (key is chainID_epoch_providerAddress, epoch in hex representation) func (k Keeper) GetProviderPaymentStorageKey(ctx sdk.Context, chainID string, epoch uint64, providerAddress sdk.AccAddress) string { return chainID + "_" + strconv.FormatUint(epoch, 16) + "_" + providerAddress.String() diff --git a/x/pairing/types/expected_keepers.go b/x/pairing/types/expected_keepers.go index 9e23dcf5f9..6a68c454b1 100644 --- a/x/pairing/types/expected_keepers.go +++ b/x/pairing/types/expected_keepers.go @@ -88,6 +88,7 @@ type SubscriptionKeeper interface { CalcTotalMonthlyReward(ctx sdk.Context, totalAmount math.Int, trackedCu uint64, totalCuUsedBySub uint64) math.Int AddTrackedCu(ctx sdk.Context, sub string, provider string, chainID string, cu uint64, block uint64) error GetAllSubscriptionsIndices(ctx sdk.Context) []string + AppendAdjustment(ctx sdk.Context, consumer string, provider string, totalConsumerUsage uint64, usageWithThisProvider uint64) } type PlanKeeper interface { diff --git a/x/projects/keeper/project_test.go b/x/projects/keeper/project_test.go index 43d1cacd90..2d491b6141 100644 --- a/x/projects/keeper/project_test.go +++ b/x/projects/keeper/project_test.go @@ -1139,9 +1139,9 @@ func TestSetPolicyByGeolocation(t *testing.T) { basicUser := common.CreateNewAccount(_ctx, *keepers, 10000) premiumUser := common.CreateNewAccount(_ctx, *keepers, 10000) - common.BuySubscription(t, _ctx, *keepers, *servers, freeUser, freePlan.Index) - common.BuySubscription(t, _ctx, *keepers, *servers, basicUser, basicPlan.Index) - common.BuySubscription(t, _ctx, *keepers, *servers, premiumUser, premiumPlan.Index) + common.BuySubscription(_ctx, *keepers, *servers, freeUser, freePlan.Index) + common.BuySubscription(_ctx, *keepers, *servers, basicUser, basicPlan.Index) + common.BuySubscription(_ctx, *keepers, *servers, premiumUser, premiumPlan.Index) templates := []struct { name string diff --git a/x/rewards/keeper/helpers_test.go b/x/rewards/keeper/helpers_test.go index 94187b8965..a0b5f3b793 100644 --- a/x/rewards/keeper/helpers_test.go +++ b/x/rewards/keeper/helpers_test.go @@ -43,6 +43,7 @@ func newTester(t *testing.T, addValidator bool) *tester { ts.plan.Price.Amount = monthlyProvidersPool.QuoRaw(5).AddRaw(5) ts.plan.PlanPolicy.EpochCuLimit = monthlyProvidersPool.Uint64() * 5 ts.plan.PlanPolicy.TotalCuLimit = monthlyProvidersPool.Uint64() * 5 + ts.plan.PlanPolicy.MaxProvidersToPair = 5 ts.AddPlan(ts.plan.Index, ts.plan) ts.spec = ts.AddSpec("mock", common.CreateMockSpec()).Spec("mock") diff --git a/x/rewards/keeper/providers.go b/x/rewards/keeper/providers.go index ff8f8df4fc..7440dd40c1 100644 --- a/x/rewards/keeper/providers.go +++ b/x/rewards/keeper/providers.go @@ -10,10 +10,10 @@ import ( "github.com/lavanet/lava/x/rewards/types" ) -func (k Keeper) AggregateRewards(ctx sdk.Context, provider, chainid string, adjustmentDenom uint64, rewards math.Int) { +func (k Keeper) AggregateRewards(ctx sdk.Context, provider, chainid string, adjustment sdk.Dec, rewards math.Int) { index := types.BasePayIndex{Provider: provider, ChainID: chainid} basepay, found := k.getBasePay(ctx, index) - adjustedPay := sdk.NewDecFromInt(rewards).QuoInt64(int64(adjustmentDenom)) + adjustedPay := adjustment.MulInt(rewards) adjustedPay = sdk.MinDec(adjustedPay, sdk.NewDecFromInt(rewards)) if !found { basepay = types.BasePay{Total: rewards, TotalAdjusted: adjustedPay} diff --git a/x/rewards/keeper/providers_test.go b/x/rewards/keeper/providers_test.go index 41573428ee..990d6b1868 100644 --- a/x/rewards/keeper/providers_test.go +++ b/x/rewards/keeper/providers_test.go @@ -3,9 +3,11 @@ package keeper_test import ( "testing" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/lavanet/lava/testutil/common" + "github.com/lavanet/lava/utils/sigs" "github.com/lavanet/lava/x/rewards/types" subscription "github.com/lavanet/lava/x/subscription/keeper" "github.com/stretchr/testify/require" @@ -83,7 +85,7 @@ func TestBasicBoostProvidersRewards(t *testing.T) { res, err = ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), providerAcc.Addr.String(), "") require.Nil(t, err) require.Len(t, res.Rewards, 1) - require.Equal(t, res.Rewards[0].Amount.Amount.Uint64(), baserewards*subscription.LIMIT_TOKEN_PER_CU*ts.Keepers.Rewards.GetParams(ts.Ctx).MaxRewardBoost) + require.Equal(t, res.Rewards[0].Amount.Amount, sdk.NewIntFromUint64(baserewards*subscription.LIMIT_TOKEN_PER_CU)) _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), providerAcc.Addr.String()) require.Nil(t, err) } @@ -126,7 +128,7 @@ func TestSpecAllocationProvidersRewards(t *testing.T) { res, err = ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), providerAcc.Addr.String(), "") require.Nil(t, err) require.Len(t, res.Rewards, 1) - require.Equal(t, res.Rewards[0].Amount.Amount, distBalance) + require.Equal(t, distBalance.QuoRaw(int64(ts.Keepers.Rewards.MaxRewardBoost(ts.Ctx))), res.Rewards[0].Amount.Amount) _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), providerAcc.Addr.String()) require.Nil(t, err) } @@ -174,7 +176,7 @@ func TestProvidersDiminishingRewards(t *testing.T) { require.Nil(t, err) require.Len(t, res.Rewards, 1) - require.Equal(t, res.Rewards[0].Amount.Amount, sdk.NewDecWithPrec(15, 1).MulInt(distBalance).Sub(sdk.NewDecWithPrec(5, 1).MulInt(ts.plan.Price.Amount.MulRaw(7))).TruncateInt()) + require.Equal(t, sdk.NewDecWithPrec(15, 1).MulInt(distBalance).Sub(sdk.NewDecWithPrec(5, 1).MulInt(ts.plan.Price.Amount.MulRaw(7))).TruncateInt().QuoRaw(int64(ts.Keepers.Rewards.MaxRewardBoost(ts.Ctx))), res.Rewards[0].Amount.Amount) _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), providerAcc.Addr.String()) require.Nil(t, err) } @@ -279,7 +281,7 @@ func Test2SpecsZeroShares(t *testing.T) { res, err = ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), providerAcc.Addr.String(), "") require.Nil(t, err) require.Len(t, res.Rewards, 1) - require.Equal(t, distBalance, res.Rewards[0].Amount.Amount) + require.Equal(t, distBalance.QuoRaw(int64(ts.Keepers.Rewards.MaxRewardBoost(ts.Ctx))), res.Rewards[0].Amount.Amount) require.Equal(t, res.Rewards[0].ChainId, ts.spec.Index) _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), providerAcc.Addr.String()) require.Nil(t, err) @@ -341,7 +343,7 @@ func Test2SpecsDoubleShares(t *testing.T) { res, err = ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), providerAcc.Addr.String(), "") require.Nil(t, err) require.Len(t, res.Rewards, 2) - require.Equal(t, res.Rewards[0].Amount.Amount, res.Rewards[1].Amount.Amount.MulRaw(2)) + require.Equal(t, res.Rewards[0].Amount.Amount.QuoRaw(2), res.Rewards[1].Amount.Amount) _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), providerAcc.Addr.String()) require.Nil(t, err) } @@ -412,7 +414,7 @@ func TestBonusRewards3Providers(t *testing.T) { require.Nil(t, err) require.Len(t, res.Rewards, 1) // we sub 3 because of truncating - require.Equal(t, res1.Rewards[0].Amount.Amount, distBalance.QuoRaw(7).SubRaw(3)) + require.Equal(t, res1.Rewards[0].Amount.Amount, distBalance.QuoRaw(7*int64(ts.Keepers.Rewards.MaxRewardBoost(ts.Ctx))).SubRaw(1)) _, err = ts.TxDualstakingClaimRewards(providerAcc1.Addr.String(), providerAcc1.Addr.String()) require.Nil(t, err) @@ -420,7 +422,7 @@ func TestBonusRewards3Providers(t *testing.T) { require.Nil(t, err) require.Len(t, res.Rewards, 1) // we sub 1 because of truncating - require.Equal(t, res2.Rewards[0].Amount.Amount, distBalance.QuoRaw(7).MulRaw(2)) + require.Equal(t, res2.Rewards[0].Amount.Amount, distBalance.QuoRaw(7*int64(ts.Keepers.Rewards.MaxRewardBoost(ts.Ctx))).MulRaw(2)) _, err = ts.TxDualstakingClaimRewards(providerAcc2.Addr.String(), providerAcc2.Addr.String()) require.Nil(t, err) @@ -428,7 +430,7 @@ func TestBonusRewards3Providers(t *testing.T) { require.Nil(t, err) require.Len(t, res.Rewards, 1) // we add 6 because of truncating - require.Equal(t, res3.Rewards[0].Amount.Amount, distBalance.QuoRaw(7).MulRaw(4).AddRaw(6)) + require.Equal(t, res3.Rewards[0].Amount.Amount, distBalance.QuoRaw(7*int64(ts.Keepers.Rewards.MaxRewardBoost(ts.Ctx))).MulRaw(4).AddRaw(1)) _, err = ts.TxDualstakingClaimRewards(providerAcc3.Addr.String(), providerAcc3.Addr.String()) require.Nil(t, err) } @@ -539,6 +541,140 @@ func TestBonusReward49months(t *testing.T) { require.Len(t, res.Rewards, 0) } +func TestBonusRewardsEquall5Providers(t *testing.T) { + ts := newTester(t, true) + + count := 5 + providerAccs := []sigs.Account{} + consAccs := []sigs.Account{} + + for i := 0; i < count; i++ { + providerAcc, _ := ts.AddAccount(common.PROVIDER, 1, testBalance) + err := ts.StakeProvider(providerAcc.Addr.String(), ts.spec, testBalance) + providerAccs = append(providerAccs, providerAcc) + require.Nil(t, err) + + consumerAcc, _ := ts.AddAccount(common.CONSUMER, 1, ts.plan.Price.Amount.Int64()) + _, err = ts.TxSubscriptionBuy(consumerAcc.Addr.String(), consumerAcc.Addr.String(), ts.plan.Index, 1, false, false) + consAccs = append(consAccs, consumerAcc) + require.Nil(t, err) + } + + for i := 1; i < 10; i++ { + ts.AdvanceEpoch() + + for _, providerAcc := range providerAccs { + for _, consAcc := range consAccs { + msg := ts.SendRelay(providerAcc.Addr.String(), consAcc, []string{ts.spec.Index}, ts.plan.Price.Amount.Uint64()/uint64(count)/1000) + _, err := ts.TxPairingRelayPayment(msg.Creator, msg.Relays...) + require.Nil(t, err) + } + } + } + + // first months there are no bonus rewards, just payment ffrom the subscription + ts.AdvanceMonths(1) + ts.AdvanceBlocks(ts.BlocksToSave() + 1) + + for _, providerAcc := range providerAccs { + res, err := ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), "", "") + require.Nil(t, err) + require.Len(t, res.Rewards, 1) + _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), "") + require.Nil(t, err) + } + + // now the provider should get all of the provider allocation + ts.AdvanceMonths(1) + distBalance := ts.Keepers.Rewards.TotalPoolTokens(ts.Ctx, types.ProviderRewardsDistributionPool) + ts.AdvanceEpoch() + + for _, providerAcc := range providerAccs { + res, err := ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), "", "") + require.Nil(t, err) + require.Len(t, res.Rewards, 1) + require.Equal(t, distBalance.QuoRaw(int64(count)), res.Rewards[0].Amount.Amount) + _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), "") + require.Nil(t, err) + } +} + +// in this test we have 5 providers and 5 consumers +// all the providers serve the same amount of cu in total +// cons1 relays only to prov1 -> expected adjustment 1/5 (1 out of maxrewardboost) +// cons2-5 relays to all prov2-5 -> expected adjustment 4/5 (1 out of maxrewardboost) +func TestBonusRewards5Providers(t *testing.T) { + ts := newTester(t, true) + + count := 5 + providerAccs := []sigs.Account{} + consAccs := []sigs.Account{} + + for i := 0; i < count; i++ { + providerAcc, _ := ts.AddAccount(common.PROVIDER, 1, testBalance) + err := ts.StakeProvider(providerAcc.Addr.String(), ts.spec, testBalance) + providerAccs = append(providerAccs, providerAcc) + require.Nil(t, err) + + consumerAcc, _ := ts.AddAccount(common.CONSUMER, 1, ts.plan.Price.Amount.Int64()) + _, err = ts.TxSubscriptionBuy(consumerAcc.Addr.String(), consumerAcc.Addr.String(), ts.plan.Index, 1, false, false) + consAccs = append(consAccs, consumerAcc) + require.Nil(t, err) + } + + for i := 1; i < 10; i++ { + ts.AdvanceEpoch() + + msg := ts.SendRelay(providerAccs[0].Addr.String(), consAccs[0], []string{ts.spec.Index}, ts.plan.Price.Amount.Uint64()/100) + _, err := ts.TxPairingRelayPayment(msg.Creator, msg.Relays...) + require.Nil(t, err) + + for _, providerAcc := range providerAccs[1:] { + for _, consAcc := range consAccs[1:] { + msg := ts.SendRelay(providerAcc.Addr.String(), consAcc, []string{ts.spec.Index}, ts.plan.Price.Amount.Uint64()/uint64(count)/100) + _, err := ts.TxPairingRelayPayment(msg.Creator, msg.Relays...) + require.Nil(t, err) + } + } + } + + // first months there are no bonus rewards, just payment ffrom the subscription + ts.AdvanceMonths(1) + ts.AdvanceBlocks(ts.BlocksToSave() + 1) + + for _, providerAcc := range providerAccs { + res, err := ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), "", "") + require.Nil(t, err) + require.Len(t, res.Rewards, 1) + _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), "") + require.Nil(t, err) + } + + // now the provider should get all of the provider allocation + ts.AdvanceMonths(1) + distBalance := ts.Keepers.Rewards.TotalPoolTokens(ts.Ctx, types.ProviderRewardsDistributionPool) + ts.AdvanceEpoch() + + // distribution pool divided between all providers (5) equally (they served the same amount of CU in total) + fullProvReward := distBalance.QuoRaw(5) + for i, providerAcc := range providerAccs { + var expected math.Int + if i == 0 { + // gets only 1/5 of the full reward (sub 1 for trancating) + expected = fullProvReward.MulRaw(1).QuoRaw(5).SubRaw(1) + } else { + // gets only 4/5 of the full reward (sub 2 for trancating) + expected = fullProvReward.MulRaw(4).QuoRaw(5).SubRaw(2) + } + res, err := ts.QueryDualstakingDelegatorRewards(providerAcc.Addr.String(), "", "") + require.Nil(t, err) + require.Len(t, res.Rewards, 1) + require.Equal(t, expected, res.Rewards[0].Amount.Amount) + _, err = ts.TxDualstakingClaimRewards(providerAcc.Addr.String(), "") + require.Nil(t, err) + } +} + // TestCommunityTaxOne checks the edge case in which the community tax is 100% // the expected behaviour is that all the provider's reward will transfer to the community pool func TestCommunityTaxOne(t *testing.T) { diff --git a/x/subscription/genesis.go b/x/subscription/genesis.go index a1a715ad4d..346e08a4ad 100644 --- a/x/subscription/genesis.go +++ b/x/subscription/genesis.go @@ -15,6 +15,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) k.InitSubscriptionsTimers(ctx, genState.SubsTS) k.InitCuTrackers(ctx, genState.CuTrackerFS) k.InitCuTrackerTimers(ctx, genState.CuTrackerTS) + k.SetAllAdjustment(ctx, genState.Adjustments) } // ExportGenesis returns the capability module's exported genesis. @@ -25,6 +26,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { genesis.SubsTS = k.ExportSubscriptionsTimers(ctx) genesis.CuTrackerFS = k.ExportCuTrackers(ctx) genesis.CuTrackerTS = k.ExportCuTrackerTimers(ctx) + genesis.Adjustments = k.GetAllAdjustment(ctx) // this line is used by starport scaffolding # genesis/module/export return genesis diff --git a/x/subscription/keeper/adjustment.go b/x/subscription/keeper/adjustment.go new file mode 100644 index 0000000000..a51d9c264c --- /dev/null +++ b/x/subscription/keeper/adjustment.go @@ -0,0 +1,173 @@ +package keeper + +import ( + "fmt" + "strings" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/lavanet/lava/utils" + "github.com/lavanet/lava/x/subscription/types" +) + +// SetAdjustment set a specific Adjustment in the store from its index +func (k Keeper) SetAdjustment(ctx sdk.Context, adjustment types.Adjustment) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AdjustmentKeyPrefix)) + b := k.cdc.MustMarshal(&adjustment) + store.Set([]byte(adjustment.Index), b) +} + +// GetAdjustment returns a Adjustment from its index +func (k Keeper) GetAdjustment( + ctx sdk.Context, + index string, +) (val types.Adjustment, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AdjustmentKeyPrefix)) + b := store.Get([]byte(index)) + if b == nil { + return val, false + } + + k.cdc.MustUnmarshal(b, &val) + return val, true +} + +// RemoveAdjustment removes a Adjustment from the store +func (k Keeper) RemoveAdjustment( + ctx sdk.Context, + index string, +) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AdjustmentKeyPrefix)) + store.Delete([]byte(index)) +} + +// GetAllAdjustment returns all Adjustment +func (k Keeper) GetAllAdjustment(ctx sdk.Context) (list []types.Adjustment) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AdjustmentKeyPrefix)) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var val types.Adjustment + k.cdc.MustUnmarshal(iterator.Value(), &val) + list = append(list, val) + } + + return +} + +// SetAllAdjustment sets all adjustments to the store +func (k Keeper) SetAllAdjustment(ctx sdk.Context, list []types.Adjustment) { + for _, a := range list { + k.SetAdjustment(ctx, a) + } +} + +func (k Keeper) GetProviderFromAdjustment(adjustment *types.Adjustment) (string, error) { + index := adjustment.Index + // index consists of chain_epoch_providerAddress + lastIndex := strings.LastIndex(index, "_") + if lastIndex != -1 { + return index[lastIndex+1:], nil + } + return "", fmt.Errorf("invalid adjustment key %s", index) +} + +func (k Keeper) AdjustmentIndex(consumer string, provider string) string { + // consumer must come first for the deleteConsumer iteration + return consumer + "_" + provider +} + +func (k Keeper) AppendAdjustment(ctx sdk.Context, consumer string, provider string, totalConsumerUsage uint64, usageWithThisProvider uint64) { + index := k.AdjustmentIndex(consumer, provider) + adjustment, found := k.GetAdjustment(ctx, index) + if !found { + adjustment = types.Adjustment{Index: index, AdjustedUsage: 0, TotalUsage: 0} + } + // adjustment = weighted average(adjustment/epoch) + // this epoch adjustment = usageWithThisProvider / totalConsumerUsage + // adjustment = sum(epoch_adjustment * cu_used_this_epoch) / total_used_cu + // so we need to save: + // 1. sum(epoch_adjustment * total_cu_used_this_epoch) + // 2. total_used_cu = sum(totalConsumerUsage) + + maxRewardsBoost := k.rewardsKeeper.MaxRewardBoost(ctx) + + // check for adjustment limits: adjustment = min(1,1/rewardsMaxBoost * epoch_sum_cu/cu_with_provider) + if totalConsumerUsage >= maxRewardsBoost*usageWithThisProvider { + // epoch adjustment is 1 + adjustment.TotalUsage += totalConsumerUsage + adjustment.AdjustedUsage += totalConsumerUsage + } else { + // totalConsumerUsage < uint64(maxRewardsBoost)*usageWithThisProvider + adjustment.TotalUsage += totalConsumerUsage + // epoch adjustment is (1/maxRewardsBoost * totalConsumerUsage/usageWithThisProvider) * totalConsumerUsage + adjustment.AdjustedUsage += (totalConsumerUsage / maxRewardsBoost) * (totalConsumerUsage / usageWithThisProvider) + } + + k.SetAdjustment(ctx, adjustment) +} + +func (k Keeper) GetConsumerAdjustments(ctx sdk.Context, consumer string) (list []types.Adjustment) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AdjustmentKeyPrefix)) + // set consumer prefix + iterator := sdk.KVStorePrefixIterator(store, []byte(consumer)) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var val types.Adjustment + k.cdc.MustUnmarshal(iterator.Value(), &val) + list = append(list, val) + } + return +} + +// assumes consumer comes first in the key, when querying by subscription it will catch all +func (k Keeper) RemoveConsumerAdjustments(ctx sdk.Context, consumer string) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AdjustmentKeyPrefix)) + // set consumer prefix + iterator := sdk.KVStorePrefixIterator(store, []byte(consumer)) + defer iterator.Close() + + keysToDelete := []string{} + for ; iterator.Valid(); iterator.Next() { + keysToDelete = append(keysToDelete, string(iterator.Key())) + } + for _, key := range keysToDelete { + k.RemoveAdjustment(ctx, key) + } +} + +func (k Keeper) GetAdjustmentFactorProvider(ctx sdk.Context, adjustments []types.Adjustment) map[string]sdk.Dec { + type usage struct { + total int64 + adjusted int64 + } + providers := []string{} + providerUsage := map[string]usage{} + for _, adjustment := range adjustments { + provider, err := k.GetProviderFromAdjustment(&adjustment) + if err != nil { + utils.LavaFormatError("could not get provider from adjustment", err) + continue + } + usage := providerUsage[provider] + usage.adjusted += int64(adjustment.AdjustedUsage) + usage.total += int64(adjustment.TotalUsage) + providerUsage[provider] = usage + providers = append(providers, provider) + } + + providerAdjustment := map[string]sdk.Dec{} + // we use providers list to iterate deterministically + for _, provider := range providers { + if _, ok := providerAdjustment[provider]; !ok { + totalUsage := providerUsage[provider].total + totalAdjustedUsage := providerUsage[provider].adjusted + // indexes may repeat but we only need to handle each provider once + providerAdjustment[provider] = sdk.NewDec(totalAdjustedUsage).QuoInt64(totalUsage) + } + } + return providerAdjustment +} diff --git a/x/subscription/keeper/adjustment_test.go b/x/subscription/keeper/adjustment_test.go new file mode 100644 index 0000000000..2f41889c34 --- /dev/null +++ b/x/subscription/keeper/adjustment_test.go @@ -0,0 +1,237 @@ +package keeper_test + +import ( + "testing" + + "cosmossdk.io/math" + "github.com/lavanet/lava/testutil/common" + "github.com/stretchr/testify/require" +) + +func TestAdjustment(t *testing.T) { + ts := common.NewTester(t) + err := ts.SetupForTests("../../../", "LAV1", 1, 2, 1, 2) + require.NoError(t, err) + _, consumer1Addr := ts.GetAccount(common.CONSUMER, 0) + projects := ts.Keepers.Projects.GetAllProjectsForSubscription(ts.Ctx, consumer1Addr) + require.Len(t, projects, 2) + _, consumer2Addr := ts.GetAccount(common.CONSUMER, 1) + projects = append(projects, ts.Keepers.Projects.GetAllProjectsForSubscription(ts.Ctx, consumer2Addr)...) + require.Len(t, projects, 4) + _, provider1 := ts.GetAccount(common.PROVIDER, 0) + _, provider2 := ts.GetAccount(common.PROVIDER, 1) + type indexConsumer struct { + index int + proj string + } + usage := map[indexConsumer]struct { + provider1Usage uint64 + provider2Usage uint64 + }{ + { + index: 0, + proj: projects[0], + }: { + provider1Usage: 90, + provider2Usage: 10, + }, + { + index: 1, + proj: projects[0], + }: { + provider1Usage: 180, + provider2Usage: 20, + }, + { + index: 0, + proj: projects[1], + }: { + provider1Usage: 90, + provider2Usage: 10, + }, + { + index: 1, + proj: projects[1], + }: { + provider1Usage: 180, + provider2Usage: 20, + }, + { + index: 0, + proj: projects[2], + }: { + provider1Usage: 50, + provider2Usage: 50, + }, + { + index: 1, + proj: projects[2], + }: { + provider1Usage: 100, + provider2Usage: 100, + }, + { + index: 0, + proj: projects[3], + }: { + provider1Usage: 50, + provider2Usage: 50, + }, + { + index: 1, + proj: projects[3], + }: { + provider1Usage: 100, + provider2Usage: 1, + }, + } + + for idxConsumer, cus := range usage { + totalUsageThisIndex := cus.provider1Usage + cus.provider2Usage + ts.Keepers.Subscription.AppendAdjustment(ts.Ctx, idxConsumer.proj, provider1, totalUsageThisIndex, cus.provider1Usage) + ts.Keepers.Subscription.AppendAdjustment(ts.Ctx, idxConsumer.proj, provider2, totalUsageThisIndex, cus.provider2Usage) + } + + allAdjustments := ts.Keepers.Subscription.GetAllAdjustment(ts.Ctx) + require.Len(t, allAdjustments, 8) + for _, adjustment := range allAdjustments { + provider, err := ts.Keepers.Subscription.GetProviderFromAdjustment(&adjustment) + require.NoError(t, err) + require.True(t, provider == provider1 || provider == provider2) + } + + consumerAdjustments := ts.Keepers.Subscription.GetConsumerAdjustments(ts.Ctx, consumer1Addr) + require.Len(t, consumerAdjustments, 4) + seenProviders := map[string]struct{}{} + for _, adjustment := range allAdjustments { + provider, err := ts.Keepers.Subscription.GetProviderFromAdjustment(&adjustment) + require.NoError(t, err) + require.True(t, provider == provider1 || provider == provider2) + seenProviders[provider] = struct{}{} + } + require.Len(t, seenProviders, 2) + + providersFactors := ts.Keepers.Subscription.GetAdjustmentFactorProvider(ts.Ctx, consumerAdjustments) + require.Len(t, providersFactors, 2) + ts.Keepers.Subscription.RemoveConsumerAdjustments(ts.Ctx, consumer1Addr) + allAdjustments = ts.Keepers.Subscription.GetAllAdjustment(ts.Ctx) + require.Len(t, allAdjustments, 4) + + consumerAdjustments = ts.Keepers.Subscription.GetConsumerAdjustments(ts.Ctx, consumer2Addr) + require.Len(t, consumerAdjustments, 4) + providersFactors2 := ts.Keepers.Subscription.GetAdjustmentFactorProvider(ts.Ctx, consumerAdjustments) + require.Len(t, providersFactors2, 2) + ts.Keepers.Subscription.RemoveConsumerAdjustments(ts.Ctx, consumer2Addr) + allAdjustments = ts.Keepers.Subscription.GetAllAdjustment(ts.Ctx) + require.Len(t, allAdjustments, 0) + + // check adjustment values: + // consuemr 1 + require.True(t, providersFactors[provider1].Equal(math.LegacyMustNewDecFromStr("0.2")), providersFactors[provider1].String()) + require.True(t, providersFactors[provider2].Equal(math.LegacyMustNewDecFromStr("1")), providersFactors[provider2].String()) + // consumer2 + // 120/300 = 0.4, ((100/5) + 0.4 * 100)/201 = 0.29850746, 0.4*300 + 0.29850746*201 = (60+120) / 501 = 0.35928144 + require.True(t, providersFactors2[provider1].Equal(math.LegacyMustNewDecFromStr("180").QuoInt64(501)), providersFactors2[provider1].String()) + // 120/300, 141/201 = 261/501 + require.True(t, providersFactors2[provider2].Equal(math.LegacyMustNewDecFromStr("261").QuoInt64(501)), providersFactors2[provider2].String()) +} + +func TestAdjustmentEdgeValues(t *testing.T) { + playbook := []struct { + name string + firstProviderCU uint64 + expectedAdjustmentDec string + }{ + { + name: "all-equal", + firstProviderCU: 1000, + expectedAdjustmentDec: "1", + }, + { + name: "very little", + firstProviderCU: 1, + expectedAdjustmentDec: "1", + }, + { + name: "top", + firstProviderCU: 1000000000000000000, + expectedAdjustmentDec: "0.2", + }, + } + for _, play := range playbook { + t.Run(play.name, func(t *testing.T) { + ts := common.NewTester(t) + err := ts.SetupForTests("../../../", "LAV1", 1, 1, 1, 5) + require.NoError(t, err) + _, consumer1Addr := ts.GetAccount(common.CONSUMER, 0) + projects := ts.Keepers.Projects.GetAllProjectsForSubscription(ts.Ctx, consumer1Addr) + require.Len(t, projects, 2) + providers := ts.Accounts(common.PROVIDER) + + usage := []struct { + providerUsage []uint64 + }{ + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + {providerUsage: []uint64{ + play.firstProviderCU, 1000, 1000, 1000, 1000, + }}, + } + + totalUsage := func(cus []uint64) uint64 { + res := uint64(0) + for _, cu := range cus { + res += cu + } + return res + } + + for _, usageProviders := range usage { + totalUsageThisIndex := totalUsage(usageProviders.providerUsage) + for providerIdx, cu := range usageProviders.providerUsage { + ts.Keepers.Subscription.AppendAdjustment(ts.Ctx, consumer1Addr, providers[providerIdx].Addr.String(), totalUsageThisIndex, cu) + } + } + + consumerAdjustments := ts.Keepers.Subscription.GetConsumerAdjustments(ts.Ctx, consumer1Addr) + require.NotEmpty(t, consumerAdjustments) + seenProviders := map[string]struct{}{} + for _, adjustment := range consumerAdjustments { + provider, err := ts.Keepers.Subscription.GetProviderFromAdjustment(&adjustment) + require.NoError(t, err) + seenProviders[provider] = struct{}{} + } + require.Len(t, seenProviders, len(providers)) + + providersFactors := ts.Keepers.Subscription.GetAdjustmentFactorProvider(ts.Ctx, consumerAdjustments) + require.Len(t, providersFactors, len(providers)) + prevFactor := providersFactors[providers[1].Addr.String()] + for idx, factor := range providersFactors { + if idx == providers[0].Addr.String() { + require.True(t, factor.Equal(math.LegacyMustNewDecFromStr(play.expectedAdjustmentDec)), factor.String()) + } else { + require.Equal(t, prevFactor, factor) + } + } + }) + } +} diff --git a/x/subscription/keeper/cu_tracker.go b/x/subscription/keeper/cu_tracker.go index 978e8fe6fa..5cb79012a8 100644 --- a/x/subscription/keeper/cu_tracker.go +++ b/x/subscription/keeper/cu_tracker.go @@ -147,6 +147,11 @@ func (k Keeper) RewardAndResetCuTracker(ctx sdk.Context, cuTrackerTimerKeyBytes totalTokenAmount = sdk.NewIntFromUint64(LIMIT_TOKEN_PER_CU * totalCuTracked) } + // get the adjustment factor, and delete the entries + adjustments := k.GetConsumerAdjustments(ctx, sub) + adjustmentFactorForProvider := k.GetAdjustmentFactorProvider(ctx, adjustments) + k.RemoveConsumerAdjustments(ctx, sub) + totalTokenRewarded := sdk.ZeroInt() for _, trackedCuInfo := range trackedCuList { trackedCu := trackedCuInfo.trackedCu @@ -165,6 +170,13 @@ func (k Keeper) RewardAndResetCuTracker(ctx sdk.Context, cuTrackerTimerKeyBytes return } + // provider monthly reward = (tracked_CU / total_CU_used_in_sub_this_month) * plan_price + // TODO: deal with the reward's remainder (uint division...) + providerAdjustment, ok := adjustmentFactorForProvider[provider] + if !ok { + providerAdjustment = sdk.OneDec().QuoInt64(int64(k.rewardsKeeper.MaxRewardBoost(ctx))) + } + totalMonthlyReward := k.CalcTotalMonthlyReward(ctx, totalTokenAmount, trackedCu, totalCuTracked) totalTokenRewarded = totalTokenRewarded.Add(totalMonthlyReward) @@ -179,7 +191,7 @@ func (k Keeper) RewardAndResetCuTracker(ctx sdk.Context, cuTrackerTimerKeyBytes } // aggregate the reward for the provider - k.rewardsKeeper.AggregateRewards(ctx, provider, chainID, 1, totalMonthlyReward) + k.rewardsKeeper.AggregateRewards(ctx, provider, chainID, providerAdjustment, totalMonthlyReward) // Transfer some of the total monthly reward to validators contribution and community pool totalMonthlyReward, err = k.rewardsKeeper.ContributeToValidatorsAndCommunityPool(ctx, totalMonthlyReward, types.ModuleName) @@ -209,13 +221,14 @@ func (k Keeper) RewardAndResetCuTracker(ctx sdk.Context, cuTrackerTimerKeyBytes return } else { utils.LogLavaEvent(ctx, k.Logger(ctx), types.MonthlyCuTrackerProviderRewardEventName, map[string]string{ - "provider": provider, - "sub": sub, - "plan": plan.Index, - "tracked_cu": strconv.FormatUint(trackedCu, 10), - "plan_price": plan.Price.String(), - "reward": providerReward.String(), - "block": strconv.FormatInt(ctx.BlockHeight(), 10), + "provider": provider, + "sub": sub, + "plan": plan.Index, + "tracked_cu": strconv.FormatUint(trackedCu, 10), + "plan_price": plan.Price.String(), + "reward": providerReward.String(), + "block": strconv.FormatInt(ctx.BlockHeight(), 10), + "adjustment_raw": providerAdjustment.String(), }, "Provider got monthly reward successfully") } } diff --git a/x/subscription/keeper/subscription.go b/x/subscription/keeper/subscription.go index c3097789b0..53a6280022 100644 --- a/x/subscription/keeper/subscription.go +++ b/x/subscription/keeper/subscription.go @@ -154,7 +154,7 @@ func (k Keeper) CreateSubscription( return utils.LavaFormatWarning("create subscription failed", err) } - if !found || autoRenewalFlag { + if !found || sub.AutoRenewal { expiry := uint64(utils.NextMonth(ctx.BlockTime()).UTC().Unix()) sub.MonthExpiryTime = expiry sub.Block = block diff --git a/x/subscription/types/adjustment.pb.go b/x/subscription/types/adjustment.pb.go new file mode 100644 index 0000000000..9c006ccb0c --- /dev/null +++ b/x/subscription/types/adjustment.pb.go @@ -0,0 +1,389 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: lavanet/lava/subscription/adjustment.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type Adjustment struct { + Index string `protobuf:"bytes,1,opt,name=index,proto3" json:"index,omitempty"` + AdjustedUsage uint64 `protobuf:"varint,2,opt,name=adjustedUsage,proto3" json:"adjustedUsage,omitempty"` + TotalUsage uint64 `protobuf:"varint,3,opt,name=totalUsage,proto3" json:"totalUsage,omitempty"` +} + +func (m *Adjustment) Reset() { *m = Adjustment{} } +func (m *Adjustment) String() string { return proto.CompactTextString(m) } +func (*Adjustment) ProtoMessage() {} +func (*Adjustment) Descriptor() ([]byte, []int) { + return fileDescriptor_6061843cba96837b, []int{0} +} +func (m *Adjustment) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Adjustment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Adjustment.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Adjustment) XXX_Merge(src proto.Message) { + xxx_messageInfo_Adjustment.Merge(m, src) +} +func (m *Adjustment) XXX_Size() int { + return m.Size() +} +func (m *Adjustment) XXX_DiscardUnknown() { + xxx_messageInfo_Adjustment.DiscardUnknown(m) +} + +var xxx_messageInfo_Adjustment proto.InternalMessageInfo + +func (m *Adjustment) GetIndex() string { + if m != nil { + return m.Index + } + return "" +} + +func (m *Adjustment) GetAdjustedUsage() uint64 { + if m != nil { + return m.AdjustedUsage + } + return 0 +} + +func (m *Adjustment) GetTotalUsage() uint64 { + if m != nil { + return m.TotalUsage + } + return 0 +} + +func init() { + proto.RegisterType((*Adjustment)(nil), "lavanet.lava.subscription.Adjustment") +} + +func init() { + proto.RegisterFile("lavanet/lava/subscription/adjustment.proto", fileDescriptor_6061843cba96837b) +} + +var fileDescriptor_6061843cba96837b = []byte{ + // 195 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xca, 0x49, 0x2c, 0x4b, + 0xcc, 0x4b, 0x2d, 0xd1, 0x07, 0xd1, 0xfa, 0xc5, 0xa5, 0x49, 0xc5, 0xc9, 0x45, 0x99, 0x05, 0x25, + 0x99, 0xf9, 0x79, 0xfa, 0x89, 0x29, 0x59, 0xa5, 0xc5, 0x25, 0xb9, 0xa9, 0x79, 0x25, 0x7a, 0x05, + 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x92, 0x50, 0xb5, 0x7a, 0x20, 0x5a, 0x0f, 0x59, 0xad, 0x52, 0x06, + 0x17, 0x97, 0x23, 0x5c, 0xb9, 0x90, 0x08, 0x17, 0x6b, 0x66, 0x5e, 0x4a, 0x6a, 0x85, 0x04, 0xa3, + 0x02, 0xa3, 0x06, 0x67, 0x10, 0x84, 0x23, 0xa4, 0xc2, 0xc5, 0x0b, 0x31, 0x32, 0x35, 0x25, 0xb4, + 0x38, 0x31, 0x3d, 0x55, 0x82, 0x49, 0x81, 0x51, 0x83, 0x25, 0x08, 0x55, 0x50, 0x48, 0x8e, 0x8b, + 0xab, 0x24, 0xbf, 0x24, 0x31, 0x07, 0xa2, 0x84, 0x19, 0xac, 0x04, 0x49, 0xc4, 0xc9, 0xed, 0xc4, + 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, + 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x74, 0xd2, 0x33, 0x4b, 0x32, 0x4a, 0x93, + 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x51, 0x7c, 0x55, 0x81, 0xea, 0xaf, 0x92, 0xca, 0x82, 0xd4, 0xe2, + 0x24, 0x36, 0xb0, 0x9f, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x87, 0x8e, 0x73, 0xb8, 0x01, + 0x01, 0x00, 0x00, +} + +func (m *Adjustment) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Adjustment) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Adjustment) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TotalUsage != 0 { + i = encodeVarintAdjustment(dAtA, i, uint64(m.TotalUsage)) + i-- + dAtA[i] = 0x18 + } + if m.AdjustedUsage != 0 { + i = encodeVarintAdjustment(dAtA, i, uint64(m.AdjustedUsage)) + i-- + dAtA[i] = 0x10 + } + if len(m.Index) > 0 { + i -= len(m.Index) + copy(dAtA[i:], m.Index) + i = encodeVarintAdjustment(dAtA, i, uint64(len(m.Index))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintAdjustment(dAtA []byte, offset int, v uint64) int { + offset -= sovAdjustment(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Adjustment) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Index) + if l > 0 { + n += 1 + l + sovAdjustment(uint64(l)) + } + if m.AdjustedUsage != 0 { + n += 1 + sovAdjustment(uint64(m.AdjustedUsage)) + } + if m.TotalUsage != 0 { + n += 1 + sovAdjustment(uint64(m.TotalUsage)) + } + return n +} + +func sovAdjustment(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozAdjustment(x uint64) (n int) { + return sovAdjustment(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Adjustment) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAdjustment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Adjustment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Adjustment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAdjustment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAdjustment + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAdjustment + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Index = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AdjustedUsage", wireType) + } + m.AdjustedUsage = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAdjustment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AdjustedUsage |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalUsage", wireType) + } + m.TotalUsage = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAdjustment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalUsage |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipAdjustment(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAdjustment + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipAdjustment(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAdjustment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAdjustment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAdjustment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthAdjustment + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupAdjustment + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthAdjustment + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthAdjustment = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAdjustment = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupAdjustment = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/subscription/types/expected_keepers.go b/x/subscription/types/expected_keepers.go index 6b72a64dd8..28c26e3b00 100644 --- a/x/subscription/types/expected_keepers.go +++ b/x/subscription/types/expected_keepers.go @@ -64,7 +64,8 @@ type DualStakingKeeper interface { } type RewardsKeeper interface { - AggregateRewards(ctx sdk.Context, provider, chainid string, adjustmentDenom uint64, rewards math.Int) + AggregateRewards(ctx sdk.Context, provider, chainid string, adjustment sdk.Dec, rewards math.Int) + MaxRewardBoost(ctx sdk.Context) (res uint64) ContributeToValidatorsAndCommunityPool(ctx sdk.Context, reward math.Int, senderModule string) (updatedReward math.Int, err error) FundCommunityPoolFromModule(ctx sdk.Context, amount math.Int, senderModule string) error } diff --git a/x/subscription/types/genesis.pb.go b/x/subscription/types/genesis.pb.go index 9f54aa11f5..4defbe3e97 100644 --- a/x/subscription/types/genesis.pb.go +++ b/x/subscription/types/genesis.pb.go @@ -32,6 +32,7 @@ type GenesisState struct { SubsTS types1.GenesisState `protobuf:"bytes,3,opt,name=subsTS,proto3" json:"subsTS"` CuTrackerFS types.GenesisState `protobuf:"bytes,4,opt,name=cuTrackerFS,proto3" json:"cuTrackerFS"` CuTrackerTS types1.GenesisState `protobuf:"bytes,5,opt,name=cuTrackerTS,proto3" json:"cuTrackerTS"` + Adjustments []Adjustment `protobuf:"bytes,6,rep,name=adjustments,proto3" json:"adjustments"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -102,6 +103,13 @@ func (m *GenesisState) GetCuTrackerTS() types1.GenesisState { return types1.GenesisState{} } +func (m *GenesisState) GetAdjustments() []Adjustment { + if m != nil { + return m.Adjustments + } + return nil +} + func init() { proto.RegisterType((*GenesisState)(nil), "lavanet.lava.subscription.GenesisState") } @@ -111,27 +119,29 @@ func init() { } var fileDescriptor_dc6c60f9c112fe52 = []byte{ - // 310 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0xbf, 0x4a, 0xc4, 0x30, - 0x1c, 0xc7, 0xdb, 0xf3, 0xbc, 0xa1, 0xe7, 0x14, 0x1c, 0x6a, 0x87, 0xf8, 0x0f, 0xf5, 0x04, 0x49, - 0x41, 0x1f, 0x40, 0x38, 0xa1, 0x4e, 0xc2, 0x61, 0x3a, 0xb9, 0xa5, 0x25, 0xd6, 0xa0, 0x6d, 0x4a, - 0x92, 0xca, 0xf9, 0x16, 0x3e, 0xd6, 0x8d, 0x37, 0x3a, 0x89, 0xb4, 0xcf, 0x21, 0x48, 0x93, 0xaa, - 0xcd, 0xd0, 0x41, 0xa7, 0xa4, 0xf0, 0xf9, 0x7e, 0xf2, 0xfd, 0xf1, 0xab, 0x77, 0xf2, 0x44, 0x9e, - 0x49, 0x41, 0x55, 0xd8, 0x9e, 0xa1, 0xac, 0x12, 0x99, 0x0a, 0x56, 0x2a, 0xc6, 0x8b, 0x30, 0xa3, - 0x05, 0x95, 0x4c, 0xa2, 0x52, 0x70, 0xc5, 0xc1, 0x4e, 0x07, 0xa2, 0xf6, 0x44, 0x7d, 0x30, 0xd8, - 0xce, 0x78, 0xc6, 0x35, 0x15, 0xb6, 0x37, 0x13, 0x08, 0x8e, 0x87, 0xcd, 0x25, 0x11, 0x24, 0xef, - 0xc4, 0xc1, 0xa9, 0xc5, 0xdd, 0xb3, 0x25, 0x69, 0x19, 0xa9, 0xb8, 0xa0, 0x3f, 0x5f, 0x1d, 0x7a, - 0x68, 0xa1, 0x8a, 0xe5, 0x54, 0x18, 0x4e, 0x5f, 0x0d, 0x74, 0xf0, 0x39, 0xf2, 0xb6, 0xae, 0x4d, - 0x75, 0xac, 0x88, 0xa2, 0xe0, 0xd2, 0x9b, 0x98, 0x07, 0x7d, 0x77, 0xcf, 0x9d, 0x4d, 0xcf, 0xf7, - 0xd1, 0xe0, 0x28, 0x68, 0xa1, 0xc1, 0xf9, 0x78, 0xf5, 0xbe, 0xeb, 0xdc, 0x76, 0x31, 0x10, 0x79, - 0x93, 0x16, 0x8a, 0xb0, 0x3f, 0xd2, 0x82, 0x99, 0x2d, 0xb0, 0x2a, 0xa3, 0xfe, 0xd3, 0xdf, 0x1e, - 0x93, 0x06, 0x57, 0xc6, 0x13, 0x63, 0x7f, 0x43, 0x7b, 0x8e, 0x6c, 0xcf, 0xef, 0x3c, 0x83, 0x92, - 0x18, 0x83, 0x85, 0x37, 0x4d, 0xab, 0x58, 0x90, 0xf4, 0x91, 0x8a, 0x08, 0xfb, 0xe3, 0x7f, 0x35, - 0xea, 0x2b, 0xc0, 0x4d, 0xcf, 0x18, 0x63, 0x7f, 0xf3, 0xef, 0xdd, 0xfa, 0xf9, 0x79, 0xb4, 0xaa, - 0xa1, 0xbb, 0xae, 0xa1, 0xfb, 0x51, 0x43, 0xf7, 0xb5, 0x81, 0xce, 0xba, 0x81, 0xce, 0x5b, 0x03, - 0x9d, 0xbb, 0xb3, 0x8c, 0xa9, 0x87, 0x2a, 0x41, 0x29, 0xcf, 0x43, 0x6b, 0x93, 0x4b, 0xfb, 0xf7, - 0x50, 0x2f, 0x25, 0x95, 0xc9, 0x44, 0xaf, 0xf3, 0xe2, 0x2b, 0x00, 0x00, 0xff, 0xff, 0xe5, 0x3b, - 0x9c, 0xd5, 0xa2, 0x02, 0x00, 0x00, + // 344 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0x31, 0x4e, 0xf3, 0x30, + 0x18, 0x86, 0x93, 0xbf, 0xfd, 0x33, 0xa4, 0x4c, 0x11, 0x43, 0xe8, 0x10, 0x0a, 0x08, 0x28, 0x08, + 0x39, 0x12, 0x1c, 0x00, 0x51, 0xa4, 0x30, 0x21, 0x55, 0x24, 0x13, 0x9b, 0x1b, 0x4c, 0x30, 0x90, + 0x38, 0xb2, 0x1d, 0x54, 0x6e, 0xc1, 0xb1, 0x3a, 0x30, 0x74, 0x64, 0x42, 0xa8, 0xb9, 0x08, 0xb2, + 0x9d, 0xb6, 0xb6, 0x50, 0x06, 0x98, 0xec, 0x48, 0xcf, 0xfb, 0xe4, 0xfd, 0x92, 0xcf, 0x3d, 0x7c, + 0x86, 0x2f, 0xb0, 0x40, 0x3c, 0x14, 0x67, 0xc8, 0xaa, 0x09, 0x4b, 0x29, 0x2e, 0x39, 0x26, 0x45, + 0x98, 0xa1, 0x02, 0x31, 0xcc, 0x40, 0x49, 0x09, 0x27, 0xde, 0x56, 0x03, 0x02, 0x71, 0x02, 0x1d, + 0xec, 0x6f, 0x66, 0x24, 0x23, 0x92, 0x0a, 0xc5, 0x4d, 0x05, 0xfa, 0x07, 0xed, 0xe6, 0x12, 0x52, + 0x98, 0x37, 0xe2, 0xfe, 0x71, 0x3b, 0x07, 0xef, 0x1e, 0x2b, 0xc6, 0x73, 0x54, 0xf0, 0x86, 0x3d, + 0x32, 0xd8, 0x7b, 0x3c, 0x85, 0x82, 0x63, 0x9c, 0x50, 0xb4, 0x7a, 0x6a, 0xd0, 0x3d, 0x03, 0xe5, + 0x38, 0x47, 0x54, 0x71, 0xf2, 0xaa, 0xa0, 0xdd, 0xf7, 0x8e, 0xbb, 0x71, 0xa5, 0xc6, 0x8c, 0x39, + 0xe4, 0xc8, 0x3b, 0x77, 0x1d, 0x55, 0xce, 0xb7, 0x07, 0xf6, 0xb0, 0x77, 0xba, 0x03, 0x5a, 0xc7, + 0x06, 0x63, 0x09, 0x8e, 0xba, 0xb3, 0xcf, 0x6d, 0xeb, 0xa6, 0x89, 0x79, 0x91, 0xeb, 0x08, 0x28, + 0x8a, 0xfd, 0x7f, 0x52, 0x30, 0x34, 0x05, 0x46, 0x65, 0xa0, 0xbf, 0x7a, 0xe9, 0x51, 0x69, 0xef, + 0x52, 0x79, 0x92, 0xd8, 0xef, 0x48, 0xcf, 0xbe, 0xe9, 0x59, 0xcf, 0xd3, 0x2a, 0x49, 0x62, 0x6f, + 0xec, 0xf6, 0xd2, 0x2a, 0xa1, 0x30, 0x7d, 0x42, 0x34, 0x8a, 0xfd, 0xee, 0x9f, 0x1a, 0xe9, 0x0a, + 0xef, 0x5a, 0x33, 0x26, 0xb1, 0xff, 0xff, 0xf7, 0xdd, 0xf4, 0xbc, 0xd0, 0xad, 0xff, 0x31, 0xf3, + 0x9d, 0x41, 0xe7, 0xa7, 0xce, 0xf8, 0xe6, 0x17, 0x2b, 0x7a, 0xa9, 0xd3, 0xf2, 0xa3, 0x68, 0xb6, + 0x08, 0xec, 0xf9, 0x22, 0xb0, 0xbf, 0x16, 0x81, 0xfd, 0x56, 0x07, 0xd6, 0xbc, 0x0e, 0xac, 0x8f, + 0x3a, 0xb0, 0x6e, 0x4f, 0x32, 0xcc, 0x1f, 0xaa, 0x09, 0x48, 0x49, 0x1e, 0x1a, 0x8b, 0x31, 0x35, + 0x37, 0x8e, 0xbf, 0x96, 0x88, 0x4d, 0x1c, 0xb9, 0x1d, 0x67, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, + 0xeb, 0x1a, 0x89, 0x93, 0x1d, 0x03, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -154,6 +164,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Adjustments) > 0 { + for iNdEx := len(m.Adjustments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Adjustments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } { size, err := m.CuTrackerTS.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -234,6 +258,12 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) l = m.CuTrackerTS.Size() n += 1 + l + sovGenesis(uint64(l)) + if len(m.Adjustments) > 0 { + for _, e := range m.Adjustments { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -437,6 +467,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Adjustments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Adjustments = append(m.Adjustments, Adjustment{}) + if err := m.Adjustments[len(m.Adjustments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/subscription/types/key_adjustment.go b/x/subscription/types/key_adjustment.go new file mode 100644 index 0000000000..c55e8dc4dd --- /dev/null +++ b/x/subscription/types/key_adjustment.go @@ -0,0 +1,10 @@ +package types + +import "encoding/binary" + +var _ binary.ByteOrder + +const ( + // AdjustmentKeyPrefix is the prefix to retrieve all Adjustment + AdjustmentKeyPrefix = "Adjustment/value/" +) diff --git a/x/subscription/types/keys.go b/x/subscription/types/keys.go index f32c231963..b6566263b9 100644 --- a/x/subscription/types/keys.go +++ b/x/subscription/types/keys.go @@ -43,3 +43,7 @@ func DecodeCuTrackerKey(key string) (sub string, provider string, chainID string decodedKey := strings.Split(key, " ") return decodedKey[0], decodedKey[1], decodedKey[2] } + +func KeyPrefix(p string) []byte { + return []byte(p) +}