Skip to content

Holder snapshots, key metadata, flash-loan guard, settable co-creator split - #794

Merged
Chucks1093 merged 3 commits into
accesslayerorg:mainfrom
Davidemulo:issues/778-779-781-782-snapshot-metadata-flashloan-guard-co-creator
Aug 31, 2026
Merged

Holder snapshots, key metadata, flash-loan guard, settable co-creator split#794
Chucks1093 merged 3 commits into
accesslayerorg:mainfrom
Davidemulo:issues/778-779-781-782-snapshot-metadata-flashloan-guard-co-creator

Conversation

@Davidemulo

Copy link
Copy Markdown
Contributor

Summary

  • Add a holder snapshot function that captures all holder balances at a specified ledger for offline processing #778take_snapshot: records each holder's balance at the current ledger into a snapshot map, plus metadata (snapshot_ledger, total_holders). Soroban contract storage cannot be enumerated on-chain (there's no "iterate all keys with this prefix"), so holders is a caller-supplied list — sourced off-chain, e.g. from an indexer — rather than an internal registry walk, exactly like the existing airdrop_keys entrypoint takes a caller-supplied recipient list. Capped at MAX_SNAPSHOT_HOLDERS (100) per call for the same reason airdrop_keys caps at MAX_AIRDROP_RECIPIENTS. Rejects a duplicate snapshot_id with SnapshotAlreadyExists, a non-admin caller with Unauthorized, and emits snapshot_taken.
  • Add a creator key initialisation function that stores on-chain metadata including name, bio, and avatar URI #779initialise_key: one-time on-chain identity metadata (name/bio/avatar URI) for a registered creator, validated against 64/256-byte caps, TTL-bumped on write, rejecting a second call with KeyAlreadyInitialised. Emits key_initialised.
  • Add a same-ledger flash loan guard preventing a buy and sell of the same key within a single transaction #781 — flash-loan guard: buy_key now records last_buy_ledger per (creator, holder); sell_key rejects with FlashLoanDetected (and emits flash_loan_blocked) when attempted in that same ledger. Stored as a separate persistent entry rather than folded into the balance entry as the issue's scope suggested ("Store last_buy_ledger in the same persistent entry as the holder balance") — the balance field is a plain u32 read/written at dozens of call sites across this 6000-line file (buy, sell, transfer, dividends, snapshots, batch ops), and changing its shape to a struct would have meant touching all of them for a one-extra-storage-read optimization. Happy to fold it in as a follow-up if that tradeoff is wanted.
  • Add a revenue share split allowing creators to designate a co-creator address that receives a portion of their royalties #782 — settable co-creator split: the registration-time co_creator config (register_creator's optional CoCreatorConfig param) was previously immutable. This adds a standalone set_co_creator(creator, co_creator, split_bps) entrypoint (1–9000 bps, SplitTooHigh otherwise, distinct from the registration path's own InvalidCoCreatorShare/1–9999 bound since the issue specifies a 90% cap) a creator can call anytime. It writes the same CoCreatorConfig storage the existing buy/sell fee-crediting path (credit_creator_fee) already reads — so per-trade splitting on both buy and sell, and the co_creator_fee_earned event, needed no new code; only the missing setter and a co_creator_set event were added.

Tests

Added creator-keys/src/test_issues_778_779_781_782.rs — 19 tests covering the happy path and each documented error condition for all four issues, following this repo's existing test_new_features.rs setup pattern (setup_test()/register_creator() helpers).

Verification

I could not run cargo check/cargo test in this environment — even an untouched main checkout fails at the host-target build-script link step here (MSVC linker misconfiguration, unrelated to this change). Instead I did an extensive manual review: traced every existing call site of the storage/helper functions I reused (extend_key_ttl_to_full_window, assert_is_admin, read_registered_creator_profile, validate_non_zero_address, credit_creator_fee) to confirm argument types and borrow vs. move usage match; confirmed Vec<Address>::iter() yields owned values consistent with this file's existing loop patterns (e.g. get_creators_batch); and checked the new ContractError/DataKey variants are strictly appended (per this file's own ABI-stability doc comment on ContractError). Please treat the repo's CI as the source of truth and flag anything it catches that manual review missed.

Closes #778
Closes #779
Closes #781
Closes #782

…reator split

- accesslayerorg#778 take_snapshot(admin, creator, snapshot_id, holders): records each
  holder's balance at the current ledger into a snapshot map, plus metadata
  (snapshot_ledger, total_holders). Soroban storage can't be enumerated
  on-chain, so `holders` is a caller-supplied list (e.g. from an indexer)
  rather than an internal registry walk, capped at MAX_SNAPSHOT_HOLDERS (100)
  per call the same way airdrop_keys caps recipients. Rejects a duplicate
  snapshot_id with SnapshotAlreadyExists, a non-admin caller with
  Unauthorized, and emits snapshot_taken.
- accesslayerorg#779 initialise_key(creator, name, bio, avatar_uri): one-time on-chain
  identity metadata for a registered creator, validated against 64/256-byte
  caps, TTL-bumped on write, rejecting a second call with
  KeyAlreadyInitialised. Emits key_initialised.
- accesslayerorg#781 flash-loan guard: buy_key records last_buy_ledger per (creator,
  holder); sell_key now rejects with FlashLoanDetected (and emits
  flash_loan_blocked) when attempted in that same ledger. Stored as a
  separate persistent entry rather than folded into the balance entry as the
  issue suggested, to avoid touching every one of the balance field's many
  existing read/write call sites across this file.
- accesslayerorg#782 set_co_creator(creator, co_creator, split_bps): the registration-time
  co_creator config was previously immutable; this adds a standalone
  entrypoint (1-9000 bps, SplitTooHigh otherwise) a creator can call anytime
  to set or change it. Writes the same CoCreatorConfig storage the existing
  buy/sell fee-crediting path already reads, so per-trade splitting and the
  co_creator_fee_earned event needed no new code — only the missing setter
  and a co_creator_set event were added.

Added creator-keys/src/test_issues_778_779_781_782.rs covering all four.

I could not run cargo test/check in this environment — the host-target
build itself fails at the linker step (MSVC misconfiguration) even on an
untouched main checkout, unrelated to this change. Please treat CI as the
source of truth and flag anything it catches that manual review missed.
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@Davidemulo Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Obiajulu-gif and others added 2 commits August 28, 2026 14:54
- ContractError enum: kept upstream's new GlobalTradingHalted = 51 and
  renumbered this branch's accesslayerorg#778/accesslayerorg#779/accesslayerorg#781/accesslayerorg#782 variants to 52-58 so no
  discriminant collides (both sides used name-based references only, no
  numeric literals elsewhere, so renumbering is safe).
- DataKey enum: unioned both sides' new variants (snapshot/metadata/
  flash-loan-guard from this branch, global-pause variants from upstream)
  — no naming or numeric overlap between them.
- buy_key_with_referrer: kept both this branch's flash-loan-guard write
  (LastBuyLedger, issue accesslayerorg#781 same-ledger resell guard) and upstream's
  lockup-window write (LastBuyTimestamp) side by side, then adopted
  upstream's newer fee flow (collect_protocol_trade_fee -> net_amount
  feeding the creator/protocol fee split) instead of splitting the fee
  on the gross price, since that's a real improvement independent of
  the flash-loan guard.
- Discovered upstream/main's own tip references
  constants::storage::last_buy_timestamp() and a timestamp-keyed storage
  slot for it, but never defines either — upstream/main does not compile
  as committed. Added the missing DataKey::LastBuyTimestamp(Address,
  Address) variant and its storage helper, mirroring the existing
  LastBuyLedger pattern, so the merged tree builds.

No local rustc/link toolchain available in this environment to run
`cargo build`/`cargo test` (link.exe fails on build-script compilation
unrelated to this change), so this merge is verified by manual review
and exhaustive grep-based cross-checks of every symbol touched by the
conflict instead.
@Chucks1093
Chucks1093 merged commit c0dedd7 into accesslayerorg:main Aug 31, 2026
1 check failed
Adejumo-2 pushed a commit to Adejumo-2/accesslayer-contracts that referenced this pull request Aug 31, 2026
New code from PR accesslayerorg#794 (co-creator snapshot metadata) was unformatted.
Run cargo fmt --all to pass the CI format check.
Seunfunmi-319509 pushed a commit to Seunfunmi-319509/accesslayer-contracts that referenced this pull request Sep 1, 2026
…dentation

The merge that brought in PR accesslayerorg#794 truncated the cancel_auction function
body, removing its closing braces and logic. The circuit breaker block
inside buy_key also had wrong indentation from the merge, leaving an
unclosed else delimiter that broke cargo fmt.

- Restore the full cancel_auction function body (auction config lookup,
  auction_sold guard, storage removal, and event emission)
- Re-indent the circuit breaker block inside the else branch so the
  outer `let price = if ... else { ... };` expression is properly closed
- Run cargo fmt across the workspace

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment