Skip to content

restructure and refactor the emissions keeper#919

Open
zale144 wants to merge 1 commit intodevfrom
alek/restructure-emissions-keeper
Open

restructure and refactor the emissions keeper#919
zale144 wants to merge 1 commit intodevfrom
alek/restructure-emissions-keeper

Conversation

@zale144
Copy link
Contributor

@zale144 zale144 commented Feb 26, 2026

Purpose of Changes and their Description

Link(s) to Ticket(s) or Issue(s) resolved by this PR

Are these changes tested and documented?

  • If tested, please describe how. If not, why tests are not needed.
  • If documented, please describe where. If not, describe why docs are not needed.
  • Added to Unreleased section of CHANGELOG.md?

Still Left Todo

Fill this out if this is a Draft PR so others can help.


Summary by cubic

Restructured the emissions keeper into focused sub-keepers and rewired msg/query servers to use them, improving readability, testability, and separation of concerns with no intended behavior changes.

  • Refactors

    • Split monolithic Keeper into: ParamsKeeper, TopicKeeper, NonceKeeper, StakingKeeper, BankingKeeper, ScoresKeeper, WeightsKeeper, RegretsKeeper, ReputerLossKeeper, ActorPenaltiesKeeper, WhitelistsKeeper, WorkerKeeper.
    • Moved EMA scores, reward fractions, and liveness penalties into ScoresKeeper/ActorPenaltiesKeeper.
    • Added BankingKeeper wrapper for bank/account ops; introduced Mint/params helpers.
    • Rewrote MsgServer/QueryServer to depend on sub-keepers; centralized address validation via keeper.ValidateStringIsBech32.
    • Updated genesis init/export and invariants to use sub-keepers.
    • Introduced new collections per keeper and added focused unit tests across nonces, params, scores, staking, topics, weights, regrets, and reputer losses.
    • Removed large legacy code from keeper.go and reworked tests to call sub-keepers.
  • Migration

    • No on-chain/state migration; message and query APIs are unchanged.
    • Internal code should replace direct keeper calls, e.g.:
      • k.SetInfererNetworkRegret -> k.GetRegretsKeeper().SetInfererNetworkRegret
      • k.GetParams -> k.GetParamsKeeper().GetParams
      • k.IsWorkerNonceUnfulfilled -> k.GetNonceKeeper().IsWorkerNonceUnfulfilled
      • k.InsertActiveForecasts/Inferences -> k.GetWorkerKeeper().InsertActiveForecasts/Inferences
      • Use keeper.ValidateStringIsBech32 for address checks.

Written for commit 23a7d31. Summary will update on new commits.

@zale144 zale144 self-assigned this Feb 26, 2026
@greptile-apps
Copy link

greptile-apps bot commented Feb 26, 2026

Too many files changed for review. (131 files found, 100 file limit)

@zale144 zale144 mentioned this pull request Feb 26, 2026
3 tasks
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

26 issues found across 131 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="x/emissions/migrations/v4/migrate.go">

<violation number="1" location="x/emissions/migrations/v4/migrate.go:62">
P3: Use a `var` declaration for this zero-value struct that is populated via unmarshalling, which eliminates the need for the `//nolint:exhaustruct` directive.

(Based on your team's feedback about using var declarations for zero-value structs populated later.) [FEEDBACK_USED]</violation>
</file>

<file name="x/emissions/keeper/topic.go">

<violation number="1" location="x/emissions/keeper/topic.go:202">
P2: Copy-paste error in error message: says `"error getting topic fee revenue"` but this function retrieves the total sum of previous topic weights, not topic fee revenue. This will produce misleading log messages during debugging.</violation>
</file>

<file name="x/emissions/keeper/score_utils.go">

<violation number="1" location="x/emissions/keeper/score_utils.go:154">
P2: Address validation in `SetLowestReputerScoreEma` will reject the zero-value `Score` returned by `GetLowestScoreFromAllReputers` when all score lookups fail (since `Address` will be empty). The `Update*` methods chain these calls without handling this edge case. Consider either skipping the `Set` call when no valid scores were found, or having `GetLowestScoreFromAll*` return a distinct error/flag when all lookups fail.</violation>
</file>

<file name="x/emissions/keeper/queryserver/query_server.go">

<violation number="1" location="x/emissions/keeper/queryserver/query_server.go:8">
P3: Use a zero-value `var` declaration to avoid the `//nolint:exhaustruct` directive. For example:
```go
var (
	_qs                          queryServer
	_ types.QueryServiceServer = _qs
)

This creates a zero-value struct without a composite literal, so exhaustruct won't flag it.

(Based on your team's feedback about using var declarations instead of composite literals to avoid exhaustruct nolint directives.) [FEEDBACK_USED]

P0: Potential panic: `binary.BigEndian.Uint64` requires at least 8 bytes, but only `len > 0` is checked. A pagination key with 1–7 bytes will cause an index-out-of-range panic, which could crash the node from a malformed client request. P2: Copy-paste error in comment: says `oneOutInferer` but the field stores data keyed by `oneOutForecaster`. This will mislead developers about what the second key component represents. P3: Inconsistent error-path return: `GetInfererNetworkRegret` returns `TimestampedValue{BlockHeight: 0, Value: alloraMath.ZeroDec()}` on error, while every other Get method returns `types.TimestampedValue{}`. Consider aligning with the other methods for consistency. P3: Use `var oldParams oldV4Types.Params` instead of a composite literal with a `//nolint:exhaustruct` directive. Since this struct is immediately populated via `proto.Unmarshal`, a `var` declaration is cleaner and avoids the need for the nolint comment.

(Based on your team's feedback about using var declarations for zero-value structs populated later.) [FEEDBACK_USED]

P2: Missing `weight` validation in `SetLatestInfererWeight`: unlike `SetLatestRegretScale` which calls `types.ValidateDec(regretScale)`, this setter stores the `weight` value without any validation. This could allow invalid Dec values (e.g., NaN) to be persisted. Consider adding `types.ValidateDec(weight)` for consistency. P2: Missing `weight` validation in `SetLatestForecasterWeight`: same issue as `SetLatestInfererWeight` — the `weight` Dec value is not validated before being stored, unlike `SetLatestRegretScale` which calls `types.ValidateDec`. Consider adding `types.ValidateDec(weight)` for consistency. P3: Typo in method name: `WeightstsKeeper()` should be `WeightsKeeper()`. The method has an extra "ts" in the name ("Weights**ts**Keeper" vs "WeightsKeeper"). Since this PR is restructuring the keeper, this is a good opportunity to fix the method name in `test_utils.go` and all call sites. P2: All methods in this file use value receiver `(k Keeper)` instead of pointer receiver `(k *Keeper)`, which is inconsistent with the rest of the codebase (29 of 30 existing `Keeper` methods use pointer receivers). This also causes the entire `Keeper` struct to be copied on every call, which is unnecessary overhead given the struct contains many fields. P1: Missing `defer iter.Close()` — iterator resource leak. Every other `Iterate` call in this file is followed by `defer iter.Close()`, but this one is not. This can leak resources, especially under repeated calls. P2: Incorrect struct field comments for `infererScoreEmas`, `forecasterScoreEmas`, and `reputerScoreEmas`: they describe the key as `(topic, block_height, worker)` but the actual key type is `Pair[TopicId, ActorId]` — there is no `block_height` in the key. These appear to be copy-paste errors from the `*ScoresByBlock` fields. P2: Inconsistent error return: `GetReputerScoreEma` returns a populated `Score` struct on error, while the analogous `GetInfererScoreEma` and `GetForecasterScoreEma` return an empty `types.Score{}`. Returning a plausible value alongside an error risks callers using the value without checking the error. Align with the other getters by returning an empty struct on error. P1: `GetPreviousPercentageRewardToStakedReputers` doesn't handle `collections.ErrNotFound`, unlike every other getter in this file. When the value hasn't been set yet, callers will receive an unexpected error instead of a sensible default. P3: Inconsistent package used for `BlockHeight` type: `RemoveDelegateStakeFn` uses `emissionskeeper.BlockHeight` while all other function types in this file use `emissionstypes.BlockHeight`. Use `emissionstypes.BlockHeight` consistently. P1: Bug: when `!found` is true (and `err` is nil), `errorsmod.Wrap(err, ...)` returns `nil` because `err` is nil at this point (already checked in the prior branch). The function returns `(nil, nil)` — no response and no error — which is incorrect. Use `status.Errorf` or `errorsmod.New` instead of wrapping a nil error. Same issue in `GetCurrentLowestForecasterScore` and `GetCurrentLowestReputerScore`. P1: Bug: error check is evaluated after `!topicExists`, so if `TopicExists` returns an error, `topicExists` defaults to `false` and the code returns a misleading "topic not found" instead of the actual error. Check `err != nil` first. This same pattern appears in all three `GetTopicInitial*EmaScore` functions. P3: Use `(*msgServer)(nil)` instead of `msgServer{}` for the interface compliance check. This avoids the `exhaustruct` warning without needing the `//nolint` directive.

(Based on your team's feedback about using var declarations to avoid exhaustruct warnings and nolint directives.) [FEEDBACK_USED]

P1: Nil pointer dereference: `nonce.Validate()` is called before the `nonce == nil` check on line 245. If `nonce` is nil, this will panic. Move the nil check before `Validate()`. P2: Inconsistent error handling: `PruneWorkerNonces` directly accesses the collection and returns an error on `ErrNotFound`, while `PruneReputerNonces` uses `GetUnfulfilledReputerNonces` which handles `ErrNotFound` gracefully. Use the helper here too for consistent behavior (no error when there's nothing to prune). P2: Potential nil pointer dereference: `nonce.ReputerNonce.BlockHeight` is accessed without checking if `nonce` or `nonce.ReputerNonce` is nil. Other methods in this file (e.g., `IsReputerNonceUnfulfilled`, `GetOpenReputerSubmissionWindows`) include nil guards in their loops. P1: Bug: `previousEmaScore` is not updated after setting the initial EMA score for first-time reputers. The local variable retains `Score: ZeroDec()` and `BlockHeight: 0` from the initial `GetReputerScoreEma` call, but the code later uses it for lowest-score comparisons and to set the lowest EMA tracker. This causes incorrect lowest score tracking and potentially wrong admission/rejection decisions for new reputers.

After calling SetReputerScoreEma for the initial score, update previousEmaScore to reflect the newly stored value.

P2: `GetBankBalance` is an exact duplicate of `GetBalance` — both delegate to `k.bankKeeper.GetBalance` with the same signature. One of these methods should be removed and callers consolidated to use the other. P1: `MoveCoinsFromAlloraRewardsToEcosystem` checks for zero but not negative amounts. `sdk.NewCoin` panics on negative values, so a negative `amount` will cause a runtime panic. Add a negativity check or use `amount.IsNonPositive()`. ```

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


if pagination != nil {
if len(pagination.Key) > 0 {
cursor = binary.BigEndian.Uint64(pagination.Key)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Potential panic: binary.BigEndian.Uint64 requires at least 8 bytes, but only len > 0 is checked. A pagination key with 1–7 bytes will cause an index-out-of-range panic, which could crash the node from a malformed client request.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/params.go, line 54:

<comment>Potential panic: `binary.BigEndian.Uint64` requires at least 8 bytes, but only `len > 0` is checked. A pagination key with 1–7 bytes will cause an index-out-of-range panic, which could crash the node from a malformed client request.</comment>

<file context>
@@ -0,0 +1,65 @@
+
+	if pagination != nil {
+		if len(pagination.Key) > 0 {
+			cursor = binary.BigEndian.Uint64(pagination.Key)
+		}
+		if pagination.Limit > 0 {
</file context>

topicId uint64,
) (removal types.DelegateStakeRemovalInfo, found bool, err error) {
rng := NewTriplePrefixedQuadrupleRange[ActorId, ActorId, TopicId, BlockHeight](delegator, reputer, topicId)
iter, err := k.delegateStakeRemovalsByActor.Iterate(ctx, rng)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing defer iter.Close() — iterator resource leak. Every other Iterate call in this file is followed by defer iter.Close(), but this one is not. This can leak resources, especially under repeated calls.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/staking.go, line 512:

<comment>Missing `defer iter.Close()` — iterator resource leak. Every other `Iterate` call in this file is followed by `defer iter.Close()`, but this one is not. This can leak resources, especially under repeated calls.</comment>

<file context>
@@ -0,0 +1,953 @@
+	topicId uint64,
+) (removal types.DelegateStakeRemovalInfo, found bool, err error) {
+	rng := NewTriplePrefixedQuadrupleRange[ActorId, ActorId, TopicId, BlockHeight](delegator, reputer, topicId)
+	iter, err := k.delegateStakeRemovalsByActor.Iterate(ctx, rng)
+	if err != nil {
+		return types.DelegateStakeRemovalInfo{}, false, errorsmod.Wrap(err, "error iterating over delegate stake removals by actor")
</file context>

return k.previousPercentageRewardToStakedReputers.Set(ctx, percentageRewardToStakedReputers)
}

func (k *ScoresKeeper) GetPreviousPercentageRewardToStakedReputers(ctx context.Context) (alloraMath.Dec, error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: GetPreviousPercentageRewardToStakedReputers doesn't handle collections.ErrNotFound, unlike every other getter in this file. When the value hasn't been set yet, callers will receive an unexpected error instead of a sensible default.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/scores.go, line 628:

<comment>`GetPreviousPercentageRewardToStakedReputers` doesn't handle `collections.ErrNotFound`, unlike every other getter in this file. When the value hasn't been set yet, callers will receive an unexpected error instead of a sensible default.</comment>

<file context>
@@ -0,0 +1,704 @@
+	return k.previousPercentageRewardToStakedReputers.Set(ctx, percentageRewardToStakedReputers)
+}
+
+func (k *ScoresKeeper) GetPreviousPercentageRewardToStakedReputers(ctx context.Context) (alloraMath.Dec, error) {
+	return k.previousPercentageRewardToStakedReputers.Get(ctx)
+}
</file context>

Comment on lines +181 to 186
topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
if !topicExists {
return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
} else if err != nil {
return nil, err
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Bug: error check is evaluated after !topicExists, so if TopicExists returns an error, topicExists defaults to false and the code returns a misleading "topic not found" instead of the actual error. Check err != nil first. This same pattern appears in all three GetTopicInitial*EmaScore functions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/queryserver/query_server_score.go, line 181:

<comment>Bug: error check is evaluated after `!topicExists`, so if `TopicExists` returns an error, `topicExists` defaults to `false` and the code returns a misleading "topic not found" instead of the actual error. Check `err != nil` first. This same pattern appears in all three `GetTopicInitial*EmaScore` functions.</comment>

<file context>
@@ -177,14 +178,14 @@ func (qs queryServer) GetListeningCoefficient(ctx context.Context, req *types.Ge
 	defer metrics.RecordMetrics("GetTopicInitialInfererEmaScore", time.Now(), &err)
 
-	topicExists, err := qs.k.TopicExists(ctx, req.TopicId)
+	topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
 	if !topicExists {
 		return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
</file context>
Suggested change
topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
if !topicExists {
return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
} else if err != nil {
return nil, err
}
topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
if err != nil {
return nil, err
} else if !topicExists {
return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
}

func (qs queryServer) GetCurrentLowestInfererScore(ctx context.Context, req *types.GetCurrentLowestInfererScoreRequest) (_ *types.GetCurrentLowestInfererScoreResponse, err error) {
defer metrics.RecordMetrics("GetCurrentLowestInfererScore", time.Now(), &err)
lowestInfererScore, found, err := qs.k.GetLowestInfererScoreEma(ctx, req.TopicId)
lowestInfererScore, found, err := qs.sck.GetLowestInfererScoreEma(ctx, req.TopicId)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Bug: when !found is true (and err is nil), errorsmod.Wrap(err, ...) returns nil because err is nil at this point (already checked in the prior branch). The function returns (nil, nil) — no response and no error — which is incorrect. Use status.Errorf or errorsmod.New instead of wrapping a nil error. Same issue in GetCurrentLowestForecasterScore and GetCurrentLowestReputerScore.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/queryserver/query_server_score.go, line 103:

<comment>Bug: when `!found` is true (and `err` is nil), `errorsmod.Wrap(err, ...)` returns `nil` because `err` is nil at this point (already checked in the prior branch). The function returns `(nil, nil)` — no response and no error — which is incorrect. Use `status.Errorf` or `errorsmod.New` instead of wrapping a nil error. Same issue in `GetCurrentLowestForecasterScore` and `GetCurrentLowestReputerScore`.</comment>

<file context>
@@ -99,7 +100,7 @@ func (qs queryServer) GetWorkerInferenceScoresAtBlock(ctx context.Context, req *
 func (qs queryServer) GetCurrentLowestInfererScore(ctx context.Context, req *types.GetCurrentLowestInfererScoreRequest) (_ *types.GetCurrentLowestInfererScoreResponse, err error) {
 	defer metrics.RecordMetrics("GetCurrentLowestInfererScore", time.Now(), &err)
-	lowestInfererScore, found, err := qs.k.GetLowestInfererScoreEma(ctx, req.TopicId)
+	lowestInfererScore, found, err := qs.sck.GetLowestInfererScoreEma(ctx, req.TopicId)
 	if err != nil {
 		return nil, errorsmod.Wrap(err, "error getting lowest inferer score EMA")
</file context>

if errors.Is(err, collections.ErrNotFound) {
topic, err := k.topicKeeper.GetTopic(ctx, topicId)
if err != nil {
return types.TimestampedValue{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Inconsistent error-path return: GetInfererNetworkRegret returns TimestampedValue{BlockHeight: 0, Value: alloraMath.ZeroDec()} on error, while every other Get method returns types.TimestampedValue{}. Consider aligning with the other methods for consistency.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/regrets.go, line 76:

<comment>Inconsistent error-path return: `GetInfererNetworkRegret` returns `TimestampedValue{BlockHeight: 0, Value: alloraMath.ZeroDec()}` on error, while every other Get method returns `types.TimestampedValue{}`. Consider aligning with the other methods for consistency.</comment>

<file context>
@@ -0,0 +1,403 @@
+	if errors.Is(err, collections.ErrNotFound) {
+		topic, err := k.topicKeeper.GetTopic(ctx, topicId)
+		if err != nil {
+			return types.TimestampedValue{
+				BlockHeight: 0,
+				Value:       alloraMath.ZeroDec(),
</file context>

@@ -58,7 +59,7 @@ func MigrateStore(ctx sdk.Context, emissionsKeeper keeper.Keeper) error {
// migrate params for this new version
// the changes are the addition of InitialRegretQuantile,PNormSafeDiv
func MigrateParams(store storetypes.KVStore, cdc codec.BinaryCodec) error {
oldParams := oldV4Types.Params{} //nolint: exhaustruct // empty struct used by cosmos-sdk Unmarshal below
oldParams := oldV4Types.Params{} //nolint:exhaustruct // empty struct used by cosmos-sdk Unmarshal below
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Use var oldParams oldV4Types.Params instead of a composite literal with a //nolint:exhaustruct directive. Since this struct is immediately populated via proto.Unmarshal, a var declaration is cleaner and avoids the need for the nolint comment.

(Based on your team's feedback about using var declarations for zero-value structs populated later.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/migrations/v5/migrate.go, line 62:

<comment>Use `var oldParams oldV4Types.Params` instead of a composite literal with a `//nolint:exhaustruct` directive. Since this struct is immediately populated via `proto.Unmarshal`, a `var` declaration is cleaner and avoids the need for the nolint comment.

(Based on your team's feedback about using var declarations for zero-value structs populated later.) </comment>

<file context>
@@ -58,7 +59,7 @@ func MigrateStore(ctx sdk.Context, emissionsKeeper keeper.Keeper) error {
 // the changes are the addition of InitialRegretQuantile,PNormSafeDiv
 func MigrateParams(store storetypes.KVStore, cdc codec.BinaryCodec) error {
-	oldParams := oldV4Types.Params{} //nolint: exhaustruct // empty struct used by cosmos-sdk Unmarshal below
+	oldParams := oldV4Types.Params{} //nolint:exhaustruct // empty struct used by cosmos-sdk Unmarshal below
 	oldParamsBytes := store.Get(emissionstypes.ParamsKey)
 	if oldParamsBytes == nil {
</file context>
Suggested change
oldParams := oldV4Types.Params{} //nolint:exhaustruct // empty struct used by cosmos-sdk Unmarshal below
var oldParams oldV4Types.Params // empty struct used by cosmos-sdk Unmarshal below

// Verify stored weights
for worker, expectedWeight := range weights.Inferers { // nolint: maprange // reason: order not relevant
storedWeight, err := s.EmissionsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
storedWeight, err := s.WeightstsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Typo in method name: WeightstsKeeper() should be WeightsKeeper(). The method has an extra "ts" in the name ("WeightstsKeeper" vs "WeightsKeeper"). Since this PR is restructuring the keeper, this is a good opportunity to fix the method name in test_utils.go and all call sites.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/inference_synthesis/weights_test.go, line 154:

<comment>Typo in method name: `WeightstsKeeper()` should be `WeightsKeeper()`. The method has an extra "ts" in the name ("Weights**ts**Keeper" vs "WeightsKeeper"). Since this PR is restructuring the keeper, this is a good opportunity to fix the method name in `test_utils.go` and all call sites.</comment>

<file context>
@@ -151,7 +151,7 @@ func (s *WeightsTestSuite) TestStoreLatestNormalizedWeights() {
 		// Verify stored weights
 		for worker, expectedWeight := range weights.Inferers { // nolint: maprange // reason: order not relevant
-			storedWeight, err := s.EmissionsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
+			storedWeight, err := s.WeightstsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
 			s.Require().NoError(err)
 			s.Require().True(expectedWeight.Equal(storedWeight))
</file context>

@@ -108,14 +97,30 @@ func RemoveStakes(
return nil
}

type RemoveDelegateStakeFn func(
ctx context.Context,
stakeRemovalBlockHeight emissionskeeper.BlockHeight,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Inconsistent package used for BlockHeight type: RemoveDelegateStakeFn uses emissionskeeper.BlockHeight while all other function types in this file use emissionstypes.BlockHeight. Use emissionstypes.BlockHeight consistently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/module/stake_removals.go, line 102:

<comment>Inconsistent package used for `BlockHeight` type: `RemoveDelegateStakeFn` uses `emissionskeeper.BlockHeight` while all other function types in this file use `emissionstypes.BlockHeight`. Use `emissionstypes.BlockHeight` consistently.</comment>

<file context>
@@ -108,14 +97,30 @@ func RemoveStakes(
 
+type RemoveDelegateStakeFn func(
+	ctx context.Context,
+	stakeRemovalBlockHeight emissionskeeper.BlockHeight,
+	topicId emissionskeeper.TopicId,
+	delegator, reputer emissionskeeper.ActorId,
</file context>
Suggested change
stakeRemovalBlockHeight emissionskeeper.BlockHeight,
stakeRemovalBlockHeight emissionstypes.BlockHeight,

Comment on lines +22 to +23
//nolint:exhaustruct
var _ types.MsgServiceServer = msgServer{}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Use (*msgServer)(nil) instead of msgServer{} for the interface compliance check. This avoids the exhaustruct warning without needing the //nolint directive.

(Based on your team's feedback about using var declarations to avoid exhaustruct warnings and nolint directives.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/msgserver/msg_server.go, line 22:

<comment>Use `(*msgServer)(nil)` instead of `msgServer{}` for the interface compliance check. This avoids the `exhaustruct` warning without needing the `//nolint` directive.

(Based on your team's feedback about using var declarations to avoid exhaustruct warnings and nolint directives.) </comment>

<file context>
@@ -1,20 +1,42 @@
 }
 
-var _ types.MsgServiceServer = msgServer{k: keeper.Keeper{}}
+//nolint:exhaustruct
+var _ types.MsgServiceServer = msgServer{}
 
</file context>
Suggested change
//nolint:exhaustruct
var _ types.MsgServiceServer = msgServer{}
var _ types.MsgServiceServer = (*msgServer)(nil)

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

26 issues found across 131 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="x/emissions/migrations/v4/migrate.go">

<violation number="1" location="x/emissions/migrations/v4/migrate.go:62">
P3: Use a `var` declaration for this zero-value struct that is populated via unmarshalling, which eliminates the need for the `//nolint:exhaustruct` directive.

(Based on your team's feedback about using var declarations for zero-value structs populated later.) [FEEDBACK_USED]</violation>
</file>

<file name="x/emissions/keeper/topic.go">

<violation number="1" location="x/emissions/keeper/topic.go:202">
P2: Copy-paste error in error message: says `"error getting topic fee revenue"` but this function retrieves the total sum of previous topic weights, not topic fee revenue. This will produce misleading log messages during debugging.</violation>
</file>

<file name="x/emissions/keeper/score_utils.go">

<violation number="1" location="x/emissions/keeper/score_utils.go:154">
P2: Address validation in `SetLowestReputerScoreEma` will reject the zero-value `Score` returned by `GetLowestScoreFromAllReputers` when all score lookups fail (since `Address` will be empty). The `Update*` methods chain these calls without handling this edge case. Consider either skipping the `Set` call when no valid scores were found, or having `GetLowestScoreFromAll*` return a distinct error/flag when all lookups fail.</violation>
</file>

<file name="x/emissions/keeper/queryserver/query_server.go">

<violation number="1" location="x/emissions/keeper/queryserver/query_server.go:8">
P3: Use a zero-value `var` declaration to avoid the `//nolint:exhaustruct` directive. For example:
```go
var (
	_qs                          queryServer
	_ types.QueryServiceServer = _qs
)

This creates a zero-value struct without a composite literal, so exhaustruct won't flag it.

(Based on your team's feedback about using var declarations instead of composite literals to avoid exhaustruct nolint directives.) [FEEDBACK_USED]

P0: Potential panic: `binary.BigEndian.Uint64` requires at least 8 bytes, but only `len > 0` is checked. A pagination key with 1–7 bytes will cause an index-out-of-range panic, which could crash the node from a malformed client request. P2: Copy-paste error in comment: says `oneOutInferer` but the field stores data keyed by `oneOutForecaster`. This will mislead developers about what the second key component represents. P3: Inconsistent error-path return: `GetInfererNetworkRegret` returns `TimestampedValue{BlockHeight: 0, Value: alloraMath.ZeroDec()}` on error, while every other Get method returns `types.TimestampedValue{}`. Consider aligning with the other methods for consistency. P3: Use `var oldParams oldV4Types.Params` instead of a composite literal with a `//nolint:exhaustruct` directive. Since this struct is immediately populated via `proto.Unmarshal`, a `var` declaration is cleaner and avoids the need for the nolint comment.

(Based on your team's feedback about using var declarations for zero-value structs populated later.) [FEEDBACK_USED]

P2: Missing `weight` validation in `SetLatestInfererWeight`: unlike `SetLatestRegretScale` which calls `types.ValidateDec(regretScale)`, this setter stores the `weight` value without any validation. This could allow invalid Dec values (e.g., NaN) to be persisted. Consider adding `types.ValidateDec(weight)` for consistency. P2: Missing `weight` validation in `SetLatestForecasterWeight`: same issue as `SetLatestInfererWeight` — the `weight` Dec value is not validated before being stored, unlike `SetLatestRegretScale` which calls `types.ValidateDec`. Consider adding `types.ValidateDec(weight)` for consistency. P3: Typo in method name: `WeightstsKeeper()` should be `WeightsKeeper()`. The method has an extra "ts" in the name ("Weights**ts**Keeper" vs "WeightsKeeper"). Since this PR is restructuring the keeper, this is a good opportunity to fix the method name in `test_utils.go` and all call sites. P2: All methods in this file use value receiver `(k Keeper)` instead of pointer receiver `(k *Keeper)`, which is inconsistent with the rest of the codebase (29 of 30 existing `Keeper` methods use pointer receivers). This also causes the entire `Keeper` struct to be copied on every call, which is unnecessary overhead given the struct contains many fields. P1: Missing `defer iter.Close()` — iterator resource leak. Every other `Iterate` call in this file is followed by `defer iter.Close()`, but this one is not. This can leak resources, especially under repeated calls. P2: Incorrect struct field comments for `infererScoreEmas`, `forecasterScoreEmas`, and `reputerScoreEmas`: they describe the key as `(topic, block_height, worker)` but the actual key type is `Pair[TopicId, ActorId]` — there is no `block_height` in the key. These appear to be copy-paste errors from the `*ScoresByBlock` fields. P2: Inconsistent error return: `GetReputerScoreEma` returns a populated `Score` struct on error, while the analogous `GetInfererScoreEma` and `GetForecasterScoreEma` return an empty `types.Score{}`. Returning a plausible value alongside an error risks callers using the value without checking the error. Align with the other getters by returning an empty struct on error. P1: `GetPreviousPercentageRewardToStakedReputers` doesn't handle `collections.ErrNotFound`, unlike every other getter in this file. When the value hasn't been set yet, callers will receive an unexpected error instead of a sensible default. P3: Inconsistent package used for `BlockHeight` type: `RemoveDelegateStakeFn` uses `emissionskeeper.BlockHeight` while all other function types in this file use `emissionstypes.BlockHeight`. Use `emissionstypes.BlockHeight` consistently. P1: Bug: when `!found` is true (and `err` is nil), `errorsmod.Wrap(err, ...)` returns `nil` because `err` is nil at this point (already checked in the prior branch). The function returns `(nil, nil)` — no response and no error — which is incorrect. Use `status.Errorf` or `errorsmod.New` instead of wrapping a nil error. Same issue in `GetCurrentLowestForecasterScore` and `GetCurrentLowestReputerScore`. P1: Bug: error check is evaluated after `!topicExists`, so if `TopicExists` returns an error, `topicExists` defaults to `false` and the code returns a misleading "topic not found" instead of the actual error. Check `err != nil` first. This same pattern appears in all three `GetTopicInitial*EmaScore` functions. P3: Use `(*msgServer)(nil)` instead of `msgServer{}` for the interface compliance check. This avoids the `exhaustruct` warning without needing the `//nolint` directive.

(Based on your team's feedback about using var declarations to avoid exhaustruct warnings and nolint directives.) [FEEDBACK_USED]

P1: Nil pointer dereference: `nonce.Validate()` is called before the `nonce == nil` check on line 245. If `nonce` is nil, this will panic. Move the nil check before `Validate()`. P2: Inconsistent error handling: `PruneWorkerNonces` directly accesses the collection and returns an error on `ErrNotFound`, while `PruneReputerNonces` uses `GetUnfulfilledReputerNonces` which handles `ErrNotFound` gracefully. Use the helper here too for consistent behavior (no error when there's nothing to prune). P2: Potential nil pointer dereference: `nonce.ReputerNonce.BlockHeight` is accessed without checking if `nonce` or `nonce.ReputerNonce` is nil. Other methods in this file (e.g., `IsReputerNonceUnfulfilled`, `GetOpenReputerSubmissionWindows`) include nil guards in their loops. P1: Bug: `previousEmaScore` is not updated after setting the initial EMA score for first-time reputers. The local variable retains `Score: ZeroDec()` and `BlockHeight: 0` from the initial `GetReputerScoreEma` call, but the code later uses it for lowest-score comparisons and to set the lowest EMA tracker. This causes incorrect lowest score tracking and potentially wrong admission/rejection decisions for new reputers.

After calling SetReputerScoreEma for the initial score, update previousEmaScore to reflect the newly stored value.

P2: `GetBankBalance` is an exact duplicate of `GetBalance` — both delegate to `k.bankKeeper.GetBalance` with the same signature. One of these methods should be removed and callers consolidated to use the other. P1: `MoveCoinsFromAlloraRewardsToEcosystem` checks for zero but not negative amounts. `sdk.NewCoin` panics on negative values, so a negative `amount` will cause a runtime panic. Add a negativity check or use `amount.IsNonPositive()`. ```

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


if pagination != nil {
if len(pagination.Key) > 0 {
cursor = binary.BigEndian.Uint64(pagination.Key)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Potential panic: binary.BigEndian.Uint64 requires at least 8 bytes, but only len > 0 is checked. A pagination key with 1–7 bytes will cause an index-out-of-range panic, which could crash the node from a malformed client request.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/params.go, line 54:

<comment>Potential panic: `binary.BigEndian.Uint64` requires at least 8 bytes, but only `len > 0` is checked. A pagination key with 1–7 bytes will cause an index-out-of-range panic, which could crash the node from a malformed client request.</comment>

<file context>
@@ -0,0 +1,65 @@
+
+	if pagination != nil {
+		if len(pagination.Key) > 0 {
+			cursor = binary.BigEndian.Uint64(pagination.Key)
+		}
+		if pagination.Limit > 0 {
</file context>
Fix with Cubic

topicId uint64,
) (removal types.DelegateStakeRemovalInfo, found bool, err error) {
rng := NewTriplePrefixedQuadrupleRange[ActorId, ActorId, TopicId, BlockHeight](delegator, reputer, topicId)
iter, err := k.delegateStakeRemovalsByActor.Iterate(ctx, rng)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing defer iter.Close() — iterator resource leak. Every other Iterate call in this file is followed by defer iter.Close(), but this one is not. This can leak resources, especially under repeated calls.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/staking.go, line 512:

<comment>Missing `defer iter.Close()` — iterator resource leak. Every other `Iterate` call in this file is followed by `defer iter.Close()`, but this one is not. This can leak resources, especially under repeated calls.</comment>

<file context>
@@ -0,0 +1,953 @@
+	topicId uint64,
+) (removal types.DelegateStakeRemovalInfo, found bool, err error) {
+	rng := NewTriplePrefixedQuadrupleRange[ActorId, ActorId, TopicId, BlockHeight](delegator, reputer, topicId)
+	iter, err := k.delegateStakeRemovalsByActor.Iterate(ctx, rng)
+	if err != nil {
+		return types.DelegateStakeRemovalInfo{}, false, errorsmod.Wrap(err, "error iterating over delegate stake removals by actor")
</file context>
Fix with Cubic

return k.previousPercentageRewardToStakedReputers.Set(ctx, percentageRewardToStakedReputers)
}

func (k *ScoresKeeper) GetPreviousPercentageRewardToStakedReputers(ctx context.Context) (alloraMath.Dec, error) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: GetPreviousPercentageRewardToStakedReputers doesn't handle collections.ErrNotFound, unlike every other getter in this file. When the value hasn't been set yet, callers will receive an unexpected error instead of a sensible default.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/scores.go, line 628:

<comment>`GetPreviousPercentageRewardToStakedReputers` doesn't handle `collections.ErrNotFound`, unlike every other getter in this file. When the value hasn't been set yet, callers will receive an unexpected error instead of a sensible default.</comment>

<file context>
@@ -0,0 +1,704 @@
+	return k.previousPercentageRewardToStakedReputers.Set(ctx, percentageRewardToStakedReputers)
+}
+
+func (k *ScoresKeeper) GetPreviousPercentageRewardToStakedReputers(ctx context.Context) (alloraMath.Dec, error) {
+	return k.previousPercentageRewardToStakedReputers.Get(ctx)
+}
</file context>
Fix with Cubic

Comment on lines +181 to 186
topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
if !topicExists {
return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
} else if err != nil {
return nil, err
}
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Bug: error check is evaluated after !topicExists, so if TopicExists returns an error, topicExists defaults to false and the code returns a misleading "topic not found" instead of the actual error. Check err != nil first. This same pattern appears in all three GetTopicInitial*EmaScore functions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/queryserver/query_server_score.go, line 181:

<comment>Bug: error check is evaluated after `!topicExists`, so if `TopicExists` returns an error, `topicExists` defaults to `false` and the code returns a misleading "topic not found" instead of the actual error. Check `err != nil` first. This same pattern appears in all three `GetTopicInitial*EmaScore` functions.</comment>

<file context>
@@ -177,14 +178,14 @@ func (qs queryServer) GetListeningCoefficient(ctx context.Context, req *types.Ge
 	defer metrics.RecordMetrics("GetTopicInitialInfererEmaScore", time.Now(), &err)
 
-	topicExists, err := qs.k.TopicExists(ctx, req.TopicId)
+	topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
 	if !topicExists {
 		return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
</file context>
Suggested change
topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
if !topicExists {
return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
} else if err != nil {
return nil, err
}
topicExists, err := qs.tk.TopicExists(ctx, req.TopicId)
if err != nil {
return nil, err
} else if !topicExists {
return nil, status.Errorf(codes.NotFound, "topic %v not found", req.TopicId)
}
Fix with Cubic

func (qs queryServer) GetCurrentLowestInfererScore(ctx context.Context, req *types.GetCurrentLowestInfererScoreRequest) (_ *types.GetCurrentLowestInfererScoreResponse, err error) {
defer metrics.RecordMetrics("GetCurrentLowestInfererScore", time.Now(), &err)
lowestInfererScore, found, err := qs.k.GetLowestInfererScoreEma(ctx, req.TopicId)
lowestInfererScore, found, err := qs.sck.GetLowestInfererScoreEma(ctx, req.TopicId)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Bug: when !found is true (and err is nil), errorsmod.Wrap(err, ...) returns nil because err is nil at this point (already checked in the prior branch). The function returns (nil, nil) — no response and no error — which is incorrect. Use status.Errorf or errorsmod.New instead of wrapping a nil error. Same issue in GetCurrentLowestForecasterScore and GetCurrentLowestReputerScore.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/queryserver/query_server_score.go, line 103:

<comment>Bug: when `!found` is true (and `err` is nil), `errorsmod.Wrap(err, ...)` returns `nil` because `err` is nil at this point (already checked in the prior branch). The function returns `(nil, nil)` — no response and no error — which is incorrect. Use `status.Errorf` or `errorsmod.New` instead of wrapping a nil error. Same issue in `GetCurrentLowestForecasterScore` and `GetCurrentLowestReputerScore`.</comment>

<file context>
@@ -99,7 +100,7 @@ func (qs queryServer) GetWorkerInferenceScoresAtBlock(ctx context.Context, req *
 func (qs queryServer) GetCurrentLowestInfererScore(ctx context.Context, req *types.GetCurrentLowestInfererScoreRequest) (_ *types.GetCurrentLowestInfererScoreResponse, err error) {
 	defer metrics.RecordMetrics("GetCurrentLowestInfererScore", time.Now(), &err)
-	lowestInfererScore, found, err := qs.k.GetLowestInfererScoreEma(ctx, req.TopicId)
+	lowestInfererScore, found, err := qs.sck.GetLowestInfererScoreEma(ctx, req.TopicId)
 	if err != nil {
 		return nil, errorsmod.Wrap(err, "error getting lowest inferer score EMA")
</file context>
Fix with Cubic

if errors.Is(err, collections.ErrNotFound) {
topic, err := k.topicKeeper.GetTopic(ctx, topicId)
if err != nil {
return types.TimestampedValue{
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Inconsistent error-path return: GetInfererNetworkRegret returns TimestampedValue{BlockHeight: 0, Value: alloraMath.ZeroDec()} on error, while every other Get method returns types.TimestampedValue{}. Consider aligning with the other methods for consistency.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/regrets.go, line 76:

<comment>Inconsistent error-path return: `GetInfererNetworkRegret` returns `TimestampedValue{BlockHeight: 0, Value: alloraMath.ZeroDec()}` on error, while every other Get method returns `types.TimestampedValue{}`. Consider aligning with the other methods for consistency.</comment>

<file context>
@@ -0,0 +1,403 @@
+	if errors.Is(err, collections.ErrNotFound) {
+		topic, err := k.topicKeeper.GetTopic(ctx, topicId)
+		if err != nil {
+			return types.TimestampedValue{
+				BlockHeight: 0,
+				Value:       alloraMath.ZeroDec(),
</file context>
Fix with Cubic

@@ -58,7 +59,7 @@ func MigrateStore(ctx sdk.Context, emissionsKeeper keeper.Keeper) error {
// migrate params for this new version
// the changes are the addition of InitialRegretQuantile,PNormSafeDiv
func MigrateParams(store storetypes.KVStore, cdc codec.BinaryCodec) error {
oldParams := oldV4Types.Params{} //nolint: exhaustruct // empty struct used by cosmos-sdk Unmarshal below
oldParams := oldV4Types.Params{} //nolint:exhaustruct // empty struct used by cosmos-sdk Unmarshal below
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Use var oldParams oldV4Types.Params instead of a composite literal with a //nolint:exhaustruct directive. Since this struct is immediately populated via proto.Unmarshal, a var declaration is cleaner and avoids the need for the nolint comment.

(Based on your team's feedback about using var declarations for zero-value structs populated later.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/migrations/v5/migrate.go, line 62:

<comment>Use `var oldParams oldV4Types.Params` instead of a composite literal with a `//nolint:exhaustruct` directive. Since this struct is immediately populated via `proto.Unmarshal`, a `var` declaration is cleaner and avoids the need for the nolint comment.

(Based on your team's feedback about using var declarations for zero-value structs populated later.) </comment>

<file context>
@@ -58,7 +59,7 @@ func MigrateStore(ctx sdk.Context, emissionsKeeper keeper.Keeper) error {
 // the changes are the addition of InitialRegretQuantile,PNormSafeDiv
 func MigrateParams(store storetypes.KVStore, cdc codec.BinaryCodec) error {
-	oldParams := oldV4Types.Params{} //nolint: exhaustruct // empty struct used by cosmos-sdk Unmarshal below
+	oldParams := oldV4Types.Params{} //nolint:exhaustruct // empty struct used by cosmos-sdk Unmarshal below
 	oldParamsBytes := store.Get(emissionstypes.ParamsKey)
 	if oldParamsBytes == nil {
</file context>
Suggested change
oldParams := oldV4Types.Params{} //nolint:exhaustruct // empty struct used by cosmos-sdk Unmarshal below
var oldParams oldV4Types.Params // empty struct used by cosmos-sdk Unmarshal below
Fix with Cubic

// Verify stored weights
for worker, expectedWeight := range weights.Inferers { // nolint: maprange // reason: order not relevant
storedWeight, err := s.EmissionsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
storedWeight, err := s.WeightstsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Typo in method name: WeightstsKeeper() should be WeightsKeeper(). The method has an extra "ts" in the name ("WeightstsKeeper" vs "WeightsKeeper"). Since this PR is restructuring the keeper, this is a good opportunity to fix the method name in test_utils.go and all call sites.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/inference_synthesis/weights_test.go, line 154:

<comment>Typo in method name: `WeightstsKeeper()` should be `WeightsKeeper()`. The method has an extra "ts" in the name ("Weights**ts**Keeper" vs "WeightsKeeper"). Since this PR is restructuring the keeper, this is a good opportunity to fix the method name in `test_utils.go` and all call sites.</comment>

<file context>
@@ -151,7 +151,7 @@ func (s *WeightsTestSuite) TestStoreLatestNormalizedWeights() {
 		// Verify stored weights
 		for worker, expectedWeight := range weights.Inferers { // nolint: maprange // reason: order not relevant
-			storedWeight, err := s.EmissionsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
+			storedWeight, err := s.WeightstsKeeper().GetLatestInfererWeight(s.Ctx(), topicId, worker)
 			s.Require().NoError(err)
 			s.Require().True(expectedWeight.Equal(storedWeight))
</file context>
Fix with Cubic

@@ -108,14 +97,30 @@ func RemoveStakes(
return nil
}

type RemoveDelegateStakeFn func(
ctx context.Context,
stakeRemovalBlockHeight emissionskeeper.BlockHeight,
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Inconsistent package used for BlockHeight type: RemoveDelegateStakeFn uses emissionskeeper.BlockHeight while all other function types in this file use emissionstypes.BlockHeight. Use emissionstypes.BlockHeight consistently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/module/stake_removals.go, line 102:

<comment>Inconsistent package used for `BlockHeight` type: `RemoveDelegateStakeFn` uses `emissionskeeper.BlockHeight` while all other function types in this file use `emissionstypes.BlockHeight`. Use `emissionstypes.BlockHeight` consistently.</comment>

<file context>
@@ -108,14 +97,30 @@ func RemoveStakes(
 
+type RemoveDelegateStakeFn func(
+	ctx context.Context,
+	stakeRemovalBlockHeight emissionskeeper.BlockHeight,
+	topicId emissionskeeper.TopicId,
+	delegator, reputer emissionskeeper.ActorId,
</file context>
Suggested change
stakeRemovalBlockHeight emissionskeeper.BlockHeight,
stakeRemovalBlockHeight emissionstypes.BlockHeight,
Fix with Cubic

Comment on lines +22 to +23
//nolint:exhaustruct
var _ types.MsgServiceServer = msgServer{}
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Use (*msgServer)(nil) instead of msgServer{} for the interface compliance check. This avoids the exhaustruct warning without needing the //nolint directive.

(Based on your team's feedback about using var declarations to avoid exhaustruct warnings and nolint directives.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At x/emissions/keeper/msgserver/msg_server.go, line 22:

<comment>Use `(*msgServer)(nil)` instead of `msgServer{}` for the interface compliance check. This avoids the `exhaustruct` warning without needing the `//nolint` directive.

(Based on your team's feedback about using var declarations to avoid exhaustruct warnings and nolint directives.) </comment>

<file context>
@@ -1,20 +1,42 @@
 }
 
-var _ types.MsgServiceServer = msgServer{k: keeper.Keeper{}}
+//nolint:exhaustruct
+var _ types.MsgServiceServer = msgServer{}
 
</file context>
Suggested change
//nolint:exhaustruct
var _ types.MsgServiceServer = msgServer{}
var _ types.MsgServiceServer = (*msgServer)(nil)
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant