diff --git a/CHANGELOG.md b/CHANGELOG.md index b632bc313..fa4921ea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [v3.2.0](https://github.com/MinterTeam/minter-go-node/tree/v3.2.0) + +[Full Changelog](https://github.com/MinterTeam/minter-go-node/compare/v3.1.1...v3.2.0) + +### Fixed + +- Smooth increase in rewards after the fall +- Accruals for DAOs and developers, taking into account blocked stakes + ## [v3.1.1](https://github.com/MinterTeam/minter-go-node/tree/v3.1.1) [Full Changelog](https://github.com/MinterTeam/minter-go-node/compare/v3.1.0...v3.1.1) @@ -7,7 +16,7 @@ ### Fixed - Find coins with last symbol `-` -- Accrual of rewards x3 with `GetAccumReward == 0` +- Accrual of rewards x3 with candidate's `AccumReward` is 0 ## [v3.1.0](https://github.com/MinterTeam/minter-go-node/tree/v3.1.0) diff --git a/cmd/minter/cmd/export.go b/cmd/minter/cmd/export.go index 7bbbbf112..6eef5218e 100644 --- a/cmd/minter/cmd/export.go +++ b/cmd/minter/cmd/export.go @@ -3,8 +3,6 @@ package cmd import ( "crypto/sha256" "encoding/json" - "github.com/MinterTeam/minter-go-node/coreV2/minter" - "github.com/MinterTeam/minter-go-node/coreV2/rewards" "github.com/MinterTeam/minter-go-node/version" "github.com/tendermint/go-amino" "io" @@ -90,19 +88,16 @@ func export(cmd *cobra.Command, args []string) error { } log.Printf("Verify state OK\n") - appState.Version = minter.V3 - //versions := db.GetVersions() - //for _, v := range versions { - // appState.Versions = append(appState.Versions, mtypes.Version{ - // Height: v.Height, - // Name: v.Name, - // }) - //} - - //appState.Emission = db.Emission().String() - appState.Emission = rewards.NewReward().GetBeforeBlock(height).String() - reserve0, reserve1 := currentState.Swap().GetSwapper(0, 1993).Reserves() - db.UpdatePrice(time.Unix(0, int64(genesisTime)).UTC(), reserve0, reserve1) + //appState.Version = minter.V3 + versions := db.GetVersions() + for _, v := range versions { + appState.Versions = append(appState.Versions, mtypes.Version{ + Height: v.Height, + Name: v.Name, + }) + } + + appState.Emission = db.Emission().String() t, r0, r1, reward, off := db.GetPrice() appState.PrevReward = mtypes.RewardPrice{ Time: uint64(t.UTC().UnixNano()), diff --git a/coreV2/appdb/appdb.go b/coreV2/appdb/appdb.go index 7d2bcf971..e95f2378b 100644 --- a/coreV2/appdb/appdb.go +++ b/coreV2/appdb/appdb.go @@ -447,7 +447,51 @@ type TimePrice struct { Last *big.Int } -func (appDB *AppDB) UpdatePrice(t time.Time, r0, r1 *big.Int) (reward, safeReward *big.Int) { +func (appDB *AppDB) UpdatePriceFix(t time.Time, r0, r1 *big.Int) (reward, safeReward *big.Int) { + tOld, reserve0, reserve1, last, off := appDB.GetPrice() + + fNew := big.NewRat(1, 1).SetFrac(r1, r0) + // Price ^ (1/4) * 350 + priceCount, _ := new(big.Float).Mul(new(big.Float).Mul(math.Pow(new(big.Float).SetRat(fNew), big.NewFloat(0.25)), big.NewFloat(350)), big.NewFloat(1e18)).Int(nil) + if tOld.IsZero() { + appDB.SetPrice(t, r0, r1, priceCount, false) + return new(big.Int).Set(priceCount), new(big.Int).Set(priceCount) + } + + defer func() { appDB.SetPrice(t, r0, r1, last, off) }() + + fOld := big.NewRat(1, 1).SetFrac(reserve1, reserve0) + + rat := new(big.Rat).Mul(new(big.Rat).Quo(new(big.Rat).Sub(fNew, fOld), fOld), new(big.Rat).SetInt64(100)) + diff := big.NewInt(0).Div(rat.Num(), rat.Denom()) + + if diff.Cmp(big.NewInt(-10)) != 1 { + last.SetInt64(0) + off = true + return last, new(big.Int).Set(priceCount) + } + + if off && last.Cmp(priceCount) == -1 { + last.Add(last, big.NewInt(5e18)) + last.Add(last, big.NewInt(5e18)) + burn := big.NewInt(0).Sub(priceCount, last) + if burn.Sign() != 1 { + last.Set(priceCount) + off = false + return new(big.Int).Set(last), new(big.Int).Set(priceCount) + } + return new(big.Int).Set(last), new(big.Int).Set(priceCount) + } + + off = false + last.Set(priceCount) + + return new(big.Int).Set(last), new(big.Int).Set(priceCount) +} + +// UpdatePriceBug +// Deprecated +func (appDB *AppDB) UpdatePriceBug(t time.Time, r0, r1 *big.Int) (reward, safeReward *big.Int) { tOld, reserve0, reserve1, last, off := appDB.GetPrice() fNew := big.NewRat(1, 1).SetFrac(r1, r0) diff --git a/coreV2/minter/blockchain.go b/coreV2/minter/blockchain.go index 6b1e8f80d..69dca2701 100644 --- a/coreV2/minter/blockchain.go +++ b/coreV2/minter/blockchain.go @@ -146,6 +146,7 @@ func NewMinterBlockchain(storages *utils.Storage, cfg *config.Config, ctx contex knownUpdates: map[string]struct{}{ V3: {}, // tokenomics V310: {}, // hotfix + V320: {}, }, executor: GetExecutor(V3), } @@ -177,6 +178,7 @@ func GetExecutor(v string) transaction.ExecutorTx { const ( // known update versions V3 = "v300" // tokenomics V310 = "v310" // hotfix + V320 = "v320" // hotfix ) func (blockchain *Blockchain) initState() { @@ -275,7 +277,11 @@ func (blockchain *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTy t, _, _, _, _ := blockchain.appDB.GetPrice() if height%blockchain.updateStakesAndPayRewardsPeriod == 1 && (t.IsZero() || (req.Header.Time.Hour() >= 12 && req.Header.Time.Hour() <= 14) && req.Header.Time.Sub(t) > 3*time.Hour) { reserve0, reserve1 := blockchain.stateCheck.Swap().GetSwapper(0, types.USDTID).Reserves() - newRewards, safeReward := blockchain.appDB.UpdatePrice(req.Header.Time, reserve0, reserve1) + funcUpdatePrice := blockchain.appDB.UpdatePriceBug + if h := blockchain.appDB.GetVersionHeight(V320); h > 0 && height > h { + funcUpdatePrice = blockchain.appDB.UpdatePriceFix + } + newRewards, safeReward := funcUpdatePrice(req.Header.Time, reserve0, reserve1) blockchain.stateDeliver.App.SetReward(newRewards, safeReward) blockchain.eventsDB.AddEvent(&eventsdb.UpdatedBlockRewardEvent{Value: newRewards.String(), ValueLockedStakeRewards: new(big.Int).Mul(safeReward, big.NewInt(3)).String()}) } @@ -445,15 +451,17 @@ func (blockchain *Blockchain) EndBlock(req abciTypes.RequestEndBlock) abciTypes. // pay rewards var moreRewards = big.NewInt(0) if height%blockchain.updateStakesAndPayRewardsPeriod == 0 { - if h := blockchain.appDB.GetVersionHeight(V310); h > 0 && height > h { - moreRewards = blockchain.stateDeliver.Validators.PayRewardsV4(heightIsMaxIfIssueIsOverOrNotDynamic, int64(blockchain.updateStakesAndPayRewardsPeriod)) - blockchain.appDB.SetEmission(big.NewInt(0).Add(blockchain.appDB.Emission(), moreRewards)) - blockchain.stateDeliver.Checker.AddCoinVolume(types.GetBaseCoinID(), moreRewards) - } else { - moreRewards = blockchain.stateDeliver.Validators.PayRewardsV3(heightIsMaxIfIssueIsOverOrNotDynamic, int64(blockchain.updateStakesAndPayRewardsPeriod)) - blockchain.appDB.SetEmission(big.NewInt(0).Add(blockchain.appDB.Emission(), moreRewards)) - blockchain.stateDeliver.Checker.AddCoinVolume(types.GetBaseCoinID(), moreRewards) + PayRewards := blockchain.stateDeliver.Validators.PayRewardsV3 + if h := blockchain.appDB.GetVersionHeight(V320); h > 0 && height > h { + PayRewards = blockchain.stateDeliver.Validators.PayRewardsV5 + } else if h := blockchain.appDB.GetVersionHeight(V310); h > 0 && height > h { + PayRewards = blockchain.stateDeliver.Validators.PayRewardsV4 } + + moreRewards = PayRewards(heightIsMaxIfIssueIsOverOrNotDynamic, int64(blockchain.updateStakesAndPayRewardsPeriod)) + blockchain.appDB.SetEmission(big.NewInt(0).Add(blockchain.appDB.Emission(), moreRewards)) + blockchain.stateDeliver.Checker.AddCoinVolume(types.GetBaseCoinID(), moreRewards) + } if heightIsMaxIfIssueIsOverOrNotDynamic != math.MaxUint64 { diff --git a/coreV2/state/validators/validators.go b/coreV2/state/validators/validators.go index 08fa5914d..d3e9c9b6c 100644 --- a/coreV2/state/validators/validators.go +++ b/coreV2/state/validators/validators.go @@ -431,7 +431,203 @@ func (v *Validators) PayRewardsV3(height uint64, period int64) (moreRewards *big return moreRewards } -// PayRewardsV4 distributes accumulated rewards between validator, delegators, DAO and developers addresses +// PayRewardsV5 distributes accumulated rewards between validator, delegators, DAO and developers addresses +func (v *Validators) PayRewardsV5(height uint64, period int64) (moreRewards *big.Int) { + moreRewards = big.NewInt(0) + + vals := v.GetValidators() + + calcReward, safeReward := v.bus.App().Reward() + var totalAccumRewards = big.NewInt(0) + for _, validator := range vals { + totalAccumRewards = totalAccumRewards.Add(totalAccumRewards, validator.GetAccumReward()) + } + + var totalStakes = big.NewInt(0) + if totalAccumRewards.Sign() != 1 { + for _, validator := range vals { + totalStakes = totalStakes.Add(totalStakes, validator.GetTotalBipStake()) + } + } + + for _, validator := range vals { + candidate := v.bus.Candidates().GetCandidate(validator.PubKey) + + totalReward := big.NewInt(0).Set(validator.GetAccumReward()) + remainder := big.NewInt(0).Set(validator.GetAccumReward()) + + // pay commission to DAO + + DAOReward := big.NewInt(0).Set(totalReward) + DAOReward.Mul(DAOReward, big.NewInt(int64(dao.Commission))) + DAOReward.Div(DAOReward, big.NewInt(100)) + + // pay commission to Developers + + DevelopersReward := big.NewInt(0).Set(totalReward) + DevelopersReward.Mul(DevelopersReward, big.NewInt(int64(developers.Commission))) + DevelopersReward.Div(DevelopersReward, big.NewInt(100)) + + totalReward.Sub(totalReward, DevelopersReward) + totalReward.Sub(totalReward, DAOReward) + remainder.Sub(remainder, DAOReward) + remainder.Sub(remainder, DevelopersReward) + + // pay commission to validator + validatorReward := big.NewInt(0).Set(totalReward) + validatorReward.Mul(validatorReward, big.NewInt(int64(candidate.Commission))) + validatorReward.Div(validatorReward, big.NewInt(100)) + totalReward.Sub(totalReward, validatorReward) + + candidate.AddUpdate(types.GetBaseCoinID(), validatorReward, validatorReward, candidate.RewardAddress) + v.bus.Checker().AddCoin(types.GetBaseCoinID(), validatorReward) + + remainder.Sub(remainder, validatorReward) + v.bus.Events().AddEvent(&eventsdb.RewardEvent{ + Role: eventsdb.RoleValidator.String(), + Address: candidate.RewardAddress, + Amount: validatorReward.String(), + ValidatorPubKey: validator.PubKey, + ForCoin: 0, + }) + + stakes := v.bus.Candidates().GetStakes(validator.PubKey) + for _, stake := range stakes { + if stake.BipValue.Sign() == 0 { + continue + } + + reward := big.NewInt(0).Set(totalReward) + reward.Mul(reward, stake.BipValue) + + reward.Div(reward, validator.GetTotalBipStake()) + + remainder.Sub(remainder, reward) + + safeRewardVariable := big.NewInt(0).Set(reward) + if validator.bus.Accounts().IsX3Mining(stake.Owner, height) { + if totalAccumRewards.Sign() == 1 && validator.GetAccumReward().Sign() == 1 { + safeRewards := big.NewInt(0).Mul(safeReward, big.NewInt(period)) + safeRewards.Mul(safeRewards, stake.BipValue) + safeRewards.Mul(safeRewards, big.NewInt(3)) + safeRewards.Mul(safeRewards, validator.GetAccumReward()) + safeRewards.Div(safeRewards, validator.GetTotalBipStake()) + + taxDAOx3 := big.NewInt(0).Div(big.NewInt(0).Mul(safeRewards, big.NewInt(int64(developers.Commission))), big.NewInt(100)) + taxDEVx3 := big.NewInt(0).Div(big.NewInt(0).Mul(safeRewards, big.NewInt(int64(dao.Commission))), big.NewInt(100)) + + safeRewards.Sub(safeRewards, taxDAOx3) + safeRewards.Sub(safeRewards, taxDEVx3) + safeRewards.Sub(safeRewards, big.NewInt(0).Div(big.NewInt(0).Mul(safeRewards, big.NewInt(int64(candidate.Commission))), big.NewInt(100))) + safeRewards.Div(safeRewards, totalAccumRewards) + + calcRewards := big.NewInt(0).Mul(calcReward, big.NewInt(period)) + calcRewards.Mul(calcRewards, stake.BipValue) + calcRewards.Mul(calcRewards, validator.GetAccumReward()) + calcRewards.Div(calcRewards, validator.GetTotalBipStake()) + + taxDAO := big.NewInt(0).Div(big.NewInt(0).Mul(calcRewards, big.NewInt(int64(developers.Commission))), big.NewInt(100)) + taxDEV := big.NewInt(0).Div(big.NewInt(0).Mul(calcRewards, big.NewInt(int64(dao.Commission))), big.NewInt(100)) + + calcRewards.Sub(calcRewards, taxDAO) + calcRewards.Sub(calcRewards, taxDEV) + calcRewards.Sub(calcRewards, big.NewInt(0).Div(big.NewInt(0).Mul(calcRewards, big.NewInt(int64(developers.Commission+dao.Commission))), big.NewInt(100))) + calcRewards.Sub(calcRewards, big.NewInt(0).Div(big.NewInt(0).Mul(calcRewards, big.NewInt(int64(candidate.Commission))), big.NewInt(100))) + calcRewards.Div(calcRewards, totalAccumRewards) + + diffDAO := big.NewInt(0).Sub(taxDAOx3, taxDAO) + diffDEV := big.NewInt(0).Sub(taxDAOx3, taxDEV) + DAOReward.Add(DAOReward, diffDAO) + DevelopersReward.Add(DevelopersReward, diffDEV) + + moreRewards.Add(moreRewards, diffDAO) + moreRewards.Add(moreRewards, diffDEV) + + feeRewards := big.NewInt(0).Sub(reward, calcRewards) + safeRewardVariable.Set(big.NewInt(0).Add(safeRewards, feeRewards)) + } else if totalAccumRewards.Sign() != 1 && validator.GetAccumReward().Sign() != 1 { + safeRewards := big.NewInt(0).Mul(safeReward, big.NewInt(period)) + safeRewards.Mul(safeRewards, stake.BipValue) + safeRewards.Mul(safeRewards, big.NewInt(3)) + + taxDAO := big.NewInt(0).Div(big.NewInt(0).Mul(safeRewards, big.NewInt(int64(developers.Commission))), big.NewInt(100)) + taxDEV := big.NewInt(0).Div(big.NewInt(0).Mul(safeRewards, big.NewInt(int64(dao.Commission))), big.NewInt(100)) + + DAOReward.Add(DAOReward, taxDAO) + DevelopersReward.Add(DevelopersReward, taxDEV) + moreRewards.Add(moreRewards, taxDAO) + moreRewards.Add(moreRewards, taxDEV) + + safeRewards.Sub(safeRewards, taxDAO) + safeRewards.Sub(safeRewards, taxDEV) + + safeRewards.Sub(safeRewards, big.NewInt(0).Div(big.NewInt(0).Mul(safeRewards, big.NewInt(int64(candidate.Commission))), big.NewInt(100))) + safeRewards.Div(safeRewards, totalStakes) + + safeRewardVariable.Set(safeRewards) + } + + if safeRewardVariable.Sign() < 1 { + continue + } + + moreRewards.Add(moreRewards, new(big.Int).Sub(safeRewardVariable, reward)) + } + + if safeRewardVariable.Sign() < 1 { + continue + } + + candidate.AddUpdate(types.GetBaseCoinID(), safeRewardVariable, safeRewardVariable, stake.Owner) + v.bus.Checker().AddCoin(types.GetBaseCoinID(), safeRewardVariable) + + v.bus.Events().AddEvent(&eventsdb.RewardEvent{ + Role: eventsdb.RoleDelegator.String(), + Address: stake.Owner, + Amount: safeRewardVariable.String(), + ValidatorPubKey: validator.PubKey, + ForCoin: uint64(stake.Coin), + }) + } + + { + candidate.AddUpdate(types.GetBaseCoinID(), DAOReward, DAOReward, dao.Address) + v.bus.Checker().AddCoin(types.GetBaseCoinID(), DAOReward) + v.bus.Events().AddEvent(&eventsdb.RewardEvent{ + Role: eventsdb.RoleDAO.String(), + Address: dao.Address, + Amount: DAOReward.String(), + ValidatorPubKey: validator.PubKey, + ForCoin: 0, + }) + } + + { + candidate.AddUpdate(types.GetBaseCoinID(), DevelopersReward, DevelopersReward, developers.Address) + v.bus.Checker().AddCoin(types.GetBaseCoinID(), DevelopersReward) + v.bus.Events().AddEvent(&eventsdb.RewardEvent{ + Role: eventsdb.RoleDevelopers.String(), + Address: developers.Address, + Amount: DevelopersReward.String(), + ValidatorPubKey: validator.PubKey, + ForCoin: 0, + }) + } + + validator.SetAccumReward(big.NewInt(0)) + + if remainder.Sign() != -1 { + v.bus.App().AddTotalSlashed(remainder) + } else { + panic(fmt.Sprintf("Negative remainder: %s", remainder.String())) + } + } + + return moreRewards +} + +// PayRewardsV4 +// Deprecated func (v *Validators) PayRewardsV4(height uint64, period int64) (moreRewards *big.Int) { moreRewards = big.NewInt(0) diff --git a/tests/helpers_test.go b/tests/helpers_test.go index 25d0dde47..e9c018721 100644 --- a/tests/helpers_test.go +++ b/tests/helpers_test.go @@ -173,13 +173,14 @@ func CreateAddress() (types.Address, *ecdsa.PrivateKey) { // DefaultAppState returns new AppState with some predefined values func DefaultAppState() types.AppState { return types.AppState{ - Version: "v260", Note: "", Validators: nil, Candidates: nil, BlockListCandidates: nil, + DeletedCandidates: nil, Waitlist: nil, Pools: nil, + NextOrderID: 0, Accounts: nil, Coins: nil, FrozenFunds: nil, @@ -231,11 +232,24 @@ func DefaultAppState() types.AppState { FailedTx: "10000000000000000", AddLimitOrder: "100000000000000000", RemoveLimitOrder: "100000000000000000", + MoveStake: "100000000000000000", + LockStake: "100000000000000000", + Lock: "100000000000000000", }, CommissionVotes: nil, UpdateVotes: nil, UsedChecks: nil, MaxGas: 0, TotalSlashed: "0", + Emission: "9999", + PrevReward: types.RewardPrice{ + Time: 0, + AmountBIP: "350", + AmountUSDT: "1", + Off: false, + Reward: "74000000000000000000", + }, + Version: "v300", + Versions: nil, } } diff --git a/tests/reward3_test.go b/tests/reward3_test.go index 0a336bf53..3bee0007a 100644 --- a/tests/reward3_test.go +++ b/tests/reward3_test.go @@ -246,7 +246,7 @@ func TestReward_Update_Down(t *testing.T) { Balance: []types.Balance{ { Coin: uint64(types.GetBaseCoinID()), - Value: helpers.StringToBigInt("1000000000100000000000000000").String(), + Value: helpers.StringToBigInt("1000000000300000000000000000").String(), }, }, Nonce: 0, @@ -373,11 +373,15 @@ func TestReward_Update_Down(t *testing.T) { SendCommit(app) SendBeginBlock(app, 14, time.Unix(1646308803, 0).UTC()) // send BeginBlock { - //tx := CreateTx(app, address, transaction.TypeSend, transaction.SendData{}, types.USDTID) - //response := SendTx(app, SignTx(pk, tx)) // compose and send tx - //if response.Code != code.OK { - // t.Fatalf("Response code is not OK: %s, %d", response.Log, response.Code) - //} + tx := CreateTx(app, address, transaction.TypeSellSwapPool, transaction.SellSwapPoolDataV260{ + Coins: []types.CoinID{0, types.USDTID}, + ValueToSell: helpers.StringToBigInt("100000000000000000"), + MinimumValueToBuy: helpers.StringToBigInt("1"), + }, 0) + response := SendTx(app, SignTx(pk, tx)) // compose and send tx + if response.Code != code.OK { + t.Fatalf("Response code is not OK: %s, %d", response.Log, response.Code) + } } SendEndBlock(app, 14) // send EndBlock SendCommit(app) // send Commit @@ -390,6 +394,12 @@ func TestReward_Update_Down(t *testing.T) { t.Log(app.GetEventsDB().LoadEvents(14)[3]) //t.Log(app.GetEventsDB().LoadEvents(14)[4]) t.Log(app.CurrentState().App().Reward()) + + SendBeginBlock(app, 15, time.Unix(1650628838, 0).UTC()) // send BeginBlock + SendEndBlock(app, 15) // send EndBlock + SendCommit(app) + + t.Log(app.CurrentState().App().Reward()) } func TestReward_Update_Up(t *testing.T) { diff --git a/version/version.go b/version/version.go index 4f8cf517d..c5a1533f1 100755 --- a/version/version.go +++ b/version/version.go @@ -7,7 +7,7 @@ const ( var ( // Version must be a string because scripts like dist.sh read this file. - Version = "3.1.1" + Version = "3.2.0" // GitCommit is the current HEAD set using ldflags. GitCommit string