[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
Open
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #319 — without ever needing Metaplex to respond
PR #319 was blocked for months on an upstream
borshversion stalemate (E0433maybestd) 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'sanchorfeature flips kaigan onto Anchor's reexported borsh 0.10, creating themaybestdcoexistence conflict. But our code never used Anchor account-derives against mpl-core types — the position NFT isUncheckedAccount<'info>+ program-id pin, and all interaction goes throughinstructions::*CpiBuilder+accounts::BaseAssetV1. So the feature was dead weight. Dropping it + opting intoborsh-v1explicitly removes the conflict entirely.What changed
0.30.1 → 1.0, mpl-core0.8 → 0.12(no-anchor + borsh-v1), added directsolana-program 3.xto core + yield-kamino forhash/sysvar::instructions.CpiContext::new(program.to_account_info(), …)→CpiContext::new(program.key(), …)(now takesPubkey) — 20 sitesContext<'_,'_,'_,'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::ID→id();hashvia solana-program 3.xapp_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)
anchor build(SBF, all 4 programs)cargo check --workspace --all-targetspnpm test:litesvm(REQUIRED CI lane — SEV-012)setupBankrunEnvin-memory specs (edge_cycle_boundary, edge_grace_default[_shield1_only], app_encoders_bankrun, security_kamino_cpi)Not in scope (environment-gated, not #319)
4 specs use the localnet harness (
setupEnv) and need a runningsolana-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)
cargo-auditignore list in.github/workflows/ci.yml(the advisories were tied tosolana-program 1.18.x, no longer reachable).@roundfi/sdkIDL-derived encoders if any downstream decoder drifts.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