diff --git a/app/app.go b/app/app.go index 3d3581b70..d2ff3a58f 100644 --- a/app/app.go +++ b/app/app.go @@ -73,6 +73,7 @@ import ( v1_23 "github.com/scrtlabs/SecretNetwork/app/upgrades/v1.23" v1_23_1 "github.com/scrtlabs/SecretNetwork/app/upgrades/v1.23.1" v1_23_2 "github.com/scrtlabs/SecretNetwork/app/upgrades/v1.23.2" + v1_24 "github.com/scrtlabs/SecretNetwork/app/upgrades/v1.24" v1_4 "github.com/scrtlabs/SecretNetwork/app/upgrades/v1.4" v1_5 "github.com/scrtlabs/SecretNetwork/app/upgrades/v1.5" v1_6 "github.com/scrtlabs/SecretNetwork/app/upgrades/v1.6" @@ -154,6 +155,7 @@ var ( v1_23.Upgrade, v1_23_1.Upgrade, v1_23_2.Upgrade, + v1_24.Upgrade, } ) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 2242d2ffc..04fd5c823 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -28,9 +28,9 @@ import ( govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/cosmos/cosmos-sdk/x/params" + mintkeeper "github.com/scrtlabs/SecretNetwork/x/mint/keeper" + minttypes "github.com/scrtlabs/SecretNetwork/x/mint/types" paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal" diff --git a/app/modules.go b/app/modules.go index 905826cc0..d25586bde 100644 --- a/app/modules.go +++ b/app/modules.go @@ -20,9 +20,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/gov" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/cosmos/cosmos-sdk/x/mint" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/cosmos/cosmos-sdk/x/params" + "github.com/scrtlabs/SecretNetwork/x/mint" + minttypes "github.com/scrtlabs/SecretNetwork/x/mint/types" "github.com/cosmos/cosmos-sdk/x/slashing" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" @@ -73,7 +73,7 @@ func Modules( consensus.NewAppModule(appCodec, app.AppKeepers.ConsensusParamsKeeper), feegrantmodule.NewAppModule(appCodec, app.AppKeepers.AccountKeeper, *app.AppKeepers.BankKeeper, *app.AppKeepers.FeegrantKeeper, app.GetInterfaceRegistry()), gov.NewAppModule(appCodec, app.AppKeepers.GovKeeper, app.AppKeepers.AccountKeeper, *app.AppKeepers.BankKeeper, app.AppKeepers.GetSubspace(govtypes.ModuleName)), - mint.NewAppModule(appCodec, *app.AppKeepers.MintKeeper, app.AppKeepers.AccountKeeper, nil, app.AppKeepers.GetSubspace(minttypes.ModuleName)), + mint.NewAppModule(appCodec, *app.AppKeepers.MintKeeper, app.AppKeepers.AccountKeeper, app.AppKeepers.GetSubspace(minttypes.ModuleName)), slashing.NewAppModule(appCodec, *app.AppKeepers.SlashingKeeper, app.AppKeepers.AccountKeeper, *app.AppKeepers.BankKeeper, *app.AppKeepers.StakingKeeper, app.AppKeepers.GetSubspace(slashingtypes.ModuleName), app.GetInterfaceRegistry()), distr.NewAppModule(appCodec, *app.AppKeepers.DistrKeeper, app.AppKeepers.AccountKeeper, *app.AppKeepers.BankKeeper, *app.AppKeepers.StakingKeeper, app.AppKeepers.GetSubspace(distrtypes.ModuleName)), staking.NewAppModule(appCodec, app.AppKeepers.StakingKeeper, app.AppKeepers.AccountKeeper, *app.AppKeepers.BankKeeper, app.AppKeepers.GetSubspace(stakingtypes.ModuleName)), diff --git a/app/upgrades/v1.24/upgrade.go b/app/upgrades/v1.24/upgrade.go new file mode 100644 index 000000000..f9f44d05b --- /dev/null +++ b/app/upgrades/v1.24/upgrade.go @@ -0,0 +1,90 @@ +package v1_24 + +import ( + "context" + "fmt" + "os" + + "cosmossdk.io/log" + "cosmossdk.io/math" + store "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + oldminttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/scrtlabs/SecretNetwork/app/keepers" + "github.com/scrtlabs/SecretNetwork/app/upgrades" + minttypes "github.com/scrtlabs/SecretNetwork/x/mint/types" +) + +const upgradeName = "v1.24" + +var Upgrade = upgrades.Upgrade{ + UpgradeName: upgradeName, + CreateUpgradeHandler: createUpgradeHandler, + StoreUpgrades: store.StoreUpgrades{}, +} + +func createUpgradeHandler(mm *module.Manager, keepers *keepers.SecretAppKeepers, configurator module.Configurator, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + logger := log.NewLogger(os.Stderr) + logger.Info(` _ _ _____ _____ _____ _____ ______ `) + logger.Info(`| | | | __ \ / ____| __ \ /\ | __ \| ____|`) + logger.Info(`| | | | |__) | | __| |__) | / \ | | | | |__ `) + logger.Info(`| | | | ___/| | |_ | _ / / /\ \ | | | | __| `) + logger.Info(`| |__| | | | |__| | | \ \ / ____ \| |__| | |____ `) + logger.Info(` \____/|_| \_____|_| \_\/_/ \_\_____/|______|`) + logger.Info("") + logger.Info("🔥 SWITCHING TO FIXED BLOCK REWARDS 🔥") + logger.Info("") + + logger.Info(fmt.Sprintf("Running module migrations for %s...", upgradeName)) + + // Migrate mint module parameters from percentage-based to fixed block rewards + logger.Info("Migrating mint module to fixed block rewards...") + + // Read existing BlocksPerYear from legacy mint params + // This value is governance-adjustable and should be preserved + sdkCtx := sdk.UnwrapSDKContext(ctx) + mintSubspace := keepers.GetSubspace(oldminttypes.ModuleName) + if !mintSubspace.HasKeyTable() { + mintSubspace.WithKeyTable(oldminttypes.ParamKeyTable()) + } + var oldBlocksPerYear uint64 + mintSubspace.Get(sdkCtx, oldminttypes.KeyBlocksPerYear, &oldBlocksPerYear) + logger.Info(fmt.Sprintf(" - Existing blocks per year from chain state: %d", oldBlocksPerYear)) + + // Set new mint parameters with fixed block reward of 4 SCRT (4,000,000 uscrt) + // Preserve BlocksPerYear from existing chain state (governance-adjustable target) + newParams := minttypes.NewParams( + "uscrt", // MintDenom + math.NewInt(4_000_000), // FixedBlockReward: 4 SCRT = 4,000,000 uscrt + oldBlocksPerYear, // BlocksPerYear: preserve existing governance-managed value + ) + + if err := keepers.MintKeeper.SetParams(ctx, newParams); err != nil { + return nil, fmt.Errorf("failed to set mint params: %w", err) + } + + // Initialize minter with annual provisions calculated from fixed block reward + // AnnualProvisions = FixedBlockReward * BlocksPerYear + annualProvisions := math.LegacyNewDecFromInt(newParams.FixedBlockReward). + Mul(math.LegacyNewDec(int64(newParams.BlocksPerYear))) + + minter := minttypes.NewMinter(annualProvisions) + if err := keepers.MintKeeper.SetMinter(ctx, minter); err != nil { + return nil, fmt.Errorf("failed to set minter: %w", err) + } + + logger.Info(fmt.Sprintf("✅ Mint module migrated successfully:")) + logger.Info(fmt.Sprintf(" - Fixed block reward: %s uscrt (4 SCRT)", newParams.FixedBlockReward.String())) + logger.Info(fmt.Sprintf(" - Blocks per year: %d", newParams.BlocksPerYear)) + logger.Info(fmt.Sprintf(" - Annual provisions: %s uscrt (~21M SCRT)", annualProvisions.TruncateInt().String())) + logger.Info(fmt.Sprintf(" - Current inflation will decrease from ~9%% to ~6.2%%")) + logger.Info(fmt.Sprintf(" - Inflation will continue to decrease over time as supply grows")) + logger.Info("") + + return mm.RunMigrations(ctx, configurator, vm) + } +} diff --git a/x/mint/abci.go b/x/mint/abci.go new file mode 100644 index 000000000..c6a705139 --- /dev/null +++ b/x/mint/abci.go @@ -0,0 +1,71 @@ +package mint + +import ( + "context" + "time" + + "cosmossdk.io/math" + "github.com/scrtlabs/SecretNetwork/x/mint/keeper" + "github.com/scrtlabs/SecretNetwork/x/mint/types" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BeginBlocker mints new tokens for the previous block. +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + + // Fetch stored minter + minter, err := k.GetMinter(ctx) + if err != nil { + return err + } + + // Fetch params + params, err := k.GetParams(ctx) + if err != nil { + return err + } + + // Calculate annual provisions for informational purposes + // AnnualProvisions = FixedBlockReward * BlocksPerYear + annualProvisions := math.LegacyNewDecFromInt(params.FixedBlockReward). + Mul(math.LegacyNewDec(int64(params.BlocksPerYear))) + minter.AnnualProvisions = annualProvisions + + // Save updated minter + if err := k.SetMinter(ctx, minter); err != nil { + return err + } + + // Mint coins for this block - FIXED amount regardless of supply + mintedCoin := minter.BlockProvision(params) + mintedCoins := sdk.NewCoins(mintedCoin) + + // Mint the coins + if err := k.MintCoins(ctx, mintedCoins); err != nil { + return err + } + + // Send minted coins to the fee collector account + if err := k.AddCollectedFees(ctx, mintedCoins); err != nil { + return err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + if mintedCoin.Amount.IsInt64() { + defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens") + } + + sdkCtx.EventManager().EmitEvent( + sdk.NewEvent( + types.ModuleName, + sdk.NewAttribute(types.KeyMintDenom, params.MintDenom), + sdk.NewAttribute(types.KeyFixedBlockReward, params.FixedBlockReward.String()), + sdk.NewAttribute("amount", mintedCoin.Amount.String()), + ), + ) + + return nil +} diff --git a/x/mint/keeper/keeper.go b/x/mint/keeper/keeper.go new file mode 100644 index 000000000..021163fdc --- /dev/null +++ b/x/mint/keeper/keeper.go @@ -0,0 +1,127 @@ +package keeper + +import ( + "context" + + "cosmossdk.io/log" + "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/scrtlabs/SecretNetwork/x/mint/types" +) + +// Keeper of the mint store +type Keeper struct { + cdc codec.BinaryCodec + storeService storetypes.KVStoreService + paramSpace paramtypes.Subspace + stakingKeeper types.StakingKeeper + bankKeeper types.BankKeeper + feeCollectorName string + authority string +} + +// NewKeeper creates a new mint Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, + storeService storetypes.KVStoreService, + sk types.StakingKeeper, + ak types.AccountKeeper, + bk types.BankKeeper, + feeCollectorName string, + authority string, +) Keeper { + return Keeper{ + cdc: cdc, + storeService: storeService, + stakingKeeper: sk, + bankKeeper: bk, + feeCollectorName: feeCollectorName, + authority: authority, + } +} + +// SetLegacyParamSubspace sets the param subspace for migration purposes +func (k *Keeper) SetLegacyParamSubspace(ps paramtypes.Subspace) { + k.paramSpace = ps +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx context.Context) log.Logger { + sdkCtx := sdk.UnwrapSDKContext(ctx) + return sdkCtx.Logger().With("module", "x/"+types.ModuleName) +} + +// GetMinter returns the minter +func (k Keeper) GetMinter(ctx context.Context) (minter types.Minter, err error) { + store := k.storeService.OpenKVStore(ctx) + b, err := store.Get([]byte(types.MinterKey)) + if err != nil { + return minter, err + } + if b == nil { + return minter, nil + } + + k.cdc.MustUnmarshal(b, &minter) + return minter, nil +} + +// SetMinter sets the minter +func (k Keeper) SetMinter(ctx context.Context, minter types.Minter) error { + store := k.storeService.OpenKVStore(ctx) + b := k.cdc.MustMarshal(&minter) + return store.Set([]byte(types.MinterKey), b) +} + +// GetParams returns the total set of minting parameters. +func (k Keeper) GetParams(ctx context.Context) (params types.Params, err error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + if k.paramSpace.HasKeyTable() { + k.paramSpace.GetParamSet(sdkCtx, ¶ms) + return params, nil + } + return params, nil +} + +// SetParams sets the total set of minting parameters. +func (k Keeper) SetParams(ctx context.Context, params types.Params) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + k.paramSpace.SetParamSet(sdkCtx, ¶ms) + return nil +} + +// StakingTokenSupply implements an alias call to the underlying staking keeper's +// StakingTokenSupply to be used in BeginBlocker. +func (k Keeper) StakingTokenSupply(ctx context.Context) (math.Int, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + return k.stakingKeeper.StakingTokenSupply(sdkCtx), nil +} + +// BondedRatio implements an alias call to the underlying staking keeper's +// BondedRatio to be used in BeginBlocker. +func (k Keeper) BondedRatio(ctx context.Context) (math.LegacyDec, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + return k.stakingKeeper.BondedRatio(sdkCtx), nil +} + +// MintCoins implements an alias call to the underlying supply keeper's +// MintCoins to be used in BeginBlocker. +func (k Keeper) MintCoins(ctx context.Context, newCoins sdk.Coins) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + if newCoins.Empty() { + // skip as no coins need to be minted + return nil + } + + return k.bankKeeper.MintCoins(sdkCtx, types.ModuleName, newCoins) +} + +// AddCollectedFees implements an alias call to the underlying supply keeper's +// SendCoinsFromModuleToModule to be used in BeginBlocker. +func (k Keeper) AddCollectedFees(ctx context.Context, fees sdk.Coins) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + return k.bankKeeper.SendCoinsFromModuleToModule(sdkCtx, types.ModuleName, k.feeCollectorName, fees) +} diff --git a/x/mint/module.go b/x/mint/module.go new file mode 100644 index 000000000..41817c9eb --- /dev/null +++ b/x/mint/module.go @@ -0,0 +1,154 @@ +package mint + +import ( + "context" + "encoding/json" + "fmt" + + "cosmossdk.io/core/appmodule" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/scrtlabs/SecretNetwork/x/mint/keeper" + "github.com/scrtlabs/SecretNetwork/x/mint/types" + "github.com/spf13/cobra" +) + +var ( + _ module.AppModuleBasic = AppModuleBasic{} + _ module.HasGenesis = AppModule{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} +) + +// AppModuleBasic defines the basic application module used by the mint module. +type AppModuleBasic struct { + cdc codec.Codec +} + +// Name returns the mint module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the mint module's types on the given LegacyAmino codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {} + +// RegisterInterfaces registers the module's interface types +func (b AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {} + +// DefaultGenesis returns default genesis state as raw bytes for the mint module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the mint module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var data types.GenesisState + if err := cdc.UnmarshalJSON(bz, &data); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return data.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the mint module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd returns no root tx command for the mint module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd returns the root query command for the mint module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +// AppModule implements an application module for the mint module. +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + authKeeper types.AccountKeeper + + // legacySubspace is used for migration + legacySubspace paramtypes.Subspace +} + +// NewAppModule creates a new AppModule object +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, + ak types.AccountKeeper, + ss paramtypes.Subspace, +) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + authKeeper: ak, + legacySubspace: ss, + } +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + +// Name returns the mint module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants registers the mint module invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + // Register legacy param subspace for migration + am.keeper.SetLegacyParamSubspace(am.legacySubspace) +} + +// InitGenesis performs genesis initialization for the mint module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + + am.keeper.SetMinter(ctx, genesisState.Minter) + am.keeper.SetParams(ctx, genesisState.Params) +} + +// ExportGenesis returns the exported genesis state as raw bytes for the mint module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + minter, _ := am.keeper.GetMinter(ctx) + params, _ := am.keeper.GetParams(ctx) + gs := types.NewGenesisState(minter, params) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock returns the begin blocker for the mint module. +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper) +} + +// GenerateGenesisState creates a randomized GenState of the mint module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) {} + +// RegisterStoreDecoder registers a decoder for mint module's types. +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) {} + +// WeightedOperations doesn't return any mint module operation. +func (AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { + return nil +} diff --git a/x/mint/types/expected_keepers.go b/x/mint/types/expected_keepers.go new file mode 100644 index 000000000..697a6b204 --- /dev/null +++ b/x/mint/types/expected_keepers.go @@ -0,0 +1,26 @@ +package types + +import ( + "context" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// StakingKeeper defines the expected staking keeper +type StakingKeeper interface { + StakingTokenSupply(ctx context.Context) math.Int + BondedRatio(ctx context.Context) math.LegacyDec +} + +// AccountKeeper defines the contract required for account APIs. +type AccountKeeper interface { + GetModuleAddress(name string) sdk.AccAddress +} + +// BankKeeper defines the contract needed to be fulfilled for banking and supply +// dependencies. +type BankKeeper interface { + SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error + MintCoins(ctx context.Context, name string, amt sdk.Coins) error +} diff --git a/x/mint/types/genesis.go b/x/mint/types/genesis.go new file mode 100644 index 000000000..68b87f596 --- /dev/null +++ b/x/mint/types/genesis.go @@ -0,0 +1,34 @@ +package types + +// NewGenesisState creates a new GenesisState object +func NewGenesisState(minter Minter, params Params) *GenesisState { + return &GenesisState{ + Minter: minter, + Params: params, + } +} + +// DefaultGenesisState creates a default GenesisState object +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Minter: DefaultMinter(), + Params: DefaultParams(), + } +} + +// GenesisState defines the mint module's genesis state. +type GenesisState struct { + // Minter is the current minter + Minter Minter `json:"minter"` + // Params defines all the parameters of the mint module. + Params Params `json:"params"` +} + +// Validate performs basic validation of mint genesis data. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return err + } + + return nil +} diff --git a/x/mint/types/keys.go b/x/mint/types/keys.go new file mode 100644 index 000000000..5144097b2 --- /dev/null +++ b/x/mint/types/keys.go @@ -0,0 +1,12 @@ +package types + +const ( + // ModuleName defines the module name + ModuleName = "mint" + + // StoreKey defines the primary module store key + StoreKey = ModuleName + + // MinterKey is the key to use for the keeper store + MinterKey = "Minter" +) diff --git a/x/mint/types/minter.go b/x/mint/types/minter.go new file mode 100644 index 000000000..18312b4d8 --- /dev/null +++ b/x/mint/types/minter.go @@ -0,0 +1,32 @@ +package types + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Minter represents the minting state. +type Minter struct { + // AnnualProvisions tracks current annual expected provisions + // This is purely informational for queries + AnnualProvisions math.LegacyDec `json:"annual_provisions"` +} + +// NewMinter returns a new Minter object +func NewMinter(annualProvisions math.LegacyDec) Minter { + return Minter{ + AnnualProvisions: annualProvisions, + } +} + +// DefaultMinter returns a default initial Minter object +func DefaultMinter() Minter { + return NewMinter(math.LegacyNewDec(0)) +} + +// BlockProvision returns the fixed provisions for a block based on the fixed block reward parameter. +// Unlike the standard SDK mint module which calculates based on inflation and supply, +// this returns a constant amount per block. +func (m Minter) BlockProvision(params Params) sdk.Coin { + return sdk.NewCoin(params.MintDenom, params.FixedBlockReward) +} diff --git a/x/mint/types/params.go b/x/mint/types/params.go new file mode 100644 index 000000000..4ceb0d089 --- /dev/null +++ b/x/mint/types/params.go @@ -0,0 +1,113 @@ +package types + +import ( + "fmt" + + "cosmossdk.io/math" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// Parameter store keys +var ( + KeyMintDenom = []byte("MintDenom") + KeyFixedBlockReward = []byte("FixedBlockReward") + KeyBlocksPerYear = []byte("BlocksPerYear") +) + +// ParamKeyTable returns the parameter key table for the mint module +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// Params holds parameters for the mint module +type Params struct { + // MintDenom is the type of coin to mint + MintDenom string `json:"mint_denom"` + // FixedBlockReward is the fixed amount of tokens minted per block (in base denom) + FixedBlockReward math.Int `json:"fixed_block_reward"` + // BlocksPerYear is expected blocks per year (used for annual provisions calculation) + BlocksPerYear uint64 `json:"blocks_per_year"` +} + +// NewParams creates a new Params instance +func NewParams(mintDenom string, fixedBlockReward math.Int, blocksPerYear uint64) Params { + return Params{ + MintDenom: mintDenom, + FixedBlockReward: fixedBlockReward, + BlocksPerYear: blocksPerYear, + } +} + +// DefaultParams returns default minting module parameters +// 4 SCRT per block = 4,000,000 uscrt per block +// BlocksPerYear is a governance-adjustable target (not derived from block time assumptions) +// This default is only used for genesis/tests; upgrades preserve the existing chain value +func DefaultParams() Params { + return Params{ + MintDenom: "uscrt", + FixedBlockReward: math.NewInt(4_000_000), // 4 SCRT = 4,000,000 uscrt + BlocksPerYear: 6_311_520, // Standard cosmos-sdk default (governance-adjustable target) + } +} + +// Validate validates params +func (p Params) Validate() error { + if err := validateMintDenom(p.MintDenom); err != nil { + return err + } + if err := validateFixedBlockReward(p.FixedBlockReward); err != nil { + return err + } + if err := validateBlocksPerYear(p.BlocksPerYear); err != nil { + return err + } + return nil +} + +func validateMintDenom(i interface{}) error { + v, ok := i.(string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if v == "" { + return fmt.Errorf("mint denom cannot be empty") + } + + return nil +} + +func validateFixedBlockReward(i interface{}) error { + v, ok := i.(math.Int) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if v.IsNegative() { + return fmt.Errorf("fixed block reward cannot be negative: %s", v) + } + + return nil +} + +func validateBlocksPerYear(i interface{}) error { + v, ok := i.(uint64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if v == 0 { + return fmt.Errorf("blocks per year must be positive") + } + + return nil +} + +// ParamSetPairs implements params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyMintDenom, &p.MintDenom, validateMintDenom), + paramtypes.NewParamSetPair(KeyFixedBlockReward, &p.FixedBlockReward, validateFixedBlockReward), + paramtypes.NewParamSetPair(KeyBlocksPerYear, &p.BlocksPerYear, validateBlocksPerYear), + } +}