Skip to content

Conversation

@notJoon
Copy link
Member

@notJoon notJoon commented Jan 8, 2026

Description

  • Add missing getter functions to expose internal staker state (incentive, deposit, pool
    data)
  • Remove deprecated JSON API getter functions and related types
  • Update staker interface

Summary by CodeRabbit

  • New Features

    • Many new read-only getters exposing pools, incentives, deposits, rewards, staked positions, counts, allowed tokens and warmup templates.
  • Improvements

    • Consolidated legacy API-style endpoints into a consistent Get*-based retrieval surface.
    • Added deep-copy/cloning for key data structures to prevent shared-state surprises.
    • Standardized numeric formatting in logs and emitted events.
  • Tests

    • Expanded comprehensive getter tests; removed obsolete v1 API/json tests.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Warning

Rate limit exceeded

@junghoon-vans has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b244820 and 8ec2d5c.

📒 Files selected for processing (11)
  • contract/r/gnoswap/pool/getter_utils.gno
  • contract/r/gnoswap/pool/pool.gno
  • contract/r/gnoswap/staker/deposit.gno
  • contract/r/gnoswap/staker/getter_utils.gno
  • contract/r/gnoswap/staker/pool.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno

Walkthrough

Removes the v1 JSON API/serialization layer and its tests; replaces ApiGet* JSON-returning endpoints with strongly-typed Get* getters across staker (core, v1, mocks, test wrappers); adds deep-clone helpers and many read-only accessors; standardizes numeric formatting to formatAnyInt in v1 code and events.

Changes

Cohort / File(s) Summary
Core getters & types
contract/r/gnoswap/staker/getter.gno, contract/r/gnoswap/staker/types.gno
Removed IStakerApi embedding and ApiGet* functions; added many typed Get* accessors (Pool/Incentive/Deposit, collectable rewards, incentive timestamps/amounts, deposit collected rewards, pool/tier queries, counts, IDs, global getters like GetTotalEmissionSent/GetAllowedTokens/GetWarmupTemplate/GetTotalStakedUserCount/GetStakedPositionsByUser).
v1 implementation — getters & event formatting
contract/r/gnoswap/staker/v1/getter.gno, contract/r/gnoswap/staker/v1/staker.gno, contract/r/gnoswap/staker/v1/manage_pool_tier_and_warmup.gno
Implemented v1 Get* accessors, added pagination/count helpers for pools/incentives/reward caches/historical ticks; removed debug/json scaffolding; replaced formatInt → formatAnyInt in event/log payloads.
v1 JSON API & serialization deletion
contract/r/gnoswap/staker/v1/api.gno, contract/r/gnoswap/staker/v1/json.gno
Deleted entire v1 API handlers and JSON serialization/types (ApiGet* handlers, response structs, builders, JSON helpers, and related tests).
Deposit, Pool & tree deep-clone
contract/r/gnoswap/staker/deposit.gno, contract/r/gnoswap/staker/pool.gno, contract/r/gnoswap/staker/tree.gno
Added Clone() implementations and helper clone functions for Deposit, Pool, ExternalIncentive, Ticks, Tick, and UintTree to produce deep copies and avoid shared mutable state when returning objects.
Mocks / Test wrappers
contract/r/gnoswap/staker/_mock_test.gno, contract/r/scenario/upgrade/implements/.../staker/test_impl.gno
Replaced ApiGet* mock/test wrappers with typed Get* methods; mocks preserve call-tracking and return typed values/nil-safe clones; TestStaker wrappers delegate Get* calls via ExecuteFn with activation guards.
v1 tests removed/expanded
contract/r/gnoswap/staker/v1/api_test.gno, contract/r/gnoswap/staker/v1/json_test.gno, contract/r/gnoswap/staker/v1/getter_test.gno, contract/r/gnoswap/staker/v1/calculate_pool_position_reward_test.gno
Removed API/JSON-focused tests and added a comprehensive getter-focused test suite; small test formatting tweak (formatInt → formatAnyInt).
Integration test data updates
tests/integration/testdata/staker/*.txtar
Updated VM query references from ApiGetExternalIncentivesByPoolPath(...) to GetExternalIncentiveByPoolPath(...) in test data.
Numeric formatting standardization
contract/r/gnoswap/staker/v1/*.gno (multiple files)
Replaced occurrences of formatInt(...) with formatAnyInt(...) in emitted events/logs and logging to support broader integer formatting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: adding missing getter functions and removing API functions from the staker module.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
contract/r/gnoswap/staker/v1/getter.gno (2)

249-254: Note: GetDepositStakeTimestamp duplicates GetDepositStakeTime functionality.

Both methods (lines 249-254 and 200-205) return deposit.StakeTime(). Consider consolidating into a single method or documenting the distinction if there's a semantic difference.


369-378: Consider performance implications of GetTotalStakedPositionCount.

This method iterates over all stakers and their deposit trees to count positions, resulting in O(n×m) complexity where n is the number of stakers and m is the average deposits per staker. For large deployments, this could be expensive.

Consider maintaining a running counter that's updated on stake/unstake operations if this getter is called frequently.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a2a7e6 and a983ba8.

📒 Files selected for processing (14)
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/getter.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/api.gno
  • contract/r/gnoswap/staker/v1/api_test.gno
  • contract/r/gnoswap/staker/v1/calculate_pool_position_reward_test.gno
  • contract/r/gnoswap/staker/v1/external_deposit_fee.gno
  • contract/r/gnoswap/staker/v1/external_incentive.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/gnoswap/staker/v1/json.gno
  • contract/r/gnoswap/staker/v1/json_test.gno
  • contract/r/gnoswap/staker/v1/manage_pool_tier_and_warmup.gno
  • contract/r/gnoswap/staker/v1/staker.gno
💤 Files with no reviewable changes (5)
  • contract/r/gnoswap/staker/v1/api_test.gno
  • contract/r/gnoswap/staker/v1/json_test.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/gnoswap/staker/v1/api.gno
  • contract/r/gnoswap/staker/v1/json.gno
🧰 Additional context used
📓 Path-based instructions (1)
**/*.gno

⚙️ CodeRabbit configuration file

**/*.gno: # Gno Smart Contract Code Review Rules

About Gno

Gno is a blockchain smart contract language based on Go. Key differences from Go:

  • Realms (r/): Stateful contracts with persistent storage
  • Packages (p/): Pure library code without state
  • Realm crossing: Explicit syntax for inter-realm calls using cross keyword
  • Access control: Uses runtime.PreviousRealm() and runtime.OriginCaller() for caller verification
  • State persistence: Global variables in realms persist across transactions
  • Determinism: No goroutines, limited stdlib, panic-based error handling in realms

Critical Security Violations

Flag immediately as [CRITICAL]:

  1. Exported global variables - Allows anyone to modify state
var Admin address  // CRITICAL: Anyone can reassign
  1. Missing access control - No caller verification on state mutations
func DeleteData(_ realm) {
    data.Clear()  // CRITICAL: Anyone can delete
}
  1. Error return for security - Transaction succeeds even when unauthorized
func AdminOnly(_ realm) error {
    if !isAdmin() {
        return errors.New("denied")  // CRITICAL: Should panic
    }
}
  1. Direct external realm modification - Readonly taint violation
otherrealm.GlobalVar = "hacked"  // CRITICAL: Cannot modify external realm state

Required Patterns

Access Control (MUST on all state mutations)

Every function that modifies state MUST verify the caller:

import "chain/runtime"

func MutatingFunction(cur realm, params) {
    // For immediate caller (could be user or realm)
    caller := runtime.PreviousRealm().Address()

    // For original transaction signer (always a user)
    signer := runtime.OriginCaller()

    if caller != authorized {
        panic("unauthorized")  // MUST panic, not return error
    }

    // ... mutation logic ...
}

Why panic? Panics abort and rollback the transaction. Returning errors allows unauthorized transactions t...

Files:

  • contract/r/gnoswap/staker/v1/manage_pool_tier_and_warmup.gno
  • contract/r/gnoswap/staker/v1/external_incentive.gno
  • contract/r/gnoswap/staker/v1/calculate_pool_position_reward_test.gno
  • contract/r/gnoswap/staker/v1/external_deposit_fee.gno
  • contract/r/gnoswap/staker/v1/staker.gno
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/getter.gno
🧠 Learnings (1)
📚 Learning: 2026-01-05T03:20:38.341Z
Learnt from: notJoon
Repo: gnoswap-labs/gnoswap PR: 1118
File: contract/r/gnoswap/access/access.gno:37-43
Timestamp: 2026-01-05T03:20:38.341Z
Learning: In the gnoswap codebase, enforce consistency where event attribute names use prevAddr to refer to the caller/previous realm address in emitted events, as this is the established convention. When reviewing or adding new contracts, prefer using prevAddr over caller for event fields; if a semantic renaming is ever considered, document the rationale and ensure all related events across contracts are updated to maintain consistency.

Applied to files:

  • contract/r/gnoswap/staker/v1/manage_pool_tier_and_warmup.gno
  • contract/r/gnoswap/staker/v1/external_incentive.gno
  • contract/r/gnoswap/staker/v1/calculate_pool_position_reward_test.gno
  • contract/r/gnoswap/staker/v1/external_deposit_fee.gno
  • contract/r/gnoswap/staker/v1/staker.gno
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/getter.gno
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: test-gnoswap (gnoswap/scenario/staker, contract/r/scenario/staker)
  • GitHub Check: test-gnoswap (gnoswap/scenario/position, contract/r/scenario/position)
  • GitHub Check: test-gnoswap (gnoswap/test/fuzz, contract/r/gnoswap/test/fuzz)
  • GitHub Check: test-gnoswap (gnoswap/scenario/router, contract/r/scenario/router)
  • GitHub Check: test-gnoswap (gnoswap/position/v1, contract/r/gnoswap/position/v1)
  • GitHub Check: test-gnoswap (gnoswap/staker/v1, contract/r/gnoswap/staker/v1)
  • GitHub Check: test-gnoswap (gnoswap/gov/staker, contract/r/gnoswap/gov/staker)
  • GitHub Check: test-gnoswap (gnoswap/pool/v1, contract/r/gnoswap/pool/v1)
  • GitHub Check: test-gnoswap (gnoswap/store, contract/p/gnoswap/store)
  • GitHub Check: run-integration-test
🔇 Additional comments (17)
contract/r/gnoswap/staker/v1/calculate_pool_position_reward_test.gno (1)

1872-1873: LGTM! Test expectations aligned with production formatting.

The update from formatInt to formatAnyInt correctly aligns test expectations with the formatting changes in production code. This ensures tests validate the actual emitted event format.

contract/r/gnoswap/staker/v1/manage_pool_tier_and_warmup.gno (1)

38-39: LGTM! Consistent formatting updates across administrative functions.

The formatting changes from formatInt to formatAnyInt are consistently applied across all pool tier and warmup management functions. This aligns with the broader refactoring effort across the staker module.

Also applies to: 65-66, 89-90, 109-110

contract/r/gnoswap/staker/v1/external_incentive.gno (1)

118-123: Verify downstream compatibility for incentive event format changes.

The formatting changes affect critical fields in CreateExternalIncentive and EndExternalIncentive events (reward amounts, timestamps, deposit amounts). Ensure that downstream consumers (indexers, front-end applications, monitoring systems) can handle the new format produced by formatAnyInt.

If you have integration tests or can verify with off-chain indexers, confirm they parse these events correctly. You may also want to check documentation to ensure the event schema is updated if necessary.

Also applies to: 228-229

contract/r/gnoswap/staker/v1/staker.gno (2)

383-385: Formatting change for tick values in StakeToken event.

The change from formatInt to formatAnyInt for positionUpperTick, positionLowerTick, and currentTick should maintain the same numeric representation. Tick values are int32 and are critical for position range tracking.


495-497: Verify widespread formatting changes don't break event consumers.

The formatting changes in CollectReward affect multiple high-frequency events with critical financial data (reward amounts, timestamps, penalties). Given the scope of these changes across the reward collection flow, ensure:

  1. Off-chain indexers and UIs correctly parse the new format
  2. Historical event data remains interpretable
  3. Any event-based alerting or monitoring systems are updated
#!/bin/bash
# Verify formatAnyInt is consistently used across all staker v1 events
rg -n 'chain\.Emit' contract/r/gnoswap/staker/v1/ --type=gno | rg -v 'formatAnyInt' | rg 'format(Int|Uint)'

This script will find any remaining uses of the old formatters in event emissions that might need updating for consistency.

Also applies to: 515-519, 580-583, 594-601, 617-617, 667-668, 678-683

contract/r/gnoswap/staker/v1/external_deposit_fee.gno (1)

70-71: No action needed — formatAnyInt correctly handles int64 values.

The formatAnyInt function in contract/r/gnoswap/staker/v1/utils.gno has a dedicated case for int64 that directly calls strconv.FormatInt(v, 10), which is the standard Go approach for converting int64 to base-10 string representation. When called with int64 values (both prevDepositGnsAmount and amount are int64), the function produces the correct output format suitable for event emission and downstream consumers.

contract/r/gnoswap/staker/_mock_test.gno (2)

139-193: LGTM!

The new incentive-related mock getters follow a consistent pattern and correctly handle the case when data is not found by returning sensible defaults.


483-521: LGTM!

The aggregate getter mocks (GetTotalEmissionSent, GetAllowedTokens, GetWarmupTemplate, GetTotalStakedPositionCount, GetStakedPositionsByOwner) are implemented correctly with appropriate default values.

contract/r/gnoswap/staker/types.gno (1)

53-103: LGTM! Interface expansion is well-structured.

The IStakerGetter interface additions are comprehensive and follow consistent naming conventions. The new methods appropriately expose incentive state, deposit details, and aggregate staking data.

contract/r/gnoswap/staker/getter.gno (4)

12-45: LGTM!

The new incentive-related getter wrappers are well-documented and correctly delegate to the implementation layer.


62-75: LGTM!

The deposit-related getter wrappers maintain the established pattern with clear documentation.


177-185: LGTM!

The pool getter wrappers (GetPoolStakedLiquidity, GetPoolsByTier) are correctly implemented.


227-250: LGTM!

The aggregate getter wrappers expose system-wide state appropriately with clear documentation.

contract/r/gnoswap/staker/v1/getter.gno (4)

353-366: Good defensive copying practice.

The slice copying in GetAllowedTokens and GetWarmupTemplate prevents external callers from modifying internal state. This is a good security practice.


380-395: LGTM!

The GetStakedPositionsByOwner implementation correctly handles the case when the owner has no staked positions by returning an empty slice. The pre-allocation with depositTree.Size() is a nice optimization.


403-417: LGTM!

The initialization change to make([]sr.ExternalIncentive, 0) is a good practice, ensuring a non-nil empty slice is returned when no incentives match.


136-143: No issues with time.Now() usage — this is the correct pattern in Gno.

The IsIncentiveActive method correctly uses time.Now().Unix() to check incentive timing. In Gno, time.Now() returns the deterministic block/transaction inclusion timestamp (not the host OS clock), ensuring all validators see the same value. This is the intended way to gate on-chain logic based on time.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a983ba8 and 02dfc91.

📒 Files selected for processing (6)
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
  • tests/integration/testdata/staker/end_non_existent_external_incentive_fail.txtar
  • tests/integration/testdata/staker/staker_create_external_incentive.txtar
🧰 Additional context used
📓 Path-based instructions (1)
**/*.gno

⚙️ CodeRabbit configuration file

**/*.gno: # Gno Smart Contract Code Review Rules

About Gno

Gno is a blockchain smart contract language based on Go. Key differences from Go:

  • Realms (r/): Stateful contracts with persistent storage
  • Packages (p/): Pure library code without state
  • Realm crossing: Explicit syntax for inter-realm calls using cross keyword
  • Access control: Uses runtime.PreviousRealm() and runtime.OriginCaller() for caller verification
  • State persistence: Global variables in realms persist across transactions
  • Determinism: No goroutines, limited stdlib, panic-based error handling in realms

Critical Security Violations

Flag immediately as [CRITICAL]:

  1. Exported global variables - Allows anyone to modify state
var Admin address  // CRITICAL: Anyone can reassign
  1. Missing access control - No caller verification on state mutations
func DeleteData(_ realm) {
    data.Clear()  // CRITICAL: Anyone can delete
}
  1. Error return for security - Transaction succeeds even when unauthorized
func AdminOnly(_ realm) error {
    if !isAdmin() {
        return errors.New("denied")  // CRITICAL: Should panic
    }
}
  1. Direct external realm modification - Readonly taint violation
otherrealm.GlobalVar = "hacked"  // CRITICAL: Cannot modify external realm state

Required Patterns

Access Control (MUST on all state mutations)

Every function that modifies state MUST verify the caller:

import "chain/runtime"

func MutatingFunction(cur realm, params) {
    // For immediate caller (could be user or realm)
    caller := runtime.PreviousRealm().Address()

    // For original transaction signer (always a user)
    signer := runtime.OriginCaller()

    if caller != authorized {
        panic("unauthorized")  // MUST panic, not return error
    }

    // ... mutation logic ...
}

Why panic? Panics abort and rollback the transaction. Returning errors allows unauthorized transactions t...

Files:

  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
🧠 Learnings (1)
📚 Learning: 2026-01-05T03:20:38.341Z
Learnt from: notJoon
Repo: gnoswap-labs/gnoswap PR: 1118
File: contract/r/gnoswap/access/access.gno:37-43
Timestamp: 2026-01-05T03:20:38.341Z
Learning: In the gnoswap codebase, enforce consistency where event attribute names use prevAddr to refer to the caller/previous realm address in emitted events, as this is the established convention. When reviewing or adding new contracts, prefer using prevAddr over caller for event fields; if a semantic renaming is ever considered, document the rationale and ensure all related events across contracts are updated to maintain consistency.

Applied to files:

  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-gnoswap (gnoswap/test/fuzz, contract/r/gnoswap/test/fuzz)
  • GitHub Check: test-gnoswap (gnoswap/scenario/router, contract/r/scenario/router)
  • GitHub Check: test-gnoswap (gnoswap/scenario/staker, contract/r/scenario/staker)
  • GitHub Check: test-gnoswap (gnoswap/scenario/upgrade, contract/r/scenario/upgrade)
  • GitHub Check: test-gnoswap (gnoswap/staker/v1, contract/r/gnoswap/staker/v1)
  • GitHub Check: test-gnoswap (gnoswap/pool/v1, contract/r/gnoswap/pool/v1)
  • GitHub Check: run-integration-test
🔇 Additional comments (6)
contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno (1)

166-213: LGTM! New getter implementations follow the established delegation pattern correctly.

All 18 new getter methods consistently implement the activation-guarded delegation pattern used throughout this test wrapper:

  • Proper activation checks using method name strings
  • Clear panic messages when methods are not activated
  • Correct delegation to the underlying instance
  • Appropriate return types for each accessor

The additions properly expand the test wrapper's API surface to match the updated staker interface without introducing inconsistencies.

Also applies to: 236-255, 313-318, 397-409, 467-499

contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno (1)

189-596: LGTM! New getter methods properly implemented.

All 18 new getter methods (GetIncentiveCreatedTimestamp, GetIncentiveTotalRewardAmount, GetIncentiveDistributedRewardAmount, GetIncentiveRemainingRewardAmount, GetIncentiveDepositGnsAmount, GetIncentiveRefunded, IsIncentiveActive, GetDepositStakeTimestamp, GetDepositCollectedInternalReward, GetDepositCollectedExternalReward, GetDepositExternalIncentiveIdList, GetPoolStakedLiquidity, GetPoolsByTier, GetTotalEmissionSent, GetAllowedTokens, GetWarmupTemplate, GetTotalStakedPositionCount, GetStakedPositionsByOwner) correctly follow the ExecuteFn delegation pattern. Type assertions match return types, and the implementation maintains mockability for testing scenarios.

contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno (1)

97-287: LGTM! New getter methods properly delegated.

All 18 new getter methods correctly delegate to the underlying instance. The straightforward pass-through pattern is appropriate for the v3_valid test scenario and maintains consistency with the existing delegation approach in this wrapper.

contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno (1)

161-495: LGTM! New getter methods with proper activation guards.

All 18 new getter methods correctly implement the activation-guard pattern, checking isActive() before delegating to the underlying instance. Panic messages are consistent and descriptive. This implementation is appropriate for the v2_invalid test scenario to verify upgrade activation behavior.

tests/integration/testdata/staker/staker_create_external_incentive.txtar (2)

55-55: LGTM: Whitespace cleanup.

Trailing space removal improves formatting consistency.


110-110: The function GetExternalIncentiveByPoolPath is properly defined and exported in the staker package (contract/r/gnoswap/staker/getter.gno), with matching implementations across v1 and test code. Despite the singular name, it returns []ExternalIncentive (an array), so the test expectations remain compatible. No references to the old ApiGetExternalIncentivesByPoolPath API remain in the codebase. The naming change is intentional and aligns with the PR objective of replacing JSON API getters with strongly-typed getters.

SetUnStakingFee(fee int64)
}

type IStakerGetter interface {
Copy link
Member

Choose a reason for hiding this comment

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

We need functions to get Pool, Incentive, and Deposit.

And We also need a getter to query the available rewards.

  • CollectableEmissionReward(positionId uint64) int64
  • CollectableExternalIncentiveReward(positionId uint64, incentiveId string) int64

Copy link
Member Author

Choose a reason for hiding this comment

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

@notJoon notJoon marked this pull request as draft January 8, 2026 03:05
`GetTotalStakedUserCount`, `GetTotalStakedUserPositionCount(user
address)` methods
@notJoon notJoon marked this pull request as ready for review January 8, 2026 03:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
contract/r/gnoswap/staker/tree.gno (1)

80-124: Consider documenting or restricting the default case in clone helpers.

The cloneUintTreeValue and cloneAvlTreeValue functions have default cases (lines 92-93, 121-122) that return values as-is without cloning. If unknown pointer types are stored in the tree, they won't be deep copied, potentially allowing external callers to mutate internal state.

Consider either:

  1. Adding a panic in the default case to fail-fast if unexpected types are encountered, or
  2. Documenting that only the listed types should be stored in these trees.
🛡️ Option 1: Fail-fast on unexpected types
 func cloneUintTreeValue(value any) any {
 	switch v := value.(type) {
 	case *u256.Uint:
 		return v.Clone()
 	case *avl.Tree:
 		return cloneAvlTree(v)
 	case []string:
 		copied := make([]string, len(v))
 		copy(copied, v)
 		return copied
 	case int64, int32, uint64, bool, string:
 		return v
 	default:
-		return value
+		panic(ufmt.Sprintf("cloneUintTreeValue: unsupported type %T", value))
 	}
 }

 func cloneAvlTreeValue(value any) any {
 	switch v := value.(type) {
 	case []string:
 		copied := make([]string, len(v))
 		copy(copied, v)
 		return copied
 	case *u256.Uint:
 		return v.Clone()
 	case int64, int32, uint64, bool, string:
 		return v
 	default:
-		return value
+		panic(ufmt.Sprintf("cloneAvlTreeValue: unsupported type %T", value))
 	}
 }
contract/r/gnoswap/staker/types.gno (1)

108-108: Consider renaming parameter for consistency.

The function is named GetStakedPositionsByUser but the parameter is named owner. For consistency with the function name and the apparent intent to use "user" terminology (as mentioned in the PR summary where GetStakedPositionsByOwner was renamed to GetStakedPositionsByUser), consider renaming the parameter:

-	GetStakedPositionsByUser(owner address, offset, count int) []uint64
+	GetStakedPositionsByUser(user address, offset, count int) []uint64
contract/r/gnoswap/staker/v1/getter_test.gno (1)

630-694: Consider asserting expected behavior for incentive active status.

The test currently only verifies that IsIncentiveActive doesn't panic for various incentive states, but doesn't assert the expected boolean return value. This could miss logic errors.

♻️ Suggested improvement
 	tests := []struct {
 		name           string
 		incentiveId    string
 		startTimestamp int64
 		endTimestamp   int64
 		refunded       bool
+		expectActive   bool
 	}{
 		{
 			name:           "future incentive (not yet started)",
 			incentiveId:    "future-incentive",
 			startTimestamp: fixedTime + 1000,
 			endTimestamp:   fixedTime + 2000,
 			refunded:       false,
+			expectActive:   false, // not started yet
 		},
 		// ... add expectActive to other cases
 	}

 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			// ... setup ...
-			// IsIncentiveActive uses time.Now(), so we just verify it doesn't panic
-			_ = instance.IsIncentiveActive(poolPath, tt.incentiveId)
+			result := instance.IsIncentiveActive(poolPath, tt.incentiveId)
+			// Note: time.Now() makes exact assertions tricky, but we can verify refunded case
+			if tt.refunded {
+				uassert.Equal(t, false, result)
+			}
 		})
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02dfc91 and 4ae8c24.

📒 Files selected for processing (13)
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/deposit.gno
  • contract/r/gnoswap/staker/getter.gno
  • contract/r/gnoswap/staker/pool.gno
  • contract/r/gnoswap/staker/tree.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/upgrade_test.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
🚧 Files skipped from review as they are similar to previous changes (1)
  • contract/r/gnoswap/staker/getter.gno
🧰 Additional context used
📓 Path-based instructions (1)
**/*.gno

⚙️ CodeRabbit configuration file

**/*.gno: # Gno Smart Contract Code Review Rules

About Gno

Gno is a blockchain smart contract language based on Go. Key differences from Go:

  • Realms (r/): Stateful contracts with persistent storage
  • Packages (p/): Pure library code without state
  • Realm crossing: Explicit syntax for inter-realm calls using cross keyword
  • Access control: Uses runtime.PreviousRealm() and runtime.OriginCaller() for caller verification
  • State persistence: Global variables in realms persist across transactions
  • Determinism: No goroutines, limited stdlib, panic-based error handling in realms

Critical Security Violations

Flag immediately as [CRITICAL]:

  1. Exported global variables - Allows anyone to modify state
var Admin address  // CRITICAL: Anyone can reassign
  1. Missing access control - No caller verification on state mutations
func DeleteData(_ realm) {
    data.Clear()  // CRITICAL: Anyone can delete
}
  1. Error return for security - Transaction succeeds even when unauthorized
func AdminOnly(_ realm) error {
    if !isAdmin() {
        return errors.New("denied")  // CRITICAL: Should panic
    }
}
  1. Direct external realm modification - Readonly taint violation
otherrealm.GlobalVar = "hacked"  // CRITICAL: Cannot modify external realm state

Required Patterns

Access Control (MUST on all state mutations)

Every function that modifies state MUST verify the caller:

import "chain/runtime"

func MutatingFunction(cur realm, params) {
    // For immediate caller (could be user or realm)
    caller := runtime.PreviousRealm().Address()

    // For original transaction signer (always a user)
    signer := runtime.OriginCaller()

    if caller != authorized {
        panic("unauthorized")  // MUST panic, not return error
    }

    // ... mutation logic ...
}

Why panic? Panics abort and rollback the transaction. Returning errors allows unauthorized transactions t...

Files:

  • contract/r/gnoswap/staker/tree.gno
  • contract/r/gnoswap/staker/upgrade_test.gno
  • contract/r/gnoswap/staker/pool.gno
  • contract/r/gnoswap/staker/deposit.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
🧠 Learnings (1)
📚 Learning: 2026-01-05T03:20:38.341Z
Learnt from: notJoon
Repo: gnoswap-labs/gnoswap PR: 1118
File: contract/r/gnoswap/access/access.gno:37-43
Timestamp: 2026-01-05T03:20:38.341Z
Learning: In the gnoswap codebase, enforce consistency where event attribute names use prevAddr to refer to the caller/previous realm address in emitted events, as this is the established convention. When reviewing or adding new contracts, prefer using prevAddr over caller for event fields; if a semantic renaming is ever considered, document the rationale and ensure all related events across contracts are updated to maintain consistency.

Applied to files:

  • contract/r/gnoswap/staker/tree.gno
  • contract/r/gnoswap/staker/upgrade_test.gno
  • contract/r/gnoswap/staker/pool.gno
  • contract/r/gnoswap/staker/deposit.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test-gnoswap (gnoswap/test/fuzz, contract/r/gnoswap/test/fuzz)
  • GitHub Check: run-integration-test
🔇 Additional comments (19)
contract/r/gnoswap/staker/upgrade_test.gno (1)

563-645: LGTM! Well-structured tests for new getter functions.

The tests for GetTotalEmissionSent and GetAllowedTokens follow the established testing patterns in this file:

  • Proper realm setup using testing.SetRealm
  • Correct use of the upgrade system
  • Verification of delegation to the implementation
  • Appropriate assertions
contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno (1)

159-542: LGTM! Test mock implementation follows established patterns.

The new getter methods in the TestStaker wrapper correctly:

  • Check activation status before delegating
  • Panic with descriptive messages when methods are not activated
  • Delegate to the underlying instance when active

This maintains consistency with the existing test infrastructure in this file and supports the upgrade testing scenarios.

contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno (1)

93-311: LGTM! Test implementation correctly delegates to underlying instance.

The new getter methods in the TestStaker wrapper appropriately delegate all calls to the underlying instance. This implementation is consistent with the existing pattern in this file and supports the v3_valid upgrade testing scenario.

contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno (1)

154-537: LGTM - Consistent activation-guarded delegation pattern.

The new getter methods follow a consistent pattern: check activation status, panic if not activated, then delegate to the underlying v1 instance. This is appropriate for test scaffolding that simulates contract upgrades with selective method support.

contract/r/gnoswap/staker/deposit.gno (3)

233-254: Well-implemented deep copy for Deposit.

The Clone method correctly handles the nil case and performs deep copies of all mutable fields. The use of helper functions keeps the code organized and reusable.


256-264: Warmup clone is correct for value types.

Since Warmup is a struct containing only primitive types (int, int64, uint64), using copy() is sufficient for a deep copy.


273-305: AVL tree clone helpers are correctly implemented.

Both cloneAvlTreeInt64 and cloneAvlTreeBool properly handle nil trees, create new trees, and copy all entries with correct type assertions.

contract/r/gnoswap/staker/pool.gno (4)

167-184: LGTM - Pool.Clone() properly deep copies all nested structures.

The implementation correctly clones all mutable fields including UintTree instances and nested Incentives. The nil check at the start prevents panics on nil receivers.


624-636: Ticks.Clone() correctly uses value receiver and returns value type.

Since Ticks is a value type wrapping a tree pointer, the clone correctly creates a new tree and copies all tick entries via deep cloning.


292-313: Incentives.Clone() properly clones nested ExternalIncentive objects.

The implementation iterates through all incentives, clones each one, and creates a new tree. The unclaimablePeriods UintTree is also cloned.


707-719: Tick.Clone() properly handles nil and clones all fields.

The implementation correctly nil-checks and calls Clone() on all pointer fields:

  • stakedLiquidityGross (*u256.Uint)
  • stakedLiquidityDelta (*i256.Int)
  • outsideAccumulation (*UintTree)

All three types have Clone() methods and are properly invoked.

contract/r/gnoswap/staker/v1/getter_test.gno (1)

14-116: Well-structured test state setup.

The testState struct and setupBasicTestState helper provide a clean, reusable foundation for testing getters. Good use of direct store access for complex state setup while using manager methods where appropriate.

contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno (1)

179-646: Consistent mock implementation for new getter methods.

All new getter methods follow the established ExecuteFn pattern, enabling mock function injection while defaulting to the actual v1 implementation. The type assertions are correct for the expected return types.

contract/r/gnoswap/staker/_mock_test.gno (1)

131-569: LGTM - MockStaker correctly implements the expanded getter interface.

The mock implementation properly retrieves values from MockResponse with appropriate default values when keys are not set. Each getter uses the correct type assertion for its return type.

contract/r/gnoswap/staker/v1/getter.gno (5)

35-38: Clean public wrapper for internal pool getter.

The GetPool method properly delegates to getPoolByPoolPath, maintaining the internal validation and error handling.


209-227: CollectableReward methods correctly calculate pending rewards.

Both methods properly use calcPositionReward with current time/height and return the appropriate reward amounts. The external reward method correctly handles missing incentive IDs by returning 0.


389-403: Good defensive copying for slice returns.

GetAllowedTokens and GetWarmupTemplate correctly return copies of the internal slices, preventing callers from modifying internal state.


421-443: Pagination implementation handles edge cases correctly.

GetStakedPositionsByUser properly handles:

  • count <= 0 returns empty slice
  • Negative offset is normalized to 0
  • Missing user returns empty slice
  • Uses IterateByOffset for efficient pagination

450-466: Minor: Changed initialization style for incentives slice.

The change from []sr.ExternalIncentive{} to make([]sr.ExternalIncentive, 0) is functionally equivalent but more explicit about intent. Both produce a non-nil empty slice.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
contract/r/gnoswap/staker/v1/getter_test.gno (2)

196-200: Clarify the reward amount semantics in the comment.

The comment explains that rewardAmount represents the remaining reward initially set to totalRewardAmount. Consider clarifying that GetIncentiveRemainingRewardAmount returns the current rewardAmount field, which is distinct from distributed rewards and is not automatically decremented when distributedRewardAmount increases (unless explicitly updated via other mechanisms).

📝 Suggested comment improvement
-	// Note: RewardAmount is the remaining reward (set at creation, then updated)
-	// The NewExternalIncentive sets rewardAmount = totalRewardAmount initially
-	// So remaining should be 10_000_000 (initial) since we haven't modified it
+	// Note: RewardAmount field represents the remaining/available reward amount.
+	// NewExternalIncentive initially sets rewardAmount = totalRewardAmount (10_000_000).
+	// The remaining amount is independent of distributedRewardAmount unless explicitly updated
+	// by reward distribution logic, so it remains at the initial 10_000_000 in this test.
 	uassert.Equal(t, int64(10_000_000), remainingReward)

685-689: Non-deterministic test behavior due to time.Now().

The test only verifies that IsIncentiveActive doesn't panic but doesn't assert the expected active/inactive status. Since the method uses time.Now(), the test results depend on when it runs, making it non-deterministic.

Consider one of these approaches:

  1. Add time mocking: Inject a time provider into the implementation to allow controlled testing
  2. Assert expected behavior: If the incentive times are known relative to the test execution time, assert the expected boolean result
  3. Document the limitation: If non-determinism is acceptable here, add a comment explaining why correctness isn't verified
💡 Example: Assert expected behavior for future incentives
 			// IsIncentiveActive uses time.Now(), so we just verify it doesn't panic
-			_ = instance.IsIncentiveActive(poolPath, tt.incentiveId)
+			isActive := instance.IsIncentiveActive(poolPath, tt.incentiveId)
+			// Future incentives (not yet started) should be inactive
+			if tt.name == "future incentive (not yet started)" {
+				uassert.False(t, isActive)
+			}
+			// Refunded incentives should always be inactive
+			if tt.refunded {
+				uassert.False(t, isActive)
+			}
 		})
contract/r/gnoswap/staker/v1/getter.gno (3)

309-314: Consider defensive copy for slice return.

Returning a slice directly allows external callers to modify the slice contents. Consider creating a defensive copy similar to GetAllowedTokens and GetWarmupTemplate.

♻️ Proposed defensive copy
 // GetDepositExternalIncentiveIdList returns external incentive IDs for a deposit.
 func (s *stakerV1) GetDepositExternalIncentiveIdList(lpTokenId uint64) []string {
 	deposit := s.getDeposit(lpTokenId)
-
-	return deposit.GetExternalIncentiveIdList()
+	
+	ids := deposit.GetExternalIncentiveIdList()
+	result := make([]string, len(ids))
+	copy(result, ids)
+	return result
 }

443-459: Optional style change noted.

Changing from []sr.ExternalIncentive{} to make([]sr.ExternalIncentive, 0) is a minor style preference with no functional difference. Both create an empty slice with zero length.

The method appends struct values (not pointers), which creates copies and is safe from external mutation.


72-75: ExternalIncentive fields are properly unexported; however, GetIncentive() should return a defensive copy for consistency.

All fields in ExternalIncentive (contract/r/gnoswap/staker/pool.gno) are unexported with protected access through setter methods, so external state mutation is not possible. However, the base staker's GetIncentive() function (contract/r/gnoswap/staker/getter.gno) returns a defensive copy via Clone(), while the v1 implementation returns the direct pointer. For consistency and to follow the established pattern in the codebase, return s.getIncentive(poolPath, incentiveId).Clone() instead.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ae8c24 and b2a9884.

📒 Files selected for processing (9)
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/getter.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
🧰 Additional context used
📓 Path-based instructions (1)
**/*.gno

⚙️ CodeRabbit configuration file

**/*.gno: # Gno Smart Contract Code Review Rules

About Gno

Gno is a blockchain smart contract language based on Go. Key differences from Go:

  • Realms (r/): Stateful contracts with persistent storage
  • Packages (p/): Pure library code without state
  • Realm crossing: Explicit syntax for inter-realm calls using cross keyword
  • Access control: Uses runtime.PreviousRealm() and runtime.OriginCaller() for caller verification
  • State persistence: Global variables in realms persist across transactions
  • Determinism: No goroutines, limited stdlib, panic-based error handling in realms

Critical Security Violations

Flag immediately as [CRITICAL]:

  1. Exported global variables - Allows anyone to modify state
var Admin address  // CRITICAL: Anyone can reassign
  1. Missing access control - No caller verification on state mutations
func DeleteData(_ realm) {
    data.Clear()  // CRITICAL: Anyone can delete
}
  1. Error return for security - Transaction succeeds even when unauthorized
func AdminOnly(_ realm) error {
    if !isAdmin() {
        return errors.New("denied")  // CRITICAL: Should panic
    }
}
  1. Direct external realm modification - Readonly taint violation
otherrealm.GlobalVar = "hacked"  // CRITICAL: Cannot modify external realm state

Required Patterns

Access Control (MUST on all state mutations)

Every function that modifies state MUST verify the caller:

import "chain/runtime"

func MutatingFunction(cur realm, params) {
    // For immediate caller (could be user or realm)
    caller := runtime.PreviousRealm().Address()

    // For original transaction signer (always a user)
    signer := runtime.OriginCaller()

    if caller != authorized {
        panic("unauthorized")  // MUST panic, not return error
    }

    // ... mutation logic ...
}

Why panic? Panics abort and rollback the transaction. Returning errors allows unauthorized transactions t...

Files:

  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/gnoswap/staker/getter.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
  • contract/r/gnoswap/staker/_mock_test.gno
🧠 Learnings (1)
📚 Learning: 2026-01-05T03:20:38.341Z
Learnt from: notJoon
Repo: gnoswap-labs/gnoswap PR: 1118
File: contract/r/gnoswap/access/access.gno:37-43
Timestamp: 2026-01-05T03:20:38.341Z
Learning: In the gnoswap codebase, enforce consistency where event attribute names use prevAddr to refer to the caller/previous realm address in emitted events, as this is the established convention. When reviewing or adding new contracts, prefer using prevAddr over caller for event fields; if a semantic renaming is ever considered, document the rationale and ensure all related events across contracts are updated to maintain consistency.

Applied to files:

  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno
  • contract/r/gnoswap/staker/getter.gno
  • contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno
  • contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno
  • contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno
  • contract/r/gnoswap/staker/_mock_test.gno
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: test-gnoswap (gnoswap/scenario/router, contract/r/scenario/router)
  • GitHub Check: test-gnoswap (gnoswap/pool/v1, contract/r/gnoswap/pool/v1)
  • GitHub Check: test-gnoswap (gnoswap/staker/v1, contract/r/gnoswap/staker/v1)
  • GitHub Check: test-gnoswap (gnoswap/scenario/staker, contract/r/scenario/staker)
  • GitHub Check: test-gnoswap (gnoswap/scenario/upgrade, contract/r/scenario/upgrade)
  • GitHub Check: test-gnoswap (gnoswap/test/fuzz, contract/r/gnoswap/test/fuzz)
  • GitHub Check: test-gnoswap (gnoswap/scenario/position, contract/r/scenario/position)
  • GitHub Check: test-gnoswap (gnoswap/scenario/gov/staker, contract/r/scenario/gov/staker)
  • GitHub Check: test-gnoswap (gnoswap/scenario/halt, contract/r/scenario/halt)
  • GitHub Check: test-gnoswap (gnoswap/gov/staker/v1, contract/r/gnoswap/gov/staker/v1)
  • GitHub Check: test-gnoswap (gnoswap/scenario/launchpad, contract/r/scenario/launchpad)
  • GitHub Check: test-gnoswap (gnoswap/position/v1, contract/r/gnoswap/position/v1)
  • GitHub Check: test-gnoswap (gnoswap/router/v1, contract/r/gnoswap/router/v1)
  • GitHub Check: test-gnoswap (gnoswap/staker, contract/r/gnoswap/staker)
  • GitHub Check: run-integration-test
🔇 Additional comments (18)
contract/r/gnoswap/staker/v1/getter_test.gno (1)

325-338: Excellent clone/copy verification tests.

The tests properly verify that returned values are clones/copies that don't affect internal state when modified. This is critical for maintaining encapsulation and preventing accidental mutation of contract state through getter methods.

Also applies to: 473-501

contract/r/gnoswap/staker/types.gno (1)

53-108: Well-designed getter interface expansion.

The IStakerGetter interface has been properly expanded with a comprehensive set of accessor methods. The naming is consistent (Get* prefix), return types are appropriate, and the interface provides good read-only access to staker state without exposing internal implementation details.

contract/r/scenario/upgrade/implements/mock/staker/test_impl.gno (1)

179-640: Consistent mock wrapper implementation.

The mock test implementation follows a consistent ExecuteFn pattern that allows selective method mocking while delegating to the underlying v1 implementation by default. This provides good flexibility for upgrade testing scenarios.

contract/r/gnoswap/staker/getter.gno (2)

7-32: Good use of Clone() to prevent state mutation.

The GetPool, GetIncentive, and GetDeposit methods properly return cloned copies rather than references to internal state. This prevents external callers from accidentally or maliciously mutating contract state through returned object references.


154-157: No issue found. GetExternalIncentiveByPoolPath correctly returns independent copies of ExternalIncentive structs via value append semantics. Since the struct contains only primitive types, value copying provides complete isolation from internal state.

contract/r/scenario/upgrade/implements/security/insecure_staker_callback/test_impl.gno (1)

159-535: Well-structured activation-based test implementation.

The security test implementation properly guards each method with activation checks, panicking with descriptive messages when methods are called before being activated. This pattern is effective for testing upgrade scenarios and ensuring that only explicitly enabled methods are accessible during testing.

contract/r/gnoswap/staker/v1/getter.gno (9)

105-154: LGTM! Safe primitive return types.

These getter methods return primitive types (int64, bool) which are copied by value, preventing any external mutation of internal state.


209-227: LGTM! Safe calculation methods.

These methods perform read-only calculations and return primitive int64 values, which are safe from external mutation.


345-354: LGTM! Safe string representation.

Returning a string is safe as strings are immutable in Gno. The nil check and zero handling are correct.


356-375: LGTM! Safe slice construction.

This method builds and returns a new slice, preventing external mutation of internal state.


382-388: LGTM! Excellent defensive copying.

Using copy() to return a defensive copy of the slice is a best practice that prevents external mutation of internal state.


390-396: LGTM! Consistent defensive copying.

Properly uses defensive copy to protect internal state.


414-436: LGTM! Safe pagination implementation.

This method builds a new slice with proper offset/count handling, and returns primitive uint64 values which are safe from mutation.


35-38: All fields in sr.Pool are unexported—no security issue.

The Pool struct (contract/r/gnoswap/staker/pool.gno) has only unexported fields (lowercase identifiers), preventing external callers from bypassing access control. Returning a pointer to Pool in GetPool() is safe and follows the correct Gno pattern for encapsulation.

Likely an incorrect or invalid review comment.


204-207: No issue found. The Deposit struct in gno.land/r/gnoswap/staker has all fields unexported (lowercase), preventing external modification. Returning a pointer to Deposit is architecturally sound.

Likely an incorrect or invalid review comment.

contract/r/scenario/upgrade/implements/v3_valid/staker/test_impl.gno (1)

93-307: LGTM! Proper test delegation.

The test implementation correctly delegates all new getter methods to the underlying v1 instance. This is appropriate for upgrade testing scenarios.

contract/r/scenario/upgrade/implements/v2_invalid/staker/test_impl.gno (1)

154-530: LGTM! Consistent activation pattern.

The test implementation correctly applies activation guards to all new getter methods, consistent with the existing pattern. This is appropriate for testing incomplete or invalid upgrade scenarios.

contract/r/gnoswap/staker/_mock_test.gno (1)

131-561: LGTM! Consistent mock implementation.

The mock implementation correctly follows the established MockResponse pattern for all new getter methods. Type assertions and zero-value returns are handled appropriately.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
contract/r/gnoswap/staker/tree.gno (1)

74-86: Shallow copy in Clone() may not provide true isolation.

The Clone() method performs a shallow copy of values (line 81). If the tree stores pointers to mutable objects (e.g., *Pool, *ExternalIncentive), both the original and cloned tree will reference the same underlying objects. Mutations to these objects will affect both trees, which may not provide the isolation expected from a "clone" operation.

Problem: The current implementation only copies the tree structure, not the values themselves. This can lead to unexpected aliasing issues where modifications through one tree affect the other.

Recommendation:

If deep isolation is required, consider one of these approaches:

  1. Document that this is a shallow clone and callers must not mutate values
  2. Require values to implement a Cloner interface and deep-copy them
  3. Rename to ShallowClone() to clarify semantics

Based on the usage in getter.gno where Pool/Incentive/Deposit objects are cloned separately after retrieval, the current approach may be acceptable if properly documented.

📝 Add documentation to clarify shallow copy semantics
+// Clone creates a shallow copy of the tree structure.
+// Note: Values are not cloned - both trees will reference the same value objects.
+// Callers must ensure values are not mutated or perform additional cloning as needed.
 func (self *UintTree) Clone() *UintTree {
 	if self == nil {
 		return nil
 	}

 	cloned := NewUintTree()
 	self.tree.Iterate("", "", func(key string, value any) bool {
 		cloned.tree.Set(key, value)
 		return false
 	})

 	return cloned
 }
contract/r/gnoswap/staker/_mock_test.gno (1)

339-345: Consider standardizing default return values for slice types.

The mock getter methods return slices inconsistently when data is not found:

  • Some return nil: lines 342, 526, 534, 558, 574, 598, 614, 640
  • Others return empty slices: lines 430, 446

While both behave similarly in most Go/Gno contexts (len() and range), they differ in nil checks and JSON serialization. For test mocks, consistency improves maintainability and reduces surprises.

♻️ Standardize to return nil consistently

For example, update line 430 and 446:

 func (m *MockStaker) GetPoolIncentiveIdList(poolPath string) []string {
 	res, ok := m.Response.Get("GetPoolIncentiveIdList")
 	if !ok {
-		return []string{}
+		return nil
 	}
 	return res[0].([]string)
 }

 func (m *MockStaker) GetPoolsByTier(tier uint64) []string {
 	res, ok := m.Response.Get("GetPoolsByTier")
 	if !ok {
-		return []string{}
+		return nil
 	}
 	return res[0].([]string)
 }

Also applies to: 427-449, 523-561, 571-641

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2a9884 and b244820.

📒 Files selected for processing (7)
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/getter.gno
  • contract/r/gnoswap/staker/pool.gno
  • contract/r/gnoswap/staker/tree.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/v1/getter_test.gno
🚧 Files skipped from review as they are similar to previous changes (1)
  • contract/r/gnoswap/staker/pool.gno
🧰 Additional context used
📓 Path-based instructions (1)
**/*.gno

⚙️ CodeRabbit configuration file

**/*.gno: # Gno Smart Contract Code Review Rules

About Gno

Gno is a blockchain smart contract language based on Go. Key differences from Go:

  • Realms (r/): Stateful contracts with persistent storage
  • Packages (p/): Pure library code without state
  • Realm crossing: Explicit syntax for inter-realm calls using cross keyword
  • Access control: Uses runtime.PreviousRealm() and runtime.OriginCaller() for caller verification
  • State persistence: Global variables in realms persist across transactions
  • Determinism: No goroutines, limited stdlib, panic-based error handling in realms

Critical Security Violations

Flag immediately as [CRITICAL]:

  1. Exported global variables - Allows anyone to modify state
var Admin address  // CRITICAL: Anyone can reassign
  1. Missing access control - No caller verification on state mutations
func DeleteData(_ realm) {
    data.Clear()  // CRITICAL: Anyone can delete
}
  1. Error return for security - Transaction succeeds even when unauthorized
func AdminOnly(_ realm) error {
    if !isAdmin() {
        return errors.New("denied")  // CRITICAL: Should panic
    }
}
  1. Direct external realm modification - Readonly taint violation
otherrealm.GlobalVar = "hacked"  // CRITICAL: Cannot modify external realm state

Required Patterns

Access Control (MUST on all state mutations)

Every function that modifies state MUST verify the caller:

import "chain/runtime"

func MutatingFunction(cur realm, params) {
    // For immediate caller (could be user or realm)
    caller := runtime.PreviousRealm().Address()

    // For original transaction signer (always a user)
    signer := runtime.OriginCaller()

    if caller != authorized {
        panic("unauthorized")  // MUST panic, not return error
    }

    // ... mutation logic ...
}

Why panic? Panics abort and rollback the transaction. Returning errors allows unauthorized transactions t...

Files:

  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/gnoswap/staker/tree.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/getter.gno
🧠 Learnings (1)
📚 Learning: 2026-01-05T03:20:38.341Z
Learnt from: notJoon
Repo: gnoswap-labs/gnoswap PR: 1118
File: contract/r/gnoswap/access/access.gno:37-43
Timestamp: 2026-01-05T03:20:38.341Z
Learning: In the gnoswap codebase, enforce consistency where event attribute names use prevAddr to refer to the caller/previous realm address in emitted events, as this is the established convention. When reviewing or adding new contracts, prefer using prevAddr over caller for event fields; if a semantic renaming is ever considered, document the rationale and ensure all related events across contracts are updated to maintain consistency.

Applied to files:

  • contract/r/gnoswap/staker/v1/getter_test.gno
  • contract/r/gnoswap/staker/tree.gno
  • contract/r/gnoswap/staker/types.gno
  • contract/r/gnoswap/staker/v1/getter.gno
  • contract/r/gnoswap/staker/_mock_test.gno
  • contract/r/gnoswap/staker/getter.gno
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-gnoswap (gnoswap/pool/v1, contract/r/gnoswap/pool/v1)
  • GitHub Check: test-gnoswap (gnoswap/scenario/staker, contract/r/scenario/staker)
  • GitHub Check: test-gnoswap (gnoswap/scenario/router, contract/r/scenario/router)
  • GitHub Check: test-gnoswap (gnoswap/router/v1, contract/r/gnoswap/router/v1)
  • GitHub Check: test-gnoswap (gnoswap/test/fuzz, contract/r/gnoswap/test/fuzz)
  • GitHub Check: test-gnoswap (gnoswap/version_manager, contract/p/gnoswap/version_manager)
  • GitHub Check: run-integration-test

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

@junghoon-vans junghoon-vans merged commit 46cd5c9 into main Jan 9, 2026
59 of 60 checks passed
@junghoon-vans junghoon-vans deleted the feat/staker-getters branch January 9, 2026 14:47
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.

4 participants