diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 638a352..9a55496 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,7 +13,7 @@ updates: interval: "weekly" open-pull-requests-limit: 5 labels: ["dependencies", "typescript"] - # Pin Node typings to the major matching engines.node (20 LTS). + # Pin Node typings to the major matching engines.node (24 LTS). # Bumping @types/node above the engines major causes typecheck to fail # against APIs that don't exist on the runtime. When the engines major is # raised, update this allow-list at the same time. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08336ab..4011aad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,8 +67,15 @@ jobs: # phase). Drops total anchor-build job time from ~10 min to ~2 min. - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@main + # `--force` is required because Swatinem/rust-cache restores + # `~/.cargo/.crates2.json` (the binstall "is-installed" registry) + # but does NOT restore `~/.cargo/bin/`. Without `--force`, binstall + # finds the cached metadata, reports "already installed", and skips + # the actual binary install — leaving subsequent `anchor build` + # steps with `command not found`. See: + # https://github.com/Swatinem/rust-cache/issues/161 - name: Install Anchor CLI - run: cargo binstall --no-confirm --version ${ANCHOR_VERSION} anchor-cli + run: cargo binstall --no-confirm --force --version ${ANCHOR_VERSION} anchor-cli # Capture anchor build's full output to a file and upload it as a # workflow artifact when the job fails — needed because the actual # error message is otherwise only visible via the GitHub Actions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00bab8c..db14c00 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,10 +50,10 @@ anchor test # localnet integration tests Required toolchain (pinned in `rust-toolchain.toml`, `Anchor.toml`, `package.json`): -- Solana CLI **2.0.x** -- Anchor **0.31.x** -- Rust **1.79+** stable -- Node **20 LTS** +- Solana CLI **3.0.10** +- Anchor **0.32.1** +- Rust **1.91.1** stable +- Node **24 LTS** - pnpm **9** ## Workflow @@ -103,7 +103,7 @@ Use [Conventional Commits](https://www.conventionalcommits.org/): feat(grave-vault): add claim_lp_proceeds instruction fix(grave-scanner): tighten epoch confirmation check docs(charter): clarify 20% protocol-share ceiling -chore(ci): pin anchor 0.31.1 +chore(ci): pin anchor 0.32.1 ``` Scopes mirror the workspace tree: `grave-scanner`, `grave-vault`, `sdk`, `indexer`, `adapters`, diff --git a/Cargo.lock b/Cargo.lock index 44867ef..411f307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,9 +67,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anchor-attribute-access-control" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f70fd141a4d18adf11253026b32504f885447048c7494faf5fa83b01af9c0cf" +checksum = "7a883ca44ef14b2113615fc6d3a85fefc68b5002034e88db37f7f1f802f88aa9" dependencies = [ "anchor-syn", "proc-macro2", @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "anchor-attribute-account" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715a261c57c7679581e06f07a74fa2af874ac30f86bd8ea07cca4a7e5388a064" +checksum = "61c4d97763b29030412b4b80715076377edc9cc63bc3c9e667297778384b9fd2" dependencies = [ "anchor-syn", "bs58", @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anchor-attribute-constant" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730d6df8ae120321c5c25e0779e61789e4b70dc8297102248902022f286102e4" +checksum = "aae3328bbf9bbd517a51621b1ba6cbec06cbbc25e8cfc7403bddf69bcf088206" dependencies = [ "anchor-syn", "quote", @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "anchor-attribute-error" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e6e449cc3a37b2880b74dcafb8e5a17b954c0e58e376432d7adc646fb333ef" +checksum = "cf2398a6d9e16df1ee9d7d37d970a8246756de898c8dd16ef6bdbe4da20cf39a" dependencies = [ "anchor-syn", "quote", @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "anchor-attribute-event" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7710e4c54adf485affcd9be9adec5ef8846d9c71d7f31e16ba86ff9fc1dd49f" +checksum = "f12758f4ec2f0e98d4d56916c6fe95cb23d74b8723dd902c762c5ef46ebe7b65" dependencies = [ "anchor-syn", "proc-macro2", @@ -126,9 +126,9 @@ dependencies = [ [[package]] name = "anchor-attribute-program" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ecfd49b2aeadeb32f35262230db402abed76ce87e27562b34f61318b2ec83c" +checksum = "8c7193b5af2649813584aae6e3569c46fd59616a96af2083c556b13136c3830f" dependencies = [ "anchor-lang-idl", "anchor-syn", @@ -143,9 +143,9 @@ dependencies = [ [[package]] name = "anchor-derive-accounts" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be89d160793a88495af462a7010b3978e48e30a630c91de47ce2c1d3cb7a6149" +checksum = "d332d1a13c0fca1a446de140b656e66110a5e8406977dcb6a41e5d6f323760b0" dependencies = [ "anchor-syn", "quote", @@ -154,9 +154,9 @@ dependencies = [ [[package]] name = "anchor-derive-serde" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc6ee78acb7bfe0c2dd2abc677aaa4789c0281a0c0ef01dbf6fe85e0fd9e6e4" +checksum = "8656e4af182edaeae665fa2d2d7ee81148518b5bd0be9a67f2a381bb17da7d46" dependencies = [ "anchor-syn", "borsh-derive-internal", @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "anchor-derive-space" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134a01c0703f6fd355a0e472c033f6f3e41fac1ef6e370b20c50f4c8d022cea7" +checksum = "dcff2a083560cd79817db07d89a4de39a2c4b2eaa00c1742cf0df49b25ff2bed" dependencies = [ "proc-macro2", "quote", @@ -178,9 +178,9 @@ dependencies = [ [[package]] name = "anchor-lang" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6bab117055905e930f762c196e08f861f8dfe7241b92cee46677a3b15561a0a" +checksum = "e67d85d5376578f12d840c29ff323190f6eecd65b00a0b5f2b2f232751d049cc" dependencies = [ "anchor-attribute-access-control", "anchor-attribute-account", @@ -196,7 +196,26 @@ dependencies = [ "bincode", "borsh 0.10.4", "bytemuck", - "solana-program", + "solana-account-info", + "solana-clock", + "solana-cpi", + "solana-define-syscall", + "solana-feature-gate-interface", + "solana-instruction", + "solana-instructions-sysvar", + "solana-invoke", + "solana-loader-v3-interface", + "solana-msg", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-program-option", + "solana-program-pack", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", "thiserror 1.0.69", ] @@ -227,9 +246,9 @@ dependencies = [ [[package]] name = "anchor-spl" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c08cb5d762c0694f74bd02c9a5b04ea53cefc496e2c27b3234acffca5cd076b" +checksum = "3397ab3fc5b198bbfe55d827ff58bd69f2a8d3f9f71c3732c23c2093fec4d3ef" dependencies = [ "anchor-lang", "spl-associated-token-account", @@ -242,9 +261,9 @@ dependencies = [ [[package]] name = "anchor-syn" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc7a6d90cc643df0ed2744862cdf180587d1e5d28936538c18fc8908489ed67" +checksum = "b93b69aa7d099b59378433f6d7e20e1008fc10c69e48b220270e5b3f2ec4c8be" dependencies = [ "anyhow", "bs58", @@ -1763,6 +1782,19 @@ dependencies = [ "solana-sysvar-id", ] +[[package]] +name = "solana-invoke" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f5693c6de226b3626658377168b0184e94e8292ff16e3d31d4766e65627565" +dependencies = [ + "solana-account-info", + "solana-define-syscall", + "solana-instruction", + "solana-program-entrypoint", + "solana-stable-layout", +] + [[package]] name = "solana-keccak-hasher" version = "2.2.1" @@ -2375,9 +2407,9 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76fee7d65013667032d499adc3c895e286197a35a0d3a4643c80e7fd3e9969e3" +checksum = "ae179d4a26b3c7a20c839898e6aed84cb4477adf108a366c95532f058aea041b" dependencies = [ "borsh 1.6.1", "num-derive", @@ -2386,7 +2418,7 @@ dependencies = [ "spl-associated-token-account-client", "spl-token", "spl-token-2022", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -2437,12 +2469,22 @@ dependencies = [ [[package]] name = "spl-elgamal-registry" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce0f668975d2b0536e8a8fd60e56a05c467f06021dae037f1d0cfed0de2e231d" +checksum = "65edfeed09cd4231e595616aa96022214f9c9d2be02dea62c2b30d5695a6833a" dependencies = [ "bytemuck", - "solana-program", + "solana-account-info", + "solana-cpi", + "solana-instruction", + "solana-msg", + "solana-program-entrypoint", + "solana-program-error", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-system-interface", + "solana-sysvar", "solana-zk-sdk", "spl-pod", "spl-token-confidential-transfer-proof-extraction", @@ -2484,22 +2526,24 @@ dependencies = [ [[package]] name = "spl-program-error" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d39b5186f42b2b50168029d81e58e800b690877ef0b30580d107659250da1d1" +checksum = "9cdebc8b42553070b75aa5106f071fef2eb798c64a7ec63375da4b1f058688c6" dependencies = [ "num-derive", "num-traits", - "solana-program", + "solana-decode-error", + "solana-msg", + "solana-program-error", "spl-program-error-derive", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "spl-program-error-derive" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d375dd76c517836353e093c2dbb490938ff72821ab568b545fd30ab3256b3e" +checksum = "2a2539e259c66910d78593475540e8072f0b10f0f61d7607bbf7593899ed52d0" dependencies = [ "proc-macro2", "quote", @@ -2509,9 +2553,9 @@ dependencies = [ [[package]] name = "spl-tlv-account-resolution" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd99ff1e9ed2ab86e3fd582850d47a739fec1be9f4661cba1782d3a0f26805f3" +checksum = "1408e961215688715d5a1063cbdcf982de225c45f99c82b4f7d7e1dd22b998d7" dependencies = [ "bytemuck", "num-derive", @@ -2526,37 +2570,66 @@ dependencies = [ "spl-pod", "spl-program-error", "spl-type-length-value", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "spl-token" -version = "7.0.0" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed320a6c934128d4f7e54fe00e16b8aeaecf215799d060ae14f93378da6dc834" +checksum = "053067c6a82c705004f91dae058b11b4780407e9ccd6799dc9e7d0fab5f242da" dependencies = [ "arrayref", "bytemuck", "num-derive", "num-traits", "num_enum", - "solana-program", - "thiserror 1.0.69", + "solana-account-info", + "solana-cpi", + "solana-decode-error", + "solana-instruction", + "solana-msg", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-program-option", + "solana-program-pack", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-sysvar", + "thiserror 2.0.18", ] [[package]] name = "spl-token-2022" -version = "6.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b27f7405010ef816587c944536b0eafbcc35206ab6ba0f2ca79f1d28e488f4f" +checksum = "31f0dfbb079eebaee55e793e92ca5f433744f4b71ee04880bfd6beefba5973e5" dependencies = [ "arrayref", "bytemuck", "num-derive", "num-traits", "num_enum", - "solana-program", + "solana-account-info", + "solana-clock", + "solana-cpi", + "solana-decode-error", + "solana-instruction", + "solana-msg", + "solana-native-token", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-program-option", + "solana-program-pack", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", "solana-security-txt", + "solana-system-interface", + "solana-sysvar", "solana-zk-sdk", "spl-elgamal-registry", "spl-memo", @@ -2569,14 +2642,14 @@ dependencies = [ "spl-token-metadata-interface", "spl-transfer-hook-interface", "spl-type-length-value", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "spl-token-confidential-transfer-ciphertext-arithmetic" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170378693c5516090f6d37ae9bad2b9b6125069be68d9acd4865bbe9fc8499fd" +checksum = "cddd52bfc0f1c677b41493dafa3f2dbbb4b47cf0990f08905429e19dc8289b35" dependencies = [ "base64 0.22.1", "bytemuck", @@ -2586,13 +2659,19 @@ dependencies = [ [[package]] name = "spl-token-confidential-transfer-proof-extraction" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff2d6a445a147c9d6dd77b8301b1e116c8299601794b558eafa409b342faf96" +checksum = "fe2629860ff04c17bafa9ba4bed8850a404ecac81074113e1f840dbd0ebb7bd6" dependencies = [ "bytemuck", + "solana-account-info", "solana-curve25519", - "solana-program", + "solana-instruction", + "solana-instructions-sysvar", + "solana-msg", + "solana-program-error", + "solana-pubkey", + "solana-sdk-ids", "solana-zk-sdk", "spl-pod", "thiserror 2.0.18", @@ -2600,20 +2679,20 @@ dependencies = [ [[package]] name = "spl-token-confidential-transfer-proof-generation" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8627184782eec1894de8ea26129c61303f1f0adeed65c20e0b10bc584f09356d" +checksum = "fa27b9174bea869a7ebf31e0be6890bce90b1a4288bc2bbf24bd413f80ae3fde" dependencies = [ "curve25519-dalek", "solana-zk-sdk", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "spl-token-group-interface" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d595667ed72dbfed8c251708f406d7c2814a3fa6879893b323d56a10bedfc799" +checksum = "5597b4cd76f85ce7cd206045b7dc22da8c25516573d42d267c8d1fd128db5129" dependencies = [ "bytemuck", "num-derive", @@ -2625,14 +2704,14 @@ dependencies = [ "solana-pubkey", "spl-discriminator", "spl-pod", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "spl-token-metadata-interface" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb9c89dbc877abd735f05547dcf9e6e12c00c11d6d74d8817506cab4c99fdbb" +checksum = "304d6e06f0de0c13a621464b1fd5d4b1bebf60d15ca71a44d3839958e0da16ee" dependencies = [ "borsh 1.6.1", "num-derive", @@ -2646,14 +2725,14 @@ dependencies = [ "spl-discriminator", "spl-pod", "spl-type-length-value", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "spl-transfer-hook-interface" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa7503d52107c33c88e845e1351565050362c2314036ddf19a36cd25137c043" +checksum = "a7e905b849b6aba63bde8c4badac944ebb6c8e6e14817029cbe1bc16829133bd" dependencies = [ "arrayref", "bytemuck", @@ -2671,14 +2750,14 @@ dependencies = [ "spl-program-error", "spl-tlv-account-resolution", "spl-type-length-value", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "spl-type-length-value" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba70ef09b13af616a4c987797870122863cba03acc4284f226a4473b043923f9" +checksum = "d417eb548214fa822d93f84444024b4e57c13ed6719d4dcc68eec24fb481e9f5" dependencies = [ "bytemuck", "num-derive", @@ -2689,7 +2768,7 @@ dependencies = [ "solana-program-error", "spl-discriminator", "spl-pod", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] diff --git a/README.md b/README.md index 8cb43f1..ea7e094 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![CI](https://github.com/graveyieldprotocol/protocol/actions/workflows/ci.yml/badge.svg)](https://github.com/graveyieldprotocol/protocol/actions/workflows/ci.yml) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) -[![Solana 2.0](https://img.shields.io/badge/solana-2.0.x-14F195.svg)](https://docs.solanalabs.com/) -[![Anchor 0.31](https://img.shields.io/badge/anchor-0.31.x-512DA8.svg)](https://www.anchor-lang.com/) +[![Solana 3.0](https://img.shields.io/badge/solana-3.0.x-14F195.svg)](https://solana.com/docs/intro/installation) +[![Anchor 0.32](https://img.shields.io/badge/anchor-0.32.x-512DA8.svg)](https://www.anchor-lang.com/) GraveYield is the **settlement layer for derelict liquidity** on Solana. When AMM pools cross all six derelict-pool criteria — long inactivity, ≥99% price collapse, low TVL, no LP burn or lock, @@ -35,15 +35,15 @@ protocol/ ## Toolchain -Pinned defaults: +Pinned defaults (sources: `Anchor.toml`, `rust-toolchain.toml`, `package.json`): -| Tool | Version | -|------------|------------| -| Solana CLI | 2.0.x | -| Anchor | 0.31.x | -| Rust | 1.79+ (stable) | -| Node | 20 LTS | -| pnpm | 9 | +| Tool | Version | +|------------|------------------| +| Solana CLI | 3.0.10 | +| Anchor | 0.32.1 | +| Rust | 1.91.1 (stable) | +| Node | 24 LTS | +| pnpm | 9 | Run `./scripts/check-toolchain.sh` to verify your local installation. @@ -53,7 +53,7 @@ Run `./scripts/check-toolchain.sh` to verify your local installation. # Install JS workspace deps pnpm install -# Build Anchor programs (requires Solana 2.0.x + Anchor 0.31.x) +# Build Anchor programs (requires Solana 3.0.x + Anchor 0.32.x) anchor build # Run TypeScript typecheck across all packages diff --git a/programs/grave-scanner/Cargo.toml b/programs/grave-scanner/Cargo.toml index 37f3296..485035d 100644 --- a/programs/grave-scanner/Cargo.toml +++ b/programs/grave-scanner/Cargo.toml @@ -20,7 +20,7 @@ no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] -# Anchor 0.31.x's `#[program]` macro emits `#[cfg(feature = "anchor-debug")]` +# Anchor 0.32.x's `#[program]` macro emits `#[cfg(feature = "anchor-debug")]` # tags. Declaring the feature here silences the unexpected_cfgs lint on # Rust 1.80+. The feature is a no-op flag the macro author uses internally. anchor-debug = [] diff --git a/programs/grave-scanner/src/adapters/raydium_v4.rs b/programs/grave-scanner/src/adapters/raydium_v4.rs index 33c0ff4..8eee461 100644 --- a/programs/grave-scanner/src/adapters/raydium_v4.rs +++ b/programs/grave-scanner/src/adapters/raydium_v4.rs @@ -36,7 +36,7 @@ pub const PROGRAM_ID: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt /// SPL Token Program ID — both the vault SPL token accounts and the LP /// mint are owned by this program. We verify ownership explicitly rather /// than relying on `anchor_spl::Account::try_from`, which was tripping -/// the BPF compile under Anchor 0.31.1. +/// the BPF compile under Anchor 0.31.x / 0.32.x. const SPL_TOKEN_PROGRAM_ID: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); /// Canonical AmmInfo account size on Raydium V4. Pool accounts that @@ -88,7 +88,7 @@ mod offsets { /// deserialization AND the `find_account_by_key` helper. Everything is /// inlined inside this function so no cross-function lifetime constraint /// requires explicit `'info` threading on the calling handler — the -/// `#[program]` macro under Anchor 0.31.1 rejects generic-lifetime +/// `#[program]` macro under Anchor 0.31.x / 0.32.x rejects generic-lifetime /// instruction handlers at BPF compile time. pub fn parse( pool_account_info: &AccountInfo, diff --git a/programs/grave-scanner/src/errors.rs b/programs/grave-scanner/src/errors.rs index 4a1aa41..3a7cfd0 100644 --- a/programs/grave-scanner/src/errors.rs +++ b/programs/grave-scanner/src/errors.rs @@ -97,3 +97,50 @@ pub enum GraveScannerError { #[msg("CertTtlBelowMinimum: cert_ttl_seconds below MIN_CERT_TTL_SECONDS floor.")] CertTtlBelowMinimum = 19, } + +#[cfg(test)] +mod tests { + use super::*; + + /// Locks down the on-chain error code numbering against accidental drift. + /// + /// Anchor's `#[error_code]` macro adds the default 6000 offset to every + /// Rust discriminant. We rely on the discriminants below being 0..=19 + /// (with the 12..=14 gap) so the on-chain codes match the documented + /// 6000..=6019 range in `docs/error_codes.md`. A future contributor who + /// switches to explicit `= 6000`-style discriminants would unknowingly + /// shift every code by +6000. + #[test] + fn on_chain_codes_match_docs() { + let cases: &[(GraveScannerError, u32)] = &[ + (GraveScannerError::Unauthorized, 6000), + (GraveScannerError::PoolNotEligible, 6001), + (GraveScannerError::LaunchPriceNotFound, 6002), + (GraveScannerError::UnsupportedAmm, 6003), + (GraveScannerError::MathOverflow, 6004), + (GraveScannerError::InvalidClock, 6005), + (GraveScannerError::InvariantViolation, 6006), + (GraveScannerError::AmmAdapterUnimplemented, 6007), + (GraveScannerError::LockerAdapterUnimplemented, 6008), + (GraveScannerError::PoolDataParseError, 6009), + (GraveScannerError::ProtocolPaused, 6010), + (GraveScannerError::CriteriaBitmapMismatch, 6011), + (GraveScannerError::AnchorNotFound, 6015), + (GraveScannerError::EpochConfirmationPending, 6016), + (GraveScannerError::AnchorInvalidated, 6017), + (GraveScannerError::AnchorNotStale, 6018), + (GraveScannerError::CertTtlBelowMinimum, 6019), + ]; + for (variant, expected) in cases { + let actual: u32 = u32::from(*variant); + assert_eq!( + actual, + *expected, + "{} expected on-chain code {}, got {}", + variant.name(), + expected, + actual, + ); + } + } +} diff --git a/programs/grave-scanner/src/instructions/emergency_pause.rs b/programs/grave-scanner/src/instructions/emergency_pause.rs new file mode 100644 index 0000000..cfb0da2 --- /dev/null +++ b/programs/grave-scanner/src/instructions/emergency_pause.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// emergency_pause — multisig-only, immediate. Toggles `ProtocolConfig.paused`. +// +// When paused, `evaluate_pool_phase_1` and `evaluate_pool_phase_2` revert with +// `ProtocolPaused`. `sweep_stale_anchor`, `invalidate_anchor`, and config +// updates remain callable — pause is an evaluation kill-switch, not a +// full-program halt. +// +// Unlike `update_protocol_config` (which the multisig schedules behind its own +// 72h timelock), emergency pause is intentionally immediate so the multisig +// can stop new evaluations the moment an exploit or AMM-side incident is +// observed. The Charter framing — "Emergency pause is immediate" — mirrors +// the GraveVault equivalent (programs/grave-vault/src/instructions/emergency_pause.rs). + +use anchor_lang::prelude::*; + +use crate::errors::GraveScannerError; +use crate::state::ProtocolConfig; + +#[derive(Accounts)] +pub struct EmergencyPause<'info> { + #[account( + mut, + seeds = [ProtocolConfig::SEED], + bump = protocol_config.bump, + has_one = authority @ GraveScannerError::Unauthorized, + )] + pub protocol_config: Account<'info, ProtocolConfig>, + + pub authority: Signer<'info>, +} + +pub fn handler(ctx: Context, paused: bool) -> Result<()> { + let cfg = &mut ctx.accounts.protocol_config; + cfg.paused = paused; + + emit!(ProtocolPauseChanged { + paused, + changed_by: ctx.accounts.authority.key(), + }); + + Ok(()) +} + +#[event] +pub struct ProtocolPauseChanged { + pub paused: bool, + pub changed_by: Pubkey, +} diff --git a/programs/grave-scanner/src/instructions/mod.rs b/programs/grave-scanner/src/instructions/mod.rs index 90ee1d1..d252355 100644 --- a/programs/grave-scanner/src/instructions/mod.rs +++ b/programs/grave-scanner/src/instructions/mod.rs @@ -3,13 +3,15 @@ // Each instruction module exports its `Accounts` context struct, `Params` // struct, any `#[event]` it emits, and a `handler` function invoked by // lib.rs's `#[program]` block. The glob re-exports below are required by -// Anchor 0.31.x's `#[program]` macro, which expects each instruction's +// Anchor 0.32.x's `#[program]` macro, which expects each instruction's // auto-generated `__cpi_client_accounts_*` / `__client_accounts_*` helper // modules to be in scope at the parent namespace. The `handler` symbol // duplicates across modules, but that ambiguity is harmless: lib.rs only // ever invokes `handler` via the fully-qualified path // `instructions::::handler(...)`, never through the glob. +#[allow(ambiguous_glob_reexports)] +pub use emergency_pause::*; #[allow(ambiguous_glob_reexports)] pub use evaluate_pool_phase1::*; #[allow(ambiguous_glob_reexports)] @@ -25,6 +27,7 @@ pub use sweep_stale_anchor::*; #[allow(ambiguous_glob_reexports)] pub use update_protocol_config::*; +pub mod emergency_pause; pub mod evaluate_pool_phase1; pub mod evaluate_pool_phase2; pub mod initialize; diff --git a/programs/grave-scanner/src/instructions/update_protocol_config.rs b/programs/grave-scanner/src/instructions/update_protocol_config.rs index bdae4dd..e740c53 100644 --- a/programs/grave-scanner/src/instructions/update_protocol_config.rs +++ b/programs/grave-scanner/src/instructions/update_protocol_config.rs @@ -15,6 +15,10 @@ pub struct UpdateProtocolConfigParams { pub price_collapse_bps: Option, pub min_tvl_lamports: Option, pub anchor_staleness_seconds: Option, + /// LP-supply dust threshold for Criterion 4 ("LP not burned"). Pools + /// with `lp_supply <= lp_burn_dust_threshold` are treated as burned. + /// Governance-tunable to match dust outcomes seen on mainnet. + pub lp_burn_dust_threshold: Option, /// EligibilityCert TTL in seconds. Floor: `MIN_CERT_TTL_SECONDS` (600). /// Any value below the floor reverts with `CertTtlBelowMinimum`. pub cert_ttl_seconds: Option, @@ -52,6 +56,9 @@ pub fn handler( if let Some(v) = params.anchor_staleness_seconds { cfg.anchor_staleness_seconds = v; } + if let Some(v) = params.lp_burn_dust_threshold { + cfg.lp_burn_dust_threshold = v; + } if let Some(v) = params.cert_ttl_seconds { // Hardcoded floor: governance cannot push cert_ttl below 600s. // Raising the floor requires a program upgrade. diff --git a/programs/grave-scanner/src/lib.rs b/programs/grave-scanner/src/lib.rs index 504b545..6fcf785 100644 --- a/programs/grave-scanner/src/lib.rs +++ b/programs/grave-scanner/src/lib.rs @@ -13,13 +13,14 @@ // - docs/architecture/eligibility-anchors.md #![allow(clippy::result_large_err)] -// Anchor 0.31.1's `#[program]` macro expansion calls the deprecated +// Anchor 0.32.1's `#[program]` macro expansion calls the deprecated // `AccountInfo::realloc()` (replaced by `AccountInfo::resize()` in Solana SDK -// 2.x). Until Anchor's upstream fix lands, we silence the lint at crate level -// so `cargo clippy -D warnings` stays green. The deprecation does not affect -// runtime behaviour — `realloc` is still available, just discouraged. +// 2.x / 3.x). Until Anchor's upstream fix lands, we silence the lint at +// crate level so `cargo clippy -D warnings` stays green. The deprecation +// does not affect runtime behaviour — `realloc` is still available, just +// discouraged. #![allow(deprecated)] -// Anchor 0.31.x's `#[program]` macro and Solana's +// Anchor 0.32.x's `#[program]` macro and Solana's // `solana_program_entrypoint::custom_panic_default!` macro emit // `#[cfg(feature = "custom-panic")]`, `#[cfg(feature = "anchor-debug")]`, and // `#[cfg(target_os = "solana")]` tags inside our crate. On Rust 1.80+ these @@ -108,4 +109,13 @@ pub mod grave_scanner { ) -> Result<()> { instructions::update_protocol_config::handler(ctx, params) } + + /// Multisig-only, immediate. Toggles `paused` in ProtocolConfig. When + /// `true`, both `evaluate_pool_phase_1` and `evaluate_pool_phase_2` revert + /// with `ProtocolPaused`. `sweep_stale_anchor`, `invalidate_anchor`, and + /// config updates remain callable so the multisig can recover the protocol + /// from a paused state. Mirrors GraveVault's `emergency_pause`. + pub fn emergency_pause(ctx: Context, paused: bool) -> Result<()> { + instructions::emergency_pause::handler(ctx, paused) + } } diff --git a/programs/grave-vault/Cargo.toml b/programs/grave-vault/Cargo.toml index 71c3a28..04d2ce0 100644 --- a/programs/grave-vault/Cargo.toml +++ b/programs/grave-vault/Cargo.toml @@ -24,7 +24,7 @@ idl-build = [ "anchor-spl/idl-build", "grave-scanner/idl-build", ] -# Anchor 0.31.x's `#[program]` macro emits `#[cfg(feature = "anchor-debug")]` +# Anchor 0.32.x's `#[program]` macro emits `#[cfg(feature = "anchor-debug")]` # tags. Declaring the feature here silences the unexpected_cfgs lint on # Rust 1.80+. The feature is a no-op flag the macro author uses internally. anchor-debug = [] diff --git a/programs/grave-vault/src/errors.rs b/programs/grave-vault/src/errors.rs index bc0ac85..09d88cc 100644 --- a/programs/grave-vault/src/errors.rs +++ b/programs/grave-vault/src/errors.rs @@ -1,67 +1,125 @@ // SPDX-License-Identifier: Apache-2.0 +// +// GraveVault error codes. The on-chain code numbers are stable and MUST match +// the spec (docs/error_codes.md, Combined Tech Doc v3.0.1 §3.5). +// +// Anchor's `#[error_code]` macro emits `From for u32` as +// `(self as u32) + OFFSET`, where OFFSET defaults to 6000. We override OFFSET +// to 7000 so the discriminants below stay at 0..=14 and the on-chain codes +// land at the canonical 7000..=7014 range documented in `docs/error_codes.md`. +// +// Without the `offset = 7000` override, the explicit discriminants (7000..) +// would compound with the 6000 default offset and emit codes 13000..=13014 +// instead — silently breaking every downstream consumer (indexer parsers, +// SDK error matchers, IDL clients). +// +// Do not renumber existing variants. New variants append at the next free +// discriminant. use anchor_lang::prelude::*; -#[error_code] +#[error_code(offset = 7000)] pub enum GraveVaultError { - /// Caller lacks the multisig authority required for this instruction. + /// On-chain code 7000. Caller lacks the multisig authority required. #[msg("Unauthorized: caller is not the protocol multisig.")] - Unauthorized = 7000, + Unauthorized = 0, - /// EligibilityCert PDA missing, expired, or owned by the wrong program. + /// On-chain code 7001. EligibilityCert PDA missing or owned by the wrong program. #[msg("Invalid or expired EligibilityCert.")] - InvalidEligibilityCert = 7001, + InvalidEligibilityCert = 1, - /// EligibilityCert TTL has passed. Re-run Phase 2 to mint a fresh cert. + /// On-chain code 7002. EligibilityCert TTL has passed. Re-run Phase 2. #[msg("EligibilityCert is expired.")] - EligibilityCertExpired = 7002, + EligibilityCertExpired = 2, - /// Protocol is paused — only `claim_lp_proceeds` is callable. + /// On-chain code 7003. Protocol is paused — only `claim_lp_proceeds` is callable. #[msg("Protocol is paused. salvage_pool is unavailable.")] - ProtocolPaused = 7003, + ProtocolPaused = 3, - /// Distribution shares (LP / salvor / protocol) did not sum to 10_000 bps. + /// On-chain code 7004. Distribution shares did not sum to 10_000 bps. #[msg("Share basis-point sum is not exactly 10_000.")] - InvalidShareSplit = 7004, + InvalidShareSplit = 4, - /// Attempted to raise `protocol_share_bps` above the Charter ceiling. + /// On-chain code 7005. `protocol_share_bps` exceeds the Charter ceiling. #[msg("Protocol share exceeds Charter ceiling (PROTOCOL_SHARE_BPS_CEILING).")] - ProtocolShareExceedsCeiling = 7005, + ProtocolShareExceedsCeiling = 5, - /// Attempted to sweep, close, or otherwise drain `lp_holder_pool_vault`. - /// This account is unsweepable by any admin key, ever (Charter invariant). + /// On-chain code 7006. Charter invariant: `lp_holder_pool_vault` is unsweepable. #[msg("Charter violation: lp_holder_pool_vault is unsweepable.")] - LpHolderPoolUnsweepable = 7006, + LpHolderPoolUnsweepable = 6, - /// Slippage on the Jupiter swap leg exceeded the configured maximum. + /// On-chain code 7007. Slippage on the Jupiter swap leg exceeded the maximum. #[msg("Slippage exceeded configured maximum.")] - SlippageExceeded = 7007, + SlippageExceeded = 7, - /// Transaction priority fee exceeds the Charter ceiling. + /// On-chain code 7008. Transaction priority fee exceeds the Charter ceiling. #[msg("Priority fee exceeds Charter ceiling.")] - PriorityFeeExceedsCeiling = 7008, + PriorityFeeExceedsCeiling = 8, - /// Arithmetic overflow during distribution math. + /// On-chain code 7009. Arithmetic overflow during distribution math. #[msg("Arithmetic overflow during distribution.")] - MathOverflow = 7009, + MathOverflow = 9, - /// LP holder is not in the snapshot Merkle tree, or proof is invalid. + /// On-chain code 7010. Claim proof failed verification against snapshot root. #[msg("Claim proof failed verification against the snapshot Merkle root.")] - InvalidClaimProof = 7010, + InvalidClaimProof = 10, - /// Claim has already been processed for this (pool, lp_holder) pair. + /// On-chain code 7011. Claim record already exists for this (pool, lp_holder). #[msg("Claim record already exists; proceeds were already withdrawn.")] - ClaimAlreadyProcessed = 7011, + ClaimAlreadyProcessed = 11, - /// Quote output below the Jupiter dust threshold; salvage skipped or aborted. + /// On-chain code 7012. Quote output below the Jupiter dust threshold. #[msg("Output below Jupiter dust threshold.")] - BelowDustThreshold = 7012, + BelowDustThreshold = 12, - /// Pre-flight check against the on-chain pool failed. + /// On-chain code 7013. Pre-flight check against the on-chain pool failed. #[msg("Pre-flight check against pool state failed.")] - PreflightFailed = 7013, + PreflightFailed = 13, - /// Timelock window has not yet elapsed for a queued parameter change. + /// On-chain code 7014. Timelock window has not yet elapsed. #[msg("Timelock window has not elapsed.")] - TimelockNotElapsed = 7014, + TimelockNotElapsed = 14, +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Locks down the on-chain error code numbering against accidental drift. + /// + /// `docs/error_codes.md` documents 7000..=7014 as the canonical GraveVault + /// range. Without `#[error_code(offset = 7000)]`, Anchor would add the + /// default 6000 offset on top of any explicit discriminant — silently + /// shifting every code by +6000 and breaking downstream consumers. + #[test] + fn on_chain_codes_match_docs() { + let cases: &[(GraveVaultError, u32)] = &[ + (GraveVaultError::Unauthorized, 7000), + (GraveVaultError::InvalidEligibilityCert, 7001), + (GraveVaultError::EligibilityCertExpired, 7002), + (GraveVaultError::ProtocolPaused, 7003), + (GraveVaultError::InvalidShareSplit, 7004), + (GraveVaultError::ProtocolShareExceedsCeiling, 7005), + (GraveVaultError::LpHolderPoolUnsweepable, 7006), + (GraveVaultError::SlippageExceeded, 7007), + (GraveVaultError::PriorityFeeExceedsCeiling, 7008), + (GraveVaultError::MathOverflow, 7009), + (GraveVaultError::InvalidClaimProof, 7010), + (GraveVaultError::ClaimAlreadyProcessed, 7011), + (GraveVaultError::BelowDustThreshold, 7012), + (GraveVaultError::PreflightFailed, 7013), + (GraveVaultError::TimelockNotElapsed, 7014), + ]; + for (variant, expected) in cases { + let actual: u32 = u32::from(*variant); + assert_eq!( + actual, + *expected, + "{} expected on-chain code {}, got {}", + variant.name(), + expected, + actual, + ); + } + } } diff --git a/programs/grave-vault/src/instructions/mod.rs b/programs/grave-vault/src/instructions/mod.rs index 3b5b3da..8bab541 100644 --- a/programs/grave-vault/src/instructions/mod.rs +++ b/programs/grave-vault/src/instructions/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // // See programs/grave-scanner/src/instructions/mod.rs for the rationale on -// `#[allow(ambiguous_glob_reexports)]`. Anchor 0.31.x's `#[program]` macro +// `#[allow(ambiguous_glob_reexports)]`. Anchor 0.32.x's `#[program]` macro // requires the glob form to bring each instruction's auto-generated // `__cpi_client_accounts_*` / `__client_accounts_*` modules into scope. diff --git a/programs/grave-vault/src/lib.rs b/programs/grave-vault/src/lib.rs index b3a7a8b..abc9285 100644 --- a/programs/grave-vault/src/lib.rs +++ b/programs/grave-vault/src/lib.rs @@ -23,13 +23,14 @@ // - docs/architecture/charter-invariants.md #![allow(clippy::result_large_err)] -// Anchor 0.31.1's `#[program]` macro expansion calls the deprecated +// Anchor 0.32.1's `#[program]` macro expansion calls the deprecated // `AccountInfo::realloc()` (replaced by `AccountInfo::resize()` in Solana SDK -// 2.x). Until Anchor's upstream fix lands, we silence the lint at crate level -// so `cargo clippy -D warnings` stays green. The deprecation does not affect -// runtime behaviour — `realloc` is still available, just discouraged. +// 2.x / 3.x). Until Anchor's upstream fix lands, we silence the lint at +// crate level so `cargo clippy -D warnings` stays green. The deprecation +// does not affect runtime behaviour — `realloc` is still available, just +// discouraged. #![allow(deprecated)] -// Anchor 0.31.x's `#[program]` macro and Solana's +// Anchor 0.32.x's `#[program]` macro and Solana's // `solana_program_entrypoint::custom_panic_default!` macro emit // `#[cfg(feature = "custom-panic")]`, `#[cfg(feature = "anchor-debug")]`, and // `#[cfg(target_os = "solana")]` tags inside our crate. On Rust 1.80+ these diff --git a/scripts/check-toolchain.sh b/scripts/check-toolchain.sh index 7700f47..8976de9 100644 --- a/scripts/check-toolchain.sh +++ b/scripts/check-toolchain.sh @@ -3,10 +3,12 @@ # Exits non-zero if any required tool is missing or has an unexpected major/minor. set -euo pipefail -EXPECTED_SOLANA_MAJOR_MINOR="2.0" -EXPECTED_ANCHOR_MAJOR_MINOR="0.31" -EXPECTED_RUST_MIN="1.79" -EXPECTED_NODE_MAJOR="20" +# Expected versions reflect what's pinned in Anchor.toml, rust-toolchain.toml, +# and package.json. Bump these together when the workspace toolchain moves. +EXPECTED_SOLANA_MAJOR_MINOR="3.0" +EXPECTED_ANCHOR_MAJOR_MINOR="0.32" +EXPECTED_RUST_MIN="1.91" +EXPECTED_NODE_MAJOR="24" EXPECTED_PNPM_MAJOR="9" err=0 diff --git a/sdk/src/priorityFee.ts b/sdk/src/priorityFee.ts index a384f32..36288b7 100644 --- a/sdk/src/priorityFee.ts +++ b/sdk/src/priorityFee.ts @@ -13,6 +13,9 @@ import type { PriorityFeePolicy } from "./types.js"; /** Default operational margin ratio: 25% of expected profit. */ export const DEFAULT_MARGIN_RATIO = 0.25; +/** Basis-point precision used when converting `marginRatio` (a JS number) to BN. */ +const MARGIN_RATIO_PRECISION_BPS = 10_000; + /** * Compute the SDK's operational max compute-unit price for a transaction * given (a) the protocol Charter ceiling and (b) the salvor's expected @@ -20,18 +23,29 @@ export const DEFAULT_MARGIN_RATIO = 0.25; * * The returned value is bounded above by the Charter ceiling. If the * computed value would exceed the ceiling, the function returns the ceiling. + * + * Math runs in BN throughout — `BN.toNumber()` throws for values above + * `Number.MAX_SAFE_INTEGER` (~9.0e15), which is reachable for real Solana + * lamport amounts. We instead quantise `marginRatio` to a 10_000-bps integer + * and multiply/divide in BN. */ export function computeOperationalMaxLamportsPerCu( expectedProfitLamports: BN, protocolCeilingLamportsPerCu: BN, marginRatio: number = DEFAULT_MARGIN_RATIO, ): BN { - if (marginRatio < 0 || marginRatio > 1) { - throw new RangeError("marginRatio must be in [0, 1]"); + if (!Number.isFinite(marginRatio) || marginRatio < 0 || marginRatio > 1) { + throw new RangeError("marginRatio must be a finite number in [0, 1]"); + } + if (expectedProfitLamports.isNeg()) { + throw new RangeError("expectedProfitLamports must be non-negative"); } - const scaledProfit = new BN( - Math.floor(expectedProfitLamports.toNumber() * marginRatio), + const marginBps = new BN( + Math.round(marginRatio * MARGIN_RATIO_PRECISION_BPS), ); + const scaledProfit = expectedProfitLamports + .mul(marginBps) + .div(new BN(MARGIN_RATIO_PRECISION_BPS)); if (scaledProfit.gt(protocolCeilingLamportsPerCu)) { return protocolCeilingLamportsPerCu; }