Skip to content

[FREEZE-EXCEPTION] [#319] Migrate to Anchor 1.0 + mpl-core 0.12 — escape the mpl-core/anchor borsh stalemate#434

Open
alrimarleskovar wants to merge 11 commits into
mainfrom
claude/path-b-mpl-core-no-anchor-feature
Open

[FREEZE-EXCEPTION] [#319] Migrate to Anchor 1.0 + mpl-core 0.12 — escape the mpl-core/anchor borsh stalemate#434
alrimarleskovar wants to merge 11 commits into
mainfrom
claude/path-b-mpl-core-no-anchor-feature

Conversation

@alrimarleskovar
Copy link
Copy Markdown
Owner

Closes #319 — without ever needing Metaplex to respond

PR #319 was blocked for months on an upstream borsh version stalemate (E0433 maybestd) attributed to mpl-core ↔ Anchor coordination. This PR proves the blocker was a feature-flag misconfiguration on our side, not an upstream problem, and completes the full migration.

The key insight

mpl-core's anchor feature flips kaigan onto Anchor's reexported borsh 0.10, creating the maybestd coexistence conflict. But our code never used Anchor account-derives against mpl-core types — the position NFT is UncheckedAccount<'info> + program-id pin, and all interaction goes through instructions::*CpiBuilder + accounts::BaseAssetV1. So the feature was dead weight. Dropping it + opting into borsh-v1 explicitly removes the conflict entirely.

-anchor-lang = { version = "0.30.1", ... }
-mpl-core    = { version = "=0.8.0", features = ["anchor"] }
+anchor-lang = { version = "1.0", ... }
+mpl-core    = { version = "0.12", default-features = false, features = ["borsh-v1"] }

What changed

  • Cargo.toml (4 programs): anchor-lang/spl 0.30.1 → 1.0, mpl-core 0.8 → 0.12 (no-anchor + borsh-v1), added direct solana-program 3.x to core + yield-kamino for hash / sysvar::instructions.
  • Anchor 0.30 → 1.0 API migration (mechanical, no economic-logic change):
    • CpiContext::new(program.to_account_info(), …)CpiContext::new(program.key(), …) (now takes Pubkey) — 20 sites
    • Context<'_,'_,'_,'info,T>Context<'info,T> (lifetime collapse)
    • ProfileSnapshot::try_to_vec()AnchorSerialize::serialize (borsh 1.x removed the inherent method)
    • AccountInfo::realloc(len, zero)resize(len)
    • sysvar::instructions::IDid(); hash via solana-program 3.x
  • Test refreshes (app_encoders_bankrun): Anchor 1.0 hex-code error matching, IDL account-client naming, state-shape fixture refresh (escrow seeding, settle cycle arg, deposit remaining-account, production GRACE_PERIOD_SECS).

Validation (operator-run, SBF toolchain)

Check Result
anchor build (SBF, all 4 programs)
cargo check --workspace --all-targets ✅ 0 errors
pnpm test:litesvm (REQUIRED CI lane — SEV-012) 19/19
setupBankrunEnv in-memory specs (edge_cycle_boundary, edge_grace_default[_shield1_only], app_encoders_bankrun, security_kamino_cpi) 24 passing, 0 failing

Not in scope (environment-gated, not #319)

4 specs use the localnet harness (setupEnv) and need a running solana-test-validator: economic_parity, lifecycle, edge_degenerate_shapes, edge_tiny_lifecycle. They fail at the first RPC call without one. The same economic invariants are already covered in-memory by the litesvm parity slice. Their harness selection predates this migration.

Follow-ups (post-merge, separate)

  • Drop the 11-RUSTSEC cargo-audit ignore list in .github/workflows/ci.yml (the advisories were tied to solana-program 1.18.x, no longer reachable).
  • Regenerate @roundfi/sdk IDL-derived encoders if any downstream decoder drifts.
  • Devnet redeploy + OtterSec verify-build PDA refresh (the bytecode is no longer 0.30-identical).
  • Re-evaluate MAINNET_READINESS.md §7 critical-path step 1 (this was the blocker).

Full rationale + resolution log: docs/319-path-b-spike.md.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm


Generated by Claude Code

claude added 7 commits May 30, 2026 14:59
…anchor 1.0 + mpl-core 0.12

WHAT
- 4 Cargo.toml edits across roundfi-core / -reputation / -yield-kamino /
  -yield-mock. No source changes.
- mpl-core 0.8 -> 0.12 with default-features=false + features=['borsh-v1']
- anchor-lang/spl 0.30.1 -> 1.0

WHY
PR #319 documented a borsh-coexistence stalemate (E0433 maybestd) blamed on
Metaplex / Anchor coordination. The mpl-core 'anchor' feature is what flips
kaigan onto Anchor's reexported borsh 0.10 and creates that conflict. Our
code never used Anchor account-derives against mpl-core types (the position
NFT is UncheckedAccount + program-id pin), so the feature is dead weight.

EVIDENCE (cargo check)
- Step 1: 'mpl-core 0.8 default-features=false' on anchor 0.30 -> 0 errors,
  proves the feature is unused at our call sites.
- Step 2: 'mpl-core 0.12 default-features=false features=["borsh-v1"]' +
  'anchor 1.0' workspace-wide -> 15 errors, ALL Anchor 0.30->1.0 API migrations
  (Context lifetime collapse, borsh::to_vec, sysvar relocation). The
  maybestd / borsh-version conflict that killed PR #319 is GONE.

WHAT REMAINS (documented in docs/319-path-b-spike.md)
- 15 mechanical API patches, ~50-100 net LoC, 3 files
- anchor build SBF validation (out of cargo check scope)
- IDL regeneration for the SDK encoders
- bankrun + litesvm re-validation
- devnet redeploy + OtterSec attestation refresh

This commit does NOT close #319 - it provides the missing experimental
evidence that the path forward does not require Metaplex to respond to any
issue. The remaining work is mainstream API migration, on the team's clock.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
Builds on the Path B spike (969861d). All 4 programs now pass
`cargo check --workspace --all-targets` with 0 errors against
anchor-lang 1.0.2 + mpl-core 0.12 + solana-program 3.x.

API migrations applied (mechanical, no economic-logic changes):

1. CpiContext::new / new_with_signer (15 sites in core, 2 in yield-kamino,
   2 in yield-mock, 1 in reputation): first arg changed from
   program.to_account_info() to program.key() (Anchor 1.0 takes Pubkey).
   Struct-field token_program inside ATA Create{} left as AccountInfo.

2. Context<'_,'_,'_,'info,T> -> Context<'info,T> (Anchor 1.0 collapsed the
   4 lifetimes to 1): harvest_yield, deposit_idle_to_yield handlers + lib.rs
   #[program] shims (core); harvest + kamino_cpi_redeem/deposit (kamino).

3. anchor_lang::solana_program::hash removed from the 1.0 shim: added a
   direct solana-program 3.0 dep to roundfi-core + roundfi-yield-kamino and
   import hash from there (reputation.rs, yield_adapter.rs,
   escape_valve_list_reveal.rs, kamino lib.rs).

4. sysvar::instructions::ID -> sysvar::instructions::id() (function form),
   2 sites in yield-kamino.

5. ProfileSnapshot::try_to_vec() (borsh 0.10 inherent method, removed in
   borsh 1.x) -> AnchorSerialize::serialize into an owned Vec
   (get_profile.rs).

6. AccountInfo::realloc(len, zero_init) -> resize(len) (solana-account-info
   3.x renamed it; zero-init implicit on growth) in
   migrate_reputation_config.rs.

NOT yet validated (requires operator with SBF toolchain):
- anchor build (SBF target) end-to-end
- IDL regeneration for @roundfi/sdk
- bankrun + litesvm suites
- devnet redeploy + OtterSec attestation refresh

See docs/319-path-b-spike.md for the full sequence + remaining steps.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
Anchor 0.30 surfaced AnchorError names in the bankrun error log; Anchor
1.0's bankrun error object only carries 'custom program error: 0x<code>'.
Three negative-path assertions matched on error NAMES only and broke.
Added the corresponding hex codes (0x1773=PoolNotActive, 0x1777=WrongCycle,
0x177c=EscrowNothingToRelease) to the regex so they match both Anchor's
old named form and the 1.0 code-only form. No program-logic change.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
Anchor 1.0 keys the IDL account client by the exact struct name
(escapeValveListing), camelCased. The test used the legacy loose alias
'.account.listing' which is undefined under the 1.0 IDL, throwing
"Cannot read properties of undefined (reading 'fetch')". Renamed to
'.account.escapeValveListing' to match the on-chain struct
(programs/roundfi-core/src/state/listing.rs: pub struct EscapeValveListing).

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
…hapes

Closes the 3 state-shape-drift failures the CI lane comment flagged as a
'separate spec-refresh PR' (0x177c / 0x1777 / 0xbbd). None is a migration
regression — git diff vs origin/main shows the setup was byte-identical;
these tests had been failing pre-#319 and bankrun-full isn't a required CI
lane.

#5 release_escrow (0x177c EscrowNothingToRelease):
  The SEV-034 vesting derivation (compute_release_delta_target) computes
  total_paid = (stake_initial + total_escrow_deposited) - escrow_balance.
  The fixture seeded escrow_balance = 0, but real join_pool locks the
  stake (join_pool.rs:263: escrow_balance = stake). With escrow seeded 0
  the math concluded the stake was already released -> delta 0 -> revert.
  Fix: seed member.escrow_balance, pool.escrow_balance, and the escrow
  vault token account to STAKE_INITIAL (2_500_000), modelling join_pool.
  Verified safe against the shared contribute/claim_payout tests: both
  use deltas, and claim_payout's seed-draw guard only gets easier.

#6 settle_default (0x1777 WrongCycle):
  Guard is "args.cycle == pool.current_cycle" (settle_default.rs:161), as
  the working edge_grace_default tests use (cycle == CURRENT_CYCLE == 2).
  The spec passed the stale "current_cycle - 1" contract (cycle:1) against
  a pool seeded current_cycle=2. Fix: cycle 1 -> 2 + corrected doc-header.

#7 deposit_idle_to_yield (0xbbd AccountNotEnoughKeys):
  yield-mock's Deposit needs its YieldVaultState PDA at position 5 (after
  the 4-account adapter prelude). buildDepositIdleToYieldIx only emits the
  8 explicit core accounts (per its doc-comment); the adapter tail is the
  caller's job (sendDepositIdleToYield does it via remainingAccounts). Fix:
  append yieldStatePk as a remaining account in the test, mut + non-signer.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
#6 advanced past WrongCycle (fixed last commit) to GracePeriodNotElapsed
(0x1796 = 6038): the fixture's clock warp used the stale 60s devnet-patch
grace value, but `anchor build` compiles WITHOUT the devnet-canary
feature so the on-chain GRACE_PERIOD_SECS is the 604_800 (7d) production
constant (constants.rs:62). The +70s warp never cleared the 7d deadline.
Bumped SETTLE_GRACE_PERIOD_SECS 60 -> 604_800 to match the working
edge_grace_default* specs, so setBankrunUnixTs clears the real deadline.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
round_financial Ignored Ignored Preview May 30, 2026 4:31pm

@alrimarleskovar alrimarleskovar changed the title [#319] Migrate to Anchor 1.0 + mpl-core 0.12 — escape the mpl-core/anchor borsh stalemate [FREEZE-EXCEPTION] [#319] Migrate to Anchor 1.0 + mpl-core 0.12 — escape the mpl-core/anchor borsh stalemate May 30, 2026
claude added 4 commits May 30, 2026 16:00
The anchor build / bankrun-no-mpl-core lanes installed anchor-cli v0.30.1,
which can't parse anchor-lang 1.0 macros/IDL — so 'anchor · build' (a
required check) failed on this PR even though the migration builds clean
locally with anchor-cli 1.0.2.

- All 3 'Install Anchor' steps: v0.30.1 -> v1.0.2.
- Dropped the --no-idl flag + rebuild-idls.sh patch workaround: Anchor 1.0
  builds IDLs natively (the anchor-syn Span::source_file() removal that
  forced --no-idl under 0.30.1 is fixed upstream in 1.0), so plain
  'anchor build' emits target/idl/*.json for the bankrun harness.
- Refreshed the stale lane-header comments.

Advisory 'deny · supply-chain' may still flag the 1.18.x ignore list as
stale (those advisories are now unreachable post-migration) — that's a
non-blocking follow-up, tracked in deny.toml's #319 note.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
lifecycle + economic_parity seeded cycle_duration at 60s / 3_600s, the
stale pre-SEV-023 devnet values. The on-chain MIN_CYCLE_DURATION is
86_400 (constants.rs:152), so create_pool reverts InvalidCycleDuration
(6033) and the whole describe cascades to undefined-fixture failures.
Same state-shape drift class as the app_encoders_bankrun refresh — the
cycle counter is driven by claim_payout (not Clock), so the 1-day floor
adds no wall-time cost. edge_tiny_lifecycle + edge_degenerate_shapes were
already on 86_400.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
PR #434's anchor·build lane failed with:
  Error: Program ID mismatch detected for program 'roundfi_core':
    Keypair file has: 6rHgZF5YmJHgPSprx6NpZUNEyKipiwC1ffvxwdzDVq4v
    Source code has:  8LVrgxKwKwqjcdq7rUUwWY2zPNk8anpo2JsaR9jTQQjw

Anchor 1.0 added a pre-build check that compares declare_id! against
target/deploy/<program>-keypair.json. On a fresh CI runner those keypair
files don't exist, so Anchor generates random ones and the check fails.
The keypairs are operator-side deploy artifacts (gitignored) — they have
no role in CI compile, where declare_id! is the authoritative source.

The Anchor error message itself suggests --ignore-keys as the fix.
Applied to all 3 'anchor build' invocations in ci.yml (anchor lane +
bankrun-no-mpl-core lane + the third build block).

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
#2 in the localnet lifecycle spec hit ReputationLevelMismatch
(join_pool.rs:172): the test asserted reputationLevel=2, but join_pool
derives the trusted level from the on-chain ReputationProfile PDA (the
Step-4d audit close-out). Fresh wallets have no profile, so the program
treats them as level 1; asserting level 2 reverts. The fixture predates
that hardening (it was written when join_pool trusted the client-supplied
level). Fresh members ARE level 1 (50% stake), so LEVEL 2 -> 1 +
LEVEL_STAKE_BPS 3_000 -> 5_000; STAKE_BASE follows automatically and the
conservation assertions track. Refreshed the two stale doc comments.

https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
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.

2 participants