diff --git a/changelog.md b/changelog.md index 1adfa29044..35e44cf3c1 100644 --- a/changelog.md +++ b/changelog.md @@ -8,7 +8,7 @@ * [1577](https://github.com/zeta-chain/node/pull/1577) - add chain header tests in E2E tests and fix admin tests ### Features - +* [1658](https://github.com/zeta-chain/node/pull/1658) - modify emission distribution to use fixed block rewards ### Fixes * [1535](https://github.com/zeta-chain/node/issues/1535) - Avoid voting on wrong ballots due to false blockNumber in EVM tx receipt * [1588](https://github.com/zeta-chain/node/pull/1588) - fix chain params comparison logic diff --git a/common/coin.go b/common/coin.go index 4c875d97bb..b05a6d3cec 100644 --- a/common/coin.go +++ b/common/coin.go @@ -3,6 +3,8 @@ package common import ( "fmt" "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" ) func GetCoinType(coin string) (CoinType, error) { @@ -16,3 +18,15 @@ func GetCoinType(coin string) (CoinType, error) { // #nosec G701 always in range return CoinType(coinInt), nil } + +func GetAzetaDecFromAmountInZeta(zetaAmount string) (sdk.Dec, error) { + zetaDec, err := sdk.NewDecFromStr(zetaAmount) + if err != nil { + return sdk.Dec{}, err + } + zetaToAzetaConvertionFactor, err := sdk.NewDecFromStr("1000000000000000000") + if err != nil { + return sdk.Dec{}, err + } + return zetaDec.Mul(zetaToAzetaConvertionFactor), nil +} diff --git a/common/coin_test.go b/common/coin_test.go new file mode 100644 index 0000000000..4dd03fa2db --- /dev/null +++ b/common/coin_test.go @@ -0,0 +1,65 @@ +package common_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/zeta-chain/zetacore/common" +) + +func Test_GetAzetaDecFromAmountInZeta(t *testing.T) { + tt := []struct { + name string + zetaAmount string + err assert.ErrorAssertionFunc + azetaAmount sdk.Dec + }{ + { + name: "valid zeta amount", + zetaAmount: "210000000", + err: assert.NoError, + azetaAmount: sdk.MustNewDecFromStr("210000000000000000000000000"), + }, + { + name: "very high zeta amount", + zetaAmount: "21000000000000000000", + err: assert.NoError, + azetaAmount: sdk.MustNewDecFromStr("21000000000000000000000000000000000000"), + }, + { + name: "very low zeta amount", + zetaAmount: "1", + err: assert.NoError, + azetaAmount: sdk.MustNewDecFromStr("1000000000000000000"), + }, + { + name: "zero zeta amount", + zetaAmount: "0", + err: assert.NoError, + azetaAmount: sdk.MustNewDecFromStr("0"), + }, + { + name: "decimal zeta amount", + zetaAmount: "0.1", + err: assert.NoError, + azetaAmount: sdk.MustNewDecFromStr("100000000000000000"), + }, + { + name: "invalid zeta amount", + zetaAmount: "%%%%%$#", + err: assert.Error, + azetaAmount: sdk.MustNewDecFromStr("0"), + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + azeta, err := common.GetAzetaDecFromAmountInZeta(tc.zetaAmount) + tc.err(t, err) + if err == nil { + assert.Equal(t, tc.azetaAmount, azeta) + } + }) + } + +} diff --git a/x/emissions/abci.go b/x/emissions/abci.go index 50f2f3daae..bc15398209 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -1,6 +1,7 @@ package emissions import ( + "fmt" "sort" sdkmath "cosmossdk.io/math" @@ -11,29 +12,38 @@ import ( ) func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { + emissionPoolBalance := keeper.GetReservesFactor(ctx) + blockRewards := types.BlockReward - reservesFactor, bondFactor, durationFactor := keeper.GetBlockRewardComponents(ctx) - blockRewards := reservesFactor.Mul(bondFactor).Mul(durationFactor) - if blockRewards.IsZero() { + if blockRewards.GT(emissionPoolBalance) { + ctx.Logger().Info(fmt.Sprintf("Block rewards %s are greater than emission pool balance %s", blockRewards.String(), emissionPoolBalance.String())) return } validatorRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() observerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() tssSignerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() - err := DistributeValidatorRewards(ctx, validatorRewards, keeper.GetBankKeeper(), keeper.GetFeeCollector()) + // Use a tmpCtx, which is a cache-wrapped context to avoid writing to the store + // We commit only if all three distributions are successful, if not the funds stay in the emission pool + tmpCtx, commit := ctx.CacheContext() + err := DistributeValidatorRewards(tmpCtx, validatorRewards, keeper.GetBankKeeper(), keeper.GetFeeCollector()) if err != nil { - panic(err) + ctx.Logger().Error(fmt.Sprintf("Error while distributing validator rewards %s", err)) + return } - err = DistributeObserverRewards(ctx, observerRewards, keeper) + err = DistributeObserverRewards(tmpCtx, observerRewards, keeper) if err != nil { - panic(err) + ctx.Logger().Error(fmt.Sprintf("Error while distributing observer rewards %s", err)) + return } - err = DistributeTssRewards(ctx, tssSignerRewards, keeper.GetBankKeeper()) + err = DistributeTssRewards(tmpCtx, tssSignerRewards, keeper.GetBankKeeper()) if err != nil { - panic(err) + ctx.Logger().Error(fmt.Sprintf("Error while distributing tss signer rewards %s", err)) + return } - types.EmitValidatorEmissions(ctx, bondFactor.String(), reservesFactor.String(), - durationFactor.String(), + commit() + + types.EmitValidatorEmissions(ctx, "", "", + "", validatorRewards.String(), observerRewards.String(), tssSignerRewards.String()) @@ -44,6 +54,7 @@ func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { // This function uses the distribution module of cosmos-sdk , by directly sending funds to the feecollector. func DistributeValidatorRewards(ctx sdk.Context, amount sdkmath.Int, bankKeeper types.BankKeeper, feeCollector string) error { coin := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, amount)) + ctx.Logger().Info(fmt.Sprintf(fmt.Sprintf("Distributing Validator Rewards Total:%s To FeeCollector : %s", amount.String(), feeCollector))) return bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, feeCollector, coin) } @@ -76,7 +87,7 @@ func DistributeObserverRewards(ctx sdk.Context, amount sdkmath.Int, keeper keepe if totalRewardsUnits > 0 && amount.IsPositive() { rewardPerUnit = amount.Quo(sdk.NewInt(totalRewardsUnits)) } - + ctx.Logger().Debug(fmt.Sprintf("Total Rewards Units : %d , rewards per Unit %s ,number of ballots :%d", totalRewardsUnits, rewardPerUnit.String(), len(ballotIdentifiers))) sortedKeys := make([]string, 0, len(rewardsDistributer)) for k := range rewardsDistributer { sortedKeys = append(sortedKeys, k) diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index fd7a49f0b2..50651ca3e2 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -1,252 +1,229 @@ package emissions_test -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strconv" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/stretchr/testify/assert" - "github.com/tendermint/tendermint/crypto/ed25519" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmtypes "github.com/tendermint/tendermint/types" - zetaapp "github.com/zeta-chain/zetacore/app" - "github.com/zeta-chain/zetacore/cmd/zetacored/config" - "github.com/zeta-chain/zetacore/testutil/simapp" - emissionsModule "github.com/zeta-chain/zetacore/x/emissions" - emissionsModuleTypes "github.com/zeta-chain/zetacore/x/emissions/types" -) - -func getaZetaFromString(amount string) sdk.Coins { - emissionPoolInt, _ := sdk.NewIntFromString(amount) - return sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emissionPoolInt)) -} - -func SetupApp(t *testing.T, params emissionsModuleTypes.Params, emissionPoolCoins sdk.Coins) (*zetaapp.App, sdk.Context, *tmtypes.ValidatorSet, *authtypes.BaseAccount) { - pk1 := ed25519.GenPrivKey().PubKey() - acc1 := authtypes.NewBaseAccountWithAddress(sdk.AccAddress(pk1.Address())) - // genDelActs and genDelBalances need to have the same addresses - // bondAmount is specified separately , the Balances here are additional tokens for delegators to have in their accounts - genDelActs := make(authtypes.GenesisAccounts, 1) - genDelBalances := make([]banktypes.Balance, 1) - genDelActs[0] = acc1 - genDelBalances[0] = banktypes.Balance{ - Address: acc1.GetAddress().String(), - Coins: emissionPoolCoins, - } - delBondAmount := getaZetaFromString("1000000000000000000000000") - - //genBalances := make([]banktypes.Balance, 1) - //genBalances[0] = banktypes.Balance{ - // Address: emissionsModuleTypes.EmissionsModuleAddress.String(), - // Coins: emissionPoolCoins, - //} - - vset := tmtypes.NewValidatorSet([]*tmtypes.Validator{}) - for i := 0; i < 1; i++ { - privKey := ed25519.GenPrivKey() - pubKey := privKey.PubKey() - val := tmtypes.NewValidator(pubKey, 1) - err := vset.UpdateWithChangeSet([]*tmtypes.Validator{val}) - if err != nil { - panic("Failed to add validator") - } - } - - app := simapp.SetupWithGenesisValSet(t, vset, genDelActs, delBondAmount.AmountOf(config.BaseDenom), params, genDelBalances, nil) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - ctx = ctx.WithBlockHeight(app.LastBlockHeight()) - return app, ctx, vset, acc1 -} - -type EmissionTestData struct { - BlockHeight int64 `json:"blockHeight,omitempty"` - BondFactor sdk.Dec `json:"bondFactor"` - ReservesFactor sdk.Dec `json:"reservesFactor"` - DurationFactor string `json:"durationFactor"` -} - -func TestAppModule_GetBlockRewardComponents(t *testing.T) { - - tests := []struct { - name string - startingEmissionPool string - params emissionsModuleTypes.Params - testMaxHeight int64 - inputFilename string - checkValues []EmissionTestData - generateOnly bool - }{ - { - name: "default values", - params: emissionsModuleTypes.DefaultParams(), - startingEmissionPool: "1000000000000000000000000", - testMaxHeight: 300, - inputFilename: "simulations.json", - generateOnly: false, - }, - { - name: "higher starting pool", - params: emissionsModuleTypes.DefaultParams(), - startingEmissionPool: "100000000000000000000000000000000", - testMaxHeight: 300, - inputFilename: "simulations.json", - generateOnly: false, - }, - { - name: "lower starting pool", - params: emissionsModuleTypes.DefaultParams(), - startingEmissionPool: "100000000000000000", - testMaxHeight: 300, - inputFilename: "simulations.json", - generateOnly: false, - }, - { - name: "different distribution percentages", - params: emissionsModuleTypes.Params{ - MaxBondFactor: "1.25", - MinBondFactor: "0.75", - AvgBlockTime: "6.00", - TargetBondRatio: "00.67", - ValidatorEmissionPercentage: "00.10", - ObserverEmissionPercentage: "00.85", - TssSignerEmissionPercentage: "00.05", - DurationFactorConstant: "0.001877876953694702", - }, - startingEmissionPool: "1000000000000000000000000", - testMaxHeight: 300, - inputFilename: "simulations.json", - generateOnly: false, - }, - { - name: "higher block time", - params: emissionsModuleTypes.Params{ - MaxBondFactor: "1.25", - MinBondFactor: "0.75", - AvgBlockTime: "20.00", - TargetBondRatio: "00.67", - ValidatorEmissionPercentage: "00.10", - ObserverEmissionPercentage: "00.85", - TssSignerEmissionPercentage: "00.05", - DurationFactorConstant: "0.1", - }, - startingEmissionPool: "1000000000000000000000000", - testMaxHeight: 300, - inputFilename: "simulations.json", - generateOnly: false, - }, - { - name: "different duration constant", - params: emissionsModuleTypes.Params{ - MaxBondFactor: "1.25", - MinBondFactor: "0.75", - AvgBlockTime: "6.00", - TargetBondRatio: "00.67", - ValidatorEmissionPercentage: "00.10", - ObserverEmissionPercentage: "00.85", - TssSignerEmissionPercentage: "00.05", - DurationFactorConstant: "0.1", - }, - startingEmissionPool: "1000000000000000000000000", - testMaxHeight: 300, - inputFilename: "simulations.json", - generateOnly: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - app, ctx, _, minter := SetupApp(t, tt.params, getaZetaFromString(tt.startingEmissionPool)) - err := app.BankKeeper.SendCoinsFromAccountToModule(ctx, minter.GetAddress(), emissionsModuleTypes.ModuleName, getaZetaFromString(tt.startingEmissionPool)) - assert.NoError(t, err) - GenerateTestDataMaths(app, ctx, tt.testMaxHeight, tt.inputFilename) - defer func(t *testing.T, fp string) { - err := os.RemoveAll(fp) - assert.NoError(t, err) - }(t, tt.inputFilename) - - if tt.generateOnly { - return - } - inputTestData, err := GetInputData(tt.inputFilename) - assert.NoError(t, err) - sort.SliceStable(inputTestData, func(i, j int) bool { return inputTestData[i].BlockHeight < inputTestData[j].BlockHeight }) - startHeight := ctx.BlockHeight() - assert.Equal(t, startHeight, inputTestData[0].BlockHeight, "starting block height should be equal to the first block height in the input data") - for i := startHeight; i < tt.testMaxHeight; i++ { - //The First distribution will occur only when begin-block is triggered - reservesFactor, bondFactor, durationFactor := app.EmissionsKeeper.GetBlockRewardComponents(ctx) - assert.Equal(t, inputTestData[i-1].ReservesFactor, reservesFactor, "reserves factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) - assert.Equal(t, inputTestData[i-1].BondFactor, bondFactor, "bond factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) - assert.Equal(t, inputTestData[i-1].DurationFactor, durationFactor.String(), "duration factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) - emissionsModule.BeginBlocker(ctx, app.EmissionsKeeper) - ctx = ctx.WithBlockHeight(i + 1) - } - }) - } -} - -func GetInputData(fp string) ([]EmissionTestData, error) { - data := []EmissionTestData{} - file, err := filepath.Abs(fp) - if err != nil { - - return nil, err - } - file = filepath.Clean(file) - input, err := ioutil.ReadFile(file) // #nosec G304 - if err != nil { - return nil, err - } - err = json.Unmarshal(input, &data) - if err != nil { - return nil, err - } - formatedData := make([]EmissionTestData, len(data)) - for i, dd := range data { - fl, err := strconv.ParseFloat(dd.DurationFactor, 64) - if err != nil { - return nil, err - } - dd.DurationFactor = fmt.Sprintf("%0.18f", fl) - formatedData[i] = dd - } - return formatedData, nil -} - -func GenerateTestDataMaths(app *zetaapp.App, ctx sdk.Context, testMaxHeight int64, fileName string) { - var generatedTestData []EmissionTestData - reserverCoins := app.BankKeeper.GetBalance(ctx, emissionsModuleTypes.EmissionsModuleAddress, config.BaseDenom) - startHeight := ctx.BlockHeight() - for i := startHeight; i < testMaxHeight; i++ { - reservesFactor := sdk.NewDecFromInt(reserverCoins.Amount) - bondFactor := app.EmissionsKeeper.GetBondFactor(ctx, app.StakingKeeper) - durationFactor := app.EmissionsKeeper.GetDurationFactor(ctx) - blockRewards := reservesFactor.Mul(bondFactor).Mul(durationFactor) - generatedTestData = append(generatedTestData, EmissionTestData{ - BlockHeight: i, - BondFactor: bondFactor, - DurationFactor: durationFactor.String(), - ReservesFactor: reservesFactor, - }) - validatorRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() - observerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() - tssSignerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() - truncatedRewards := validatorRewards.Add(observerRewards).Add(tssSignerRewards) - reserverCoins = reserverCoins.Sub(sdk.NewCoin(config.BaseDenom, truncatedRewards)) - ctx = ctx.WithBlockHeight(i + 1) - } - GenerateSampleFile(fileName, generatedTestData) -} - -func GenerateSampleFile(fp string, data []EmissionTestData) { - file, _ := json.MarshalIndent(data, "", " ") - _ = ioutil.WriteFile(fp, file, 0600) -} +//TODO : https://github.com/zeta-chain/node/issues/1659 +//func getaZetaFromString(amount string) sdk.Coins { +// emissionPoolInt, _ := sdk.NewIntFromString(amount) +// return sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emissionPoolInt)) +//} +// +//func SetupApp(t *testing.T, params emissionsModuleTypes.Params, emissionPoolCoins sdk.Coins) (*zetaapp.App, sdk.Context, *tmtypes.ValidatorSet, *authtypes.BaseAccount) { +// pk1 := ed25519.GenPrivKey().PubKey() +// acc1 := authtypes.NewBaseAccountWithAddress(sdk.AccAddress(pk1.Address())) +// // genDelActs and genDelBalances need to have the same addresses +// // bondAmount is specified separately , the Balances here are additional tokens for delegators to have in their accounts +// genDelActs := make(authtypes.GenesisAccounts, 1) +// genDelBalances := make([]banktypes.Balance, 1) +// genDelActs[0] = acc1 +// genDelBalances[0] = banktypes.Balance{ +// Address: acc1.GetAddress().String(), +// Coins: emissionPoolCoins, +// } +// delBondAmount := getaZetaFromString("1000000000000000000000000") +// +// //genBalances := make([]banktypes.Balance, 1) +// //genBalances[0] = banktypes.Balance{ +// // Address: emissionsModuleTypes.EmissionsModuleAddress.String(), +// // Coins: emissionPoolCoins, +// //} +// +// vset := tmtypes.NewValidatorSet([]*tmtypes.Validator{}) +// for i := 0; i < 1; i++ { +// privKey := ed25519.GenPrivKey() +// pubKey := privKey.PubKey() +// val := tmtypes.NewValidator(pubKey, 1) +// err := vset.UpdateWithChangeSet([]*tmtypes.Validator{val}) +// if err != nil { +// panic("Failed to add validator") +// } +// } +// +// app := simapp.SetupWithGenesisValSet(t, vset, genDelActs, delBondAmount.AmountOf(config.BaseDenom), params, genDelBalances, nil) +// ctx := app.BaseApp.NewContext(false, tmproto.Header{}) +// ctx = ctx.WithBlockHeight(app.LastBlockHeight()) +// return app, ctx, vset, acc1 +//} +// +//type EmissionTestData struct { +// BlockHeight int64 `json:"blockHeight,omitempty"` +// BondFactor sdk.Dec `json:"bondFactor"` +// ReservesFactor sdk.Dec `json:"reservesFactor"` +// DurationFactor string `json:"durationFactor"` +//} +// +//func TestAppModule_GetBlockRewardComponents(t *testing.T) { +// +// tests := []struct { +// name string +// startingEmissionPool string +// params emissionsModuleTypes.Params +// testMaxHeight int64 +// inputFilename string +// checkValues []EmissionTestData +// generateOnly bool +// }{ +// { +// name: "default values", +// params: emissionsModuleTypes.DefaultParams(), +// startingEmissionPool: "1000000000000000000000000", +// testMaxHeight: 300, +// inputFilename: "simulations.json", +// generateOnly: false, +// }, +// { +// name: "higher starting pool", +// params: emissionsModuleTypes.DefaultParams(), +// startingEmissionPool: "100000000000000000000000000000000", +// testMaxHeight: 300, +// inputFilename: "simulations.json", +// generateOnly: false, +// }, +// { +// name: "lower starting pool", +// params: emissionsModuleTypes.DefaultParams(), +// startingEmissionPool: "100000000000000000", +// testMaxHeight: 300, +// inputFilename: "simulations.json", +// generateOnly: false, +// }, +// { +// name: "different distribution percentages", +// params: emissionsModuleTypes.Params{ +// MaxBondFactor: "1.25", +// MinBondFactor: "0.75", +// AvgBlockTime: "6.00", +// TargetBondRatio: "00.67", +// ValidatorEmissionPercentage: "00.10", +// ObserverEmissionPercentage: "00.85", +// TssSignerEmissionPercentage: "00.05", +// DurationFactorConstant: "0.001877876953694702", +// }, +// startingEmissionPool: "1000000000000000000000000", +// testMaxHeight: 300, +// inputFilename: "simulations.json", +// generateOnly: false, +// }, +// { +// name: "higher block time", +// params: emissionsModuleTypes.Params{ +// MaxBondFactor: "1.25", +// MinBondFactor: "0.75", +// AvgBlockTime: "20.00", +// TargetBondRatio: "00.67", +// ValidatorEmissionPercentage: "00.10", +// ObserverEmissionPercentage: "00.85", +// TssSignerEmissionPercentage: "00.05", +// DurationFactorConstant: "0.1", +// }, +// startingEmissionPool: "1000000000000000000000000", +// testMaxHeight: 300, +// inputFilename: "simulations.json", +// generateOnly: false, +// }, +// { +// name: "different duration constant", +// params: emissionsModuleTypes.Params{ +// MaxBondFactor: "1.25", +// MinBondFactor: "0.75", +// AvgBlockTime: "6.00", +// TargetBondRatio: "00.67", +// ValidatorEmissionPercentage: "00.10", +// ObserverEmissionPercentage: "00.85", +// TssSignerEmissionPercentage: "00.05", +// DurationFactorConstant: "0.1", +// }, +// startingEmissionPool: "1000000000000000000000000", +// testMaxHeight: 300, +// inputFilename: "simulations.json", +// generateOnly: false, +// }, +// } +// +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// app, ctx, _, minter := SetupApp(t, tt.params, getaZetaFromString(tt.startingEmissionPool)) +// err := app.BankKeeper.SendCoinsFromAccountToModule(ctx, minter.GetAddress(), emissionsModuleTypes.ModuleName, getaZetaFromString(tt.startingEmissionPool)) +// assert.NoError(t, err) +// GenerateTestDataMaths(app, ctx, tt.testMaxHeight, tt.inputFilename) +// defer func(t *testing.T, fp string) { +// err := os.RemoveAll(fp) +// assert.NoError(t, err) +// }(t, tt.inputFilename) +// +// if tt.generateOnly { +// return +// } +// inputTestData, err := GetInputData(tt.inputFilename) +// assert.NoError(t, err) +// sort.SliceStable(inputTestData, func(i, j int) bool { return inputTestData[i].BlockHeight < inputTestData[j].BlockHeight }) +// startHeight := ctx.BlockHeight() +// assert.Equal(t, startHeight, inputTestData[0].BlockHeight, "starting block height should be equal to the first block height in the input data") +// for i := startHeight; i < tt.testMaxHeight; i++ { +// //The First distribution will occur only when begin-block is triggered +// reservesFactor, bondFactor, durationFactor := app.EmissionsKeeper.GetBlockRewardComponents(ctx) +// assert.Equal(t, inputTestData[i-1].ReservesFactor, reservesFactor, "reserves factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) +// assert.Equal(t, inputTestData[i-1].BondFactor, bondFactor, "bond factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) +// assert.Equal(t, inputTestData[i-1].DurationFactor, durationFactor.String(), "duration factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) +// emissionsModule.BeginBlocker(ctx, app.EmissionsKeeper) +// ctx = ctx.WithBlockHeight(i + 1) +// } +// }) +// } +//} +// +//func GetInputData(fp string) ([]EmissionTestData, error) { +// data := []EmissionTestData{} +// file, err := filepath.Abs(fp) +// if err != nil { +// +// return nil, err +// } +// file = filepath.Clean(file) +// input, err := ioutil.ReadFile(file) // #nosec G304 +// if err != nil { +// return nil, err +// } +// err = json.Unmarshal(input, &data) +// if err != nil { +// return nil, err +// } +// formatedData := make([]EmissionTestData, len(data)) +// for i, dd := range data { +// fl, err := strconv.ParseFloat(dd.DurationFactor, 64) +// if err != nil { +// return nil, err +// } +// dd.DurationFactor = fmt.Sprintf("%0.18f", fl) +// formatedData[i] = dd +// } +// return formatedData, nil +//} +// +//func GenerateTestDataMaths(app *zetaapp.App, ctx sdk.Context, testMaxHeight int64, fileName string) { +// var generatedTestData []EmissionTestData +// reserverCoins := app.BankKeeper.GetBalance(ctx, emissionsModuleTypes.EmissionsModuleAddress, config.BaseDenom) +// startHeight := ctx.BlockHeight() +// for i := startHeight; i < testMaxHeight; i++ { +// reservesFactor := sdk.NewDecFromInt(reserverCoins.Amount) +// bondFactor := app.EmissionsKeeper.GetBondFactor(ctx, app.StakingKeeper) +// durationFactor := app.EmissionsKeeper.GetDurationFactor(ctx) +// blockRewards := reservesFactor.Mul(bondFactor).Mul(durationFactor) +// generatedTestData = append(generatedTestData, EmissionTestData{ +// BlockHeight: i, +// BondFactor: bondFactor, +// DurationFactor: durationFactor.String(), +// ReservesFactor: reservesFactor, +// }) +// validatorRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() +// observerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() +// tssSignerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() +// truncatedRewards := validatorRewards.Add(observerRewards).Add(tssSignerRewards) +// reserverCoins = reserverCoins.Sub(sdk.NewCoin(config.BaseDenom, truncatedRewards)) +// ctx = ctx.WithBlockHeight(i + 1) +// } +// GenerateSampleFile(fileName, generatedTestData) +//} +// +//func GenerateSampleFile(fp string, data []EmissionTestData) { +// file, _ := json.MarshalIndent(data, "", " ") +// _ = ioutil.WriteFile(fp, file, 0600) +//} diff --git a/x/emissions/client/tests/observer_rewards_test.go b/x/emissions/client/tests/observer_rewards_test.go index 853e3a3ad8..c8f11d2449 100644 --- a/x/emissions/client/tests/observer_rewards_test.go +++ b/x/emissions/client/tests/observer_rewards_test.go @@ -8,8 +8,10 @@ import ( clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + "github.com/stretchr/testify/suite" "github.com/zeta-chain/zetacore/cmd/zetacored/config" emissionscli "github.com/zeta-chain/zetacore/x/emissions/client/cli" + emissionskeeper "github.com/zeta-chain/zetacore/x/emissions/keeper" emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" observercli "github.com/zeta-chain/zetacore/x/observer/client/cli" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -59,7 +61,7 @@ func (s *CliTestSuite) TestObserverRewards() { // Duration factor is calculated in the same block,so we need to query based from the committed state at which the distribution is done // Would be cleaner to use `--height` flag, but it is not supported by the ExecTestCLICmd function yet emissionFactors.DurationFactor = resFactorsNewBlocks.DurationFactor - asertValues := CalculateObserverRewards(s.ballots, emissionParams.Params.ObserverEmissionPercentage, emissionFactors.ReservesFactor, emissionFactors.BondFactor, emissionFactors.DurationFactor) + asertValues := CalculateObserverRewards(&s.Suite, s.ballots, emissionParams.Params.ObserverEmissionPercentage, emissionFactors.ReservesFactor, emissionFactors.BondFactor, emissionFactors.DurationFactor) // Assert withdrawable rewards for each validator resAvailable := emissionstypes.QueryShowAvailableEmissionsResponse{} @@ -72,9 +74,11 @@ func (s *CliTestSuite) TestObserverRewards() { } -func CalculateObserverRewards(ballots []*observertypes.Ballot, observerEmissionPercentage, reservesFactor, bondFactor, durationFactor string) map[string]sdkmath.Int { +func CalculateObserverRewards(s *suite.Suite, ballots []*observertypes.Ballot, observerEmissionPercentage, reservesFactor, bondFactor, durationFactor string) map[string]sdkmath.Int { calculatedDistributer := map[string]sdkmath.Int{} - blockRewards := sdk.MustNewDecFromStr(reservesFactor).Mul(sdk.MustNewDecFromStr(bondFactor)).Mul(sdk.MustNewDecFromStr(durationFactor)) + //blockRewards := sdk.MustNewDecFromStr(reservesFactor).Mul(sdk.MustNewDecFromStr(bondFactor)).Mul(sdk.MustNewDecFromStr(durationFactor)) + blockRewards, err := emissionskeeper.CalculateFixedValidatorRewards(emissionstypes.AvgBlockTime) + s.Require().NoError(err) observerRewards := sdk.MustNewDecFromStr(observerEmissionPercentage).Mul(blockRewards).TruncateInt() rewardsDistributer := map[string]int64{} totalRewardsUnits := int64(0) diff --git a/x/emissions/keeper/block_rewards_components.go b/x/emissions/keeper/block_rewards_components.go index cc93fb3111..ab70e13de5 100644 --- a/x/emissions/keeper/block_rewards_components.go +++ b/x/emissions/keeper/block_rewards_components.go @@ -3,11 +3,12 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/cmd/zetacored/config" + "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/emissions/types" ) func (k Keeper) GetBlockRewardComponents(ctx sdk.Context) (sdk.Dec, sdk.Dec, sdk.Dec) { - reservesFactor := GetReservesFactor(ctx, k.GetBankKeeper()) + reservesFactor := k.GetReservesFactor(ctx) if reservesFactor.LTE(sdk.ZeroDec()) { return sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec() } @@ -55,7 +56,26 @@ func (k Keeper) GetDurationFactor(ctx sdk.Context) sdk.Dec { return fractionNumerator.Quo(fractionDenominator) } -func GetReservesFactor(ctx sdk.Context, keeper types.BankKeeper) sdk.Dec { - reserveAmount := keeper.GetBalance(ctx, types.EmissionsModuleAddress, config.BaseDenom) +func (k Keeper) GetReservesFactor(ctx sdk.Context) sdk.Dec { + reserveAmount := k.GetBankKeeper().GetBalance(ctx, types.EmissionsModuleAddress, config.BaseDenom) return sdk.NewDecFromInt(reserveAmount.Amount) } + +func (k Keeper) GetFixedBlockRewards() (sdk.Dec, error) { + return CalculateFixedValidatorRewards(types.AvgBlockTime) +} + +func CalculateFixedValidatorRewards(avgBlockTimeString string) (sdk.Dec, error) { + azetaAmountTotalRewards, err := common.GetAzetaDecFromAmountInZeta(types.BlockRewardsInZeta) + if err != nil { + return sdk.ZeroDec(), err + } + avgBlockTime, err := sdk.NewDecFromStr(avgBlockTimeString) + if err != nil { + return sdk.ZeroDec(), err + } + numberOfBlocksInAMonth := sdk.NewDec(types.SecsInMonth).Quo(avgBlockTime) + numberOfBlocksTotal := numberOfBlocksInAMonth.Mul(sdk.NewDec(12)).Mul(sdk.NewDec(types.EmissionScheduledYears)) + constantRewardPerBlock := azetaAmountTotalRewards.Quo(numberOfBlocksTotal) + return constantRewardPerBlock, nil +} diff --git a/x/emissions/keeper/block_rewards_components_test.go b/x/emissions/keeper/block_rewards_components_test.go new file mode 100644 index 0000000000..f8d206fbde --- /dev/null +++ b/x/emissions/keeper/block_rewards_components_test.go @@ -0,0 +1,50 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + emissionskeeper "github.com/zeta-chain/zetacore/x/emissions/keeper" +) + +func TestKeeper_CalculateFixedValidatorRewards(t *testing.T) { + tt := []struct { + name string + blockTimeInSecs string + expectedBlockRewards sdk.Dec + }{ + { + name: "Block Time 5.7", + blockTimeInSecs: "5.7", + expectedBlockRewards: sdk.MustNewDecFromStr("9620949074074074074.074070733466756687"), + }, + { + name: "Block Time 6", + blockTimeInSecs: "6", + expectedBlockRewards: sdk.MustNewDecFromStr("10127314814814814814.814814814814814815"), + }, + { + name: "Block Time 3", + blockTimeInSecs: "3", + expectedBlockRewards: sdk.MustNewDecFromStr("5063657407407407407.407407407407407407"), + }, + { + name: "Block Time 2", + blockTimeInSecs: "2", + expectedBlockRewards: sdk.MustNewDecFromStr("3375771604938271604.938271604938271605"), + }, + { + name: "Block Time 8", + blockTimeInSecs: "8", + expectedBlockRewards: sdk.MustNewDecFromStr("13503086419753086419.753086419753086420"), + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + blockRewards, err := emissionskeeper.CalculateFixedValidatorRewards(tc.blockTimeInSecs) + assert.NoError(t, err) + assert.Equal(t, tc.expectedBlockRewards, blockRewards) + }) + } +} diff --git a/x/emissions/types/keys.go b/x/emissions/types/keys.go index e78b0ddade..4be2d268b4 100644 --- a/x/emissions/types/keys.go +++ b/x/emissions/types/keys.go @@ -1,6 +1,7 @@ package types import ( + sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) @@ -23,7 +24,11 @@ const ( MemStoreKey = "mem_emissions" WithdrawableEmissionsKey = "WithdrawableEmissions-value-" - SecsInMonth = 30 * 24 * 60 * 60 + SecsInMonth = 30 * 24 * 60 * 60 + BlockRewardsInZeta = "210000000" + + EmissionScheduledYears = 4 + AvgBlockTime = "5.7" ) func KeyPrefix(p string) []byte { @@ -46,4 +51,5 @@ var ( EmissionsModuleAddress = authtypes.NewModuleAddress(ModuleName) UndistributedObserverRewardsPoolAddress = authtypes.NewModuleAddress(UndistributedObserverRewardsPool) UndistributedTssRewardsPoolAddress = authtypes.NewModuleAddress(UndistributedTssRewardsPool) + BlockReward = sdk.MustNewDecFromStr("9620949074074074074.074070733466756687") )