refactor(x/mint)!: avoid writing same minter and use millisecond precision for minting (#20747)

This commit is contained in:
Facundo Medica 2024-06-26 12:19:24 +02:00 committed by GitHub
parent ae9116aff1
commit 4bda5d6a4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 48 additions and 6 deletions

View File

@ -60,20 +60,19 @@ func ProvideExampleMintFn(bankKeeper MintBankKeeper) minttypes.MintFn {
minter.AnnualProvisions = minter.NextAnnualProvisions(mintParams.Params, stakingTokenSupply.Amount)
// to get a more accurate amount of tokens minted, we get, and later store, last minting time.
// if this is the first time minting, we initialize the minter.Data with the current time - 60s
// to mint tokens at the beginning. Note: this is a custom behavior to avoid breaking tests.
if minter.Data == nil {
minter.Data = make([]byte, 8)
binary.BigEndian.PutUint64(minter.Data, (uint64)(env.HeaderService.HeaderInfo(ctx).Time.Unix()-60))
binary.BigEndian.PutUint64(minter.Data, (uint64)(env.HeaderService.HeaderInfo(ctx).Time.UnixMilli()-60000))
}
lastMint := binary.BigEndian.Uint64(minter.Data)
binary.BigEndian.PutUint64(minter.Data, (uint64)(env.HeaderService.HeaderInfo(ctx).Time.Unix()))
binary.BigEndian.PutUint64(minter.Data, (uint64)(env.HeaderService.HeaderInfo(ctx).Time.UnixMilli()))
// calculate the amount of tokens to mint, based on the time since the last mint
secsSinceLastMint := env.HeaderService.HeaderInfo(ctx).Time.Unix() - (int64)(lastMint)
provisionAmt := minter.AnnualProvisions.QuoInt64(31536000).MulInt64(secsSinceLastMint) // 31536000 = seconds in a year
// calculate the amount of tokens to mint, based on the time since the last mint.
msSinceLastMint := env.HeaderService.HeaderInfo(ctx).Time.UnixMilli() - (int64)(lastMint)
provisionAmt := minter.AnnualProvisions.QuoInt64(31536000000).MulInt64(msSinceLastMint) // 31536000000 = milliseconds in a year
mintedCoin := sdk.NewCoin(mintParams.Params.MintDenom, provisionAmt.TruncateInt())
maxSupply := mintParams.Params.MaxSupply
totalSupply := stakingTokenSupply.Amount

View File

@ -20,11 +20,17 @@ func (am AppModule) BeforeEpochStart(ctx context.Context, epochIdentifier string
return err
}
oldMinter := minter
err = am.mintFn(ctx, am.keeper.Environment, &minter, epochIdentifier, epochNumber)
if err != nil {
return err
}
if minter.IsEqual(oldMinter) {
return nil
}
return am.keeper.Minter.Set(ctx, minter)
}

View File

@ -18,6 +18,8 @@ func (k Keeper) BeginBlocker(ctx context.Context, mintFn types.MintFn) error {
return err
}
oldMinter := minter
// we pass -1 as epoch number to indicate that this is not an epoch minting,
// but a regular block minting. Same with epoch id "block".
err = mintFn(ctx, k.Environment, &minter, "block", -1)
@ -25,5 +27,9 @@ func (k Keeper) BeginBlocker(ctx context.Context, mintFn types.MintFn) error {
return err
}
if minter.IsEqual(oldMinter) {
return nil
}
return k.Minter.Set(ctx, minter)
}

View File

@ -1,11 +1,13 @@
package keeper_test
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/suite"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/log"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
@ -152,6 +154,17 @@ func (s *KeeperTestSuite) TestBeginBlocker() {
newMinter, err := s.mintKeeper.Minter.Get(s.ctx)
s.NoError(err)
s.NotEqual(minter, newMinter)
// now use a mintfn that doesn't do anything
err = s.mintKeeper.BeginBlocker(s.ctx, func(ctx context.Context, env appmodule.Environment, minter *types.Minter, epochId string, epochNumber int64) error {
return nil
})
s.NoError(err)
// get minter again and compare
unchangedMinter, err := s.mintKeeper.Minter.Get(s.ctx)
s.NoError(err)
s.Equal(newMinter, unchangedMinter)
}
func (s *KeeperTestSuite) TestMigrator() {

View File

@ -1,6 +1,7 @@
package types
import (
"bytes"
"fmt"
"cosmossdk.io/math"
@ -80,3 +81,20 @@ func (m Minter) BlockProvision(params Params) sdk.Coin {
provisionAmt := m.AnnualProvisions.QuoInt(math.NewInt(int64(params.BlocksPerYear)))
return sdk.NewCoin(params.MintDenom, provisionAmt.TruncateInt())
}
// IsEqual returns true if two minters are equal, it checks all the fields
func (m Minter) IsEqual(minter Minter) bool {
if !m.Inflation.Equal(minter.Inflation) {
return false
}
if !m.AnnualProvisions.Equal(minter.AnnualProvisions) {
return false
}
if !bytes.Equal(m.Data, minter.Data) {
return false
}
return true
}