Skip to content

Test/transfer keys holder count - #842

Open
N-thnI wants to merge 2 commits into
accesslayerorg:mainfrom
N-thnI:test/transfer-keys-holder-count
Open

Test/transfer keys holder count#842
N-thnI wants to merge 2 commits into
accesslayerorg:mainfrom
N-thnI:test/transfer-keys-holder-count

Conversation

@N-thnI

@N-thnI N-thnI commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds unit-test coverage for transfer_keys holder-count accounting: a sender emptied out, a first-time recipient, and a partial transfer that changes neither.

Closes #422

Important

These tests have not been executed. creator-keys does not compile on main. Details in "Blocked on" below — please read that section before merging.

The implementation was already correct

transfer_keys already handles all three transitions: it decrements when the sender's new balance reaches zero, increments when the recipient's prior balance was zero, and does neither on a partial transfer between existing holders. No production logic needed changing.

What was missing is coverage. test_transfer_keys_basic, test_transfer_keys_sender_zeroed_out and test_transfer_keys_new_recipient all assert balances and total supply, but none of them reads holder_count — so every one of these transitions could regress without a test failing.

Tests added (6)

Test What it isolates
full_balance_decrements_holder_count Sender empties out to a first-time recipient — the headline case
full_balance_to_existing_holder_decrements_count Only the decrement can fire (2 → 1)
first_time_recipient_increments_holder_count Only the increment can fire (1 → 2)
partial_to_existing_holder_leaves_count_unchanged Neither branch fires
holder_count_survives_a_round_trip Count does not drift across repeated transfers
holder_count_never_exceeds_distinct_holders A repeat recipient is not counted twice

Why six rather than three. In the headline case — sender transfers their entire balance to a first-time recipient — the sender leaves the holder set as the recipient joins it, so the net count is unchanged at 1. A decrement-only bug and an increment-only bug would both satisfy a naive total-count assertion. The two isolating tests (full transfer to an existing holder; partial transfer to a new holder) each exercise exactly one branch, and the headline test asserts both balances alongside the count.

Blocked on: creator-keys does not compile on main

Two separate pre-existing problems, found in this order:

1. Unclosed delimiter — fixed here (ca012db).

The circuit-breaker check inside buy_key's price branch was missing its closing brace:

} else {
    let post_price = ...;

if pre_price > 0 && post_price > pre_price {   // de-indented, never closed
    ...
    pre_price
};                                             // closes the if, not the else

The brace intended for the else arm was consumed by the if, so impl CreatorKeysContract ran to end-of-file — reported as this file contains an unclosed delimiter at lib.rs:7764. The de-indentation makes the intent unambiguous (pre_price is the else arm's tail expression, not a value yielded only when the breaker check runs), so the fix is the missing brace plus the indentation presumably lost with it. Same comparison, same early return, same yielded value — no logic change.

This is a prerequisite, not the issue's subject: nothing in the crate compiles without it.

2. 76 duplicate-definition errors — NOT fixed here.

Once parsing succeeds, the crate reports 76 E0428 "defined multiple times" errors across events.rs (lines 758–1253) and lib.rs (lines 362–5422) — FEE_COLLECTED_EVENT_NAME, LOCKUP_BLOCKED_EVENT_NAME, holder_cap_bps, last_buy_timestamp, credit_staking_rewards_pool, FeeCollectedEvent and others.

These are interleaved duplicate symbols spread across both files, not one cleanly appended block, which is consistent with merge 352f93a ("Merge branch 'main' into feat/governance-quorum-requirement").

I stopped rather than guess. Resolving this means deciding which of two copies is authoritative for 76 symbols — event names, storage keys, a rewards-pool credit function — in a contract that moves funds. Choosing wrong changes behaviour silently and would not show up as a compile error. That is the merge author's call, and it is well outside the scope of "add three unit tests."

Suggested path: raise the duplicate-symbol breakage as its own issue against the owner of 352f93a, and hold this PR until it lands. CI on this branch will be red until then, for reasons unrelated to this work.

Verification status

Check Result
Tests executed ❌ Blocked — crate does not compile
Errors introduced by this PR Zero — 76 errors before the tests, 76 after
Parse failure fixed ca012db
Production logic changed None (the brace fix is structural; no behaviour change)

The tests use only the existing public surface — get_creator_holder_count, get_key_balance, buy_key, transfer_keys — and follow the setup pattern of the surrounding tests in test.rs, so they should pass as written once the duplicates are resolved. I would not merge on that assumption without a green run.

Acceptance criteria

  • Full transfer out decrements holder count — full_balance_to_existing_holder_decrements_count (isolated), plus the headline case
  • First-time recipient increments holder count — first_time_recipient_increments_holder_count
  • Partial transfer leaves holder count unchanged — partial_to_existing_holder_leaves_count_unchanged
  • Tests verified passing — blocked on the duplicate definitions above

N-thnI added 2 commits August 31, 2026 11:52
creator-keys does not parse on main. The circuit-breaker check inside
buy_key's price branch was missing its closing brace:

    } else {
        let post_price = ...;

    if pre_price > 0 && post_price > pre_price {   // de-indented, unclosed
        ...
        pre_price
    };                                            // closes the if, not the else

so the brace intended for the else arm was consumed by the if, and the
unclosed impl CreatorKeysContract block ran to the end of the file:
"this file contains an unclosed delimiter" at lib.rs:7764.

The de-indentation makes the intent unambiguous -- pre_price is the else
arm's tail expression, not a value returned only when the breaker check
runs -- so the fix is the missing brace plus the indentation that was
presumably lost with it. No logic changed: the same comparison guards the
same early return, and the same pre_price is yielded.

This is a prerequisite rather than the issue's subject; nothing in the crate
compiles without it. See the accompanying test commit for what still blocks
a green build.
transfer_keys is the one path that can both remove and add a holder in a
single call, and holder_count is the number of wallets with a non-zero
balance. The existing transfer tests assert balances and supply but never
the holder count, so all three transitions were untested.

Six tests:

  * full balance out, first-time recipient -- the sender leaves the holder
    set as the recipient joins it. Net count is unchanged here, so the test
    asserts both balances as well: a decrement-only or increment-only bug
    would satisfy the total on its own.
  * full balance out to an existing holder -- only the decrement can fire,
    isolating it. Count 2 -> 1.
  * partial transfer to a first-time recipient -- only the increment can
    fire, isolating it. Count 1 -> 2.
  * partial transfer between two existing holders -- neither side crosses
    zero, so the count must not move.
  * a round trip out and back -- a decrement not paired with its increment
    would leave the count drifting across repeated transfers.
  * repeat transfer to a wallet that already received keys -- it must not
    be counted twice.

Not yet executed. creator-keys does not compile on main: the preceding
commit fixes an unclosed delimiter, and past that the crate reports 76
E0428 "defined multiple times" errors across events.rs (758-1253) and
lib.rs (362-5422) -- interleaved duplicate symbols, not one appended block,
consistent with merge 352f93a "Merge branch main into
feat/governance-quorum-requirement".

Resolving those means choosing which of two copies is authoritative for 76
symbols in a contract that moves funds, which is the merge author's call
and not something to guess at from here. The error count is identical with
and without these tests, so they introduce none; they should pass as
written once the duplicates are resolved.
@Chucks1093

Copy link
Copy Markdown
Member

❌ CI Failed — verify (Contracts CI)

The verify check is failing on this PR.

Likely causes:

  • New event or type missing the #[contracttype] attribute
  • Type mismatch between what is stored and what is read back
  • Formatting not applied

Steps to fix:

  1. Run cargo build locally — fix any type mismatch or missing attribute errors
  2. Run cargo fmt --all and commit
  3. Push

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.

Add unit tests for transfer holder count update when sender balance reaches zero

2 participants