Skip to content

feat(governance-token): add voting checkpoint history and latest chec…#508

Open
MarcusDavidG wants to merge 4 commits intotheblockcade:mainfrom
MarcusDavidG:feat/466-governance-token-checkpoint-history
Open

feat(governance-token): add voting checkpoint history and latest chec…#508
MarcusDavidG wants to merge 4 commits intotheblockcade:mainfrom
MarcusDavidG:feat/466-governance-token-checkpoint-history

Conversation

@MarcusDavidG
Copy link
Copy Markdown

@MarcusDavidG MarcusDavidG commented Apr 7, 2026

Description

Adds voting checkpoint visibility to the governance token contract so callers can inspect a holder's balance history without reconstructing it off-
chain. Each mint, burn, or transfer now writes a Checkpoint { ledger, balance } snapshot for the affected holder(s). Two new read-only accessors
expose this data.

Fixes #466

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

How Has This Been Tested?

Five new unit tests were added in contracts/governance-token/src/lib.rs and run via cargo +nightly test -p stellarcade-governance-token:

  • test_latest_checkpoint_after_mint — verifies a checkpoint is recorded with the correct balance after minting
  • test_latest_checkpoint_missing_holder_returns_none — verifies latest_checkpoint returns None for a holder with no history (deterministic, not
    ambiguous zero)
  • test_checkpoint_history_bounded — verifies checkpoint_history respects the limit parameter and that same-ledger writes overwrite rather than
    duplicate
  • test_checkpoint_history_missing_holder_returns_empty — verifies checkpoint_history returns an empty vec for unknown holders
  • test_checkpoint_recorded_on_transfer — verifies both sender and receiver get checkpoints written on a transfer

All 7 tests (5 new + 2 existing) pass.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

  • New Features

    • Per-holder balance checkpointing on mint/burn/transfer; queries for latest checkpoint, paged history (oldest-first, capped at 50), and checkpoint at-or-before a given ledger.
  • Chores

    • Contract init now requires admin authorization and accepts explicit token metadata (name, symbol, decimals).
    • Balance query renamed to a shorter accessor.
  • Documentation

    • Updated docs describing checkpoint semantics: write conditions, ordering, same-ledger overwrite, retention and unknown-holder behavior.

…kpoint accessor

Closes theblockcade#466

- Add Checkpoint { ledger, balance } contracttype
- Write checkpoint on every mint, burn, and transfer
- latest_checkpoint(holder) -> Option<Checkpoint>: returns most recent snapshot or None for unknown holders
- checkpoint_history(holder, limit) -> Vec<Checkpoint>: returns up to limit (capped at 50) most-recent checkpoints oldest-first; empty vec for unknown holders
- Same-ledger writes overwrite rather than duplicate; oldest entry evicted when cap (50) is reached
- Remove unused stellarcade-shared dependency from Cargo.toml
- Add tests: latest checkpoint after mint, missing holder returns None/empty, bounded history, checkpoint on transfer
- Update contracts/governance-token/README.md and docs/contracts/governance-token.md
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbf501f5-a7fc-401d-9cbd-e9a4ebfbab50

📥 Commits

Reviewing files that changed from the base of the PR and between 3f8cae3 and b9b0ae5.

📒 Files selected for processing (1)
  • contracts/governance-token/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/governance-token/src/lib.rs

📝 Walkthrough

Walkthrough

Adds per-holder balance checkpointing to the governance-token contract (new Checkpoint type, DataKey::Checkpoints(Address), capped history of 50, same-ledger overwrite, oldest eviction), writes checkpoints on mint/burn/transfer, and exposes three read accessors; also removes a local dependency and updates README/docs signatures.

Changes

Cohort / File(s) Summary
Dependency
contracts/governance-token/Cargo.toml
Removed local stellarcade-shared dependency reference.
Documentation
contracts/governance-token/README.md, docs/contracts/governance-token.md
Updated API docs: init signature changed (admin, name, symbol, decimals), balance_of renamed to balance, added Checkpoint type and three new accessors (latest_checkpoint, checkpoint_history, checkpoint_at_ledger), and documented checkpoint write/ordering/retention rules.
Contract Implementation & Tests
contracts/governance-token/src/lib.rs
Introduced Checkpoint { ledger, balance }, const MAX_CHECKPOINTS = 50, DataKey::Checkpoints(Address), write_checkpoint() logic (overwrite same-ledger, cap eviction), checkpoint writes on mint/burn/transfer, and public accessors latest_checkpoint, checkpoint_history, checkpoint_at_ledger; added/updated tests covering creation, overwrite, eviction, ordering, and unknown-holder behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Contract as GovernanceToken
  participant Storage

  Client->>Contract: call mint/burn/transfer(...)
  Contract->>Storage: update `Balance` for holder(s)
  Contract->>Contract: write_checkpoint(holder, new_balance)
  Contract->>Storage: read/write `Checkpoints\<Address>` (overwrite if same ledger, evict oldest if >50)
  Client->>Contract: call latest_checkpoint / checkpoint_history / checkpoint_at_ledger
  Contract->>Storage: read `Checkpoints\<Address>`
  Contract-->>Client: return Option\<Checkpoint\> / Vec\<Checkpoint\>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through ledger fields at dawn,

I stamped each balance as I moved along,
Fifty markers kept in tidy rows,
Same-day steps replace what goes,
Ask me later — I still hum the song. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(governance-token): add voting checkpoint history and latest chec…' directly matches the PR's main objective of adding checkpoint visibility features to the governance token.
Linked Issues check ✅ Passed All requirements from issue #466 are met: checkpoint accessors implemented, bounded history with deterministic ordering added, storage layout documented, missing holders return deterministic output, API is compact, and tests cover all acceptance criteria.
Out of Scope Changes check ✅ Passed All changes directly support the checkpoint visibility feature. The removal of the stellarcade-shared dependency is a cleanup that enables the crate's independence, aligning with the scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@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 (1)
contracts/governance-token/src/lib.rs (1)

368-425: The new tests still miss the eviction and multi-ledger ordering path.

All of these assertions run at the default ledger sequence, so they validate missing-holder behavior and same-ledger overwrite, but not the oldest-first ordering or head-eviction branch once history grows past 50 distinct ledgers. Please add one case that advances the ledger between writes and pushes past MAX_CHECKPOINTS.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 368 - 425, Add a test
that advances the ledger sequence between checkpointing operations and writes
more than MAX_CHECKPOINTS distinct-ledger checkpoints to exercise oldest-first
ordering and head eviction: use setup() to get (env, _, _, client), create a
holder Address, loop to mint (or transfer/burn) small amounts while advancing
the ledger sequence between each operation (so each write is on a new ledger),
repeat for MAX_CHECKPOINTS + 1 iterations, then call
client.checkpoint_history(&holder, &MAX_CHECKPOINTS) and assert the returned
history length equals MAX_CHECKPOINTS and that the earliest returned checkpoint
corresponds to the most recent MAX_CHECKPOINTS ledgers (i.e., the oldest
checkpoint was evicted); also verify client.latest_checkpoint(&holder) reflects
the final balance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/governance-token/README.md`:
- Around line 25-45: The README's method list is out of sync with the contract
ABI: replace the stale signatures `init(admin: Address, token_config:
TokenConfig)` and `balance_of(owner: Address)` with the actual exported
signatures `init(admin, name, symbol, decimals)` and `balance(owner)` and ensure
naming matches throughout (e.g., use `latest_checkpoint(holder: Address)`,
`checkpoint_history(holder: Address, limit: u32)`, and the storage item
`Checkpoints`); update any occurrence of `balance_of`/`token_config` to
`balance`/the explicit init parameters and confirm the method descriptions,
parameter names, and return types in the README now match the contract symbols
(`init`, `balance`, `latest_checkpoint`, `checkpoint_history`, `Checkpoints`).

In `@docs/contracts/governance-token.md`:
- Around line 197-211: Update the "Checkpoint Ordering & Retention" section to
explicitly state when checkpoints are written: on mint, burn, and transfer
operations affecting a holder's balance. Mention that each such operation
records (or updates) a Checkpoint (the Checkpoint struct with fields ledger and
balance) for the involved holder(s), follows the per-holder ascending ledger
ordering, overwrites multiple changes in the same ledger, and participates in
the 50-checkpoint retention/eviction policy; also clarify the behavior for
unknown holders remains None/empty list.

---

Nitpick comments:
In `@contracts/governance-token/src/lib.rs`:
- Around line 368-425: Add a test that advances the ledger sequence between
checkpointing operations and writes more than MAX_CHECKPOINTS distinct-ledger
checkpoints to exercise oldest-first ordering and head eviction: use setup() to
get (env, _, _, client), create a holder Address, loop to mint (or
transfer/burn) small amounts while advancing the ledger sequence between each
operation (so each write is on a new ledger), repeat for MAX_CHECKPOINTS + 1
iterations, then call client.checkpoint_history(&holder, &MAX_CHECKPOINTS) and
assert the returned history length equals MAX_CHECKPOINTS and that the earliest
returned checkpoint corresponds to the most recent MAX_CHECKPOINTS ledgers
(i.e., the oldest checkpoint was evicted); also verify
client.latest_checkpoint(&holder) reflects the final balance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6bfbf94d-414e-49a1-b67b-ba29a1624813

📥 Commits

Reviewing files that changed from the base of the PR and between bd55024 and 8a4edd8.

📒 Files selected for processing (4)
  • contracts/governance-token/Cargo.toml
  • contracts/governance-token/README.md
  • contracts/governance-token/src/lib.rs
  • docs/contracts/governance-token.md
💤 Files with no reviewable changes (1)
  • contracts/governance-token/Cargo.toml

- README: fix stale init signature (token_config -> name/symbol/decimals) and balance_of -> balance
- docs: clarify checkpoint write triggers (mint/burn/transfer) in Ordering & Retention section
- tests: add test_checkpoint_eviction_and_ordering to cover oldest-first ordering and head eviction past MAX_CHECKPOINTS across distinct ledgers
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (3)
contracts/governance-token/README.md (1)

44-44: Minor: Storage key name mismatch.

The storage documentation says Checkpoints but the contract uses DataKey::Checkpoints(Address) - consider clarifying that this is a per-holder key:

📝 Suggested clarification
-- `Checkpoints`: Per-holder ordered list of `Checkpoint` entries (bounded to 50).
+- `Checkpoints(Address)`: Per-holder ordered list of `Checkpoint` entries (bounded to 50).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/README.md` at line 44, Update the README entry for
"Checkpoints" to reflect that checkpoints are stored per-holder under the
storage key DataKey::Checkpoints(Address): describe it as a per-holder ordered
list of Checkpoint entries (bounded to 50) and mention the exact storage key
symbol DataKey::Checkpoints(Address) so readers understand it's scoped by holder
address.
contracts/governance-token/src/lib.rs (2)

248-279: Governance voting should use balance snapshots, not current balance.

The governance contract votes using TokenClient::balance(&voter) at voting time, allowing voters to manipulate their weight by acquiring tokens immediately before voting and disposing afterward. While Soroban's transaction atomicity prevents single-transaction flash-loan-style attacks, the window remains exploitable across separate transactions.

The governance-token contract already captures ledger-aware checkpoints (with ledger: u32 and balance: i128 fields) and the Proposal struct stores start_ledger. However, voting never queries the checkpoint at the proposal's start_ledger — instead it uses current balance.

Consider implementing checkpoint_at_ledger(holder: Address, ledger: u32) -> Option<Checkpoint> to enable snapshot voting. Voters could then be weighted by their balance at the proposal's start ledger, not at voting time. Alternatively, document why current-balance voting is preferred (e.g., if it's intentional policy).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 248 - 279, The contract
currently uses current balances (e.g., TokenClient::balance) when tallying votes
instead of ledgered snapshots; add a function checkpoint_at_ledger(env: Env,
holder: Address, ledger: u32) -> Option<Checkpoint> that searches the stored
Vec<Checkpoint> (same storage key used by latest_checkpoint/checkpoint_history
and type Checkpoint) and returns the checkpoint whose ledger equals or is the
most-recent <= requested ledger, then update the vote-weight calculation in the
proposal-voting logic (where Proposal.start_ledger is available) to call
checkpoint_at_ledger(holder, proposal.start_ledger) and use its balance if
present (fallback to zero if None) rather than querying current
TokenClient::balance; ensure this new helper reuses existing storage access
patterns used by latest_checkpoint/checkpoint_history.

102-109: Use Vec::slice() for more efficient eviction.

The current eviction creates a new Vec and copies elements one-by-one. While bounded to 50 iterations, soroban-sdk Vec supports slice() which is more efficient. The integer-overflow vulnerability (CVE-2026-24889) that previously affected slice() is patched in soroban-sdk 25.0.2 (the version in use), making this refactor safe.

♻️ Suggested refactor
     // Evict oldest entry when the cap is reached.
     if history.len() >= MAX_CHECKPOINTS {
-        let mut trimmed: Vec<Checkpoint> = Vec::new(env);
-        for i in 1..history.len() {
-            trimmed.push_back(history.get(i).unwrap());
-        }
-        history = trimmed;
+        history = history.slice(1..);
     }

As a best practice, also consider setting overflow-checks = true in [profile.release] in Cargo.toml to ensure arithmetic operations fail safely in all build configurations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 102 - 109, The eviction
code for history currently allocates a new Vec and pushes elements one-by-one
(using trimmed and push_back) which is inefficient; replace that logic by using
Vec::slice on history to create a view of the elements after the first entry
(e.g., slice(1, history.len())) and assign that slice back to history (or copy
the slice in one operation) so you avoid the per-element loop; update uses of
MAX_CHECKPOINTS, history, and Checkpoint to operate with the slice-based
eviction and ensure bounds are checked (use history.len() for the slice end).
Also consider enabling overflow-checks = true in Cargo.toml [profile.release] as
a follow-up to guard arithmetic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@contracts/governance-token/README.md`:
- Line 44: Update the README entry for "Checkpoints" to reflect that checkpoints
are stored per-holder under the storage key DataKey::Checkpoints(Address):
describe it as a per-holder ordered list of Checkpoint entries (bounded to 50)
and mention the exact storage key symbol DataKey::Checkpoints(Address) so
readers understand it's scoped by holder address.

In `@contracts/governance-token/src/lib.rs`:
- Around line 248-279: The contract currently uses current balances (e.g.,
TokenClient::balance) when tallying votes instead of ledgered snapshots; add a
function checkpoint_at_ledger(env: Env, holder: Address, ledger: u32) ->
Option<Checkpoint> that searches the stored Vec<Checkpoint> (same storage key
used by latest_checkpoint/checkpoint_history and type Checkpoint) and returns
the checkpoint whose ledger equals or is the most-recent <= requested ledger,
then update the vote-weight calculation in the proposal-voting logic (where
Proposal.start_ledger is available) to call checkpoint_at_ledger(holder,
proposal.start_ledger) and use its balance if present (fallback to zero if None)
rather than querying current TokenClient::balance; ensure this new helper reuses
existing storage access patterns used by latest_checkpoint/checkpoint_history.
- Around line 102-109: The eviction code for history currently allocates a new
Vec and pushes elements one-by-one (using trimmed and push_back) which is
inefficient; replace that logic by using Vec::slice on history to create a view
of the elements after the first entry (e.g., slice(1, history.len())) and assign
that slice back to history (or copy the slice in one operation) so you avoid the
per-element loop; update uses of MAX_CHECKPOINTS, history, and Checkpoint to
operate with the slice-based eviction and ensure bounds are checked (use
history.len() for the slice end). Also consider enabling overflow-checks = true
in Cargo.toml [profile.release] as a follow-up to guard arithmetic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3404d9e2-f307-46e7-9a66-bca1f5ed420a

📥 Commits

Reviewing files that changed from the base of the PR and between 8a4edd8 and 68a2240.

📒 Files selected for processing (3)
  • contracts/governance-token/README.md
  • contracts/governance-token/src/lib.rs
  • docs/contracts/governance-token.md
✅ Files skipped from review due to trivial changes (1)
  • docs/contracts/governance-token.md

…mments

- README: clarify storage key as Checkpoints(Address) (per-holder scoped)
- lib.rs: replace manual eviction loop with history.slice(1..history.len())
- lib.rs: add checkpoint_at_ledger(holder, ledger) -> Option<Checkpoint> for snapshot-based vote weighting
- docs: document checkpoint_at_ledger method signature and semantics
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (2)
contracts/governance-token/src/lib.rs (2)

447-473: Missing test coverage for checkpoint_at_ledger.

The test suite covers latest_checkpoint and checkpoint_history thoroughly, including edge cases like missing holders and eviction. However, checkpoint_at_ledger — a key method for snapshot-based vote weighting — has no test coverage. Consider adding tests for:

  1. Finding a checkpoint at an exact ledger match
  2. Finding the most recent checkpoint before a given ledger
  3. Returning None when the requested ledger precedes all checkpoints
  4. Returning None for unknown holders

Would you like me to generate test cases for checkpoint_at_ledger?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 447 - 473, Add unit tests
covering the checkpoint_at_ledger function: write tests (using setup(),
Address::generate, client, and env.ledger().with_mut to advance ledger and
client.mint) that assert (1) exact ledger match returns that checkpoint, (2) a
ledger between checkpoints returns the most recent prior checkpoint, (3) a
ledger before all checkpoints returns None, and (4) unknown holder returns None;
use MAX_CHECKPOINTS to create checkpoints and re-use patterns from
test_checkpoint_eviction_and_ordering for ledger sequencing and assertions
against client.checkpoint_at_ledger and client.latest_checkpoint.

281-299: Linear search is acceptable for bounded history, but consider binary search for future optimization.

The checkpoint_at_ledger implementation correctly finds the latest checkpoint with ledger <= requested by iterating forward through the ascending-ordered history and updating the result until it overshoots. With MAX_CHECKPOINTS = 50, linear search is acceptable, but a binary search could be a future optimization if this becomes a hot path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 281 - 299, The current
checkpoint_at_ledger function scans history linearly from start and updates
result until an overshoot; to optimize, replace that forward linear scan with a
binary search over the ascending-ordered Vec<Checkpoint> (retrieved from
DataKey::Checkpoints) to find the rightmost Checkpoint with cp.ledger <= ledger
and return it as Option<Checkpoint>. Locate the function checkpoint_at_ledger
and change the loop logic to use a standard binary-search pattern (low/high/mid)
comparing history.get(mid).unwrap().ledger to ledger, moving low or high
accordingly and returning the found checkpoint or None; keep all other storage
retrieval and types (Checkpoint, Env, Address) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@contracts/governance-token/src/lib.rs`:
- Around line 447-473: Add unit tests covering the checkpoint_at_ledger
function: write tests (using setup(), Address::generate, client, and
env.ledger().with_mut to advance ledger and client.mint) that assert (1) exact
ledger match returns that checkpoint, (2) a ledger between checkpoints returns
the most recent prior checkpoint, (3) a ledger before all checkpoints returns
None, and (4) unknown holder returns None; use MAX_CHECKPOINTS to create
checkpoints and re-use patterns from test_checkpoint_eviction_and_ordering for
ledger sequencing and assertions against client.checkpoint_at_ledger and
client.latest_checkpoint.
- Around line 281-299: The current checkpoint_at_ledger function scans history
linearly from start and updates result until an overshoot; to optimize, replace
that forward linear scan with a binary search over the ascending-ordered
Vec<Checkpoint> (retrieved from DataKey::Checkpoints) to find the rightmost
Checkpoint with cp.ledger <= ledger and return it as Option<Checkpoint>. Locate
the function checkpoint_at_ledger and change the loop logic to use a standard
binary-search pattern (low/high/mid) comparing history.get(mid).unwrap().ledger
to ledger, moving low or high accordingly and returning the found checkpoint or
None; keep all other storage retrieval and types (Checkpoint, Env, Address)
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11a6576c-f7f5-41c9-9c84-4ad95194228e

📥 Commits

Reviewing files that changed from the base of the PR and between 68a2240 and 3f8cae3.

📒 Files selected for processing (3)
  • contracts/governance-token/README.md
  • contracts/governance-token/src/lib.rs
  • docs/contracts/governance-token.md

- Replace linear scan with binary search (O(log n)) in checkpoint_at_ledger
- Add test_checkpoint_at_ledger_exact_match
- Add test_checkpoint_at_ledger_between_checkpoints
- Add test_checkpoint_at_ledger_before_all_checkpoints_returns_none
- Add test_checkpoint_at_ledger_unknown_holder_returns_none
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.

Contract governance-token: add voting checkpoint history and latest checkpoint accessor

1 participant