diff --git a/.github/actions/setup-common/action.yaml b/.github/actions/setup-common/action.yaml new file mode 100644 index 000000000..941e95513 --- /dev/null +++ b/.github/actions/setup-common/action.yaml @@ -0,0 +1,25 @@ +name: "Setup common" +description: "Setup common" +runs: + using: "composite" + steps: + - run: sudo apt-get update + shell: bash + + - run: sudo apt-get install -y pkg-config build-essential libudev-dev + shell: bash + + - uses: actions-rs/toolchain@v1 + name: Install minimal rust toolchain with clippy and rustfmt + with: + profile: minimal + toolchain: ${{ env.RUST_TOOLCHAIN }} + target: x86_64-unknown-linux-gnu + components: rustfmt, clippy + default: true + + - run: cargo install cargo-nextest --version "0.9.81" --locked + shell: bash + + - run: cargo nextest --version + shell: bash diff --git a/.github/workflows/release-program.yaml b/.github/workflows/release-program.yaml new file mode 100644 index 000000000..1cf51149e --- /dev/null +++ b/.github/workflows/release-program.yaml @@ -0,0 +1,109 @@ +name: Program+IDL Upgrade MS Proposal + +on: + push: + tags: + - "testest-program-*" + +env: + RUST_TOOLCHAIN: 1.75.0 + SOLANA_CLI_VERSION: 1.18.17 + ANCHOR_CLI_VERSION: 0.30.1 + ANCHOR_SHA: e6d7dafe12da661a36ad1b4f3b5970e8986e5321 + CARGO_TERM_COLOR: always + +jobs: + build-verifiable: + name: Generate verifiable build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install common dependencies + uses: ./.github/actions/setup-common/ + + - name: Install Anchor CLI + uses: ./.github/actions/setup-anchor-cli/ + + - name: Extract tag information + run: | + TAG=${GITHUB_REF#refs/tags/} # Extract tag name from ref + echo "Tag name: $TAG" + PROGRAM=$(echo $TAG | sed 's/testest-program-\(.*\)-[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}/\1/') + VERSION=$(echo $TAG | sed 's/.*-\([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\)$/\1/') + PROGRAM_LIB_NAME=${PROGRAM//-/_} # Substitute dashes with underscores + PROGRAM_ID=$(~/.cargo/bin/toml get Anchor.toml programs.mainnet.${PROGRAM_LIB_NAME} | tr -d '"') + + echo "Program: $PROGRAM" + echo "Program ID: $PROGRAM_ID" + echo "Version: $VERSION" + echo "PROGRAM_ID=${PROGRAM_ID}" >> $GITHUB_ENV + echo "PROGRAM_LIB_NAME=${PROGRAM_LIB_NAME}" >> $GITHUB_ENV + + # Solely to generate the IDL + - name: Build program using Anchor + uses: ./.github/actions/build-program/ + with: + program_lib_name: ${{ env.PROGRAM_LIB_NAME }} + + # Build verifiable program - store it in target/deploy/${PROGRAM_LIB_NAME}.so + - name: Build verifiable program + uses: ./.github/actions/build-verifiable-program/ + with: + program_lib_name: ${{ env.PROGRAM_LIB_NAME }} + + - name: Patch IDL and TS files + run: | + cargo run \ + --release \ + --package marginfi-v2-cli \ + --features dev \ + -- patch-idl target/idl/${{ env.PROGRAM_LIB_NAME }}.json + rm target/idl/${{ env.PROGRAM_LIB_NAME }}.json + mv target/idl/${{ env.PROGRAM_LIB_NAME }}_patched.json target/idl/${{ env.PROGRAM_LIB_NAME }}.json + + # Display contents of /target/deploy and /target/idl + - run: ls -l target/deploy + - run: ls -l target/idl + + - name: Upload program and IDL to buffers + uses: ./.github/actions/deploy-buffers/ + id: deploy-buffers + with: + program-lib-name: ${{ env.PROGRAM_LIB_NAME }} + program-id: ${{ env.PROGRAM_ID }} + deployer-keypair: ${{ secrets.DEPLOYER_KEYPAIR }} + buffer-authority: ${{ secrets.MULTISIG_VAULT }} + rpc-endpoint: ${{ secrets.RPC_ENDPOINT }} + + # # Create MS proposal for program upgrade + # - name: Squads program upgrade + # uses: mrgnlabs/squads-program-upgrade@v0.3.1 + # with: + # network-url: "https://api.mainnet-beta.solana.com" + # program-multisig: ${{ secrets.MULTISIG }} + # program-id: ${{ env.PROGRAM_ID }} + # buffer: ${{ steps.deploy-buffers.outputs.program-buffer }} + # idl-buffer: ${{ steps.deploy-buffers.outputs.idl-buffer }} + # spill-address: ${{ secrets.DEPLOYER_ADDRESS }} + # authority: ${{ secrets.MULTISIG_VAULT }} + # name: "Deploy ${{env.PROGRAM_LIB_NAME}} ${{env.VERSION}}" + # keypair: ${{ secrets.DEPLOYER_KEYPAIR }} + + # - name: Upload program + # uses: actions/upload-artifact@v2 + # with: + # name: ${{ env.PROGRAM_LIB_NAME }}-verifiable_build-${{ github.run_id }}-${{ github.run_attempt }} + # path: ./target/deploy/${{ env.PROGRAM_LIB_NAME }}.so + + # - name: Upload IDL (json) + # uses: actions/upload-artifact@v2 + # with: + # name: ${{ env.PROGRAM_LIB_NAME }}-idl-${{ github.run_id }}-${{ github.run_attempt }} + # path: ./target/idl/${{ env.PROGRAM_LIB_NAME }}_patched.json + + # - name: Upload IDL (types) + # uses: actions/upload-artifact@v2 + # with: + # name: ${{ env.PROGRAM_LIB_NAME }}-types-${{ github.run_id }}-${{ github.run_attempt }} + # path: ./target/idl/${{ env.PROGRAM_LIB_NAME }}_patched.ts diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000..fa411c538 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,185 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + branches: + - "*" + +defaults: + run: + shell: bash + working-directory: . + +env: + RUST_TOOLCHAIN: 1.75.0 + SOLANA_CLI_VERSION: 1.18.11 + ANCHOR_CLI_VERSION: 0.30.1 + ANCHOR_SHA: e6d7dafe12da661a36ad1b4f3b5970e8986e5321 + CARGO_TERM_COLOR: always + +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Rust Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: ./.github/actions/setup-common/ + - uses: ./.github/actions/setup-anchor-cli/ + + # - uses: actions/cache@v2 + # name: Cache Cargo registry + index + # id: cache-cargo-build + # with: + # path: | + # ~/.cargo/bin/ + # ~/.cargo/registry/index/ + # ~/.cargo/registry/cache/ + # ~/.cargo/git/db/ + # key: cargo-${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + + - run: cargo fmt -- --check + + - run: ./scripts/lint.sh + + test-unit: + name: Rust Unit Tests + runs-on: ubuntu-latest + # env: + # RUSTUP_TOOLCHAIN: stable + steps: + - uses: actions/checkout@v3 + + - uses: ./.github/actions/setup-common/ + - uses: ./.github/actions/setup-anchor-cli/ + + # - uses: actions/cache@v2 + # name: Cache Cargo registry + index + # id: cache-cargo-build + # with: + # path: | + # ~/.cargo/bin/ + # ~/.cargo/registry/index/ + # ~/.cargo/registry/cache/ + # ~/.cargo/git/db/ + # key: cargo-${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + + - run: cargo test --lib + + build-and-test-workspace: + name: Build And Test Anchor Programs + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: ./.github/actions/setup-common/ + - uses: ./.github/actions/setup-anchor-cli/ + + - uses: ./.github/actions/build-workspace/ + + - run: ./scripts/test-program.sh all --sane + shell: bash + + fuzz: + name: Fuzz The marginfi Program + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./programs/marginfi/fuzz + + steps: + - uses: actions/checkout@v3 + # - name: cache dependencies + # uses: Swatinem/rust-cache@v2 + - name: Install full rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2024-06-05 + components: rust-src + - name: Run fuzz tests in fuzz dir + run: | + python ./generate_corpus.py + cargo install cargo-fuzz --locked + cargo +nightly-2024-06-05 fuzz run lend -Zbuild-std --strip-dead-code --no-cfg-fuzzing -- -max_total_time=300 + - name: Pass after fuzzing + run: echo "Fuzzing completed" + + localnet-test-marginfi: + name: Anchor localnet tests marginfi + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.10.0" + + - uses: ./.github/actions/setup-common/ + - uses: ./.github/actions/setup-anchor-cli/ + + - uses: ./.github/actions/build-workspace/ + + - name: Install Node.js dependencies + run: yarn install + + - name: Build marginfi program + run: anchor build -p marginfi -- --no-default-features + + - name: Build mocks program + run: anchor build -p mocks + + # Handles extraneous (os error 2) that appears during testing in some versions of solana. See: + # https://solana.stackexchange.com/questions/1648/error-no-such-file-or-directory-os-error-2-error-from-anchor-test + - name: Run Anchor tests + run: | + set +e + anchor test --skip-build 2>&1 | tee test_output.log + ANCHOR_EXIT_CODE=$? + set -e + + if grep -q "failing" test_output.log; then + echo "Real test failure detected." + exit 1 + fi + + if grep -q "No such file or directory (os error 2)" test_output.log; then + echo "Extraneous error detected, ignoring it..." + exit 0 + fi + + if [ $ANCHOR_EXIT_CODE -ne 0 ]; then + echo "Anchor test exited with code $ANCHOR_EXIT_CODE due to an unexpected error." + exit 1 + else + echo "Test run completed successfully without extraneous errors." + exit 0 + fi + + # - name: Start Solana Test Validator + # run: | + # solana-test-validator --reset --limit-ledger-size 1000 \ + + # - name: Wait for Validator to Start + # run: sleep 60 + + # - name: Deploy Liquidity Incentive Program + # run: solana program deploy --program-id Lip1111111111111111111111111111111111111111 target/deploy/liquidity_incentive_program.so + + # - name: Deploy Marginfi Program + # run: solana program deploy --program-id 2jGhuVUuy3umdzByFx8sNWUAaf5vaeuDm78RDPEnhrMr target/deploy/marginfi.so + + # - name: Deploy Mocks Program + # run: solana program deploy --program-id 5XaaR94jBubdbrRrNW7DtRvZeWvLhSHkEGU3jHTEXV3C target/deploy/mocks.so diff --git a/.gitignore b/.gitignore index 9f44766f6..464aa4e37 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ target # IDEs .idea *.iml -**/.vscode # other .env @@ -32,3 +31,4 @@ test-ledger/ # Allow specific json files !tests/fixtures/**/*.json !programs/marginfi/tests/fixtures/**/*.json +!.vscode/tasks.json \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..533963911 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,77 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Setup & Run Anchor Tests", + "type": "shell", + "command": "./scripts/build-workspace.sh && yarn install && anchor build -p marginfi -- --no-default-features && anchor build -p mocks && anchor build -p liquidity_incentive_program -- --no-default-features && anchor test --skip-build", + "problemMatcher": [], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": true + } + }, + { + "label": "Run Anchor Tests", + "type": "shell", + "command": "anchor test --skip-build", + "problemMatcher": [], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": true + } + }, + { + "label": "Setup & Run Rust Tests", + "type": "shell", + "command": "./scripts/build-workspace.sh && cargo fmt && ./scripts/test-program.sh all --sane", + "problemMatcher": [], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": true + } + }, + { + "label": "Run Rust Tests", + "type": "shell", + "command": "cargo fmt && ./scripts/test-program.sh all --sane", + "problemMatcher": [], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": true + } + }, + ] +} \ No newline at end of file diff --git a/Anchor.toml b/Anchor.toml index 8afc1f917..18141c9c0 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,15 +1,17 @@ [toolchain] anchor_version = "0.30.1" -solana_version = "1.18.11" +solana_version = "1.18.17" +# Getting "thread 'main' panicked at cli/src/lib.rs:545:18:"? Check your toolchain matches the above. [features] resolution = true skip-lint = false [programs.localnet] -liquidity_incentive_program = "Lip1111111111111111111111111111111111111111" +# liquidity_incentive_program = "Lip1111111111111111111111111111111111111111" marginfi = "2jGhuVUuy3umdzByFx8sNWUAaf5vaeuDm78RDPEnhrMr" mocks = "5XaaR94jBubdbrRrNW7DtRvZeWvLhSHkEGU3jHTEXV3C" +spl_single_pool = "SVSPxpvHdN29nkVg9rPapPNDddN5DipNLRUFhyjFThE" # cloned from solana-labs repo (see below) [programs.mainnet] liquidity_incentive_program = "LipsxuAkFkwa4RKNzn51wAsW7Dedzt1RNHMkTkDEZUW" @@ -19,15 +21,21 @@ marginfi = "MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA" url = "https://api.apr.dev" [provider] -cluster = "localnet" -# cluster = "https://devnet.rpcpool.com/" +cluster = "Localnet" wallet = "~/.config/solana/id.json" +# (remove RUST_LOG= to see bankRun logs) [scripts] -test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/*.spec.ts --exit --require tests/rootHooks.ts" +test = "RUST_LOG= yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/*.spec.ts --exit --require tests/rootHooks.ts" + +# Staked collateral tests only +# test = "RUST_LOG= yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/s*.spec.ts --exit --require tests/rootHooks.ts" + +# Pyth pull tests only +# test = "RUST_LOG= yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/p*.spec.ts --exit --require tests/rootHooks.ts" [test] -startup_wait = 5000 +startup_wait = 60000 shutdown_wait = 2000 upgradeable = false @@ -44,6 +52,24 @@ filename = "tests/fixtures/bonk_bank.json" address = "4kNXetv8hSv9PzvzPZzEs1CTH6ARRRi2b8h6jk1ad1nP" filename = "tests/fixtures/cloud_bank.json" +[[test.validator.account]] +address = "Fe5QkKPVAh629UPP5aJ8sDZu8HTfe6M26jDQkKyXVhoA" +filename = "tests/fixtures/pyusd_bank.json" + [[test.validator.account]] address = "8FRFC6MoGGkMFQwngccyu69VnYbzykGeez7ignHVAFSN" filename = "tests/fixtures/localnet_usdc.json" + +[[test.validator.account]] +address = "7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE" +filename = "tests/fixtures/sol_pyth_price_feed.json" + +[[test.validator.account]] +address = "H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG" +filename = "tests/fixtures/sol_pyth_oracle.json" + +# To update: +# clone https://github.com/solana-labs/solana-program-library/tree/master and run cargo build-sbf in spl_single_pool +[[test.genesis]] +address = "SVSPxpvHdN29nkVg9rPapPNDddN5DipNLRUFhyjFThE" # spl single pool program +program = "tests/fixtures/spl_single_pool.so" diff --git a/Cargo.lock b/Cargo.lock index 2a9791853..40e1b640c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -988,6 +988,14 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "brick" +version = "0.1.0" +dependencies = [ + "anchor-lang 0.31.1", + "solana-program", +] + [[package]] name = "brotli" version = "6.0.0" @@ -1126,7 +1134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a98356df42a2eb1bd8f1793ae4ee4de48e384dd974ce5eac8eee802edb7492be" dependencies = [ "serde", - "toml 0.8.14", + "toml 0.8.19", ] [[package]] @@ -2887,7 +2895,7 @@ dependencies = [ [[package]] name = "marginfi" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anchor-lang 0.31.1", "anchor-spl", @@ -3038,16 +3046,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "mocks" -version = "0.1.0" -dependencies = [ - "anchor-lang 0.31.1", - "anchor-spl", - "bytemuck", - "static_assertions", -] - [[package]] name = "modular-bitfield" version = "0.11.2" @@ -4525,9 +4523,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -8847,21 +8845,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.15", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -8879,15 +8877,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.15" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.20", ] [[package]] @@ -9647,9 +9645,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/README.md b/README.md index a3c19641d..d55f6f925 100644 --- a/README.md +++ b/README.md @@ -76,14 +76,12 @@ use an x86 toolchain when compiling and running the tests. ## Rust Tests -Run the full test suite with `.scripts/test-program.sh ` - -- e.g. `.scripts/test-program.sh all --sane` +Run the full test suite with `./scripts/test-program.sh ` +* e.g. `./scripts/test-program.sh all --sane` Run a single test: -`.scripts/test-program.sh ` - -- e.g. `.scripts/test-program.sh marginfi configure_bank_success --verbose` +`./scripts/test-program.sh ` +* e.g. `./scripts/test-program.sh marginfi configure_bank_success --verbose` ## Localnet Anchor Tests diff --git a/package.json b/package.json index fdb1184eb..8e5e0f561 100644 --- a/package.json +++ b/package.json @@ -6,21 +6,25 @@ "dependencies": { "@coral-xyz/anchor": "^0.30.1", "@coral-xyz/spl-token": "^0.30.1", + "@mrgnlabs/marginfi-client-v2": "^4.0.0", + "@mrgnlabs/mrgn-common": "^1.8.0", + "@solana/spl-single-pool-classic": "^1.0.2", "@solana/spl-token": "^0.4.8", "@solana/web3.js": "^1.95.2", - "@mrgnlabs/mrgn-common": "^1.7.0", - "@mrgnlabs/marginfi-client-v2": "^3.1.0", + "bignumber.js": "^9.1.2", "mocha": "^10.2.0", - "ts-mocha": "^10.0.0", - "bignumber.js": "^9.1.2" + "ts-mocha": "^10.0.0" }, "devDependencies": { "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", - "@types/mocha": "^9.0.0", + "@types/mocha": "^10.0.10", + "anchor-bankrun": "^0.4.0", + "big.js": "^6.2.1", "chai": "^4.3.4", "prettier": "^2.6.2", + "solana-bankrun": "^0.3.0", "ts-node": "^10.9.1", - "typescript": "^4.3.5" + "typescript": "^5.7.3" } } diff --git a/programs/brick/Cargo.toml b/programs/brick/Cargo.toml new file mode 100644 index 000000000..1bca36c77 --- /dev/null +++ b/programs/brick/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "brick" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build"] +test = [] + +[dependencies] +# Remove workspace = true if already defined in the root Cargo.toml +anchor-lang = { workspace = true } +solana-program = { workspace = true } diff --git a/programs/brick/src/lib.rs b/programs/brick/src/lib.rs new file mode 100644 index 000000000..107285048 --- /dev/null +++ b/programs/brick/src/lib.rs @@ -0,0 +1,29 @@ +use anchor_lang::prelude::*; + +declare_id!("MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA"); + +#[program] +pub mod brick { + use super::*; + + pub fn fallback( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], + ) -> Result<()> { + Err(ErrorCode::ProgramDisabled.into()) + } + + pub fn initialize(_ctx: Context, _val: u64) -> Result<()> { + Ok(()) + } +} + +#[error_code] +pub enum ErrorCode { + #[msg("This program is temporarily disabled.")] + ProgramDisabled, +} + +#[derive(Accounts)] +pub struct Initialize {} diff --git a/programs/marginfi/Cargo.toml b/programs/marginfi/Cargo.toml index d9e8b3ded..2a7bf2d7b 100644 --- a/programs/marginfi/Cargo.toml +++ b/programs/marginfi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marginfi" -version = "0.1.0" +version = "0.1.2" description = "Created with Anchor" edition = "2021" diff --git a/programs/marginfi/fuzz/Cargo.lock b/programs/marginfi/fuzz/Cargo.lock new file mode 100644 index 000000000..f5b285a25 --- /dev/null +++ b/programs/marginfi/fuzz/Cargo.lock @@ -0,0 +1,7532 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "anchor-attribute-access-control" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" +dependencies = [ + "anchor-syn 0.29.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-access-control" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-syn 0.30.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" +dependencies = [ + "anchor-syn 0.29.0", + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-syn 0.30.1", + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" +dependencies = [ + "anchor-syn 0.29.0", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-syn 0.30.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" +dependencies = [ + "anchor-syn 0.29.0", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-syn 0.30.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" +dependencies = [ + "anchor-syn 0.29.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-syn 0.30.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" +dependencies = [ + "anchor-syn 0.29.0", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-lang-idl", + "anchor-syn 0.30.1", + "anyhow", + "bs58 0.5.1", + "heck 0.3.3", + "proc-macro2", + "quote", + "serde_json", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" +dependencies = [ + "anchor-syn 0.29.0", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-syn 0.30.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +dependencies = [ + "anchor-syn 0.29.0", + "borsh-derive-internal 0.10.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-syn 0.30.1", + "borsh-derive-internal 0.10.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-space" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-space" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" +dependencies = [ + "anchor-attribute-access-control 0.29.0", + "anchor-attribute-account 0.29.0", + "anchor-attribute-constant 0.29.0", + "anchor-attribute-error 0.29.0", + "anchor-attribute-event 0.29.0", + "anchor-attribute-program 0.29.0", + "anchor-derive-accounts 0.29.0", + "anchor-derive-serde 0.29.0", + "anchor-derive-space 0.29.0", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.10.3", + "bytemuck", + "getrandom 0.2.15", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-lang" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-attribute-access-control 0.30.1", + "anchor-attribute-account 0.30.1", + "anchor-attribute-constant 0.30.1", + "anchor-attribute-error 0.30.1", + "anchor-attribute-event 0.30.1", + "anchor-attribute-program 0.30.1", + "anchor-derive-accounts 0.30.1", + "anchor-derive-serde 0.30.1", + "anchor-derive-space 0.30.1", + "arrayref", + "base64 0.21.7", + "bincode", + "borsh 0.10.3", + "bytemuck", + "getrandom 0.2.15", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-lang-idl" +version = "0.1.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-lang-idl-spec", + "anyhow", + "heck 0.3.3", + "serde", + "serde_json", + "sha2 0.10.8", +] + +[[package]] +name = "anchor-lang-idl-spec" +version = "0.1.0" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anyhow", + "serde", +] + +[[package]] +name = "anchor-spl" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a" +dependencies = [ + "anchor-lang 0.29.0", + "solana-program", + "spl-associated-token-account 2.3.0", + "spl-token 4.0.0", + "spl-token-2022 0.9.0", +] + +[[package]] +name = "anchor-spl" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anchor-lang 0.30.1", + "spl-associated-token-account 3.0.2", + "spl-pod 0.2.2", + "spl-token 4.0.0", + "spl-token-2022 3.0.2", + "spl-token-group-interface 0.2.3", + "spl-token-metadata-interface 0.3.3", +] + +[[package]] +name = "anchor-syn" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" +dependencies = [ + "anyhow", + "bs58 0.5.1", + "heck 0.3.3", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "anchor-syn" +version = "0.30.1" +source = "git+https://github.com/mrgnlabs/anchor.git?rev=fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d#fdcf299dc55ecf7cfa8c4d598aecb1363b99c02d" +dependencies = [ + "anyhow", + "bs58 0.5.1", + "heck 0.3.3", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "aquamarine" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-compression" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive 1.5.1", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal 0.10.3", + "borsh-schema-derive-internal 0.10.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.58", + "syn_derive", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "caps" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b" +dependencies = [ + "libc", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "chrono-humanize" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b" +dependencies = [ + "chrono", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "once_cell", + "strsim 0.10.0", + "termcolor", + "textwrap 0.16.1", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.58", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "rayon", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint 0.4.6", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "destructure_traitobject" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" + +[[package]] +name = "dialoguer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +dependencies = [ + "console", + "shell-words", + "tempfile", + "zeroize", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "dir-diff" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ad16bf5f84253b50d6557681c58c3ab67c47c77d39fed9aeb56e947290bd10" +dependencies = [ + "walkdir", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "dlopen2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "eager" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe71d579d1812060163dff96056261deb5bf6729b100fa2e36a68b9649ba3d3" + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.8", +] + +[[package]] +name = "educe" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-iterator" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "enum-ordinalize" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fast-math" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2465292146cdfc2011350fe3b1c616ac83cf0faeedb33463ba1c332ed8948d66" +dependencies = [ + "ieee754", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "fixed" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "fixed-macro" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0c48af8cb14e02868f449f8a2187bd78af7a08da201fdc78d518ecb1675bc" +dependencies = [ + "fixed", + "fixed-macro-impl", + "fixed-macro-types", +] + +[[package]] +name = "fixed-macro-impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c93086f471c0a1b9c5e300ea92f5cd990ac6d3f8edf27616ef624b8fa6402d4b" +dependencies = [ + "fixed", + "paste", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fixed-macro-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "044a61b034a2264a7f65aa0c3cd112a01b4d4ee58baace51fead3f21b993c7e4" +dependencies = [ + "fixed", + "fixed-macro-impl", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "goblin" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7666983ed0dd8d21a6f6576ee00053ca0926fb281a5522577a4dbd0f1b54143" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util 0.7.11", + "tracing", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "histogram" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cb882ccb290b8646e554b157ab0b71e64e8d5bef775cd66b6531e52d302669" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ieee754" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9007da9cacbd3e6343da136e98b0d2df013f553d35bdec8b518f07bea768e19c" + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "index_list" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb725b6505e51229de32027e0cfcd9db29da4d89156f9747b0a5195643fa3e1" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +dependencies = [ + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core 0.2.2", + "libsecp256k1-gen-ecmult 0.2.1", + "libsecp256k1-gen-genmult 0.2.1", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core 0.3.0", + "libsecp256k1-gen-ecmult 0.3.0", + "libsecp256k1-gen-genmult 0.3.0", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core 0.2.2", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core 0.3.0", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core 0.2.2", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core 0.3.0", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint 0.4.6", + "thiserror", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "serde", + "value-bag", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "once_cell", + "parking_lot", + "rand 0.8.5", + "serde", + "serde-value", + "serde_json", + "serde_yaml", + "thiserror", + "thread-id", + "typemap-ors", + "winapi", +] + +[[package]] +name = "lru" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +dependencies = [ + "hashbrown 0.12.3", +] + +[[package]] +name = "lz4" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eab492fe7f8651add23237ea56dbf11b3c4ff762ab83d40a47f11433421f91" +dependencies = [ + "libc", + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9764018d143cc854c9f17f0b907de70f14393b1f502da6375dce70f00514eb3" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "marginfi" +version = "0.1.2" +dependencies = [ + "anchor-lang 0.29.0", + "anchor-lang 0.30.1", + "anchor-spl 0.30.1", + "borsh 0.10.3", + "bytemuck", + "cfg-if", + "enum_dispatch", + "fixed", + "fixed-macro", + "lazy_static", + "pyth-sdk-solana", + "pyth-solana-receiver-sdk", + "solana-program", + "solana-security-txt", + "spl-tlv-account-resolution 0.6.3", + "spl-transfer-hook-interface 0.6.3", + "static_assertions", + "switchboard-on-demand", + "switchboard-solana", + "type-layout", +] + +[[package]] +name = "marginfi-fuzz" +version = "0.0.0" +dependencies = [ + "anchor-lang 0.30.1", + "anchor-spl 0.30.1", + "anyhow", + "arbitrary", + "base64 0.22.1", + "bumpalo", + "bytemuck", + "fixed", + "fixed-macro", + "itertools 0.12.1", + "lazy_static", + "libfuzzer-sys", + "log", + "log4rs", + "marginfi", + "once_cell", + "pyth-sdk-solana", + "quinn-proto", + "rand 0.8.5", + "safe-transmute", + "solana-program", + "solana-program-test", + "solana-sdk", + "spl-token 4.0.0", + "strum 0.26.3", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.6", + "num-complex 0.4.6", + "num-integer", + "num-iter", + "num-rational 0.4.2", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive 0.7.2", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "opentelemetry" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "ouroboros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" +dependencies = [ + "aliasable", + "ouroboros_macro", +] + +[[package]] +name = "ouroboros_macro" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.2", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "percentage" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd23b938276f14057220b707937bcb42fa76dda7560e57a2da30cb52d557937" +dependencies = [ + "num 0.2.1", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools 0.10.5", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pyth-sdk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7aeef4d5f0a9c98ff5af2ddd84a8b89919c512188305b497a9eb9afa97a949" +dependencies = [ + "borsh 0.10.3", + "borsh-derive 0.10.3", + "getrandom 0.2.15", + "hex", + "schemars", + "serde", +] + +[[package]] +name = "pyth-sdk-solana" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f913de6eb29d8def199af3beaee645e84c5281327d58777eff3fdd9f1d37105" +dependencies = [ + "borsh 0.10.3", + "borsh-derive 0.10.3", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "pyth-sdk", + "serde", + "solana-program", + "thiserror", +] + +[[package]] +name = "pyth-solana-receiver-sdk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e6559643f0b377b6f293269251f6a804ae7332c37f7310371f50c833453cd0" +dependencies = [ + "anchor-lang 0.29.0", + "hex", + "pythnet-sdk", + "solana-program", +] + +[[package]] +name = "pythnet-sdk" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bbbc0456f9f27c9ad16b6c3bf1b2a7fea61eebf900f4d024a0468b9a84fe0c1" +dependencies = [ + "anchor-lang 0.29.0", + "bincode", + "borsh 0.10.3", + "bytemuck", + "byteorder", + "fast-math", + "hex", + "proc-macro2", + "rustc_version", + "serde", + "sha3 0.10.8", + "slow_primes", + "solana-program", + "thiserror", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "quinn" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" +dependencies = [ + "arbitrary", + "bytes", + "rand 0.8.5", + "ring 0.16.20", + "rustc-hash", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" +dependencies = [ + "bytes", + "libc", + "socket2", + "tracing", + "windows-sys 0.48.0", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "async-compression", + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-util 0.7.11", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rpassword" +version = "7.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.48.0", +] + +[[package]] +name = "rtoolbox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "borsh 1.5.1", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safe-transmute" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944826ff8fa8093089aba3acb4ef44b9446a99a16f3bf4e74af3f77d340ab7d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.58", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "seqlock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c67b6f14ecc5b86c66fa63d76b5092352678545a8a3cdae80aef5128371910" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "serde_fmt" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.2.6", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slow_primes" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58267dd2fbaa6dceecba9e3e106d2d90a2b02497c0e8b01b8759beccf5113938" +dependencies = [ + "num 0.2.1", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "solana-account-decoder" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4973213a11c2e1b924b36e0c6688682b5aa4623f8d4eeaa1204c32cee524e6d6" +dependencies = [ + "Inflector", + "base64 0.21.7", + "bincode", + "bs58 0.4.0", + "bv", + "lazy_static", + "serde", + "serde_derive", + "serde_json", + "solana-config-program", + "solana-sdk", + "spl-token 4.0.0", + "spl-token-2022 1.0.0", + "spl-token-group-interface 0.1.0", + "spl-token-metadata-interface 0.2.0", + "thiserror", + "zstd", +] + +[[package]] +name = "solana-accounts-db" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c06263320e399af20d46c8cebea7a1d5dc1bc56f31f8dfaacf7119576c48a7" +dependencies = [ + "arrayref", + "bincode", + "blake3", + "bv", + "bytemuck", + "byteorder", + "bzip2", + "crossbeam-channel", + "dashmap", + "flate2", + "fnv", + "im", + "index_list", + "itertools 0.10.5", + "lazy_static", + "log", + "lz4", + "memmap2", + "modular-bitfield", + "num-derive 0.4.2", + "num-traits", + "num_cpus", + "num_enum 0.7.2", + "ouroboros", + "percentage", + "qualifier_attr", + "rand 0.8.5", + "rayon", + "regex", + "rustc_version", + "seqlock", + "serde", + "serde_derive", + "smallvec", + "solana-bucket-map", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-measure", + "solana-metrics", + "solana-nohash-hasher", + "solana-program-runtime", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", + "static_assertions", + "strum 0.24.1", + "strum_macros 0.24.3", + "tar", + "tempfile", + "thiserror", +] + +[[package]] +name = "solana-address-lookup-table-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e57cb8f2e90361280b246f70bb7f5f86f4e4ff1ad5bbdfe18a81bea141f03a" +dependencies = [ + "bincode", + "bytemuck", + "log", + "num-derive 0.4.2", + "num-traits", + "rustc_version", + "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-program", + "solana-program-runtime", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-banks-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c65a9540370523f3ade7190526309337cc50f1d742b3341dfa7357da3f59a56" +dependencies = [ + "borsh 1.5.1", + "futures", + "solana-banks-interface", + "solana-program", + "solana-sdk", + "tarpc", + "thiserror", + "tokio", + "tokio-serde", +] + +[[package]] +name = "solana-banks-interface" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b1dc20a7a71cf37bcbc2a3a5dfd73d7410a13850aa68d954a9c09e6a77e652" +dependencies = [ + "serde", + "solana-sdk", + "tarpc", +] + +[[package]] +name = "solana-banks-server" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d449d55d3c5c3fe4c9f0c9f790a9feabe294f8ff0b4c6b771a20b2313ad8974a" +dependencies = [ + "bincode", + "crossbeam-channel", + "futures", + "solana-accounts-db", + "solana-banks-interface", + "solana-client", + "solana-runtime", + "solana-sdk", + "solana-send-transaction-service", + "tarpc", + "tokio", + "tokio-serde", +] + +[[package]] +name = "solana-bpf-loader-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1a55b8533f2dc716602e7c1b2bd555d5ac598ef6e80d28a517e6f31baf042e" +dependencies = [ + "bincode", + "byteorder", + "libsecp256k1 0.6.0", + "log", + "scopeguard", + "solana-measure", + "solana-program-runtime", + "solana-sdk", + "solana-zk-token-sdk", + "solana_rbpf", + "thiserror", +] + +[[package]] +name = "solana-bucket-map" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda213af7ae26ce249120f211060d2a85d87fe367c6490ee19b70845cbd320fc" +dependencies = [ + "bv", + "bytemuck", + "log", + "memmap2", + "modular-bitfield", + "num_enum 0.7.2", + "rand 0.8.5", + "solana-measure", + "solana-sdk", + "tempfile", +] + +[[package]] +name = "solana-clap-utils" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909f4553d0b31bb5b97533a6b64cc321a4eace9112d6efbabcf4408ea1b3f1db" +dependencies = [ + "chrono", + "clap 2.34.0", + "rpassword", + "solana-remote-wallet", + "solana-sdk", + "thiserror", + "tiny-bip39", + "uriparse", + "url", +] + +[[package]] +name = "solana-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5cc431df6cc1dd964134fa4ec7df765d3af3fae9c2148f96a3c4fb500290633" +dependencies = [ + "async-trait", + "bincode", + "dashmap", + "futures", + "futures-util", + "indexmap 2.2.6", + "indicatif", + "log", + "quinn", + "rayon", + "solana-connection-cache", + "solana-measure", + "solana-metrics", + "solana-pubsub-client", + "solana-quic-client", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-rpc-client-nonce-utils", + "solana-sdk", + "solana-streamer", + "solana-thin-client", + "solana-tpu-client", + "solana-udp-client", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-compute-budget-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb36ef3c3a1f38515c1ae0d255c4d6e5e635a856ac2aa1cd5f892b3db58e857" +dependencies = [ + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-config-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e38b040d3a42e8f7d80c4a86bb0d49d7aed663b56b0fe0ae135d2d145fb7ae3a" +dependencies = [ + "bincode", + "chrono", + "serde", + "serde_derive", + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-connection-cache" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae02622c63943485f0af3d0896626eaf6478e734f0b6bc61c7cc5320963c6e75" +dependencies = [ + "async-trait", + "bincode", + "crossbeam-channel", + "futures-util", + "indexmap 2.2.6", + "log", + "rand 0.8.5", + "rayon", + "rcgen", + "solana-measure", + "solana-metrics", + "solana-sdk", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-cost-model" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "838532d8437d00958621d2589d6033e9c69ea95cd0936efa8964146e49dcff53" +dependencies = [ + "lazy_static", + "log", + "rustc_version", + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-loader-v4-program", + "solana-metrics", + "solana-program-runtime", + "solana-sdk", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", +] + +[[package]] +name = "solana-frozen-abi" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4867f66e9527fa44451c861c1dc6d9b2a7c7a668d7c6a297cdefbe39f4395b33" +dependencies = [ + "block-buffer 0.10.4", + "bs58 0.4.0", + "bv", + "either", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "sha2 0.10.8", + "solana-frozen-abi-macro", + "subtle", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168f24d97347b85f05192df58d6be3e3047a4aadc4001bc1b9e711a5ec878eea" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.58", +] + +[[package]] +name = "solana-loader-v4-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c426482234b7c267a5e0dfa8198442e1ffad2ad6c521f6b810949bc2719215" +dependencies = [ + "log", + "solana-measure", + "solana-program-runtime", + "solana-sdk", + "solana_rbpf", +] + +[[package]] +name = "solana-logger" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0511082fc62f2d086520fff5aa1917c389d8c840930c08ad255ae05952c08a2" +dependencies = [ + "env_logger", + "lazy_static", + "log", +] + +[[package]] +name = "solana-measure" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55a3df105431d25f86f2a7da0cbbde5f54c1f0782ca59367ea4a8037bc6797" +dependencies = [ + "log", + "solana-sdk", +] + +[[package]] +name = "solana-metrics" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddec097ed7572804389195128dbd57958b427829153c6cd8ec3343c86fe3cd22" +dependencies = [ + "crossbeam-channel", + "gethostname", + "lazy_static", + "log", + "reqwest", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-net-utils" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258fa7c29fb7605b8d2ed89aa0d43c640d14f4147ad1f5b3fdad19a1ac145ca5" +dependencies = [ + "bincode", + "clap 3.2.25", + "crossbeam-channel", + "log", + "nix", + "rand 0.8.5", + "serde", + "serde_derive", + "socket2", + "solana-logger", + "solana-sdk", + "solana-version", + "tokio", + "url", +] + +[[package]] +name = "solana-nohash-hasher" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" + +[[package]] +name = "solana-perf" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca422edcf16a6e64003ca118575ea641f7b750f14a0ad28c71dd84f33dcb912a" +dependencies = [ + "ahash 0.8.11", + "bincode", + "bv", + "caps", + "curve25519-dalek", + "dlopen2", + "fnv", + "lazy_static", + "libc", + "log", + "nix", + "rand 0.8.5", + "rayon", + "rustc_version", + "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-metrics", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-vote-program", +] + +[[package]] +name = "solana-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc5a636dc75e5c25651e34f7a36afc9ae60d38166687c5b0375abb580ac81a2" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", + "bincode", + "bitflags 2.6.0", + "blake3", + "borsh 0.10.3", + "borsh 0.9.3", + "borsh 1.5.1", + "bs58 0.4.0", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.15", + "itertools 0.10.5", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1 0.6.0", + "light-poseidon", + "log", + "memoffset 0.9.1", + "num-bigint 0.4.6", + "num-derive 0.4.2", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3 0.10.8", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-program-runtime" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf373c3da0387f47fee4c5ed2465a9628b9db026a62211a692a9285aa9251544" +dependencies = [ + "base64 0.21.7", + "bincode", + "eager", + "enum-iterator", + "itertools 0.10.5", + "libc", + "log", + "num-derive 0.4.2", + "num-traits", + "percentage", + "rand 0.8.5", + "rustc_version", + "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-measure", + "solana-metrics", + "solana-sdk", + "solana_rbpf", + "thiserror", +] + +[[package]] +name = "solana-program-test" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9194b8744c5b135401ab4a2923a1072d3a67697bd50f7450a4ed5302f36a6999" +dependencies = [ + "assert_matches", + "async-trait", + "base64 0.21.7", + "bincode", + "chrono-humanize", + "crossbeam-channel", + "log", + "serde", + "solana-accounts-db", + "solana-banks-client", + "solana-banks-interface", + "solana-banks-server", + "solana-bpf-loader-program", + "solana-logger", + "solana-program-runtime", + "solana-runtime", + "solana-sdk", + "solana-vote-program", + "solana_rbpf", + "test-case", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-pubsub-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b9abc76168d19927561db6a3685b98752bd0961b4ce4f8b7f85ee12238c017" +dependencies = [ + "crossbeam-channel", + "futures-util", + "log", + "reqwest", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-rpc-client-api", + "solana-sdk", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tungstenite", + "url", +] + +[[package]] +name = "solana-quic-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7952c5306a0be5f5276448cd20246b31265bfa884f29a077a24303c6a16aeb34" +dependencies = [ + "async-mutex", + "async-trait", + "futures", + "itertools 0.10.5", + "lazy_static", + "log", + "quinn", + "quinn-proto", + "rcgen", + "rustls", + "solana-connection-cache", + "solana-measure", + "solana-metrics", + "solana-net-utils", + "solana-rpc-client-api", + "solana-sdk", + "solana-streamer", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-rayon-threadlimit" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4fa0cc66f8e73d769bca2ede3012ba2ef8ab67963e832808665369f2cf81743" +dependencies = [ + "lazy_static", + "num_cpus", +] + +[[package]] +name = "solana-remote-wallet" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "289803796d4ff7b4699504d3ab9e9d9c5205ea3892b2ebe397b377494dbd75d4" +dependencies = [ + "console", + "dialoguer", + "log", + "num-derive 0.4.2", + "num-traits", + "parking_lot", + "qstring", + "semver", + "solana-sdk", + "thiserror", + "uriparse", +] + +[[package]] +name = "solana-rpc-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb55a08018776a62ecff52139fbcdab1a7baa4e8f077202be58156e8dde4d5f" +dependencies = [ + "async-trait", + "base64 0.21.7", + "bincode", + "bs58 0.4.0", + "indicatif", + "log", + "reqwest", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-rpc-client-api", + "solana-sdk", + "solana-transaction-status", + "solana-version", + "solana-vote-program", + "tokio", +] + +[[package]] +name = "solana-rpc-client-api" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a8403038f4d6ab65bc7e7afb3afe8d9824c592232553c5cef55cf3de36025d" +dependencies = [ + "base64 0.21.7", + "bs58 0.4.0", + "jsonrpc-core", + "reqwest", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-sdk", + "solana-transaction-status", + "solana-version", + "spl-token-2022 1.0.0", + "thiserror", +] + +[[package]] +name = "solana-rpc-client-nonce-utils" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caca735caf76d51c074c3bacbfe38094bf7f92cfbe7b5b13f3bc4946e64f889" +dependencies = [ + "clap 2.34.0", + "solana-clap-utils", + "solana-rpc-client", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-runtime" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699943045665038bfa4e76dd2582b4c390f1aec6ab5edef36da43afe3469f1d" +dependencies = [ + "aquamarine", + "arrayref", + "base64 0.21.7", + "bincode", + "blake3", + "bv", + "bytemuck", + "byteorder", + "bzip2", + "crossbeam-channel", + "dashmap", + "dir-diff", + "flate2", + "fnv", + "im", + "index_list", + "itertools 0.10.5", + "lazy_static", + "log", + "lru", + "lz4", + "memmap2", + "mockall", + "modular-bitfield", + "num-derive 0.4.2", + "num-traits", + "num_cpus", + "num_enum 0.7.2", + "ouroboros", + "percentage", + "qualifier_attr", + "rand 0.8.5", + "rayon", + "regex", + "rustc_version", + "serde", + "serde_derive", + "serde_json", + "solana-accounts-db", + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-bucket-map", + "solana-compute-budget-program", + "solana-config-program", + "solana-cost-model", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-loader-v4-program", + "solana-measure", + "solana-metrics", + "solana-perf", + "solana-program-runtime", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-stake-program", + "solana-system-program", + "solana-version", + "solana-vote", + "solana-vote-program", + "solana-zk-token-proof-program", + "solana-zk-token-sdk", + "static_assertions", + "strum 0.24.1", + "strum_macros 0.24.3", + "symlink", + "tar", + "tempfile", + "thiserror", + "zstd", +] + +[[package]] +name = "solana-sdk" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df43d3a1e1637397ab43cbc216a5a8f977ec8a3cc3f3ae8c3851c83a3255dbcf" +dependencies = [ + "assert_matches", + "base64 0.21.7", + "bincode", + "bitflags 2.6.0", + "borsh 1.5.1", + "bs58 0.4.0", + "bytemuck", + "byteorder", + "chrono", + "derivation-path", + "digest 0.10.7", + "ed25519-dalek", + "ed25519-dalek-bip32", + "generic-array", + "hmac 0.12.1", + "itertools 0.10.5", + "js-sys", + "lazy_static", + "libsecp256k1 0.6.0", + "log", + "memmap2", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.2", + "pbkdf2 0.11.0", + "qstring", + "qualifier_attr", + "rand 0.7.3", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "serde_with", + "sha2 0.10.8", + "sha3 0.10.8", + "siphasher", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-program", + "solana-sdk-macro", + "thiserror", + "uriparse", + "wasm-bindgen", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c76414183a325038ff020b22c07d1e9d2da0703ddc0244acfed37ee2921d96" +dependencies = [ + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] + +[[package]] +name = "solana-security-txt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" + +[[package]] +name = "solana-send-transaction-service" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e056d865d22548bb7228121e118aa632486fc1a33a100961e5e98b5663371384" +dependencies = [ + "crossbeam-channel", + "log", + "solana-client", + "solana-measure", + "solana-metrics", + "solana-runtime", + "solana-sdk", + "solana-tpu-client", +] + +[[package]] +name = "solana-stake-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5dd1bc07beb75da5df5e07301d3d0d6104872c9afade22b910af9061fb4bc15" +dependencies = [ + "bincode", + "log", + "rustc_version", + "solana-config-program", + "solana-program-runtime", + "solana-sdk", + "solana-vote-program", +] + +[[package]] +name = "solana-streamer" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad1bdb955ec6d23a1dbf87e403ff3e610d68616275693125a893d7ed4b2d323" +dependencies = [ + "async-channel", + "bytes", + "crossbeam-channel", + "futures-util", + "histogram", + "indexmap 2.2.6", + "itertools 0.10.5", + "libc", + "log", + "nix", + "pem", + "percentage", + "pkcs8", + "quinn", + "quinn-proto", + "rand 0.8.5", + "rcgen", + "rustls", + "smallvec", + "solana-metrics", + "solana-perf", + "solana-sdk", + "thiserror", + "tokio", + "x509-parser", +] + +[[package]] +name = "solana-system-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78733745268c96d5a29c09cde9f0a6c9d662abba43e661b75dd858da8e3d0b2e" +dependencies = [ + "bincode", + "log", + "serde", + "serde_derive", + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-thin-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc301310ba0755c449a8800136f67f8ad14419b366404629894cd10021495360" +dependencies = [ + "bincode", + "log", + "rayon", + "solana-connection-cache", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-sdk", +] + +[[package]] +name = "solana-tpu-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb887bd5078ff015e103e9ee54a6713380590efa8ff1804b3a653f07188928c6" +dependencies = [ + "async-trait", + "bincode", + "futures-util", + "indexmap 2.2.6", + "indicatif", + "log", + "rayon", + "solana-connection-cache", + "solana-measure", + "solana-metrics", + "solana-pubsub-client", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-sdk", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-transaction-status" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0cdfdf63192fb60de094fae8e81159e4e3e9aac9659fe3f9ef0e707023fb32" +dependencies = [ + "Inflector", + "base64 0.21.7", + "bincode", + "borsh 0.10.3", + "bs58 0.4.0", + "lazy_static", + "log", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-sdk", + "spl-associated-token-account 2.3.0", + "spl-memo", + "spl-token 4.0.0", + "spl-token-2022 1.0.0", + "thiserror", +] + +[[package]] +name = "solana-udp-client" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea0d6d8d66e36371577f51c4d1d6192a66f1fa4efe7161a36d94677640dcadb" +dependencies = [ + "async-trait", + "solana-connection-cache", + "solana-net-utils", + "solana-sdk", + "solana-streamer", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-version" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f4c2f531c22ce806b211118be8928a791425f97de4592371fb57b246ed33e34" +dependencies = [ + "log", + "rustc_version", + "semver", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk", +] + +[[package]] +name = "solana-vote" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28ab95a5d19ff0464def1777adaae5a74e1edc9e6818103064c18fdc2643f6cb" +dependencies = [ + "crossbeam-channel", + "itertools 0.10.5", + "log", + "rustc_version", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk", + "solana-vote-program", + "thiserror", +] + +[[package]] +name = "solana-vote-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d8a6486017e71a3714a8e1a635e17209135cc20535ba9808ccf106d80ff6e8b" +dependencies = [ + "bincode", + "log", + "num-derive 0.4.2", + "num-traits", + "rustc_version", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-metrics", + "solana-program", + "solana-program-runtime", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-zk-token-proof-program" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e3dfb2deb449f7eb1dbd0c7e66dd95ec7b1303a5788673f9fbc9b5a5ea59f2" +dependencies = [ + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "solana-program-runtime", + "solana-sdk", + "solana-zk-token-sdk", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "1.18.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513407f88394e437b4ff5aad892bc5bf51a655ae2401e6e63549734d3695c46f" +dependencies = [ + "aes-gcm-siv", + "base64 0.21.7", + "bincode", + "bytemuck", + "byteorder", + "curve25519-dalek", + "getrandom 0.1.16", + "itertools 0.10.5", + "lazy_static", + "merlin", + "num-derive 0.4.2", + "num-traits", + "rand 0.7.3", + "serde", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "solana_rbpf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d457cc2ba742c120492a64b7fa60e22c575e891f6b55039f4d736568fb112a3" +dependencies = [ + "byteorder", + "combine", + "goblin", + "hash32", + "libc", + "log", + "rand 0.8.5", + "rustc-demangle", + "scroll", + "thiserror", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "spl-associated-token-account" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" +dependencies = [ + "assert_matches", + "borsh 0.10.3", + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-token 4.0.0", + "spl-token-2022 1.0.0", + "thiserror", +] + +[[package]] +name = "spl-associated-token-account" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e688554bac5838217ffd1fab7845c573ff106b6336bf7d290db7c98d5a8efd" +dependencies = [ + "assert_matches", + "borsh 1.5.1", + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-token 4.0.0", + "spl-token-2022 3.0.2", + "thiserror", +] + +[[package]] +name = "spl-discriminator" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive 0.1.2", +] + +[[package]] +name = "spl-discriminator" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d1814406e98b08c5cd02c1126f83fd407ad084adce0b05fda5730677822eac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive 0.2.0", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" +dependencies = [ + "quote", + "spl-discriminator-syn 0.1.2", + "syn 2.0.58", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" +dependencies = [ + "quote", + "spl-discriminator-syn 0.2.0", + "syn 2.0.58", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.58", + "thiserror", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f05593b7ca9eac7caca309720f2eafb96355e037e6d373b909a80fe7b69b9" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.58", + "thiserror", +] + +[[package]] +name = "spl-memo" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" +dependencies = [ + "solana-program", +] + +[[package]] +name = "spl-pod" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +dependencies = [ + "borsh 0.10.3", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error 0.3.0", +] + +[[package]] +name = "spl-pod" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ce669f48cf2eca1ec518916d8725596bfb655beb1c74374cf71dc6cb773c9" +dependencies = [ + "borsh 1.5.1", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error 0.4.1", +] + +[[package]] +name = "spl-program-error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-program-error-derive 0.3.2", + "thiserror", +] + +[[package]] +name = "spl-program-error" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49065093ea91f57b9b2bd81493ff705e2ad4e64507a07dbc02b085778e02770e" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-program-error-derive 0.4.1", + "thiserror", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.58", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d375dd76c517836353e093c2dbb490938ff72821ab568b545fd30ab3256b3e" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.58", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "615d381f48ddd2bb3c57c7f7fb207591a2a05054639b18a62e785117dd7a8683" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cace91ba08984a41556efe49cbf2edca4db2f577b649da7827d3621161784bf8" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.2.2", + "spl-pod 0.2.2", + "spl-program-error 0.4.1", + "spl-type-length-value 0.4.3", +] + +[[package]] +name = "spl-token" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.5.11", + "solana-program", + "thiserror", +] + +[[package]] +name = "spl-token" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.6.1", + "solana-program", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.2", + "solana-program", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod 0.1.0", + "spl-token 4.0.0", + "spl-token-metadata-interface 0.2.0", + "spl-transfer-hook-interface 0.3.0", + "spl-type-length-value 0.3.0", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.2", + "solana-program", + "solana-security-txt", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod 0.1.0", + "spl-token 4.0.0", + "spl-token-group-interface 0.1.0", + "spl-token-metadata-interface 0.2.0", + "spl-transfer-hook-interface 0.4.1", + "spl-type-length-value 0.3.0", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5412f99ae7ee6e0afde00defaa354e6228e47e30c0e3adf553e2e01e6abb584" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.2", + "solana-program", + "solana-security-txt", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod 0.2.2", + "spl-token 4.0.0", + "spl-token-group-interface 0.2.3", + "spl-token-metadata-interface 0.3.3", + "spl-transfer-hook-interface 0.6.3", + "spl-type-length-value 0.4.3", + "thiserror", +] + +[[package]] +name = "spl-token-group-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", +] + +[[package]] +name = "spl-token-group-interface" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d419b5cfa3ee8e0f2386fd7e02a33b3ec8a7db4a9c7064a2ea24849dc4a273b6" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.2.2", + "spl-pod 0.2.2", + "spl-program-error 0.4.1", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" +dependencies = [ + "borsh 0.10.3", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30179c47e93625680dabb620c6e7931bd12d62af390f447bc7beb4a3a9b5feee" +dependencies = [ + "borsh 1.5.1", + "solana-program", + "spl-discriminator 0.2.2", + "spl-pod 0.2.2", + "spl-program-error 0.4.1", + "spl-type-length-value 0.4.3", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-tlv-account-resolution 0.4.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-tlv-account-resolution 0.5.1", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a98359769cd988f7b35c02558daa56d496a7e3bd8626e61f90a7c757eedb9b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator 0.2.2", + "spl-pod 0.2.2", + "spl-program-error 0.4.1", + "spl-tlv-account-resolution 0.6.3", + "spl-type-length-value 0.4.3", +] + +[[package]] +name = "spl-type-length-value" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", +] + +[[package]] +name = "spl-type-length-value" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ce13429dbd41d2cee8a73931c05fda0b0c8ca156a8b0c19445642550bb61a" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.2.2", + "spl-pod 0.2.2", + "spl-program-error 0.4.1", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "superslice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" + +[[package]] +name = "sval" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53eb957fbc79a55306d5d25d87daf3627bc3800681491cda0709eef36c748bfe" + +[[package]] +name = "sval_buffer" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96e860aef60e9cbf37888d4953a13445abf523c534640d1f6174d310917c410d" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3f2b07929a1127d204ed7cb3905049381708245727680e9139dac317ed556f" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e188677497de274a1367c4bda15bd2296de4070d91729aac8f0a09c1abf64d" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f456c07dae652744781f2245d5e3b78e6a9ebad70790ac11eb15dbdbce5282" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886feb24709f0476baaebbf9ac10671a50163caa7e439d7a7beb7f6d81d0a6fb" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2e7fc517d778f44f8cb64140afa36010999565528d48985f55e64d45f369ce" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79bf66549a997ff35cd2114a27ac4b0c2843280f2cfa84b240d169ecaa0add46" +dependencies = [ + "serde", + "sval", + "sval_nested", +] + +[[package]] +name = "switchboard-common" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96fe58be35530580b729fa5d846661c89a007982527f4ff0ca6010168564159" +dependencies = [ + "base64 0.21.7", + "hex", + "log", + "serde", + "serde_json", + "sha2 0.10.8", + "sha3 0.10.8", +] + +[[package]] +name = "switchboard-on-demand" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852951c42f8876a443060b6882bda945f1621224236ead37959e80f5369cf81" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.21.7", + "bincode", + "borsh 0.10.3", + "bytemuck", + "futures", + "lazy_static", + "libsecp256k1 0.7.1", + "log", + "num 0.4.3", + "rust_decimal", + "serde", + "serde_json", + "sha2 0.10.8", + "solana-address-lookup-table-program", + "solana-program", + "spl-associated-token-account 2.3.0", + "spl-token 3.5.0", + "switchboard-common", +] + +[[package]] +name = "switchboard-solana" +version = "0.29.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb0cb2cd26bcd72a11fae679d07207bca093c303c9cc72bcdc7866bb7bf8a6b" +dependencies = [ + "anchor-lang 0.29.0", + "anchor-spl 0.29.0", + "bytemuck", + "hex", + "kv-log-macro", + "lazy_static", + "libsecp256k1 0.7.1", + "log", + "rust_decimal", + "solana-address-lookup-table-program", + "solana-program", + "superslice", + "switchboard-common", +] + +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tarpc" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38a012bed6fb9681d3bf71ffaa4f88f3b4b9ed3198cda6e4c8462d24d4bb80" +dependencies = [ + "anyhow", + "fnv", + "futures", + "humantime", + "opentelemetry", + "pin-project", + "rand 0.8.5", + "serde", + "static_assertions", + "tarpc-plugins", + "thiserror", + "tokio", + "tokio-serde", + "tokio-util 0.6.10", + "tracing", + "tracing-opentelemetry", +] + +[[package]] +name = "tarpc-plugins" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee42b4e559f17bce0385ebf511a7beb67d5cc33c12c96b7f4e9789919d9c10f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", + "test-case-core", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "thread-id" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466" +dependencies = [ + "bincode", + "bytes", + "educe", + "futures-core", + "futures-sink", + "pin-project", + "serde", + "serde_json", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.25.4", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" +dependencies = [ + "once_cell", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.5", + "rustls", + "sha1", + "thiserror", + "url", + "utf-8", + "webpki-roots 0.24.0", +] + +[[package]] +name = "type-layout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074069282ba5be5078f1bdb34112b963516d50f262bf4c1082fee1f6ada11d74" +dependencies = [ + "memoffset 0.5.6", + "type-layout-derive", +] + +[[package]] +name = "type-layout-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4a1cf66ce820973c4b31c5ef47a8e930a53ffbcec191212c33f5a3ad75c6cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "typeid" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" + +[[package]] +name = "typemap-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +dependencies = [ + "unsafe-any-ors", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "unsafe-any-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" +dependencies = [ + "destructure_traitobject", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b" +dependencies = [ + "erased-serde", + "serde", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.58", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" +dependencies = [ + "rustls-webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x509-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" +dependencies = [ + "asn1-rs", + "base64 0.13.1", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.12+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/programs/marginfi/fuzz/README.md b/programs/marginfi/fuzz/README.md new file mode 100644 index 000000000..ebd3e99a3 --- /dev/null +++ b/programs/marginfi/fuzz/README.md @@ -0,0 +1,48 @@ +# Fuzz Tests + +These fuzz tests test the soundness of the accounting of the marginfi protocol. +The tests work by interacting directly with the smart contract instructions and at the end comparing if the internal accounting matches the real balances in the token accounts. + +The tests simulates all actions that are involved in internal accounting: + +- Depositing +- Withdrawing +- Liquidate +- Accrue Interest +- Update Price Oracle +- Handle Bankruptcy + +## How it works? + +### Program Interaction + +The framework directly invokes the functions that are normally invoked by the onchain program entry point, and stubs the cpi invoke calls to directly invoke the spl token instructions. + +### State + +The framework uses a bump allocator for account storage. All `AccountInfo` objects are referencing data in the bump allocator. + +When an instruction is invoked we direct pass in the `AccountInfo` objects referencing allocated state. +Before the invoke we also copy to a local cache and revert the state if the instructions fail. + +### Actions + +The framework uses the arbitrary library to generate a random sequence of actions that are then processed on the same state. + +### How to Run + +Run `python3 ./generate_corpus.py`. You may use python if you don't have python3 installed, or you may need to install python. + +Build with `cargo build`. + +If this fails, you probably need to update your Rust toolchain: + +`rustup install nightly-2024-06-05` + +And possibly: + +`rustup component add rust-src --toolchain nightly-2024-06-05-x86_64-unknown-linux-gnu` + +Run with `cargo +nightly-2024-06-05 fuzz run lend -Zbuild-std --strip-dead-code --no-cfg-fuzzing -- -max_total_time=300` + +To rerun some tests after a failure: `cargo +nightly-2024-06-05 fuzz run -Zbuild-std lend artifacts/lend/crash-ae5084b9433152babdaf7dcd75781eacd7ea55c7`, replacing the hash after crash- with the one you see in the terminal. diff --git a/programs/marginfi/fuzz/fuzz_targets/lend.rs b/programs/marginfi/fuzz/fuzz_targets/lend.rs new file mode 100644 index 000000000..916e60b8c --- /dev/null +++ b/programs/marginfi/fuzz/fuzz_targets/lend.rs @@ -0,0 +1,233 @@ +#![no_main] + +use anchor_lang::prelude::{AccountLoader, Clock}; +use anyhow::Result; +use arbitrary::Arbitrary; +use fixed::types::I80F48; +use lazy_static::lazy_static; +use libfuzzer_sys::fuzz_target; +use marginfi::{assert_eq_with_tolerance, prelude::MarginfiGroup, state::marginfi_group::Bank}; +use marginfi_fuzz::{ + account_state::AccountsState, arbitrary_helpers::*, metrics::Metrics, MarginfiFuzzContext, +}; +use solana_program::program_pack::Pack; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, Arbitrary)] +enum Action { + Deposit { + account: AccountIdx, + bank: BankIdx, + asset_amount: AssetAmount, + deposit_up_to_limit: bool, + }, + Borrow { + account: AccountIdx, + bank: BankIdx, + asset_amount: AssetAmount, + }, + UpdateOracle { + bank: BankIdx, + price: PriceChange, + }, + Repay { + account: AccountIdx, + bank: BankIdx, + asset_amount: AssetAmount, + repay_all: bool, + }, + Withdraw { + account: AccountIdx, + bank: BankIdx, + asset_amount: AssetAmount, + withdraw_all: bool, + }, + Liquidate { + liquidator: AccountIdx, + liquidatee: AccountIdx, + asset_amount: AssetAmount, + }, +} + +#[derive(Debug)] +pub struct ActionSequence(Vec); + +impl ActionSequence { + pub const N_ACTIONS: usize = 400; +} + +impl<'a> Arbitrary<'a> for ActionSequence { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let mut actions = Vec::with_capacity(Self::N_ACTIONS); + + for _ in 0..Self::N_ACTIONS { + if u.is_empty() { + panic!("Byte exhaustion detected, stopping early"); + } + let action = Action::arbitrary(u)?; + actions.push(action); + } + + Ok(ActionSequence(actions)) + } + + fn size_hint(_: usize) -> (usize, Option) { + (Self::N_ACTIONS * 10, Some(Self::N_ACTIONS * 10)) + } +} + +#[derive(Debug, Arbitrary)] +pub struct FuzzerContext { + pub action_sequence: ActionSequence, + pub initial_bank_configs: [BankAndOracleConfig; N_BANKS], +} + +fuzz_target!(|data: FuzzerContext| process_actions(data).unwrap()); + +fn process_actions(ctx: FuzzerContext) -> Result<()> { + let mut accounst_state = AccountsState::new(); + + if !*GET_LOGGER { + println!("Setting up logger"); + } + + let mut context = + MarginfiFuzzContext::setup(&accounst_state, &ctx.initial_bank_configs, N_USERS as u8); + + context.metrics = METRICS.clone(); + + for action in ctx.action_sequence.0.iter() { + process_action(action, &context)?; + } + + context.metrics.read().unwrap().print(); + context.metrics.read().unwrap().log(); + + verify_end_state(&context).unwrap(); + + accounst_state.reset(); + + Ok(()) +} + +static GET_LOGGER: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { + #[cfg(feature = "capture_log")] + setup_logging().unwrap(); + true +}); + +lazy_static! { + static ref METRICS: Arc> = Arc::new(RwLock::new(Metrics::default())); +} + +#[cfg(feature = "capture_log")] +fn setup_logging() -> anyhow::Result<()> { + let logfile = log4rs::append::file::FileAppender::builder() + .encoder(Box::new(log4rs::encode::pattern::PatternEncoder::new( + "{l} - {m}\n", + ))) + .build("log/history.log")?; + + let config = log4rs::Config::builder() + .appender(log4rs::config::Appender::builder().build("logfile", Box::new(logfile))) + .build( + log4rs::config::Root::builder() + .appender("logfile") + .build(log::LevelFilter::Info), + )?; + + log4rs::init_config(config)?; + + println!("Starging logger"); + + log::info!("===========START========"); + + Ok(()) +} + +fn verify_end_state<'a>(mga: &'a MarginfiFuzzContext<'a>) -> anyhow::Result<()> { + let group = AccountLoader::::try_from(&mga.marginfi_group).unwrap(); + mga.banks.iter().try_for_each(|bank| { + let bank_loader = AccountLoader::::try_from(&bank.bank).unwrap(); + let mut bank_data = bank_loader.load_mut().unwrap(); + + let latest_timestamp = *mga.last_sysvar_current_timestamp.read().unwrap(); + + let mut clock = Clock::default(); + + clock.unix_timestamp = latest_timestamp as i64 + 3600; + + bank_data.accrue_interest( + clock.unix_timestamp , + &group.load().unwrap() + )?; + + let outstanding_fees = I80F48::from(bank_data.collected_group_fees_outstanding) + + I80F48::from(bank_data.collected_insurance_fees_outstanding) + I80F48::from(bank_data.collected_program_fees_outstanding); + + let total_deposits = bank_data.get_asset_amount(bank_data.total_asset_shares.into())?; + + let total_liabilities = + bank_data.get_liability_amount(bank_data.total_liability_shares.into())?; + + let net_accounted_balance = total_deposits - total_liabilities; + + let liquidity_vault_token_account = + spl_token::state::Account::unpack(&bank.liquidity_vault.data.borrow()[..spl_token::state::Account::LEN]).unwrap(); + + marginfi_fuzz::log!("Accounted Deposits: {}, Liabs: {}, Net {}, Outstanding Fees: {}, Net with Fees {}, Value Token Balance {}, Net Without Fees {}", + total_deposits, + total_liabilities, + net_accounted_balance, + outstanding_fees, + net_accounted_balance + outstanding_fees, + liquidity_vault_token_account.amount, + liquidity_vault_token_account.amount as i64 - outstanding_fees.to_num::(), + ); + + assert_eq_with_tolerance!(I80F48::from(liquidity_vault_token_account.amount) - outstanding_fees, net_accounted_balance, I80F48::ONE); + + Ok::<_, anyhow::Error>(()) + })?; + + Ok(()) +} + +fn process_action<'bump>(action: &Action, mga: &'bump MarginfiFuzzContext<'bump>) -> Result<()> { + marginfi_fuzz::log!("==================>Action {:?}", action); + match action { + Action::Deposit { + account, + bank, + asset_amount, + deposit_up_to_limit, + } => mga.process_action_deposit(account, bank, asset_amount, Some(*deposit_up_to_limit))?, + Action::Withdraw { + account, + bank, + asset_amount, + withdraw_all, + } => mga.process_action_withdraw(account, bank, asset_amount, Some(*withdraw_all))?, + Action::Borrow { + account, + bank, + asset_amount, + } => mga.process_action_borrow(account, bank, asset_amount)?, + Action::Repay { + account, + bank, + asset_amount, + repay_all, + } => mga.process_action_repay(account, bank, asset_amount, *repay_all)?, + Action::UpdateOracle { bank, price } => mga.process_update_oracle(bank, price)?, + Action::Liquidate { + liquidator, + liquidatee, + asset_amount, + } => mga.process_liquidate_account(liquidator, liquidatee, asset_amount)?, + }; + + mga.advance_time(3600); + + Ok(()) +} diff --git a/programs/marginfi/fuzz/src/account_state.rs b/programs/marginfi/fuzz/src/account_state.rs new file mode 100644 index 000000000..f28a6424e --- /dev/null +++ b/programs/marginfi/fuzz/src/account_state.rs @@ -0,0 +1,505 @@ +use crate::arbitrary_helpers::TokenType; +use anchor_lang::{ + prelude::{AccountInfo, Pubkey, Rent, SolanaSysvar}, + Discriminator, +}; +use anchor_spl::token_2022::spl_token_2022::{ + self, + extension::{ + transfer_fee::{TransferFee, TransferFeeConfig}, + BaseStateWithExtensions, BaseStateWithExtensionsMut, ExtensionType, StateWithExtensions, + StateWithExtensionsMut, + }, + state::Mint, +}; +use bumpalo::Bump; +use marginfi::{ + constants::{FEE_STATE_SEED, PYTH_ID}, + state::marginfi_group::BankVaultType, +}; +use pyth_sdk_solana::state::{ + AccountType, PriceInfo, PriceStatus, Rational, SolanaPriceAccount, MAGIC, VERSION_2, +}; +use safe_transmute::{transmute_to_bytes, transmute_to_bytes_mut}; +use solana_program::{ + bpf_loader, program_pack::Pack, stake_history::Epoch, system_program, sysvar, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; +use std::mem::size_of; + +pub struct AccountsState { + pub bump: Bump, +} + +impl AccountsState { + pub fn new() -> Self { + Self { bump: Bump::new() } + } + + fn random_pubkey<'bump>(&'bump self) -> &Pubkey { + #[allow(deprecated)] + self.bump + .alloc(Pubkey::new(transmute_to_bytes(&rand::random::<[u64; 4]>()))) + } + + pub fn new_sol_account<'bump>( + &'bump self, + lamports: u64, + signer: bool, + writeable: bool, + ) -> AccountInfo<'bump> { + self.new_sol_account_with_pubkey(self.random_pubkey(), lamports, signer, writeable) + } + + pub fn new_sol_account_with_pubkey<'bump>( + &'bump self, + pubkey: &'bump Pubkey, + lamports: u64, + signer: bool, + writeable: bool, + ) -> AccountInfo<'bump> { + AccountInfo::new( + pubkey, + signer, + writeable, + self.bump.alloc(lamports), + &mut [], + &system_program::ID, + false, + Epoch::default(), + ) + } + + pub fn new_fee_state<'a>(&'a self, program_id: Pubkey) -> (AccountInfo<'a>, u8) { + let (fee_state_key, fee_state_bump) = + Pubkey::find_program_address(&[FEE_STATE_SEED.as_bytes()], &marginfi::id()); + + ( + AccountInfo::new( + self.bump.alloc(fee_state_key), + false, + true, + self.bump.alloc(9999999), + self.allocate_dex_owned_account(256 + 8), + self.bump.alloc(program_id), + false, + Epoch::default(), + ), + fee_state_bump, + ) + } + + pub fn new_token_mint<'bump>( + &'bump self, + rent: Rent, + decimals: u8, + token_type: TokenType, + ) -> AccountInfo<'bump> { + match token_type { + TokenType::Tokenkeg => { + let data = self.bump.alloc_slice_fill_copy(Mint::LEN, 0u8); + let mut mint = Mint::default(); + mint.is_initialized = true; + mint.decimals = decimals; + Mint::pack(mint, data).unwrap(); + AccountInfo::new( + self.random_pubkey(), + false, + true, + self.bump.alloc(rent.minimum_balance(data.len())), + data, + &spl_token::ID, + false, + Epoch::default(), + ) + } + TokenType::Token22 => { + let data = self.bump.alloc_slice_fill_copy(Mint::LEN, 0u8); + let mut mint = Mint::default(); + mint.is_initialized = true; + mint.decimals = decimals; + Mint::pack(mint, data).unwrap(); + AccountInfo::new( + self.random_pubkey(), + false, + true, + self.bump.alloc(rent.minimum_balance(data.len())), + data, + &spl_token_2022::ID, + false, + Epoch::default(), + ) + } + TokenType::Token22WithFee { + transfer_fee_basis_points, + maximum_fee, + } => { + // Calculate mint size for t22 mint with transfer fee + let data_len = ExtensionType::try_calculate_account_len::< + spl_token_2022::state::Mint, + >(&[ExtensionType::TransferFeeConfig]) + .unwrap(); + + let mut data = self.bump.alloc_slice_fill_copy(data_len, 0u8); + let mut mint_state = + StateWithExtensionsMut::::unpack_uninitialized( + &mut data, + ) + .unwrap(); + mint_state.init_account_type().unwrap(); + + let transfer_fee_config = mint_state + .init_extension::(false) + .unwrap(); + *transfer_fee_config = TransferFeeConfig { + transfer_fee_config_authority: Default::default(), + withdraw_withheld_authority: Default::default(), + withheld_amount: 0.into(), + older_transfer_fee: TransferFee { + epoch: 0.into(), + maximum_fee: maximum_fee.into(), + transfer_fee_basis_points: transfer_fee_basis_points.into(), + }, + newer_transfer_fee: TransferFee { + epoch: 0.into(), + maximum_fee: maximum_fee.into(), + transfer_fee_basis_points: transfer_fee_basis_points.into(), + }, + }; + + let mut mint = spl_token_2022::state::Mint::default(); + mint.decimals = decimals; + mint.is_initialized = true; + + mint_state.base = mint; + mint_state.pack_base(); + + AccountInfo::new( + self.random_pubkey(), + false, + true, + self.bump.alloc(rent.minimum_balance(data.len())), + data, + &spl_token_2022::ID, + false, + Epoch::default(), + ) + } + } + } + + pub fn new_token_account<'a>( + &'a self, + mint_ai: AccountInfo<'a>, + owner_pubkey: &'a Pubkey, + balance: u64, + rent: Rent, + ) -> AccountInfo<'a> { + self.new_token_account_with_pubkey( + Keypair::new().pubkey(), + mint_ai, + owner_pubkey, + balance, + rent, + ) + } + + pub fn new_token_account_with_pubkey<'a>( + &'a self, + account_pubkey: Pubkey, + mint_ai: AccountInfo<'a>, + owner_pubkey: &'a Pubkey, + balance: u64, + rent: Rent, + ) -> AccountInfo<'a> { + // Load mint + let mint_data = mint_ai.try_borrow_data().unwrap(); + let mint_state = + StateWithExtensions::::unpack(&mint_data).unwrap(); + let mint_extensions = mint_state.get_extension_types().unwrap(); + let required_extensions = + ExtensionType::get_required_init_account_extensions(&mint_extensions); + let space = ExtensionType::try_calculate_account_len::( + &required_extensions, + ) + .unwrap(); + + let data = self.bump.alloc_slice_fill_copy(space, 0u8); + + let mut token_account_state = + StateWithExtensionsMut::::unpack_uninitialized(data) + .unwrap(); + + if required_extensions.contains(&ExtensionType::TransferFeeAmount) { + token_account_state + .init_account_extension_from_type(ExtensionType::TransferFeeAmount) + .unwrap(); + } + + token_account_state.base = spl_token_2022::state::Account::default(); + token_account_state.base.state = spl_token_2022::state::AccountState::Initialized; + token_account_state.base.mint = *mint_ai.key; + token_account_state.base.owner = *owner_pubkey; + token_account_state.base.amount = balance; + + token_account_state.pack_base(); + token_account_state.init_account_type().unwrap(); + drop(token_account_state); + assert!(StateWithExtensionsMut::::unpack(data).is_ok()); + + AccountInfo::new( + self.bump.alloc(account_pubkey), + false, + true, + self.bump.alloc(rent.minimum_balance(data.len())), + data, + mint_ai.owner, + false, + Epoch::default(), + ) + } + + pub fn new_owned_account( + &self, + unpadded_len: usize, + owner_pubkey: Pubkey, + rent: Rent, + ) -> AccountInfo { + let data_len = unpadded_len; + println!("len: {:?}", data_len); + self.new_dex_owned_account_with_lamports( + unpadded_len, + rent.minimum_balance(data_len), + self.bump.alloc(owner_pubkey), + ) + } + + pub fn new_blank_owned_account_with_key( + &self, + key: Pubkey, + owner_pubkey: Pubkey, + ) -> AccountInfo { + self.new_dex_owned_blank_account_with_key( + self.bump.alloc(key), + self.bump.alloc(owner_pubkey), + ) + } + + pub fn new_dex_owned_account_with_lamports<'bump>( + &'bump self, + unpadded_len: usize, + lamports: u64, + program_id: &'bump Pubkey, + ) -> AccountInfo<'bump> { + AccountInfo::new( + self.random_pubkey(), + false, + true, + self.bump.alloc(lamports), + self.allocate_dex_owned_account(unpadded_len), + program_id, + false, + Epoch::default(), + ) + } + + pub fn new_dex_owned_blank_account_with_key<'bump>( + &'bump self, + key: &'bump Pubkey, + program_id: &'bump Pubkey, + ) -> AccountInfo<'bump> { + AccountInfo::new( + key, + false, + true, + self.bump.alloc(0), + &mut [], + program_id, + false, + Epoch::default(), + ) + } + + fn allocate_dex_owned_account<'bump>(&'bump self, unpadded_size: usize) -> &mut [u8] { + assert_eq!(unpadded_size % 8, 0); + let padded_size = unpadded_size; + let u64_data = self.bump.alloc_slice_fill_copy(padded_size / 8 + 1, 0u64); + + transmute_to_bytes_mut(u64_data) as _ + } + + pub fn new_spl_token_program(&self) -> AccountInfo { + self.new_program(spl_token::id()) + } + + pub fn new_system_program(&self) -> AccountInfo { + self.new_program(system_program::id()) + } + + pub fn new_marginfi_program(&self) -> AccountInfo { + self.new_program(marginfi::id()) + } + + pub fn new_program(&self, pubkey: Pubkey) -> AccountInfo { + AccountInfo::new( + self.bump.alloc(pubkey), + false, + false, + self.bump.alloc(0), + &mut [], + &bpf_loader::ID, + true, + Epoch::default(), + ) + } + + pub fn new_oracle_account( + &self, + rent: Rent, + native_price: i64, + mint: Pubkey, + mint_decimals: i32, + ) -> AccountInfo { + let price_account = SolanaPriceAccount { + prod: mint, + agg: PriceInfo { + conf: 0, + price: native_price, + status: PriceStatus::Trading, + ..Default::default() + }, + expo: -mint_decimals, + prev_price: native_price, + magic: MAGIC, + ver: VERSION_2, + atype: AccountType::Price as u32, + timestamp: 0, + ema_price: Rational { + val: native_price, + numer: native_price, + denom: 1, + }, + ..Default::default() + }; + + let data = bytemuck::bytes_of(&price_account); + let data_len = data.len(); + let lamports = self.bump.alloc(rent.minimum_balance(data_len)); + let data_ptr = self.bump.alloc_slice_fill_copy(data_len, 0u8); + data_ptr.copy_from_slice(data); + + AccountInfo::new( + self.random_pubkey(), + false, + true, + lamports, + data_ptr, + &PYTH_ID, + false, + Epoch::default(), + ) + } + + pub fn new_rent_sysvar_account(&self, rent: Rent) -> AccountInfo { + let data = self.bump.alloc_slice_fill_copy(size_of::(), 0u8); + let lamports = rent.minimum_balance(data.len()); + + let mut account_info = AccountInfo::new( + &sysvar::rent::ID, + false, + false, + self.bump.alloc(lamports), + data, + &sysvar::ID, + false, + Epoch::default(), + ); + + rent.to_account_info(&mut account_info).unwrap(); + + account_info + } + + pub fn new_vault_account<'a>( + &'a self, + vault_type: BankVaultType, + mint_ai: AccountInfo<'a>, + owner: &'a Pubkey, + bank: &'a Pubkey, + ) -> (AccountInfo<'a>, u8) { + let (vault_address, seed_bump) = get_vault_address(bank, vault_type); + + ( + self.new_token_account_with_pubkey(vault_address, mint_ai, owner, 0, Rent::free()), + seed_bump, + ) + } + + pub fn new_vault_authority<'a>( + &'a self, + vault_type: BankVaultType, + bank: &'a Pubkey, + ) -> (AccountInfo<'a>, u8) { + let (vault_address, seed_bump) = get_vault_authority(bank, vault_type); + + ( + AccountInfo::new( + self.bump.alloc(vault_address), + false, + false, + self.bump.alloc(0), + &mut [], + &system_program::ID, + false, + Epoch::default(), + ), + seed_bump, + ) + } + + pub fn reset(&mut self) { + self.bump.reset(); + } +} + +pub struct AccountInfoCache<'bump> { + account_data: Vec>, + account_info: Vec>, +} + +impl<'info> AccountInfoCache<'info> { + pub fn new(ais: &[AccountInfo<'info>]) -> Self { + let account_data = ais.iter().map(|ai| ai.data.borrow().to_owned()).collect(); + Self { + account_data, + account_info: ais.to_vec(), + } + } + + pub fn revert(&self) { + for (ai, data) in self.account_info.iter().zip(self.account_data.iter()) { + ai.data.borrow_mut().copy_from_slice(data); + } + } +} + +pub fn get_vault_address(bank: &Pubkey, vault_type: BankVaultType) -> (Pubkey, u8) { + Pubkey::find_program_address(&[vault_type.get_seed(), &bank.to_bytes()], &marginfi::ID) +} + +pub fn get_vault_authority(bank: &Pubkey, vault_type: BankVaultType) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[vault_type.get_authority_seed(), &bank.to_bytes()], + &marginfi::ID, + ) +} + +pub fn set_discriminator(ai: AccountInfo) { + let mut data = ai.try_borrow_mut_data().unwrap(); + + if data[..8].ne(&[0u8; 8]) { + panic!("Account discriminator is already set"); + } + + data[..8].copy_from_slice(&T::DISCRIMINATOR); +} diff --git a/programs/marginfi/fuzz/src/lib.rs b/programs/marginfi/fuzz/src/lib.rs new file mode 100644 index 000000000..01ab89ccd --- /dev/null +++ b/programs/marginfi/fuzz/src/lib.rs @@ -0,0 +1,1297 @@ +use std::{ + collections::HashMap, + mem::size_of, + ops::AddAssign, + sync::{Arc, RwLock}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use account_state::{AccountInfoCache, AccountsState}; +use anchor_lang::{ + accounts::{interface::Interface, interface_account::InterfaceAccount}, + prelude::{AccountInfo, AccountLoader, Context, Program, Pubkey, Rent, Signer, Sysvar}, + Discriminator, Key, +}; +use anchor_spl::token_2022::spl_token_2022; +use arbitrary_helpers::{ + AccountIdx, AssetAmount, BankAndOracleConfig, BankIdx, PriceChange, TokenType, +}; +use bank_accounts::{get_bank_map, BankAccounts}; +use fixed_macro::types::I80F48; +use marginfi::{ + constants::FEE_STATE_SEED, + instructions::LendingPoolConfigureBankOracleBumps, + state::{fee_state::FeeState, marginfi_group::BankConfigCompact}, +}; +use marginfi::{ + errors::MarginfiError, + instructions::LendingPoolAddBankBumps, + prelude::MarginfiGroup, + state::{ + marginfi_account::MarginfiAccount, + marginfi_group::{Bank, BankVaultType, InterestRateConfig}, + }, +}; +use metrics::{MetricAction, Metrics}; +use solana_program::system_program; +use stubs::test_syscall_stubs; +use user_accounts::UserAccount; +use utils::{ + account_info_lifetime_shortener as ails, account_info_ref_lifetime_shortener as airls, + account_info_slice_lifetime_shortener as aisls, +}; + +pub mod account_state; +pub mod arbitrary_helpers; +pub mod bank_accounts; +pub mod metrics; +pub mod stubs; +pub mod user_accounts; +pub mod utils; + +pub struct MarginfiFuzzContext<'info> { + pub marginfi_group: AccountInfo<'info>, + pub fee_state: AccountInfo<'info>, + pub fee_state_wallet: AccountInfo<'info>, + pub banks: Vec>, + pub marginfi_accounts: Vec>, + pub owner: AccountInfo<'info>, + pub system_program: AccountInfo<'info>, + pub rent_sysvar: AccountInfo<'info>, + pub last_sysvar_current_timestamp: RwLock, + pub metrics: Arc>, + pub state: &'info AccountsState, +} + +impl<'state> MarginfiFuzzContext<'state> { + pub fn setup( + state: &'state AccountsState, + bank_configs: &[BankAndOracleConfig], + n_users: u8, + ) -> Self { + let system_program = state.new_program(system_program::id()); + let admin = state.new_sol_account(1_000_000, true, true); + let fee_state_wallet = state.new_sol_account(1_000_000, true, true); + let rent_sysvar = state.new_rent_sysvar_account(Rent::free()); + let fee_state = initialize_fee_state( + state, + admin.clone(), + fee_state_wallet.clone(), + rent_sysvar.clone(), + system_program.clone(), + ); + let marginfi_group = initialize_marginfi_group( + state, + admin.clone(), + fee_state.clone(), + system_program.clone(), + ); + + let mut marginfi_state = MarginfiFuzzContext { + marginfi_group, + fee_state, + fee_state_wallet, + banks: vec![], + owner: admin, + system_program, + rent_sysvar, + marginfi_accounts: vec![], + last_sysvar_current_timestamp: RwLock::new( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ), + metrics: Arc::new(RwLock::new(Metrics::default())), + state, + }; + marginfi_state.advance_time(0); + + bank_configs + .iter() + .for_each(|config| marginfi_state.setup_bank(state, Rent::free(), config)); + + let token_vec = marginfi_state + .banks + .iter() + .map(|b| b.mint.clone()) + .collect(); + + (0..n_users).into_iter().for_each(|_| { + marginfi_state + .create_marginfi_account(state, Rent::free(), &token_vec) + .unwrap() + }); + + // Create an extra account for seeding the banks + marginfi_state + .create_marginfi_account(state, Rent::free(), &token_vec) + .unwrap(); + + // Seed the banks + for bank_idx in 0..marginfi_state.banks.len() { + marginfi_state + .process_action_deposit( + &AccountIdx(marginfi_state.marginfi_accounts.len() as u8 - 1), + &BankIdx(bank_idx as u8), + &AssetAmount( + 1_000 + * 10_u64 + .pow(marginfi_state.banks[bank_idx as usize].mint_decimals.into()), + ), + None, + ) + .unwrap(); + } + + marginfi_state + } + + fn get_bank_map<'a>(&'a self) -> HashMap> { + get_bank_map(&self.banks) + } + + fn refresh_oracle_accounts(&self) { + self.banks.iter().for_each(|bank| { + bank.refresh_oracle( + self.last_sysvar_current_timestamp + .read() + .unwrap() + .to_owned() as i64, + ) + .unwrap() + }); + } + + pub fn advance_time(&self, time: u64) { + self.last_sysvar_current_timestamp + .write() + .unwrap() + .add_assign(time); + + test_syscall_stubs(Some( + *self.last_sysvar_current_timestamp.read().unwrap() as i64 + )); + } + + pub fn setup_bank<'a>( + &'a mut self, + state: &'state AccountsState, + rent: Rent, + initial_bank_config: &BankAndOracleConfig, + ) { + log!("Setting up bank with config {:#?}", initial_bank_config); + let bank = state.new_owned_account(size_of::(), marginfi::id(), rent.clone()); + + let mint = state.new_token_mint( + rent.clone(), + initial_bank_config.mint_decimals, + initial_bank_config.token_type, + ); + let (liquidity_vault_authority, liquidity_vault_authority_bump) = + state.new_vault_authority(BankVaultType::Liquidity, bank.key); + let (liquidity_vault, liquidity_vault_bump) = state.new_vault_account( + BankVaultType::Liquidity, + mint.clone(), + liquidity_vault_authority.key, + bank.key, + ); + + let (insurance_vault_authority, insurance_vault_authority_bump) = + state.new_vault_authority(BankVaultType::Insurance, bank.key); + let (insurance_vault, insurance_vault_bump) = state.new_vault_account( + BankVaultType::Insurance, + mint.clone(), + insurance_vault_authority.key, + bank.key, + ); + + let (fee_vault_authority, fee_vault_authority_bump) = + state.new_vault_authority(BankVaultType::Fee, bank.key); + let (fee_vault, fee_vault_bump) = state.new_vault_account( + BankVaultType::Fee, + mint.clone(), + fee_vault_authority.key, + bank.key, + ); + let (_fee_state_key, fee_state_bump) = + Pubkey::find_program_address(&[FEE_STATE_SEED.as_bytes()], &marginfi::id()); + + let oracle = state.new_oracle_account( + rent.clone(), + initial_bank_config.oracle_native_price as i64, + *mint.key, + initial_bank_config.mint_decimals as i32, + ); + + let add_bank_bumps = LendingPoolAddBankBumps { + liquidity_vault_authority: liquidity_vault_authority_bump, + liquidity_vault: liquidity_vault_bump, + insurance_vault_authority: insurance_vault_authority_bump, + insurance_vault: insurance_vault_bump, + fee_vault_authority: fee_vault_authority_bump, + fee_vault: fee_vault_bump, + fee_state: fee_state_bump, + }; + let configure_bumps = LendingPoolConfigureBankOracleBumps {}; + + let token_program = match initial_bank_config.token_type { + TokenType::Tokenkeg => state.new_program(spl_token::id()), + TokenType::Token22 | TokenType::Token22WithFee { .. } => { + state.new_program(spl_token_2022::id()) + } + }; + + { + marginfi::instructions::marginfi_group::lending_pool_add_bank( + Context::new( + &marginfi::ID, + &mut marginfi::instructions::LendingPoolAddBank { + marginfi_group: AccountLoader::try_from(airls(&self.marginfi_group)) + .unwrap(), + admin: Signer::try_from(airls(&self.owner)).unwrap(), + fee_payer: Signer::try_from(airls(&self.owner)).unwrap(), + fee_state: AccountLoader::try_from(airls(&self.fee_state)).unwrap(), + global_fee_wallet: ails(self.fee_state_wallet.clone()), + bank_mint: Box::new(InterfaceAccount::try_from(airls(&mint)).unwrap()), + bank: AccountLoader::try_from_unchecked(&marginfi::ID, airls(&bank)) + .unwrap(), + liquidity_vault_authority: ails(liquidity_vault_authority.clone()), + liquidity_vault: Box::new( + InterfaceAccount::try_from(airls(&liquidity_vault)).unwrap(), + ), + insurance_vault_authority: ails(insurance_vault_authority.clone()), + insurance_vault: Box::new( + InterfaceAccount::try_from(airls(&insurance_vault)).unwrap(), + ), + fee_vault_authority: ails(fee_vault_authority.clone()), + fee_vault: Box::new(InterfaceAccount::try_from(airls(&fee_vault)).unwrap()), + rent: Sysvar::from_account_info(airls(&self.rent_sysvar)).unwrap(), + token_program: Interface::try_from(airls(&token_program)).unwrap(), + system_program: Program::try_from(airls(&self.system_program)).unwrap(), + }, + &[], + add_bank_bumps, + ), + BankConfigCompact { + asset_weight_init: initial_bank_config.asset_weight_init, + asset_weight_maint: initial_bank_config.asset_weight_maint, + liability_weight_init: initial_bank_config.liability_weight_init, + liability_weight_maint: initial_bank_config.liability_weight_maint, + deposit_limit: initial_bank_config.deposit_limit, + borrow_limit: initial_bank_config.borrow_limit, + interest_rate_config: InterestRateConfig { + optimal_utilization_rate: I80F48!(0.5).into(), + plateau_interest_rate: I80F48!(0.5).into(), + max_interest_rate: I80F48!(4).into(), + insurance_fee_fixed_apr: I80F48!(0.01).into(), + insurance_ir_fee: I80F48!(0.05).into(), + protocol_fixed_fee_apr: I80F48!(0.01).into(), + protocol_ir_fee: I80F48!(0.1).into(), + ..Default::default() + } + .into(), + operational_state: + marginfi::state::marginfi_group::BankOperationalState::Operational, + risk_tier: if !initial_bank_config.risk_tier_isolated { + marginfi::state::marginfi_group::RiskTier::Collateral + } else { + marginfi::state::marginfi_group::RiskTier::Isolated + }, + oracle_max_age: 100, + ..Default::default() + }, + ) + .unwrap(); + } + + set_discriminator::(bank.clone()); + + { + marginfi::instructions::marginfi_group::lending_pool_configure_bank_oracle( + Context::new( + &marginfi::ID, + &mut marginfi::instructions::LendingPoolConfigureBankOracle { + group: AccountLoader::try_from(airls(&self.marginfi_group)).unwrap(), + admin: Signer::try_from(airls(&self.owner)).unwrap(), + bank: AccountLoader::try_from_unchecked(&marginfi::ID, airls(&bank)) + .unwrap(), + }, + &[ails(oracle.clone())], + configure_bumps, + ), + 1, + oracle.key(), + ) + .unwrap(); + } + + self.banks.push(BankAccounts { + bank, + oracle, + liquidity_vault, + insurance_vault, + fee_vault, + mint, + liquidity_vault_authority, + insurance_vault_authority, + fee_vault_authority, + mint_decimals: initial_bank_config.mint_decimals, + token_program, + }); + } + + fn create_marginfi_account<'a>( + &'a mut self, + state: &'state AccountsState, + rent: Rent, + token_mints: &Vec>, + ) -> anyhow::Result<()> { + let marginfi_account = + state.new_owned_account(size_of::(), marginfi::id(), rent.clone()); + + marginfi::instructions::marginfi_account::initialize_account(Context::new( + &marginfi::id(), + &mut marginfi::instructions::marginfi_account::MarginfiAccountInitialize { + marginfi_group: AccountLoader::try_from(airls(&self.marginfi_group))?, + marginfi_account: AccountLoader::try_from_unchecked( + &marginfi::ID, + airls(&marginfi_account), + )?, + authority: Signer::try_from(airls(&self.owner))?, + fee_payer: Signer::try_from(airls(&self.owner))?, + system_program: Program::try_from(airls(&self.system_program))?, + }, + &[], + Default::default(), + ))?; + + let token_accounts = token_mints + .iter() + .map(|token| { + state.new_token_account( + token.clone(), + self.owner.key, + 100_000_000_000_000_000, + rent.clone(), + ) + }) + .collect(); + + set_discriminator::(marginfi_account.clone()); + + self.marginfi_accounts + .push(UserAccount::new(marginfi_account, token_accounts)); + + Ok(()) + } + + pub fn process_action_deposit( + &self, + account_idx: &AccountIdx, + bank_idx: &BankIdx, + asset_amount: &AssetAmount, + deposit_up_to_limit: Option, + ) -> anyhow::Result<()> { + let marginfi_account = &self.marginfi_accounts[account_idx.0 as usize]; + sort_balances(airls(&marginfi_account.margin_account)); + + let bank = &self.banks[bank_idx.0 as usize]; + + let cache = AccountInfoCache::new(&[ + marginfi_account.margin_account.clone(), + bank.bank.clone(), + marginfi_account.token_accounts[bank_idx.0 as usize].clone(), + bank.liquidity_vault.clone(), + ]); + + let mut remaining_accounts: Vec = vec![]; + if bank.token_program.key() == spl_token_2022::ID { + remaining_accounts.push(ails(bank.mint.clone())); + } + + let res = marginfi::instructions::marginfi_account::lending_account_deposit( + Context::new( + &marginfi::ID, + &mut marginfi::instructions::LendingAccountDeposit { + group: AccountLoader::try_from(airls(&self.marginfi_group))?, + marginfi_account: AccountLoader::try_from(airls( + &marginfi_account.margin_account, + ))?, + authority: Signer::try_from(airls(&self.owner))?, + bank: AccountLoader::try_from(airls(&bank.bank))?, + signer_token_account: ails( + marginfi_account.token_accounts[bank_idx.0 as usize].clone(), + ), + liquidity_vault: InterfaceAccount::try_from(airls( + &bank.liquidity_vault.clone(), + ))?, + token_program: Interface::try_from(airls(&bank.token_program))?, + }, + &remaining_accounts, + Default::default(), + ), + asset_amount.0, + deposit_up_to_limit, + ); + + let success = if res.is_err() { + let error = res.unwrap_err(); + + self.metrics.write().unwrap().update_error(&error); + + assert!( + [MarginfiError::AccountDisabled.into(),].contains(&error), + "Unexpected deposit error: {:?}", + error + ); + + cache.revert(); + + false + } else { + true + }; + + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Deposit, success); + + Ok(()) + } + + pub fn process_action_repay( + &self, + account_idx: &AccountIdx, + bank_idx: &BankIdx, + asset_amount: &AssetAmount, + repay_all: bool, + ) -> anyhow::Result<()> { + let marginfi_account = &self.marginfi_accounts[account_idx.0 as usize]; + let bank = &self.banks[bank_idx.0 as usize]; + sort_balances(airls(&marginfi_account.margin_account)); + + let cache = AccountInfoCache::new(&[ + marginfi_account.margin_account.clone(), + bank.bank.clone(), + marginfi_account.token_accounts[bank_idx.0 as usize].clone(), + bank.liquidity_vault.clone(), + ]); + + let mut remaining_accounts = vec![]; + if bank.token_program.key() == spl_token_2022::ID { + remaining_accounts.push(ails(bank.mint.clone())); + } + + let res = marginfi::instructions::marginfi_account::lending_account_repay( + Context::new( + &marginfi::ID, + &mut marginfi::instructions::LendingAccountRepay { + group: AccountLoader::try_from(airls(&self.marginfi_group))?, + marginfi_account: AccountLoader::try_from(airls( + &marginfi_account.margin_account, + ))?, + authority: Signer::try_from(airls(&self.owner))?, + bank: AccountLoader::try_from(airls(&bank.bank))?, + signer_token_account: ails( + marginfi_account.token_accounts[bank_idx.0 as usize].clone(), + ), + liquidity_vault: InterfaceAccount::try_from(airls( + &bank.liquidity_vault.clone(), + ))?, + token_program: Interface::try_from(airls(&bank.token_program))?, + }, + &remaining_accounts, + Default::default(), + ), + asset_amount.0, + Some(repay_all), + ); + + let success = if res.is_err() { + let error = res.unwrap_err(); + + self.metrics.write().unwrap().update_error(&error); + + assert!( + vec![ + MarginfiError::NoLiabilityFound.into(), + MarginfiError::OperationRepayOnly.into(), + // TODO: maybe change + MarginfiError::BankAccountNotFound.into(), + MarginfiError::AccountDisabled.into(), + ] + .contains(&error), + "Unexpected repay error: {:?}", + error + ); + + cache.revert(); + + false + } else { + true + }; + + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Repay, success); + + Ok(()) + } + + pub fn process_action_withdraw( + &'state self, + account_idx: &AccountIdx, + bank_idx: &BankIdx, + asset_amount: &AssetAmount, + withdraw_all: Option, + ) -> anyhow::Result<()> { + self.refresh_oracle_accounts(); + let marginfi_account = &self.marginfi_accounts[account_idx.0 as usize]; + sort_balances(airls(&marginfi_account.margin_account)); + + let bank = &self.banks[bank_idx.0 as usize]; + + let cache = AccountInfoCache::new(&[ + marginfi_account.margin_account.clone(), + bank.bank.clone(), + marginfi_account.token_accounts[bank_idx.0 as usize].clone(), + bank.liquidity_vault.clone(), + ]); + + let remove_all_bank = if let Some(withdraw_all) = withdraw_all { + if withdraw_all { + vec![bank.bank.key()] + } else { + vec![] + } + } else { + vec![] + }; + + let mut remaining_accounts = vec![]; + if bank.token_program.key() == spl_token_2022::ID { + remaining_accounts.push(ails(bank.mint.clone())); + } + remaining_accounts.extend(marginfi_account.get_remaining_accounts( + &self.get_bank_map(), + vec![], + remove_all_bank, + )); + let res = marginfi::instructions::marginfi_account::lending_account_withdraw( + Context::new( + &marginfi::ID, + &mut marginfi::instructions::LendingAccountWithdraw { + group: AccountLoader::try_from(airls(&self.marginfi_group))?, + marginfi_account: AccountLoader::try_from(airls( + &marginfi_account.margin_account, + ))?, + authority: Signer::try_from(airls(&self.owner))?, + bank: AccountLoader::try_from(airls(&bank.bank))?, + token_program: Interface::try_from(airls(&bank.token_program))?, + destination_token_account: InterfaceAccount::try_from(airls( + &marginfi_account.token_accounts[bank_idx.0 as usize], + ))?, + bank_liquidity_vault_authority: ails(bank.liquidity_vault_authority.clone()), + liquidity_vault: InterfaceAccount::try_from(airls(&bank.liquidity_vault))?, + }, + aisls(&remaining_accounts), + Default::default(), + ), + asset_amount.0, + withdraw_all, + ); + + let success = if res.is_err() { + let error = res.unwrap_err(); + + self.metrics.write().unwrap().update_error(&error); + + assert!( + [ + MarginfiError::OperationWithdrawOnly.into(), + MarginfiError::IllegalUtilizationRatio.into(), + MarginfiError::RiskEngineInitRejected.into(), + MarginfiError::NoAssetFound.into(), + MarginfiError::BankAccountNotFound.into(), + MarginfiError::AccountDisabled.into(), + ] + .contains(&error), + "Unexpected withdraw error: {:?}", + error + ); + + cache.revert(); + + false + } else { + true + }; + + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Withdraw, success); + + Ok(()) + } + + pub fn process_action_borrow( + &'state self, + account_idx: &AccountIdx, + bank_idx: &BankIdx, + asset_amount: &AssetAmount, + ) -> anyhow::Result<()> { + self.refresh_oracle_accounts(); + + let marginfi_account = &self.marginfi_accounts[account_idx.0 as usize]; + let bank = &self.banks[bank_idx.0 as usize]; + let cache = AccountInfoCache::new(&[ + marginfi_account.margin_account.clone(), + bank.bank.clone(), + marginfi_account.token_accounts[bank_idx.0 as usize].clone(), + bank.liquidity_vault.clone(), + ]); + sort_balances(airls(&marginfi_account.margin_account)); + + let mut remaining_accounts = vec![]; + if bank.token_program.key() == spl_token_2022::ID { + remaining_accounts.push(ails(bank.mint.clone())); + } + remaining_accounts.extend(marginfi_account.get_remaining_accounts( + &self.get_bank_map(), + vec![bank.bank.key()], + vec![], + )); + let res = marginfi::instructions::marginfi_account::lending_account_borrow( + Context::new( + &marginfi::ID, + &mut marginfi::instructions::LendingAccountBorrow { + group: AccountLoader::try_from(airls(&self.marginfi_group))?, + marginfi_account: AccountLoader::try_from(airls( + &marginfi_account.margin_account, + ))?, + authority: Signer::try_from(airls(&self.owner))?, + bank: AccountLoader::try_from(airls(&bank.bank))?, + token_program: Interface::try_from(airls(&bank.token_program))?, + destination_token_account: InterfaceAccount::try_from(airls( + &marginfi_account.token_accounts[bank_idx.0 as usize], + ))?, + bank_liquidity_vault_authority: ails(bank.liquidity_vault_authority.clone()), + liquidity_vault: InterfaceAccount::try_from(airls(&bank.liquidity_vault))?, + }, + aisls(&remaining_accounts), + Default::default(), + ), + asset_amount.0, + ); + + let success = if res.is_err() { + let error = res.unwrap_err(); + + self.metrics.write().unwrap().update_error(&error); + + assert!( + vec![ + MarginfiError::RiskEngineInitRejected.into(), + MarginfiError::IsolatedAccountIllegalState.into(), + MarginfiError::IllegalUtilizationRatio.into(), + MarginfiError::AccountDisabled.into(), + ] + .contains(&error), + "Unexpected borrow error: {:?}", + error + ); + + cache.revert(); + + false + } else { + true + }; + + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Borrow, success); + + Ok(()) + } + + pub fn process_liquidate_account( + &'state self, + liquidator_idx: &AccountIdx, + liquidatee_idx: &AccountIdx, + asset_amount: &AssetAmount, + ) -> anyhow::Result<()> { + self.refresh_oracle_accounts(); + let liquidator_account = &self.marginfi_accounts[liquidator_idx.0 as usize]; + let liquidatee_account = &self.marginfi_accounts[liquidatee_idx.0 as usize]; + sort_balances(airls(&liquidator_account.margin_account)); + sort_balances(airls(&liquidatee_account.margin_account)); + + if liquidator_account.margin_account.key() == liquidatee_account.margin_account.key() { + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Liquidate, false); + + return Ok(()); + } + + let (asset_bank_idx, liab_bank_idx) = + if let Some(a) = liquidatee_account.get_liquidation_banks(&self.banks) { + if a.0 == a.1 { + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Liquidate, false); + + return Ok(()); + } else { + a + } + } else { + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Liquidate, false); + + return Ok(()); + }; + + let asset_bank = &self.banks[asset_bank_idx.0 as usize]; + let liab_bank = &self.banks[liab_bank_idx.0 as usize]; + + let account_cache = AccountInfoCache::new(&[ + liquidator_account.margin_account.clone(), + liquidatee_account.margin_account.clone(), + asset_bank.bank.clone(), + asset_bank.liquidity_vault.clone(), + liab_bank.bank.clone(), + liab_bank.liquidity_vault.clone(), + liab_bank.insurance_vault.clone(), + ]); + + let mut remaining_accounts = vec![]; + if liab_bank.token_program.key() == spl_token_2022::ID { + remaining_accounts.push(ails(liab_bank.mint.clone())); + } + remaining_accounts.extend(vec![asset_bank.oracle.clone(), liab_bank.oracle.clone()]); + + let mut liquidator_remaining_accounts = liquidator_account.get_remaining_accounts( + &self.get_bank_map(), + vec![liab_bank.bank.key(), asset_bank.bank.key()], + vec![], + ); + let mut liquidatee_remaining_accounts = + liquidatee_account.get_remaining_accounts(&self.get_bank_map(), vec![], vec![]); + + remaining_accounts.append(&mut liquidator_remaining_accounts); + remaining_accounts.append(&mut liquidatee_remaining_accounts); + + let res = marginfi::instructions::lending_account_liquidate( + Context::new( + &marginfi::id(), + &mut marginfi::instructions::LendingAccountLiquidate { + group: AccountLoader::try_from(airls(&self.marginfi_group))?, + asset_bank: AccountLoader::try_from(airls(&asset_bank.bank))?, + liab_bank: AccountLoader::try_from(airls(&liab_bank.bank))?, + liquidator_marginfi_account: AccountLoader::try_from(airls( + &liquidator_account.margin_account, + ))?, + authority: Signer::try_from(airls(&self.owner))?, + liquidatee_marginfi_account: AccountLoader::try_from(airls( + &liquidatee_account.margin_account, + ))?, + bank_liquidity_vault_authority: ails( + liab_bank.liquidity_vault_authority.clone(), + ), + bank_liquidity_vault: Box::new(InterfaceAccount::try_from(airls( + &liab_bank.liquidity_vault, + ))?), + bank_insurance_vault: ails(liab_bank.insurance_vault.clone()), + token_program: Interface::try_from(airls(&liab_bank.token_program))?, + }, + aisls(&remaining_accounts), + Default::default(), + ), + asset_amount.0, + ); + + let success = if res.is_err() { + let error = res.unwrap_err(); + + self.metrics.write().unwrap().update_error(&error); + + assert!( + vec![ + MarginfiError::RiskEngineInitRejected.into(), + MarginfiError::IsolatedAccountIllegalState.into(), + MarginfiError::IllegalUtilizationRatio.into(), + MarginfiError::ZeroLiquidationAmount.into(), + MarginfiError::OverliquidationAttempt.into(), + MarginfiError::HealthyAccount.into(), + MarginfiError::ExhaustedLiability.into(), + MarginfiError::TooSevereLiquidation.into(), + MarginfiError::AccountDisabled.into(), + MarginfiError::MathError.into(), // TODO: would be best to avoid this one + ] + .contains(&error), + "Unexpected liquidate error: {:?}", + error + ); + + account_cache.revert(); + + false + } else { + self.process_handle_bankruptcy(liquidatee_idx, &liab_bank_idx)?; + + true + }; + + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Liquidate, success); + + Ok(()) + } + + pub fn process_handle_bankruptcy( + &'state self, + account_idx: &AccountIdx, + bank_idx: &BankIdx, + ) -> anyhow::Result<()> { + log!("Action: Handle Bankruptcy"); + + let marginfi_account = &self.marginfi_accounts[account_idx.0 as usize]; + let bank = &self.banks[bank_idx.0 as usize]; + + let cache = AccountInfoCache::new(&[ + bank.bank.clone(), + marginfi_account.margin_account.clone(), + bank.liquidity_vault.clone(), + bank.insurance_vault.clone(), + ]); + + let mut remaining_accounts = vec![]; + if bank.token_program.key() == spl_token_2022::ID { + remaining_accounts.push(ails(bank.mint.clone())); + } + remaining_accounts.extend(marginfi_account.get_remaining_accounts( + &self.get_bank_map(), + vec![], + vec![], + )); + let res = marginfi::instructions::lending_pool_handle_bankruptcy(Context::new( + &marginfi::ID, + &mut marginfi::instructions::LendingPoolHandleBankruptcy { + group: AccountLoader::try_from(airls(&self.marginfi_group))?, + signer: Signer::try_from(airls(&self.owner))?, + bank: AccountLoader::try_from(airls(&bank.bank))?, + marginfi_account: AccountLoader::try_from(airls(&marginfi_account.margin_account))?, + liquidity_vault: ails(bank.liquidity_vault.clone()), + insurance_vault: Box::new(InterfaceAccount::try_from(airls( + &bank.insurance_vault, + ))?), + insurance_vault_authority: ails(bank.insurance_vault_authority.clone()), + token_program: Interface::try_from(airls(&bank.token_program))?, + }, + aisls(&remaining_accounts), + Default::default(), + )); + + let success = if res.is_err() { + let error = res.unwrap_err(); + + self.metrics.write().unwrap().update_error(&error); + + assert!( + vec![ + MarginfiError::AccountDisabled.into(), + MarginfiError::AccountNotBankrupt.into(), + ] + .contains(&error), + "Unexpected handle bankruptcy error: {:?}", + error + ); + + cache.revert(); + + false + } else { + true + }; + + self.metrics + .write() + .unwrap() + .update_metric(MetricAction::Bankruptcy, success); + + Ok(()) + } + + pub fn process_update_oracle( + &self, + bank_idx: &BankIdx, + price_change: &PriceChange, + ) -> anyhow::Result<()> { + log!("Action: Update Oracle"); + let bank = &self.banks[bank_idx.0 as usize]; + + bank.update_oracle(price_change.0)?; + + self.metrics.write().unwrap().price_update += 1; + + Ok(()) + } +} + +fn sort_balances<'a>(marginfi_account_ai: &'a AccountInfo<'a>) { + let marginfi_account_loader = + AccountLoader::::try_from(marginfi_account_ai).unwrap(); + let mut marginfi_account = marginfi_account_loader.load_mut().unwrap(); + marginfi_account + .lending_account + .balances + .sort_by_key(|a| !a.active); +} + +pub fn set_discriminator(ai: AccountInfo) { + let mut data = ai.try_borrow_mut_data().unwrap(); + + if data[..8].ne(&[0u8; 8]) { + panic!("Account discriminator is already set"); + } + + data[..8].copy_from_slice(&T::DISCRIMINATOR); +} + +fn initialize_marginfi_group<'a>( + state: &'a AccountsState, + admin: AccountInfo<'a>, + fee_state: AccountInfo<'a>, + system_program: AccountInfo<'a>, +) -> AccountInfo<'a> { + let program_id = marginfi::id(); + let marginfi_group = + state.new_owned_account(size_of::(), program_id, Rent::free()); + + marginfi::instructions::marginfi_group::initialize_group( + Context::new( + &marginfi::id(), + &mut marginfi::instructions::MarginfiGroupInitialize { + // Unchecked because we are initializing the account. + marginfi_group: AccountLoader::try_from_unchecked( + &program_id, + airls(&marginfi_group), + ) + .unwrap(), + admin: Signer::try_from(airls(&admin)).unwrap(), + fee_state: AccountLoader::try_from_unchecked(&program_id, airls(&fee_state)) + .unwrap(), + system_program: Program::try_from(airls(&system_program)).unwrap(), + }, + &[], + Default::default(), + ), + false, + ) + .unwrap(); + + set_discriminator::(marginfi_group.clone()); + + marginfi_group +} + +fn initialize_fee_state<'a>( + state: &'a AccountsState, + admin: AccountInfo<'a>, + wallet: AccountInfo<'a>, + rent: AccountInfo<'a>, + system_program: AccountInfo<'a>, +) -> AccountInfo<'a> { + let program_id = marginfi::id(); + let (fee_state, _fee_state_bump) = state.new_fee_state(program_id); + + marginfi::instructions::marginfi_group::initialize_fee_state( + Context::new( + &marginfi::id(), + &mut marginfi::instructions::InitFeeState { + payer: Signer::try_from(airls(&admin)).unwrap(), + fee_state: AccountLoader::try_from_unchecked(&program_id, airls(&fee_state)) + .unwrap(), + rent: Sysvar::from_account_info(airls(&rent)).unwrap(), + system_program: Program::try_from(airls(&system_program)).unwrap(), + }, + &[], + Default::default(), + ), + admin.key(), + wallet.key(), + // WARN: tests will fail at add_bank::system_program::transfer if this is non-zero because + // the fuzz suite does not yet support the system program. + 0, + I80F48!(0).into(), + I80F48!(0).into(), + ) + .unwrap(); + + set_discriminator::(fee_state.clone()); + + fee_state +} + +#[cfg(test)] +mod tests { + use fixed::types::I80F48; + use marginfi::state::marginfi_account::RiskEngine; + use pyth_sdk_solana::state::PriceAccount; + + use super::*; + #[test] + fn deposit_test() { + let account_state = AccountsState::new(); + + let a = MarginfiFuzzContext::setup(&account_state, &[BankAndOracleConfig::dummy(); 2], 2); + + let al = + AccountLoader::::try_from_unchecked(&marginfi::id(), &a.marginfi_group) + .unwrap(); + + assert_eq!(al.load().unwrap().admin, a.owner.key()); + + a.process_action_deposit(&AccountIdx(0), &BankIdx(0), &AssetAmount(1000), None) + .unwrap(); + + let marginfi_account_ai = AccountLoader::::try_from_unchecked( + &marginfi::id(), + &a.marginfi_accounts[0].margin_account, + ) + .unwrap(); + let marginfi_account = marginfi_account_ai.load().unwrap(); + + assert_eq!( + I80F48::from(marginfi_account.lending_account.balances[0].asset_shares), + I80F48!(1000) + ); + } + + #[test] + fn borrow_test() { + let account_state = AccountsState::new(); + let a = MarginfiFuzzContext::setup(&account_state, &[BankAndOracleConfig::dummy(); 2], 2); + + a.process_action_deposit(&AccountIdx(1), &BankIdx(1), &AssetAmount(1000), None) + .unwrap(); + a.process_action_deposit(&AccountIdx(0), &BankIdx(0), &AssetAmount(1000), None) + .unwrap(); + a.process_action_borrow(&AccountIdx(0), &BankIdx(1), &AssetAmount(100)) + .unwrap(); + + let marginfi_account_ai = AccountLoader::::try_from_unchecked( + &marginfi::id(), + &a.marginfi_accounts[0].margin_account, + ) + .unwrap(); + + { + let marginfi_account = marginfi_account_ai.load().unwrap(); + + assert_eq!( + I80F48::from(marginfi_account.lending_account.balances[0].asset_shares), + I80F48!(1000) + ); + assert_eq!( + I80F48::from(marginfi_account.lending_account.balances[1].liability_shares), + I80F48!(100) + ); + } + + a.process_action_repay(&AccountIdx(0), &BankIdx(1), &AssetAmount(100), false) + .unwrap(); + + let marginfi_account = marginfi_account_ai.load().unwrap(); + + assert_eq!( + I80F48::from(marginfi_account.lending_account.balances[1].liability_shares), + I80F48!(0) + ); + } + + #[test] + fn liquidation_test() { + let account_state = AccountsState::new(); + let a = MarginfiFuzzContext::setup(&account_state, &[BankAndOracleConfig::dummy(); 2], 3); + + a.process_action_deposit(&AccountIdx(1), &BankIdx(1), &AssetAmount(1000), None) + .unwrap(); + a.process_action_deposit(&AccountIdx(0), &BankIdx(0), &AssetAmount(1000), None) + .unwrap(); + a.process_action_borrow(&AccountIdx(0), &BankIdx(1), &AssetAmount(500)) + .unwrap(); + + a.banks[1].log_oracle_price().unwrap(); + + a.process_update_oracle(&BankIdx(1), &PriceChange(10000000000000)) + .unwrap(); + + a.banks[1].log_oracle_price().unwrap(); + + let marginfi_account_ai = AccountLoader::::try_from_unchecked( + &marginfi::id(), + &a.marginfi_accounts[0].margin_account, + ) + .unwrap(); + + { + let marginfi_account = marginfi_account_ai.load().unwrap(); + let margin_account = &a.marginfi_accounts[0]; + let bank_map = a.get_bank_map(); + let remaining_accounts = + margin_account.get_remaining_accounts(&bank_map, vec![], vec![]); + + let re = RiskEngine::new(&marginfi_account, aisls(&remaining_accounts)).unwrap(); + + let (assets, liabs) = re + .get_account_health_components( + marginfi::state::marginfi_account::RiskRequirementType::Maintenance, + &mut None, + ) + .unwrap(); + + println!("assets {assets} liabs: {liabs}"); + } + + a.process_action_deposit(&AccountIdx(2), &BankIdx(1), &AssetAmount(1000), None) + .unwrap(); + + a.process_liquidate_account(&AccountIdx(2), &AccountIdx(0), &AssetAmount(50)) + .unwrap(); + + let marginfi_account_ai = AccountLoader::::try_from_unchecked( + &marginfi::id(), + &a.marginfi_accounts[0].margin_account, + ) + .unwrap(); + + let marginfi_account = marginfi_account_ai.load().unwrap(); + + assert_eq!( + I80F48::from(marginfi_account.lending_account.balances[0].asset_shares), + I80F48!(950) + ); + } + + #[test] + fn liquidation_and_bankruptcy() { + let account_state = AccountsState::new(); + + let a = MarginfiFuzzContext::setup(&account_state, &[BankAndOracleConfig::dummy(); 2], 3); + + a.process_action_deposit(&AccountIdx(1), &BankIdx(1), &AssetAmount(1000), None) + .unwrap(); + a.process_action_deposit(&AccountIdx(0), &BankIdx(0), &AssetAmount(1000), None) + .unwrap(); + a.process_action_borrow(&AccountIdx(0), &BankIdx(1), &AssetAmount(500)) + .unwrap(); + + a.process_update_oracle(&BankIdx(1), &PriceChange(1000000000000)) + .unwrap(); + + let marginfi_account_ai = AccountLoader::::try_from_unchecked( + &marginfi::id(), + &a.marginfi_accounts[0].margin_account, + ) + .unwrap(); + + { + let marginfi_account = marginfi_account_ai.load().unwrap(); + let margin_account = &a.marginfi_accounts[0]; + let bank_map = a.get_bank_map(); + let remaining_accounts = + margin_account.get_remaining_accounts(&bank_map, vec![], vec![]); + + let re = RiskEngine::new(&marginfi_account, aisls(&remaining_accounts)).unwrap(); + + let (assets, liabs) = re + .get_account_health_components( + marginfi::state::marginfi_account::RiskRequirementType::Maintenance, + &mut None, + ) + .unwrap(); + + println!("assets {assets} liabs: {liabs}"); + } + + a.process_action_deposit(&AccountIdx(2), &BankIdx(1), &AssetAmount(1000), None) + .unwrap(); + + a.process_liquidate_account(&AccountIdx(2), &AccountIdx(0), &AssetAmount(1000)) + .unwrap(); + + let marginfi_account_ai = AccountLoader::::try_from_unchecked( + &marginfi::id(), + &a.marginfi_accounts[0].margin_account, + ) + .unwrap(); + + let marginfi_account = marginfi_account_ai.load().unwrap(); + + assert_eq!( + I80F48::from(marginfi_account.lending_account.balances[0].asset_shares), + I80F48!(0) + ); + assert_eq!( + I80F48::from(marginfi_account.lending_account.balances[0].liability_shares), + I80F48!(0) + ); + } + + #[test] + fn price_update() { + let account_state = AccountsState::new(); + + let a = MarginfiFuzzContext::setup(&account_state, &[BankAndOracleConfig::dummy(); 2], 3); + + a.process_update_oracle(&BankIdx(0), &PriceChange(1100)) + .unwrap(); + + let new_price = { + let data = a.banks[0].oracle.try_borrow_data().unwrap(); + let data = bytemuck::from_bytes::(&data); + data.ema_price.val + }; + + assert_eq!(new_price, 1100); + } + + #[test] + fn pyth_timestamp_update() { + let account_state = AccountsState::new(); + + let a = MarginfiFuzzContext::setup(&account_state, &[BankAndOracleConfig::dummy(); 2], 3); + + let initial_timestamp = { + let data = a.banks[0].oracle.try_borrow_data().unwrap(); + let data = bytemuck::from_bytes::(&data); + data.timestamp + }; + assert_eq!(initial_timestamp, 0); + + a.banks[0].refresh_oracle(123_456).unwrap(); + + let updated_timestamp_via_0_10 = { + let pf = + pyth_sdk_solana::load_price_feed_from_account_info(&a.banks[0].oracle).unwrap(); + + pf.get_ema_price_unchecked().publish_time + }; + assert_eq!(updated_timestamp_via_0_10, 123_456); + } +} diff --git a/programs/marginfi/fuzz/src/stubs.rs b/programs/marginfi/fuzz/src/stubs.rs new file mode 100644 index 000000000..a099addde --- /dev/null +++ b/programs/marginfi/fuzz/src/stubs.rs @@ -0,0 +1,104 @@ +use anchor_lang::prelude::{AccountInfo, Clock, Pubkey}; +use anchor_spl::token_2022::spl_token_2022; +use lazy_static::lazy_static; +use solana_program::{entrypoint::ProgramResult, instruction::Instruction, program_stubs}; +use solana_sdk::system_program; + +use crate::log; + +#[cfg(feature = "capture_log")] +use itertools::Itertools; + +lazy_static! { + static ref VERBOSE: u32 = std::env::var("FUZZ_VERBOSE") + .map(|s| s.parse()) + .ok() + .transpose() + .ok() + .flatten() + .unwrap_or(0); +} + +pub struct TestSyscallStubs { + pub unix_timestamp: Option, +} + +impl program_stubs::SyscallStubs for TestSyscallStubs { + fn sol_log(&self, _message: &str) { + if *VERBOSE == 0 { + return; + } + log!("Program Log: {}", _message); + } + + fn sol_log_data(&self, _fields: &[&[u8]]) { + if *VERBOSE == 0 { + return; + } + log!( + "data: {}", + _fields + .iter() + .map(|field| base64::engine::general_purpose::STANDARD.encode(field)) + .join(" ") + ); + } + + fn sol_invoke_signed( + &self, + instruction: &Instruction, + account_infos: &[AccountInfo], + signers_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let mut new_account_infos = vec![]; + + for meta in instruction.accounts.iter() { + for account_info in account_infos.iter() { + if meta.pubkey == *account_info.key { + let mut new_account_info = account_info.clone(); + for seeds in signers_seeds.iter() { + let signer = + Pubkey::create_program_address(seeds, &marginfi::id()).unwrap(); + if *account_info.key == signer { + new_account_info.is_signer = true; + } + } + new_account_infos.push(new_account_info); + } + } + } + + if instruction.program_id == spl_token::ID { + spl_token::processor::Processor::process( + &instruction.program_id, + &new_account_infos, + &instruction.data, + ) + } else if instruction.program_id == spl_token_2022::ID { + spl_token_2022::processor::Processor::process( + &instruction.program_id, + &new_account_infos, + &instruction.data, + ) + } else if instruction.program_id == system_program::ID { + panic!("System program is not yet supported"); + }else{ + panic!("program not supported"); + } + } + + fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 { + let clock: Option = self.unix_timestamp; + unsafe { + *(var_addr as *mut _ as *mut Clock) = Clock { + unix_timestamp: clock.unwrap(), + ..Clock::default() + }; + } + solana_program::entrypoint::SUCCESS + } +} + +pub fn test_syscall_stubs(unix_timestamp: Option) { + program_stubs::set_syscall_stubs(Box::new(TestSyscallStubs { unix_timestamp })); +} diff --git a/programs/marginfi/fuzz/src/user_accounts.rs b/programs/marginfi/fuzz/src/user_accounts.rs new file mode 100644 index 000000000..21e77a0c3 --- /dev/null +++ b/programs/marginfi/fuzz/src/user_accounts.rs @@ -0,0 +1,118 @@ +use std::collections::{HashMap, HashSet}; + +use anchor_lang::{ + prelude::{AccountInfo, AccountLoader, Pubkey}, + Key, +}; +use fixed::types::I80F48; + +use marginfi::state::marginfi_account::MarginfiAccount; + +use crate::{arbitrary_helpers::BankIdx, bank_accounts::BankAccounts}; + +pub struct UserAccount<'info> { + pub margin_account: AccountInfo<'info>, + pub token_accounts: Vec>, +} + +impl<'info> UserAccount<'info> { + pub fn new( + margin_account: AccountInfo<'info>, + token_accounts: Vec>, + ) -> Self { + Self { + margin_account, + token_accounts, + } + } + + pub fn get_liquidation_banks( + &'info self, + banks: &[BankAccounts], + ) -> Option<(BankIdx, BankIdx)> { + let marginfi_account_al = + AccountLoader::::try_from(&self.margin_account).ok()?; + let marginfi_account = marginfi_account_al.load().ok()?; + + // let bank_map = get_bank_map(banks); + + let mut asset_balances = marginfi_account + .lending_account + .balances + .iter() + .filter(|blc| !blc.is_empty(marginfi::state::marginfi_account::BalanceSide::Assets)) + .collect::>(); + let mut liab_balances = marginfi_account + .lending_account + .balances + .iter() + .filter(|blc| { + !blc.is_empty(marginfi::state::marginfi_account::BalanceSide::Liabilities) + }) + .collect::>(); + + asset_balances + .sort_by(|a, b| I80F48::from(a.asset_shares).cmp(&I80F48::from(b.asset_shares))); + liab_balances.sort_by(|a, b| { + I80F48::from(a.liability_shares).cmp(&I80F48::from(b.liability_shares)) + }); + + let best_asset_bank = asset_balances.first()?.bank_pk; + let best_liab_bank = liab_balances.first()?.bank_pk; + + let best_asset_pos = banks + .iter() + .position(|bank| bank.bank.key.eq(&best_asset_bank))?; + let best_liab_pos = banks + .iter() + .position(|bank| bank.bank.key.eq(&best_liab_bank))?; + + Some((BankIdx(best_asset_pos as u8), BankIdx(best_liab_pos as u8))) + } + + pub fn get_remaining_accounts( + &'info self, + bank_map: &HashMap>, + include_banks: Vec, + exclude_banks: Vec, + ) -> Vec> { + let marginfi_account_al = + AccountLoader::::try_from(&self.margin_account).unwrap(); + let marginfi_account = marginfi_account_al.load().unwrap(); + + let mut already_included_banks = HashSet::new(); + + let mut ais = marginfi_account + .lending_account + .balances + .iter() + .filter(|a| a.is_active() && !exclude_banks.contains(&a.bank_pk)) + .flat_map(|balance| { + let bank_accounts = bank_map.get(&balance.bank_pk).unwrap(); + assert_eq!(balance.bank_pk, bank_accounts.bank.key()); + + already_included_banks.insert(bank_accounts.bank.key()); + + [bank_accounts.bank.clone(), bank_accounts.oracle.clone()] + }) + .collect::>(); + + let missing_banks = include_banks + .iter() + .filter(|key| !already_included_banks.contains(key)) + .collect::>(); + + let mut missing_bank_ais = missing_banks + .iter() + .flat_map(|key| { + let bank_accounts = bank_map.get(key).unwrap(); + + [bank_accounts.bank.clone(), bank_accounts.oracle.clone()] + }) + .collect::>(); + + ais.append(&mut missing_bank_ais); + + ais + } +} diff --git a/programs/marginfi/src/constants.rs b/programs/marginfi/src/constants.rs index e3b67d31f..dc8680717 100644 --- a/programs/marginfi/src/constants.rs +++ b/programs/marginfi/src/constants.rs @@ -12,6 +12,9 @@ pub const LIQUIDITY_VAULT_SEED: &str = "liquidity_vault"; pub const INSURANCE_VAULT_SEED: &str = "insurance_vault"; pub const FEE_VAULT_SEED: &str = "fee_vault"; +pub const FEE_STATE_SEED: &str = "feestate"; +pub const STAKED_SETTINGS_SEED: &str = "staked_settings"; + pub const EMISSIONS_AUTH_SEED: &str = "emissions_auth_seed"; pub const EMISSIONS_TOKEN_ACCOUNT_SEED: &str = "emissions_token_account_seed"; @@ -26,6 +29,17 @@ cfg_if::cfg_if! { } } +// TODO update to the actual deployment key on mainnet/devnet/staging +cfg_if::cfg_if! { + if #[cfg(feature = "devnet")] { + pub const SPL_SINGLE_POOL_ID: Pubkey = pubkey!("SVSPxpvHdN29nkVg9rPapPNDddN5DipNLRUFhyjFThE"); + } else if #[cfg(any(feature = "mainnet-beta", feature = "staging"))] { + pub const SPL_SINGLE_POOL_ID: Pubkey = pubkey!("SVSPxpvHdN29nkVg9rPapPNDddN5DipNLRUFhyjFThE"); + } else { + pub const SPL_SINGLE_POOL_ID: Pubkey = pubkey!("SVSPxpvHdN29nkVg9rPapPNDddN5DipNLRUFhyjFThE"); + } +} + cfg_if::cfg_if! { if #[cfg(feature = "devnet")] { pub const SWITCHBOARD_PULL_ID: Pubkey = pubkey!("Aio4gaXjXzJNVLtzwtNVmSqGKpANtXhybbkhtAC94ji2"); @@ -34,12 +48,20 @@ cfg_if::cfg_if! { } } +pub const NATIVE_STAKE_ID: Pubkey = pubkey!("Stake11111111111111111111111111111111111111"); + /// TODO: Make these variable per bank pub const LIQUIDATION_LIQUIDATOR_FEE: I80F48 = I80F48!(0.025); pub const LIQUIDATION_INSURANCE_FEE: I80F48 = I80F48!(0.025); +/// The default fee, in native SOL in native decimals (i.e. lamports) used in testing +pub const INIT_BANK_ORIGINATION_FEE_DEFAULT: u32 = 10000; + pub const SECONDS_PER_YEAR: I80F48 = I80F48!(31_536_000); +/// Due to real-world constraints, oracles using an age less than this value are typically too +/// unreliable, and we want to restrict pools from picking an oracle that is effectively unusable +pub const ORACLE_MIN_AGE: u16 = 30; pub const MAX_PYTH_ORACLE_AGE: u64 = 60; pub const MAX_SWB_ORACLE_AGE: u64 = 3 * 60; @@ -72,9 +94,10 @@ pub const ZERO_AMOUNT_THRESHOLD: I80F48 = I80F48!(0.0001); pub const EMISSIONS_FLAG_BORROW_ACTIVE: u64 = 1 << 0; pub const EMISSIONS_FLAG_LENDING_ACTIVE: u64 = 1 << 1; pub const PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG: u64 = 1 << 2; +pub const FREEZE_SETTINGS: u64 = 1 << 3; pub(crate) const EMISSION_FLAGS: u64 = EMISSIONS_FLAG_BORROW_ACTIVE | EMISSIONS_FLAG_LENDING_ACTIVE; -pub(crate) const GROUP_FLAGS: u64 = PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG; +pub(crate) const GROUP_FLAGS: u64 = PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG | FREEZE_SETTINGS; /// Cutoff timestamp for balance last_update used in accounting collected emissions. /// Any balance updates before this timestamp are ignored, and current_timestamp is used instead. @@ -136,6 +159,20 @@ pub const EXP_10: [i128; MAX_EXP_10] = [ /// Value where total_asset_value_init_limit is considered inactive pub const TOTAL_ASSET_VALUE_INIT_LIMIT_INACTIVE: u64 = 0; +/// For testing, this is a typical program fee. +pub const PROTOCOL_FEE_RATE_DEFAULT: I80F48 = I80F48!(0.025); +/// For testing, this is a typical program fee. +pub const PROTOCOL_FEE_FIXED_DEFAULT: I80F48 = I80F48!(0.01); + pub const MIN_PYTH_PUSH_VERIFICATION_LEVEL: VerificationLevel = VerificationLevel::Full; pub const PYTH_PUSH_PYTH_SPONSORED_SHARD_ID: u16 = 0; pub const PYTH_PUSH_MARGINFI_SPONSORED_SHARD_ID: u16 = 3301; + +/// A regular asset that can be comingled with any other regular asset or with `ASSET_TAG_SOL` +pub const ASSET_TAG_DEFAULT: u8 = 0; +/// Accounts with a SOL position can comingle with **either** `ASSET_TAG_DEFAULT` or +/// `ASSET_TAG_STAKED` positions, but not both +pub const ASSET_TAG_SOL: u8 = 1; +/// Staked SOL assets. Accounts with a STAKED position can only deposit other STAKED assets or SOL +/// (`ASSET_TAG_SOL`) and can only borrow SOL (`ASSET_TAG_SOL`) +pub const ASSET_TAG_STAKED: u8 = 2; diff --git a/programs/marginfi/src/errors.rs b/programs/marginfi/src/errors.rs index b68837b47..b707619f9 100644 --- a/programs/marginfi/src/errors.rs +++ b/programs/marginfi/src/errors.rs @@ -2,8 +2,8 @@ use anchor_lang::prelude::*; #[error_code] pub enum MarginfiError { - #[msg("Math error")] // 6000 - MathError, + #[msg("Internal Marginfi logic error")] // 6000 + InternalLogicError, #[msg("Invalid bank index")] // 6001 BankNotFound, #[msg("Lending account balance not found")] // 6002 @@ -12,90 +12,146 @@ pub enum MarginfiError { BankAssetCapacityExceeded, #[msg("Invalid transfer")] // 6004 InvalidTransfer, - #[msg("Missing Pyth or Bank account")] // 6005 + #[msg("Missing Oracle, Bank, LST mint, or Sol Pool")] // 6005 MissingPythOrBankAccount, #[msg("Missing Pyth account")] // 6006 MissingPythAccount, - #[msg("Invalid Pyth account")] // 6007 - InvalidOracleAccount, - #[msg("Missing Bank account")] // 6008 + #[msg("Missing Bank account")] // 6007 MissingBankAccount, - #[msg("Invalid Bank account")] // 6009 + #[msg("Invalid Bank account")] // 6008 InvalidBankAccount, - #[msg("RiskEngine rejected due to either bad health or stale oracles")] // 6010 + #[msg("RiskEngine rejected due to either bad health or stale oracles")] // 6009 RiskEngineInitRejected, - #[msg("Lending account balance slots are full")] // 6011 + #[msg("Lending account balance slots are full")] // 6010 LendingAccountBalanceSlotsFull, - #[msg("Bank already exists")] // 6012 + #[msg("Bank already exists")] // 6011 BankAlreadyExists, - #[msg("Illegal liquidation")] // 6013 - IllegalLiquidation, - #[msg("Account is not bankrupt")] // 6014 + #[msg("Amount to liquidate must be positive")] // 6012 + ZeroLiquidationAmount, + #[msg("Account is not bankrupt")] // 6013 AccountNotBankrupt, - #[msg("Account balance is not bad debt")] // 6015 + #[msg("Account balance is not bad debt")] // 6014 BalanceNotBadDebt, - #[msg("Invalid group config")] // 6016 + #[msg("Invalid group config")] // 6015 InvalidConfig, - #[msg("Stale oracle data")] // 6017 - StaleOracle, - #[msg("Bank paused")] // 6018 + #[msg("Bank paused")] // 6016 BankPaused, - #[msg("Bank is ReduceOnly mode")] // 6019 + #[msg("Bank is ReduceOnly mode")] // 6017 BankReduceOnly, - #[msg("Bank is missing")] // 6020 + #[msg("Bank is missing")] // 6018 BankAccountNotFound, - #[msg("Operation is deposit-only")] // 6021 + #[msg("Operation is deposit-only")] // 6019 OperationDepositOnly, - #[msg("Operation is withdraw-only")] // 6022 + #[msg("Operation is withdraw-only")] // 6020 OperationWithdrawOnly, - #[msg("Operation is borrow-only")] // 6023 + #[msg("Operation is borrow-only")] // 6021 OperationBorrowOnly, - #[msg("Operation is repay-only")] // 6024 + #[msg("Operation is repay-only")] // 6022 OperationRepayOnly, - #[msg("No asset found")] // 6025 + #[msg("No asset found")] // 6023 NoAssetFound, - #[msg("No liability found")] // 6026 + #[msg("No liability found")] // 6024 NoLiabilityFound, - #[msg("Invalid oracle setup")] // 6027 + #[msg("Invalid oracle setup")] // 6025 InvalidOracleSetup, - #[msg("Invalid bank utilization ratio")] // 6028 + #[msg("Invalid bank utilization ratio")] // 6026 IllegalUtilizationRatio, - #[msg("Bank borrow cap exceeded")] // 6029 + #[msg("Bank borrow cap exceeded")] // 6027 BankLiabilityCapacityExceeded, - #[msg("Invalid Price")] // 6030 + #[msg("Invalid Price")] // 6028 InvalidPrice, - #[msg("Account can have only one liability when account is under isolated risk")] // 6031 - IsolatedAccountIllegalState, // 6032 - #[msg("Emissions already setup")] + #[msg("Account can have only one liability when account is under isolated risk")] // 6029 + IsolatedAccountIllegalState, + #[msg("Emissions already setup")] // 6030 EmissionsAlreadySetup, - #[msg("Oracle is not set")] // 6033 + #[msg("Oracle is not set")] // 6031 OracleNotSetup, - #[msg("Invalid switchboard decimal conversion")] // 6034 + #[msg("Invalid switchboard decimal conversion")] // 6032 InvalidSwitchboardDecimalConversion, - #[msg("Cannot close balance because of outstanding emissions")] // 6035 + #[msg("Cannot close balance because of outstanding emissions")] // 6033 CannotCloseOutstandingEmissions, - #[msg("Update emissions error")] //6036 + #[msg("Update emissions error")] //6034 EmissionsUpdateError, - #[msg("Account disabled")] // 6037 + #[msg("Account disabled")] // 6035 AccountDisabled, - #[msg("Account can't temporarily open 3 balances, please close a balance first")] // 6038 + #[msg("Account can't temporarily open 3 balances, please close a balance first")] // 6036 AccountTempActiveBalanceLimitExceeded, - #[msg("Illegal action during flashloan")] // 6039 + #[msg("Illegal action during flashloan")] // 6037 AccountInFlashloan, - #[msg("Illegal flashloan")] // 6040 + #[msg("Illegal flashloan")] // 6038 IllegalFlashloan, - #[msg("Illegal flag")] // 6041 + #[msg("Illegal flag")] // 6039 IllegalFlag, - #[msg("Illegal balance state")] // 6043 + #[msg("Illegal balance state")] // 6040 IllegalBalanceState, - #[msg("Illegal account authority transfer")] // 6044 + #[msg("Illegal account authority transfer")] // 6041 IllegalAccountAuthorityTransfer, - #[msg("Unauthorized")] // 6045 + #[msg("Unauthorized")] // 6042 Unauthorized, - #[msg("Invalid account authority")] // 6046 + #[msg("Invalid account authority")] // 6043 IllegalAction, - #[msg("Token22 Banks require mint account as first remaining account")] // 6047 + #[msg("Token22 Banks require mint account as first remaining account")] // 6044 T22MintRequired, + #[msg("Invalid ATA for global fee account")] // 6045 + InvalidFeeAta, + #[msg("Use add pool permissionless instead")] // 6046 + AddedStakedPoolManually, + #[msg("Staked SOL accounts can only deposit staked assets and borrow SOL")] // 6047 + AssetTagMismatch, + #[msg("Stake pool validation failed: check the stake pool, mint, or sol pool")] // 6048 + StakePoolValidationFailed, + #[msg("Switchboard oracle: stale price")] // 6049 + SwitchboardStalePrice, + #[msg("Pyth Push oracle: stale price")] // 6050 + PythPushStalePrice, + #[msg("Oracle error: wrong number of accounts")] // 6051 + WrongNumberOfOracleAccounts, + #[msg("Oracle error: wrong account keys")] // 6052 + WrongOracleAccountKeys, + #[msg("Pyth Push oracle: wrong account owner")] // 6053 + PythPushWrongAccountOwner, + #[msg("Staked Pyth Push oracle: wrong account owner")] // 6054 + StakedPythPushWrongAccountOwner, + #[msg("Pyth Push oracle: mismatched feed id")] // 6055 + PythPushMismatchedFeedId, + #[msg("Pyth Push oracle: insufficient verification level")] // 6056 + PythPushInsufficientVerificationLevel, + #[msg("Pyth Push oracle: feed id must be 32 Bytes")] // 6057 + PythPushFeedIdMustBe32Bytes, + #[msg("Pyth Push oracle: feed id contains non-hex characters")] // 6058 + PythPushFeedIdNonHexCharacter, + #[msg("Switchboard oracle: wrong account owner")] // 6059 + SwitchboardWrongAccountOwner, + #[msg("Pyth Push oracle: invalid account")] // 6060 + PythPushInvalidAccount, + #[msg("Switchboard oracle: invalid account")] // 6061 + SwitchboardInvalidAccount, + #[msg("Math error")] // 6062 + MathError, + #[msg("Invalid emissions destination account")] // 6063 + InvalidEmissionsDestinationAccount, + #[msg("Asset and liability bank cannot be the same")] // 6064 + SameAssetAndLiabilityBanks, + #[msg("Trying to withdraw more assets than available")] // 6065 + OverliquidationAttempt, + #[msg("Liability bank has no liabilities")] // 6066 + NoLiabilitiesInLiabilityBank, + #[msg("Liability bank has assets")] // 6067 + AssetsInLiabilityBank, + #[msg("Account is healthy and cannot be liquidated")] // 6068 + HealthyAccount, + #[msg("Liability payoff too severe, exhausted liability")] // 6069 + ExhaustedLiability, + #[msg("Liability payoff too severe, liability balance has assets")] // 6070 + TooSeverePayoff, + #[msg("Liquidation too severe, account above maintenance requirement")] // 6071 + TooSevereLiquidation, + #[msg("Liquidation would worsen account health")] // 6072 + WorseHealthPostLiquidation, + #[msg("Arena groups can only support two banks")] // 6073 + ArenaBankLimit, + #[msg("Arena groups cannot return to non-arena status")] // 6074 + ArenaSettingCannotChange, } impl From for ProgramError { @@ -103,3 +159,104 @@ impl From for ProgramError { ProgramError::Custom(e as u32) } } + +impl From for MarginfiError { + fn from(e: pyth_solana_receiver_sdk::error::GetPriceError) -> Self { + match e { + pyth_solana_receiver_sdk::error::GetPriceError::PriceTooOld => { + MarginfiError::PythPushStalePrice + } + pyth_solana_receiver_sdk::error::GetPriceError::MismatchedFeedId => { + MarginfiError::PythPushMismatchedFeedId + } + pyth_solana_receiver_sdk::error::GetPriceError::InsufficientVerificationLevel => { + MarginfiError::PythPushInsufficientVerificationLevel + } + pyth_solana_receiver_sdk::error::GetPriceError::FeedIdMustBe32Bytes => { + MarginfiError::PythPushFeedIdMustBe32Bytes + } + pyth_solana_receiver_sdk::error::GetPriceError::FeedIdNonHexCharacter => { + MarginfiError::PythPushFeedIdNonHexCharacter + } + } + } +} +impl From for MarginfiError { + fn from(value: u32) -> Self { + match value { + 6001 => MarginfiError::BankNotFound, + 6002 => MarginfiError::LendingAccountBalanceNotFound, + 6003 => MarginfiError::BankAssetCapacityExceeded, + 6004 => MarginfiError::InvalidTransfer, + 6005 => MarginfiError::MissingPythOrBankAccount, + 6006 => MarginfiError::MissingPythAccount, + 6007 => MarginfiError::MissingBankAccount, + 6008 => MarginfiError::InvalidBankAccount, + 6009 => MarginfiError::RiskEngineInitRejected, + 6010 => MarginfiError::LendingAccountBalanceSlotsFull, + 6011 => MarginfiError::BankAlreadyExists, + 6012 => MarginfiError::ZeroLiquidationAmount, + 6013 => MarginfiError::AccountNotBankrupt, + 6014 => MarginfiError::BalanceNotBadDebt, + 6015 => MarginfiError::InvalidConfig, + 6016 => MarginfiError::BankPaused, + 6017 => MarginfiError::BankReduceOnly, + 6018 => MarginfiError::BankAccountNotFound, + 6019 => MarginfiError::OperationDepositOnly, + 6020 => MarginfiError::OperationWithdrawOnly, + 6021 => MarginfiError::OperationBorrowOnly, + 6022 => MarginfiError::OperationRepayOnly, + 6023 => MarginfiError::NoAssetFound, + 6024 => MarginfiError::NoLiabilityFound, + 6025 => MarginfiError::InvalidOracleSetup, + 6026 => MarginfiError::IllegalUtilizationRatio, + 6027 => MarginfiError::BankLiabilityCapacityExceeded, + 6028 => MarginfiError::InvalidPrice, + 6029 => MarginfiError::IsolatedAccountIllegalState, + 6030 => MarginfiError::EmissionsAlreadySetup, + 6031 => MarginfiError::OracleNotSetup, + 6032 => MarginfiError::InvalidSwitchboardDecimalConversion, + 6033 => MarginfiError::CannotCloseOutstandingEmissions, + 6034 => MarginfiError::EmissionsUpdateError, + 6035 => MarginfiError::AccountDisabled, + 6036 => MarginfiError::AccountTempActiveBalanceLimitExceeded, + 6037 => MarginfiError::AccountInFlashloan, + 6038 => MarginfiError::IllegalFlashloan, + 6039 => MarginfiError::IllegalFlag, + 6040 => MarginfiError::IllegalBalanceState, + 6041 => MarginfiError::IllegalAccountAuthorityTransfer, + 6042 => MarginfiError::Unauthorized, + 6043 => MarginfiError::IllegalAction, + 6044 => MarginfiError::T22MintRequired, + 6045 => MarginfiError::InvalidFeeAta, + 6046 => MarginfiError::AddedStakedPoolManually, + 6047 => MarginfiError::AssetTagMismatch, + 6048 => MarginfiError::StakePoolValidationFailed, + 6049 => MarginfiError::SwitchboardStalePrice, + 6050 => MarginfiError::PythPushStalePrice, + 6051 => MarginfiError::WrongNumberOfOracleAccounts, + 6052 => MarginfiError::WrongOracleAccountKeys, + 6053 => MarginfiError::PythPushWrongAccountOwner, + 6054 => MarginfiError::StakedPythPushWrongAccountOwner, + 6055 => MarginfiError::PythPushMismatchedFeedId, + 6056 => MarginfiError::PythPushInsufficientVerificationLevel, + 6057 => MarginfiError::PythPushFeedIdMustBe32Bytes, + 6058 => MarginfiError::PythPushFeedIdNonHexCharacter, + 6059 => MarginfiError::SwitchboardWrongAccountOwner, + 6060 => MarginfiError::PythPushInvalidAccount, + 6061 => MarginfiError::SwitchboardInvalidAccount, + 6062 => MarginfiError::MathError, + 6063 => MarginfiError::InvalidEmissionsDestinationAccount, + 6064 => MarginfiError::SameAssetAndLiabilityBanks, + 6065 => MarginfiError::OverliquidationAttempt, + 6066 => MarginfiError::NoLiabilitiesInLiabilityBank, + 6067 => MarginfiError::AssetsInLiabilityBank, + 6068 => MarginfiError::HealthyAccount, + 6069 => MarginfiError::ExhaustedLiability, + 6070 => MarginfiError::TooSeverePayoff, + 6071 => MarginfiError::TooSevereLiquidation, + 6072 => MarginfiError::WorseHealthPostLiquidation, + _ => MarginfiError::InternalLogicError, + } + } +} diff --git a/programs/marginfi/src/events.rs b/programs/marginfi/src/events.rs index 35518c6f6..3132cf131 100644 --- a/programs/marginfi/src/events.rs +++ b/programs/marginfi/src/events.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, state::marginfi_group::BankConfigOpt}; +use crate::{state::marginfi_group::BankConfigOpt, StakedSettingsEditConfig}; use anchor_lang::prelude::*; // Event headers @@ -27,7 +27,8 @@ pub struct MarginfiGroupCreateEvent { #[event] pub struct MarginfiGroupConfigureEvent { pub header: GroupEventHeader, - pub config: GroupConfig, + pub admin: Pubkey, + pub flags: u64, } #[event] @@ -45,6 +46,29 @@ pub struct LendingPoolBankConfigureEvent { pub config: BankConfigOpt, } +#[event] +pub struct LendingPoolBankConfigureOracleEvent { + pub header: GroupEventHeader, + pub bank: Pubkey, + pub oracle_setup: u8, + pub oracle: Pubkey, +} + +#[event] +pub struct LendingPoolBankConfigureFrozenEvent { + pub header: GroupEventHeader, + pub bank: Pubkey, + pub mint: Pubkey, + pub deposit_limit: u64, + pub borrow_limit: u64, +} + +#[event] +pub struct EditStakedSettingsEvent { + pub group: Pubkey, + pub settings: StakedSettingsEditConfig, +} + #[event] pub struct LendingPoolBankAccrueInterestEvent { pub header: GroupEventHeader, diff --git a/programs/marginfi/src/instructions/marginfi_account/borrow.rs b/programs/marginfi/src/instructions/marginfi_account/borrow.rs index b7232acc4..6c324fcb2 100644 --- a/programs/marginfi/src/instructions/marginfi_account/borrow.rs +++ b/programs/marginfi/src/instructions/marginfi_account/borrow.rs @@ -1,16 +1,19 @@ use crate::{ bank_signer, check, - constants::{LIQUIDITY_VAULT_AUTHORITY_SEED, LIQUIDITY_VAULT_SEED}, + constants::LIQUIDITY_VAULT_AUTHORITY_SEED, events::{AccountEventHeader, LendingAccountBorrowEvent}, + math_error, prelude::{MarginfiError, MarginfiGroup, MarginfiResult}, state::{ - marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, DISABLED_FLAG}, + health_cache::HealthCache, + marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, ACCOUNT_DISABLED}, marginfi_group::{Bank, BankVaultType}, }, - utils, + utils::{self, validate_asset_tags}, }; use anchor_lang::prelude::*; use anchor_spl::token_interface::{TokenAccount, TokenInterface}; +use bytemuck::Zeroable; use fixed::types::I80F48; use solana_program::{clock::Clock, sysvar::Sysvar}; @@ -28,10 +31,11 @@ pub fn lending_account_borrow<'info>( let LendingAccountBorrow { marginfi_account: marginfi_account_loader, destination_token_account, - bank_liquidity_vault, + liquidity_vault: bank_liquidity_vault, token_program, bank_liquidity_vault_authority, bank: bank_loader, + group: marginfi_group_loader, .. } = ctx.accounts; let clock = Clock::get().map_err(|_| MarginfiError::MathError)?; @@ -42,22 +46,33 @@ pub fn lending_account_borrow<'info>( )?; let mut marginfi_account = marginfi_account_loader.load_mut()?; + let group = &marginfi_group_loader.load()?; + let program_fee_rate: I80F48 = group.fee_state_cache.program_fee_rate.into(); check!( - !marginfi_account.get_flag(DISABLED_FLAG), + !marginfi_account.get_flag(ACCOUNT_DISABLED), MarginfiError::AccountDisabled ); bank_loader.load_mut()?.accrue_interest( clock.unix_timestamp, + group, #[cfg(not(feature = "client"))] bank_loader.key(), )?; + let mut origination_fee: I80F48 = I80F48::ZERO; { let mut bank = bank_loader.load_mut()?; + validate_asset_tags(&bank, &marginfi_account)?; + let liquidity_vault_authority_bump = bank.liquidity_vault_authority_bump; + let origination_fee_rate: I80F48 = bank + .config + .interest_rate_config + .protocol_origination_fee + .into(); let mut bank_account = BankAccountWrapper::find_or_create( &bank_loader.key(), @@ -78,7 +93,21 @@ pub fn lending_account_borrow<'info>( .transpose()? .unwrap_or(amount); - bank_account.borrow(I80F48::from_num(amount_pre_fee))?; + let origination_fee_u64: u64; + if !origination_fee_rate.is_zero() { + origination_fee = I80F48::from_num(amount_pre_fee) + .checked_mul(origination_fee_rate) + .ok_or_else(math_error!())?; + origination_fee_u64 = origination_fee.checked_to_num().ok_or_else(math_error!())?; + + // Incurs a borrow that includes the origination fee (but withdraws just the amt) + bank_account.borrow(I80F48::from_num(amount_pre_fee) + origination_fee)?; + } else { + // Incurs a borrow for the amount without any fee + origination_fee_u64 = 0; + bank_account.borrow(I80F48::from_num(amount_pre_fee))?; + } + bank_account.withdraw_spl_transfer( amount_pre_fee, bank_liquidity_vault.to_account_info(), @@ -96,42 +125,81 @@ pub fn lending_account_borrow<'info>( emit!(LendingAccountBorrowEvent { header: AccountEventHeader { - signer: Some(ctx.accounts.signer.key()), + signer: Some(ctx.accounts.authority.key()), marginfi_account: marginfi_account_loader.key(), marginfi_account_authority: marginfi_account.authority, marginfi_group: marginfi_account.group, }, bank: bank_loader.key(), mint: bank.mint, - amount: amount_pre_fee, + amount: amount_pre_fee + origination_fee_u64, }); + } // release mutable borrow of bank + + // The program and/or group fee account gains the origination fee + { + let mut bank = bank_loader.load_mut()?; + + if !origination_fee.is_zero() { + let mut bank_fees_after: I80F48 = bank.collected_group_fees_outstanding.into(); + + if !program_fee_rate.is_zero() { + // Some portion of the origination fee to goes to program fees + let program_fee_amount: I80F48 = origination_fee + .checked_mul(program_fee_rate) + .ok_or_else(math_error!())?; + // The remainder of the origination fee goes to group fees + bank_fees_after = bank_fees_after + .saturating_add(origination_fee.saturating_sub(program_fee_amount)); + + // Update the bank's program fees + let program_fees_before: I80F48 = bank.collected_program_fees_outstanding.into(); + bank.collected_program_fees_outstanding = program_fees_before + .saturating_add(program_fee_amount) + .into(); + } else { + // If program fee rate is zero, add the full origination fee to group fees + bank_fees_after = bank_fees_after.saturating_add(origination_fee); + } + + // Update the bank's group fees + bank.collected_group_fees_outstanding = bank_fees_after.into(); + } } + let mut health_cache = HealthCache::zeroed(); + health_cache.timestamp = clock.unix_timestamp; + // Check account health, if below threshold fail transaction // Assuming `ctx.remaining_accounts` holds only oracle accounts - RiskEngine::check_account_init_health(&marginfi_account, ctx.remaining_accounts)?; + RiskEngine::check_account_init_health( + &marginfi_account, + ctx.remaining_accounts, + &mut Some(&mut health_cache), + )?; + health_cache.set_engine_ok(true); + marginfi_account.health_cache = health_cache; Ok(()) } #[derive(Accounts)] pub struct LendingAccountBorrow<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = marginfi_account.load() ?.group == marginfi_group.key(), + has_one = group, + has_one = authority )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = marginfi_account.load() ?.authority, - )] - pub signer: Signer<'info>, + pub authority: Signer<'info>, #[account( mut, - constraint = bank.load() ?.group == marginfi_group.key(), + has_one = group, + has_one = liquidity_vault )] pub bank: AccountLoader<'info, Bank>, @@ -149,15 +217,8 @@ pub struct LendingAccountBorrow<'info> { )] pub bank_liquidity_vault_authority: AccountInfo<'info>, - #[account( - mut, - seeds = [ - LIQUIDITY_VAULT_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump = bank.load() ?.liquidity_vault_bump, - )] - pub bank_liquidity_vault: InterfaceAccount<'info, TokenAccount>, + #[account(mut)] + pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, pub token_program: Interface<'info, TokenInterface>, } diff --git a/programs/marginfi/src/instructions/marginfi_account/close.rs b/programs/marginfi/src/instructions/marginfi_account/close.rs index 817ed0121..e8158608d 100644 --- a/programs/marginfi/src/instructions/marginfi_account/close.rs +++ b/programs/marginfi/src/instructions/marginfi_account/close.rs @@ -16,9 +16,13 @@ pub fn close_account(ctx: Context) -> MarginfiResult { #[derive(Accounts)] pub struct MarginfiAccountClose<'info> { - #[account(mut, close = fee_payer)] + #[account( + mut, + has_one = authority, + close = fee_payer + )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account(address = marginfi_account.load()?.authority)] + pub authority: Signer<'info>, #[account(mut)] pub fee_payer: Signer<'info>, diff --git a/programs/marginfi/src/instructions/marginfi_account/close_balance.rs b/programs/marginfi/src/instructions/marginfi_account/close_balance.rs index 83e71d42f..e2c99f3d5 100644 --- a/programs/marginfi/src/instructions/marginfi_account/close_balance.rs +++ b/programs/marginfi/src/instructions/marginfi_account/close_balance.rs @@ -4,7 +4,7 @@ use crate::{ check, prelude::*, state::{ - marginfi_account::{BankAccountWrapper, MarginfiAccount, DISABLED_FLAG}, + marginfi_account::{BankAccountWrapper, MarginfiAccount, ACCOUNT_DISABLED}, marginfi_group::Bank, }, }; @@ -13,6 +13,7 @@ pub fn lending_account_close_balance(ctx: Context) - let LendingAccountCloseBalance { marginfi_account, bank: bank_loader, + group: marginfi_group_loader, .. } = ctx.accounts; @@ -20,12 +21,13 @@ pub fn lending_account_close_balance(ctx: Context) - let mut bank = bank_loader.load_mut()?; check!( - !marginfi_account.get_flag(DISABLED_FLAG), + !marginfi_account.get_flag(ACCOUNT_DISABLED), MarginfiError::AccountDisabled ); bank.accrue_interest( Clock::get()?.unix_timestamp, + &*marginfi_group_loader.load()?, #[cfg(not(feature = "client"))] bank_loader.key(), )?; @@ -43,22 +45,20 @@ pub fn lending_account_close_balance(ctx: Context) - #[derive(Accounts)] pub struct LendingAccountCloseBalance<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = marginfi_account.load()?.group == marginfi_group.key(), + has_one = group, + has_one = authority )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = marginfi_account.load()?.authority, - )] - pub signer: Signer<'info>, + pub authority: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group )] pub bank: AccountLoader<'info, Bank>, } diff --git a/programs/marginfi/src/instructions/marginfi_account/deposit.rs b/programs/marginfi/src/instructions/marginfi_account/deposit.rs index 4f4eec3f9..b3f9a27f9 100644 --- a/programs/marginfi/src/instructions/marginfi_account/deposit.rs +++ b/programs/marginfi/src/instructions/marginfi_account/deposit.rs @@ -1,16 +1,16 @@ use crate::{ check, - constants::LIQUIDITY_VAULT_SEED, events::{AccountEventHeader, LendingAccountDepositEvent}, + math_error, prelude::*, state::{ - marginfi_account::{BankAccountWrapper, MarginfiAccount, DISABLED_FLAG}, + marginfi_account::{BankAccountWrapper, MarginfiAccount, ACCOUNT_DISABLED}, marginfi_group::Bank, }, - utils, + utils::{self, validate_asset_tags}, }; use anchor_lang::prelude::*; -use anchor_spl::token_interface::TokenInterface; +use anchor_spl::token_interface::{TokenAccount, TokenInterface}; use fixed::types::I80F48; use solana_program::clock::Clock; use solana_program::sysvar::Sysvar; @@ -24,14 +24,16 @@ use solana_program::sysvar::Sysvar; pub fn lending_account_deposit<'info>( mut ctx: Context<'_, '_, 'info, 'info, LendingAccountDeposit<'info>>, amount: u64, + deposit_up_to_limit: Option, ) -> MarginfiResult { let LendingAccountDeposit { marginfi_account: marginfi_account_loader, - signer, + authority: signer, signer_token_account, - bank_liquidity_vault, + liquidity_vault: bank_liquidity_vault, token_program, bank: bank_loader, + group: marginfi_group_loader, .. } = ctx.accounts; let clock = Clock::get().map_err(|_| MarginfiError::MathError)?; @@ -40,17 +42,48 @@ pub fn lending_account_deposit<'info>( &*bank_loader.load()?, token_program.key, )?; + let deposit_up_to_limit = deposit_up_to_limit.unwrap_or(false); let mut bank = bank_loader.load_mut()?; let mut marginfi_account = marginfi_account_loader.load_mut()?; + validate_asset_tags(&bank, &marginfi_account)?; + check!( - !marginfi_account.get_flag(DISABLED_FLAG), + !marginfi_account.get_flag(ACCOUNT_DISABLED), MarginfiError::AccountDisabled ); + let deposit_amount = if deposit_up_to_limit && bank.config.is_deposit_limit_active() { + let current_asset_amount = bank.get_asset_amount(bank.total_asset_shares.into())?; + let deposit_limit = I80F48::from_num(bank.config.deposit_limit); + + if current_asset_amount >= deposit_limit { + 0 + } else { + let remaining_capacity = deposit_limit + .checked_sub(current_asset_amount) + .ok_or_else(math_error!())? + .checked_sub(I80F48::ONE) // Subtract 1 to ensure we stay under limit: total_deposits_amount < deposit_limit + .ok_or_else(math_error!())? + .checked_floor() + .ok_or_else(math_error!())? + .checked_to_num::() + .ok_or_else(math_error!())?; + + std::cmp::min(amount, remaining_capacity) + } + } else { + amount + }; + + if deposit_amount == 0 { + return Ok(()); + } + bank.accrue_interest( clock.unix_timestamp, + &*marginfi_group_loader.load()?, #[cfg(not(feature = "client"))] bank_loader.key(), )?; @@ -61,15 +94,19 @@ pub fn lending_account_deposit<'info>( &mut marginfi_account.lending_account, )?; - bank_account.deposit(I80F48::from_num(amount))?; + bank_account.deposit(I80F48::from_num(deposit_amount))?; let amount_pre_fee = maybe_bank_mint .as_ref() .map(|mint| { - utils::calculate_pre_fee_spl_deposit_amount(mint.to_account_info(), amount, clock.epoch) + utils::calculate_pre_fee_spl_deposit_amount( + mint.to_account_info(), + deposit_amount, + clock.epoch, + ) }) .transpose()? - .unwrap_or(amount); + .unwrap_or(deposit_amount); bank_account.deposit_spl_transfer( amount_pre_fee, @@ -90,7 +127,7 @@ pub fn lending_account_deposit<'info>( }, bank: bank_loader.key(), mint: bank.mint, - amount, + amount: deposit_amount, }); Ok(()) @@ -98,22 +135,21 @@ pub fn lending_account_deposit<'info>( #[derive(Accounts)] pub struct LendingAccountDeposit<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = marginfi_account.load()?.group == marginfi_group.key(), + has_one = group, + has_one = authority )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = marginfi_account.load()?.authority, - )] - pub signer: Signer<'info>, + pub authority: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group, + has_one = liquidity_vault )] pub bank: AccountLoader<'info, Bank>, @@ -121,16 +157,8 @@ pub struct LendingAccountDeposit<'info> { #[account(mut)] pub signer_token_account: AccountInfo<'info>, - /// CHECK: Seed constraint check - #[account( - mut, - seeds = [ - LIQUIDITY_VAULT_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump = bank.load()?.liquidity_vault_bump, - )] - pub bank_liquidity_vault: AccountInfo<'info>, + #[account(mut)] + pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, pub token_program: Interface<'info, TokenInterface>, } diff --git a/programs/marginfi/src/instructions/marginfi_account/emissions.rs b/programs/marginfi/src/instructions/marginfi_account/emissions.rs index a83c071be..15236f325 100644 --- a/programs/marginfi/src/instructions/marginfi_account/emissions.rs +++ b/programs/marginfi/src/instructions/marginfi_account/emissions.rs @@ -1,6 +1,7 @@ use anchor_lang::{prelude::*, Accounts, ToAccountInfo}; -use anchor_spl::token_interface::{ - transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked, +use anchor_spl::{ + associated_token::get_associated_token_address_with_program_id, + token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked}, }; use crate::{ @@ -9,7 +10,7 @@ use crate::{ debug, prelude::{MarginfiError, MarginfiResult}, state::{ - marginfi_account::{BankAccountWrapper, MarginfiAccount, DISABLED_FLAG}, + marginfi_account::{BankAccountWrapper, MarginfiAccount, ACCOUNT_DISABLED}, marginfi_group::{Bank, MarginfiGroup}, }, }; @@ -20,7 +21,7 @@ pub fn lending_account_withdraw_emissions<'info>( let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?; check!( - !marginfi_account.get_flag(DISABLED_FLAG), + !marginfi_account.get_flag(ACCOUNT_DISABLED), MarginfiError::AccountDisabled ); @@ -66,30 +67,27 @@ pub fn lending_account_withdraw_emissions<'info>( #[derive(Accounts)] pub struct LendingAccountWithdrawEmissions<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = marginfi_account.load()?.group == marginfi_group.key(), + has_one = group, + has_one = authority )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = marginfi_account.load()?.authority, - )] - pub signer: Signer<'info>, + pub authority: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group, + has_one = emissions_mint )] pub bank: AccountLoader<'info, Bank>, - #[account( - address = bank.load()?.emissions_mint - )] pub emissions_mint: InterfaceAccount<'info, Mint>, + /// CHECK: PDA seeds validated #[account( seeds = [ EMISSIONS_AUTH_SEED.as_bytes(), @@ -98,7 +96,6 @@ pub struct LendingAccountWithdrawEmissions<'info> { ], bump )] - /// CHECK: Asserted by PDA pub emissions_auth: AccountInfo<'info>, #[account( @@ -146,3 +143,156 @@ pub struct LendingAccountSettleEmissions<'info> { #[account(mut)] pub bank: AccountLoader<'info, Bank>, } + +/// emissions rewards will be withdrawn to the emissions_destination_account +pub fn marginfi_account_update_emissions_destination_account<'info>( + ctx: Context<'_, '_, 'info, 'info, MarginfiAccountUpdateEmissionsDestinationAccount<'info>>, +) -> MarginfiResult { + let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?; + + check!( + !marginfi_account.get_flag(ACCOUNT_DISABLED), + MarginfiError::AccountDisabled + ); + + marginfi_account.emissions_destination_account = ctx.accounts.destination_account.key(); + + Ok(()) +} + +#[derive(Accounts)] +pub struct MarginfiAccountUpdateEmissionsDestinationAccount<'info> { + #[account( + mut, + has_one = authority + )] + pub marginfi_account: AccountLoader<'info, MarginfiAccount>, + + pub authority: Signer<'info>, + + /// User's earned emissions will be sent to the cannonical ATA of this wallet. + /// + /// CHECK: Completely unchecked, user picks a destination without restrictions + pub destination_account: AccountInfo<'info>, +} + +/// Permissionlessly withdraw emissions to user emissions_destination_account +pub fn lending_account_withdraw_emissions_permissionless<'info>( + ctx: Context<'_, '_, 'info, 'info, LendingAccountWithdrawEmissionsPermissionless<'info>>, +) -> MarginfiResult { + let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?; + + check!( + !marginfi_account.get_flag(ACCOUNT_DISABLED), + MarginfiError::AccountDisabled + ); + + let emissions_dest_wallet = &marginfi_account.emissions_destination_account; + let emissions_mint = &ctx.accounts.emissions_mint.key(); + let emissions_token_program = &ctx.accounts.token_program.key(); + + // Ensure that the emissions_destination_account was previously set by the user + check!( + !emissions_dest_wallet.eq(&Pubkey::default()), + MarginfiError::InvalidEmissionsDestinationAccount + ); + + // Ensure the destination is the cannonical ATA of the user-specified wallet + let ata_expected = get_associated_token_address_with_program_id( + emissions_dest_wallet, + emissions_mint, + emissions_token_program, + ); + check!( + ata_expected == ctx.accounts.destination_account.key(), + MarginfiError::InvalidEmissionsDestinationAccount + ); + + let mut bank = ctx.accounts.bank.load_mut()?; + + let mut bank_account = BankAccountWrapper::find( + ctx.accounts.bank.to_account_info().key, + &mut bank, + &mut marginfi_account.lending_account, + )?; + + // Settle emissions + let emissions_settle_amount = bank_account.settle_emissions_and_get_transfer_amount()?; + + if emissions_settle_amount > 0 { + debug!("Transferring {} emissions to user", emissions_settle_amount); + + let signer_seeds: &[&[&[u8]]] = &[&[ + EMISSIONS_AUTH_SEED.as_bytes(), + &ctx.accounts.bank.key().to_bytes(), + &ctx.accounts.emissions_mint.key().to_bytes(), + &[ctx.bumps.emissions_auth], + ]]; + + transfer_checked( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.emissions_vault.to_account_info(), + to: ctx.accounts.destination_account.to_account_info(), + authority: ctx.accounts.emissions_auth.to_account_info(), + mint: ctx.accounts.emissions_mint.to_account_info(), + }, + signer_seeds, + ), + emissions_settle_amount, + ctx.accounts.emissions_mint.decimals, + )?; + } + + Ok(()) +} + +#[derive(Accounts)] +pub struct LendingAccountWithdrawEmissionsPermissionless<'info> { + pub group: AccountLoader<'info, MarginfiGroup>, + + #[account( + mut, + has_one = group + )] + pub marginfi_account: AccountLoader<'info, MarginfiAccount>, + + #[account( + mut, + has_one = group, + has_one = emissions_mint + )] + pub bank: AccountLoader<'info, Bank>, + + pub emissions_mint: InterfaceAccount<'info, Mint>, + + #[account( + seeds = [ + EMISSIONS_AUTH_SEED.as_bytes(), + bank.key().as_ref(), + emissions_mint.key().as_ref(), + ], + bump + )] + /// CHECK: Asserted by PDA + pub emissions_auth: AccountInfo<'info>, + + #[account( + mut, + seeds = [ + EMISSIONS_TOKEN_ACCOUNT_SEED.as_bytes(), + bank.key().as_ref(), + emissions_mint.key().as_ref(), + ], + bump, + )] + pub emissions_vault: Box>, + + /// CHECK: Handler will validate this is cannonical ATA of the `emissions_destination_account` + /// registered on `marginfi_account` + #[account(mut)] + pub destination_account: Box>, + + pub token_program: Interface<'info, TokenInterface>, +} diff --git a/programs/marginfi/src/instructions/marginfi_account/flashloan.rs b/programs/marginfi/src/instructions/marginfi_account/flashloan.rs index 6d0dff33a..279eed53c 100644 --- a/programs/marginfi/src/instructions/marginfi_account/flashloan.rs +++ b/programs/marginfi/src/instructions/marginfi_account/flashloan.rs @@ -1,15 +1,16 @@ +use crate::{ + check, + prelude::*, + state::marginfi_account::{ + MarginfiAccount, RiskEngine, ACCOUNT_DISABLED, ACCOUNT_IN_FLASHLOAN, + }, +}; use anchor_lang::{prelude::*, Discriminator}; use solana_program::{ instruction::{get_stack_height, TRANSACTION_LEVEL_STACK_HEIGHT}, sysvar::{self, instructions}, }; -use crate::{ - check, - prelude::*, - state::marginfi_account::{MarginfiAccount, RiskEngine, DISABLED_FLAG, IN_FLASHLOAN_FLAG}, -}; - pub fn lending_account_start_flashloan( ctx: Context, end_index: u64, @@ -21,17 +22,21 @@ pub fn lending_account_start_flashloan( )?; let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?; - marginfi_account.set_flag(IN_FLASHLOAN_FLAG); + marginfi_account.set_flag(ACCOUNT_IN_FLASHLOAN); Ok(()) } #[derive(Accounts)] pub struct LendingAccountStartFlashloan<'info> { - #[account(mut)] + #[account( + mut, + has_one = authority + )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account(address = marginfi_account.load()?.authority)] - pub signer: Signer<'info>, + + pub authority: Signer<'info>, + /// CHECK: Instructions sysvar #[account(address = sysvar::instructions::ID)] pub ixs_sysvar: AccountInfo<'info>, @@ -53,13 +58,7 @@ pub fn check_flashloan_can_start( sysvar_ixs: &AccountInfo, end_fl_idx: usize, ) -> MarginfiResult<()> { - // Note: FLASHLOAN_ENABLED_FLAG is now deprecated. - // Any non-disabled account can initiate a flash loan. - check!( - !marginfi_account.load()?.get_flag(DISABLED_FLAG), - MarginfiError::AccountDisabled - ); - + // Note: FLASHLOAN_ENABLED_FLAG is now deprecated, any non-disabled account can initiate a flash loan. let current_ix_idx: usize = instructions::load_current_index_checked(sysvar_ixs)?.into(); check!(current_ix_idx < end_fl_idx, MarginfiError::IllegalFlashloan); @@ -108,12 +107,12 @@ pub fn check_flashloan_can_start( let marginf_account = marginfi_account.load()?; check!( - !marginf_account.get_flag(DISABLED_FLAG), + !marginf_account.get_flag(ACCOUNT_DISABLED), MarginfiError::AccountDisabled ); check!( - !marginf_account.get_flag(IN_FLASHLOAN_FLAG), + !marginf_account.get_flag(ACCOUNT_IN_FLASHLOAN), MarginfiError::IllegalFlashloan ); @@ -131,17 +130,20 @@ pub fn lending_account_end_flashloan<'info>( let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?; - marginfi_account.unset_flag(IN_FLASHLOAN_FLAG); + marginfi_account.unset_flag(ACCOUNT_IN_FLASHLOAN); - RiskEngine::check_account_init_health(&marginfi_account, ctx.remaining_accounts)?; + RiskEngine::check_account_init_health(&marginfi_account, ctx.remaining_accounts, &mut None)?; Ok(()) } #[derive(Accounts)] pub struct LendingAccountEndFlashloan<'info> { - #[account(mut)] + #[account( + mut, + has_one = authority + )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account(address = marginfi_account.load()?.authority)] - pub signer: Signer<'info>, + + pub authority: Signer<'info>, } diff --git a/programs/marginfi/src/instructions/marginfi_account/liquidate.rs b/programs/marginfi/src/instructions/marginfi_account/liquidate.rs index e9832b13c..e1ece932c 100644 --- a/programs/marginfi/src/instructions/marginfi_account/liquidate.rs +++ b/programs/marginfi/src/instructions/marginfi_account/liquidate.rs @@ -2,9 +2,12 @@ use crate::constants::{ INSURANCE_VAULT_SEED, LIQUIDATION_INSURANCE_FEE, LIQUIDATION_LIQUIDATOR_FEE, }; use crate::events::{AccountEventHeader, LendingAccountLiquidateEvent, LiquidationBalances}; -use crate::state::marginfi_account::{calc_amount, calc_value, RiskEngine}; +use crate::state::marginfi_account::{ + calc_amount, calc_value, get_remaining_accounts_per_bank, RiskEngine, +}; use crate::state::marginfi_group::{Bank, BankVaultType}; use crate::state::price::{OraclePriceFeedAdapter, OraclePriceType, PriceAdapter, PriceBias}; +use crate::utils::{validate_asset_tags, validate_bank_asset_tags}; use crate::{ bank_signer, constants::{LIQUIDITY_VAULT_AUTHORITY_SEED, LIQUIDITY_VAULT_SEED}, @@ -79,21 +82,36 @@ pub fn lending_account_liquidate<'info>( mut ctx: Context<'_, '_, 'info, 'info, LendingAccountLiquidate<'info>>, asset_amount: u64, ) -> MarginfiResult { - check!( - asset_amount > 0, - MarginfiError::IllegalLiquidation, - "Asset amount must be positive" - ); + check!(asset_amount > 0, MarginfiError::ZeroLiquidationAmount); check!( ctx.accounts.asset_bank.key() != ctx.accounts.liab_bank.key(), - MarginfiError::IllegalLiquidation, - "Asset and liability bank cannot be the same" + MarginfiError::SameAssetAndLiabilityBanks ); + // Liquidators must repay debts in allowed asset types. A SOL debt can be repaid in any asset. A + // Staked Collateral debt must be repaid in SOL or staked collateral. A Default asset debt can + // be repaid in any Default asset or SOL. + { + let asset_bank = ctx.accounts.asset_bank.load()?; + let liab_bank = ctx.accounts.liab_bank.load()?; + validate_bank_asset_tags(&asset_bank, &liab_bank)?; + + // Sanity check user/liquidator accounts will not contain positions with mismatching tags + // after liquidation. + // * Note: user will be repaid in liab_bank + let user_acc = ctx.accounts.liquidatee_marginfi_account.load()?; + validate_asset_tags(&liab_bank, &user_acc)?; + // * Note: Liquidator repays liab bank, and is paid in asset_bank. + let liquidator_acc = ctx.accounts.liquidator_marginfi_account.load()?; + validate_asset_tags(&liab_bank, &liquidator_acc)?; + validate_asset_tags(&asset_bank, &liquidator_acc)?; + } // release immutable borrow of asset_bank/liab_bank + liquidatee/liquidator user accounts + let LendingAccountLiquidate { liquidator_marginfi_account: liquidator_marginfi_account_loader, liquidatee_marginfi_account: liquidatee_marginfi_account_loader, + group: marginfi_group_loader, .. } = ctx.accounts; @@ -108,27 +126,32 @@ pub fn lending_account_liquidate<'info>( ctx.accounts.token_program.key, )?; { + let group = &*marginfi_group_loader.load()?; ctx.accounts.asset_bank.load_mut()?.accrue_interest( current_timestamp, + group, #[cfg(not(feature = "client"))] ctx.accounts.asset_bank.key(), )?; ctx.accounts.liab_bank.load_mut()?.accrue_interest( current_timestamp, + group, #[cfg(not(feature = "client"))] ctx.accounts.liab_bank.key(), )?; } - let init_liquidatee_remaining_len = liquidatee_marginfi_account.get_remaining_accounts_len(); - let pre_liquidation_health = { - let liquidatee_accounts_starting_pos = - ctx.remaining_accounts.len() - init_liquidatee_remaining_len; - let liquidatee_remaining_accounts = - &ctx.remaining_accounts[liquidatee_accounts_starting_pos..]; + let init_liquidatee_remaining_len = liquidatee_marginfi_account.get_remaining_accounts_len()?; + + let liquidatee_accounts_starting_pos = + ctx.remaining_accounts.len() - init_liquidatee_remaining_len; + let liquidatee_remaining_accounts = &ctx.remaining_accounts[liquidatee_accounts_starting_pos..]; + + let pre_liquidation_health = RiskEngine::new(&liquidatee_marginfi_account, liquidatee_remaining_accounts)? - .check_pre_liquidation_condition_and_get_account_health(&ctx.accounts.liab_bank.key())? - }; + .check_pre_liquidation_condition_and_get_account_health( + &ctx.accounts.liab_bank.key(), + )?; // ##Accounting changes## @@ -136,8 +159,10 @@ pub fn lending_account_liquidate<'info>( let asset_amount = I80F48::from_num(asset_amount); let mut asset_bank = ctx.accounts.asset_bank.load_mut()?; + let asset_bank_remaining_accounts_len = get_remaining_accounts_per_bank(&asset_bank)? - 1; + let asset_price = { - let oracle_ais = &ctx.remaining_accounts[0..1]; + let oracle_ais = &ctx.remaining_accounts[0..asset_bank_remaining_accounts_len]; let asset_pf = OraclePriceFeedAdapter::try_from_bank_config( &asset_bank.config, oracle_ais, @@ -147,8 +172,10 @@ pub fn lending_account_liquidate<'info>( }; let mut liab_bank = ctx.accounts.liab_bank.load_mut()?; + let liab_bank_remaining_accounts_len = get_remaining_accounts_per_bank(&liab_bank)? - 1; let liab_price = { - let oracle_ais = &ctx.remaining_accounts[1..2]; + let oracle_ais = &ctx.remaining_accounts[asset_bank_remaining_accounts_len + ..(asset_bank_remaining_accounts_len + liab_bank_remaining_accounts_len)]; let liab_pf = OraclePriceFeedAdapter::try_from_bank_config( &liab_bank.config, oracle_ais, @@ -232,7 +259,7 @@ pub fn lending_account_liquidate<'info>( bank_account .withdraw(asset_amount) - .map_err(|_| MarginfiError::IllegalLiquidation)?; + .map_err(|_| MarginfiError::OverliquidationAttempt)?; let post_balance = bank_account .bank @@ -340,12 +367,10 @@ pub fn lending_account_liquidate<'info>( // ## Risk checks ## - let liquidatee_accounts_starting_pos = - ctx.remaining_accounts.len() - init_liquidatee_remaining_len; + let liquidator_remaining_acc_len = liquidator_marginfi_account.get_remaining_accounts_len()?; let liquidator_accounts_starting_pos = - liquidatee_accounts_starting_pos - liquidator_marginfi_account.get_remaining_accounts_len(); + liquidatee_accounts_starting_pos - liquidator_remaining_acc_len; - let liquidatee_remaining_accounts = &ctx.remaining_accounts[liquidatee_accounts_starting_pos..]; let liquidator_remaining_accounts = &ctx.remaining_accounts[liquidator_accounts_starting_pos..liquidatee_accounts_starting_pos]; @@ -357,18 +382,21 @@ pub fn lending_account_liquidate<'info>( pre_liquidation_health, )?; + // TODO consider if health cache update here is worth blowing the extra CU + // Verify liquidator account health RiskEngine::check_account_init_health( &liquidator_marginfi_account, liquidator_remaining_accounts, + &mut None, )?; emit!(LendingAccountLiquidateEvent { header: AccountEventHeader { - signer: Some(ctx.accounts.signer.key()), + signer: Some(ctx.accounts.authority.key()), marginfi_account: liquidator_marginfi_account_loader.key(), marginfi_account_authority: liquidator_marginfi_account.authority, - marginfi_group: ctx.accounts.marginfi_group.key(), + marginfi_group: ctx.accounts.group.key(), }, liquidatee_marginfi_account: liquidatee_marginfi_account_loader.key(), liquidatee_marginfi_account_authority: liquidatee_marginfi_account.authority, @@ -387,34 +415,32 @@ pub fn lending_account_liquidate<'info>( #[derive(Accounts)] pub struct LendingAccountLiquidate<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = asset_bank.load()?.group == marginfi_group.key() + has_one = group )] pub asset_bank: AccountLoader<'info, Bank>, #[account( mut, - constraint = liab_bank.load()?.group == marginfi_group.key() + has_one = group )] pub liab_bank: AccountLoader<'info, Bank>, #[account( mut, - constraint = liquidator_marginfi_account.load()?.group == marginfi_group.key() + has_one = group, + has_one = authority )] pub liquidator_marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = liquidator_marginfi_account.load()?.authority - )] - pub signer: Signer<'info>, + pub authority: Signer<'info>, #[account( mut, - constraint = liquidatee_marginfi_account.load()?.group == marginfi_group.key() + has_one = group )] pub liquidatee_marginfi_account: AccountLoader<'info, MarginfiAccount>, diff --git a/programs/marginfi/src/instructions/marginfi_account/mod.rs b/programs/marginfi/src/instructions/marginfi_account/mod.rs index 41b669784..ca17b3f6c 100644 --- a/programs/marginfi/src/instructions/marginfi_account/mod.rs +++ b/programs/marginfi/src/instructions/marginfi_account/mod.rs @@ -6,6 +6,7 @@ mod emissions; mod flashloan; mod initialize; mod liquidate; +mod pulse_health; mod repay; mod transfer_authority; mod withdraw; @@ -18,6 +19,7 @@ pub use emissions::*; pub use flashloan::*; pub use initialize::*; pub use liquidate::*; +pub use pulse_health::*; pub use repay::*; pub use transfer_authority::*; pub use withdraw::*; diff --git a/programs/marginfi/src/instructions/marginfi_account/pulse_health.rs b/programs/marginfi/src/instructions/marginfi_account/pulse_health.rs new file mode 100644 index 000000000..b0b32d614 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_account/pulse_health.rs @@ -0,0 +1,44 @@ +use anchor_lang::prelude::*; +use bytemuck::Zeroable; +use solana_program::{clock::Clock, sysvar::Sysvar}; + +use crate::{ + state::{ + health_cache::HealthCache, + marginfi_account::{MarginfiAccount, RiskEngine}, + }, + MarginfiResult, +}; + +pub fn lending_account_pulse_health<'info>( + ctx: Context<'_, '_, 'info, 'info, PulseHealth<'info>>, +) -> MarginfiResult { + let clock = Clock::get()?; + let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?; + + let mut health_cache = HealthCache::zeroed(); + health_cache.timestamp = clock.unix_timestamp; + + match RiskEngine::check_account_init_health( + &marginfi_account, + ctx.remaining_accounts, + &mut Some(&mut health_cache), + ) { + Ok(()) => { + health_cache.set_engine_ok(true); + } + Err(_) => { + health_cache.set_engine_ok(false); + } + } + + marginfi_account.health_cache = health_cache; + + Ok(()) +} + +#[derive(Accounts)] +pub struct PulseHealth<'info> { + #[account(mut)] + pub marginfi_account: AccountLoader<'info, MarginfiAccount>, +} diff --git a/programs/marginfi/src/instructions/marginfi_account/repay.rs b/programs/marginfi/src/instructions/marginfi_account/repay.rs index 8015ebfa7..648a77cd2 100644 --- a/programs/marginfi/src/instructions/marginfi_account/repay.rs +++ b/programs/marginfi/src/instructions/marginfi_account/repay.rs @@ -1,16 +1,15 @@ use crate::{ check, - constants::LIQUIDITY_VAULT_SEED, events::{AccountEventHeader, LendingAccountRepayEvent}, prelude::{MarginfiError, MarginfiGroup, MarginfiResult}, state::{ - marginfi_account::{BankAccountWrapper, MarginfiAccount, DISABLED_FLAG}, + marginfi_account::{BankAccountWrapper, MarginfiAccount, ACCOUNT_DISABLED}, marginfi_group::Bank, }, utils, }; use anchor_lang::prelude::*; -use anchor_spl::token_interface::TokenInterface; +use anchor_spl::token_interface::{TokenAccount, TokenInterface}; use fixed::types::I80F48; use solana_program::{clock::Clock, sysvar::Sysvar}; @@ -27,11 +26,12 @@ pub fn lending_account_repay<'info>( ) -> MarginfiResult { let LendingAccountRepay { marginfi_account: marginfi_account_loader, - signer, + authority: signer, signer_token_account, - bank_liquidity_vault, + liquidity_vault: bank_liquidity_vault, token_program, bank: bank_loader, + group: marginfi_group_loader, .. } = ctx.accounts; let clock = Clock::get().map_err(|_| MarginfiError::MathError)?; @@ -46,12 +46,13 @@ pub fn lending_account_repay<'info>( let mut marginfi_account = marginfi_account_loader.load_mut()?; check!( - !marginfi_account.get_flag(DISABLED_FLAG), + !marginfi_account.get_flag(ACCOUNT_DISABLED), MarginfiError::AccountDisabled ); bank.accrue_interest( clock.unix_timestamp, + &*marginfi_group_loader.load()?, #[cfg(not(feature = "client"))] bank_loader.key(), )?; @@ -94,7 +95,7 @@ pub fn lending_account_repay<'info>( emit!(LendingAccountRepayEvent { header: AccountEventHeader { - signer: Some(ctx.accounts.signer.key()), + signer: Some(ctx.accounts.authority.key()), marginfi_account: marginfi_account_loader.key(), marginfi_account_authority: marginfi_account.authority, marginfi_group: marginfi_account.group, @@ -110,22 +111,21 @@ pub fn lending_account_repay<'info>( #[derive(Accounts)] pub struct LendingAccountRepay<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = marginfi_account.load()?.group == marginfi_group.key(), + has_one = group, + has_one = authority )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = marginfi_account.load()?.authority, - )] - pub signer: Signer<'info>, + pub authority: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group, + has_one = liquidity_vault )] pub bank: AccountLoader<'info, Bank>, @@ -133,16 +133,8 @@ pub struct LendingAccountRepay<'info> { #[account(mut)] pub signer_token_account: AccountInfo<'info>, - /// CHECK: Seed constraint check - #[account( - mut, - seeds = [ - LIQUIDITY_VAULT_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump = bank.load()?.liquidity_vault_bump, - )] - pub bank_liquidity_vault: AccountInfo<'info>, + #[account(mut)] + pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, pub token_program: Interface<'info, TokenInterface>, } diff --git a/programs/marginfi/src/instructions/marginfi_account/transfer_authority.rs b/programs/marginfi/src/instructions/marginfi_account/transfer_authority.rs index 57e808413..2b92f0370 100644 --- a/programs/marginfi/src/instructions/marginfi_account/transfer_authority.rs +++ b/programs/marginfi/src/instructions/marginfi_account/transfer_authority.rs @@ -18,19 +18,17 @@ pub fn set_account_transfer_authority( #[derive(Accounts)] pub struct MarginfiAccountSetAccountAuthority<'info> { - #[account(mut)] - pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - - /// CHECK: The group is confirmed by the address macro #[account( - address = marginfi_account.load()?.group, + mut, + has_one = authority, + has_one = group )] - pub marginfi_group: AccountInfo<'info>, + pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = marginfi_account.load()?.authority, - )] - pub signer: Signer<'info>, + /// CHECK: Validated against account + pub group: AccountInfo<'info>, + + pub authority: Signer<'info>, /// CHECK: The new account authority doesn't need explicit checks pub new_authority: AccountInfo<'info>, diff --git a/programs/marginfi/src/instructions/marginfi_account/withdraw.rs b/programs/marginfi/src/instructions/marginfi_account/withdraw.rs index a5f50e817..ce1025fc2 100644 --- a/programs/marginfi/src/instructions/marginfi_account/withdraw.rs +++ b/programs/marginfi/src/instructions/marginfi_account/withdraw.rs @@ -1,16 +1,18 @@ use crate::{ bank_signer, check, - constants::{LIQUIDITY_VAULT_AUTHORITY_SEED, LIQUIDITY_VAULT_SEED}, + constants::LIQUIDITY_VAULT_AUTHORITY_SEED, events::{AccountEventHeader, LendingAccountWithdrawEvent}, prelude::*, state::{ - marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, DISABLED_FLAG}, + health_cache::HealthCache, + marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, ACCOUNT_DISABLED}, marginfi_group::{Bank, BankVaultType}, }, utils, }; use anchor_lang::prelude::*; use anchor_spl::token_interface::{TokenAccount, TokenInterface}; +use bytemuck::Zeroable; use fixed::types::I80F48; use solana_program::{clock::Clock, sysvar::Sysvar}; @@ -29,10 +31,11 @@ pub fn lending_account_withdraw<'info>( let LendingAccountWithdraw { marginfi_account: marginfi_account_loader, destination_token_account, - bank_liquidity_vault, + liquidity_vault: bank_liquidity_vault, token_program, bank_liquidity_vault_authority, bank: bank_loader, + group: marginfi_group_loader, .. } = ctx.accounts; let clock = Clock::get().map_err(|_| MarginfiError::MathError)?; @@ -41,7 +44,7 @@ pub fn lending_account_withdraw<'info>( let mut marginfi_account = marginfi_account_loader.load_mut()?; check!( - !marginfi_account.get_flag(DISABLED_FLAG), + !marginfi_account.get_flag(ACCOUNT_DISABLED), MarginfiError::AccountDisabled ); @@ -53,6 +56,7 @@ pub fn lending_account_withdraw<'info>( bank_loader.load_mut()?.accrue_interest( clock.unix_timestamp, + &*marginfi_group_loader.load()?, #[cfg(not(feature = "client"))] bank_loader.key(), )?; @@ -105,7 +109,7 @@ pub fn lending_account_withdraw<'info>( emit!(LendingAccountWithdrawEvent { header: AccountEventHeader { - signer: Some(ctx.accounts.signer.key()), + signer: Some(ctx.accounts.authority.key()), marginfi_account: marginfi_account_loader.key(), marginfi_account_authority: marginfi_account.authority, marginfi_group: marginfi_account.group, @@ -117,31 +121,39 @@ pub fn lending_account_withdraw<'info>( }); } + let mut health_cache = HealthCache::zeroed(); + health_cache.timestamp = clock.unix_timestamp; + // Check account health, if below threshold fail transaction // Assuming `ctx.remaining_accounts` holds only oracle accounts - RiskEngine::check_account_init_health(&marginfi_account, ctx.remaining_accounts)?; + RiskEngine::check_account_init_health( + &marginfi_account, + ctx.remaining_accounts, + &mut Some(&mut health_cache), + )?; + health_cache.set_engine_ok(true); + marginfi_account.health_cache = health_cache; Ok(()) } #[derive(Accounts)] pub struct LendingAccountWithdraw<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = marginfi_account.load()?.group == marginfi_group.key(), + has_one = group, + has_one = authority )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - #[account( - address = marginfi_account.load()?.authority, - )] - pub signer: Signer<'info>, + pub authority: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group, + has_one = liquidity_vault )] pub bank: AccountLoader<'info, Bank>, @@ -159,15 +171,8 @@ pub struct LendingAccountWithdraw<'info> { )] pub bank_liquidity_vault_authority: AccountInfo<'info>, - #[account( - mut, - seeds = [ - LIQUIDITY_VAULT_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump = bank.load()?.liquidity_vault_bump, - )] - pub bank_liquidity_vault: InterfaceAccount<'info, TokenAccount>, + #[account(mut)] + pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, pub token_program: Interface<'info, TokenInterface>, } diff --git a/programs/marginfi/src/instructions/marginfi_group/accrue_bank_interest.rs b/programs/marginfi/src/instructions/marginfi_group/accrue_bank_interest.rs index 9db568366..9803f9b92 100644 --- a/programs/marginfi/src/instructions/marginfi_group/accrue_bank_interest.rs +++ b/programs/marginfi/src/instructions/marginfi_group/accrue_bank_interest.rs @@ -12,6 +12,7 @@ pub fn lending_pool_accrue_bank_interest( bank.accrue_interest( clock.unix_timestamp, + &*ctx.accounts.group.load()?, #[cfg(not(feature = "client"))] ctx.accounts.bank.key(), )?; @@ -21,11 +22,11 @@ pub fn lending_pool_accrue_bank_interest( #[derive(Accounts)] pub struct LendingPoolAccrueBankInterest<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group )] pub bank: AccountLoader<'info, Bank>, } diff --git a/programs/marginfi/src/instructions/marginfi_group/add_pool.rs b/programs/marginfi/src/instructions/marginfi_group/add_pool.rs index 35895a1f2..2839e1e01 100644 --- a/programs/marginfi/src/instructions/marginfi_group/add_pool.rs +++ b/programs/marginfi/src/instructions/marginfi_group/add_pool.rs @@ -1,11 +1,17 @@ use crate::{ + check, constants::{ - FEE_VAULT_AUTHORITY_SEED, FEE_VAULT_SEED, INSURANCE_VAULT_AUTHORITY_SEED, - INSURANCE_VAULT_SEED, LIQUIDITY_VAULT_AUTHORITY_SEED, LIQUIDITY_VAULT_SEED, + ASSET_TAG_STAKED, FEE_STATE_SEED, FEE_VAULT_AUTHORITY_SEED, FEE_VAULT_SEED, + INSURANCE_VAULT_AUTHORITY_SEED, INSURANCE_VAULT_SEED, LIQUIDITY_VAULT_AUTHORITY_SEED, + LIQUIDITY_VAULT_SEED, }, events::{GroupEventHeader, LendingPoolBankCreateEvent}, - state::marginfi_group::{Bank, BankConfig, BankConfigCompact, MarginfiGroup}, - MarginfiResult, + log_pool_info, + state::{ + fee_state::FeeState, + marginfi_group::{Bank, BankConfigCompact, MarginfiGroup}, + }, + MarginfiError, MarginfiResult, }; use anchor_lang::prelude::*; use anchor_spl::token_interface::*; @@ -17,8 +23,18 @@ use anchor_spl::token_interface::*; /// TODO: Allow for different oracle configurations pub fn lending_pool_add_bank( ctx: Context, - bank_config: BankConfig, + bank_config: BankConfigCompact, ) -> MarginfiResult { + // Transfer the flat sol init fee to the global fee wallet + let fee_state = ctx.accounts.fee_state.load()?; + let bank_init_flat_sol_fee = fee_state.bank_init_flat_sol_fee; + if bank_init_flat_sol_fee > 0 { + anchor_lang::system_program::transfer( + ctx.accounts.transfer_flat_fee(), + bank_init_flat_sol_fee as u64, + )?; + } + let LendingPoolAddBank { bank_mint, liquidity_vault, @@ -29,9 +45,13 @@ pub fn lending_pool_add_bank( } = ctx.accounts; let mut bank = bank_loader.load_init()?; + check!( + bank_config.asset_tag != ASSET_TAG_STAKED, + MarginfiError::AddedStakedPoolManually + ); let liquidity_vault_bump = ctx.bumps.liquidity_vault; - let liquidity_vault_authority_bump = ctx.bumps.liquidity_vault_authority; + let liquidity_vault_authority_bump: u8 = ctx.bumps.liquidity_vault_authority; let insurance_vault_bump = ctx.bumps.insurance_vault; let insurance_vault_authority_bump = ctx.bumps.insurance_vault_authority; let fee_vault_bump = ctx.bumps.fee_vault; @@ -39,7 +59,7 @@ pub fn lending_pool_add_bank( *bank = Bank::new( ctx.accounts.marginfi_group.key(), - bank_config, + bank_config.into(), bank_mint.key(), bank_mint.decimals, liquidity_vault.key(), @@ -54,8 +74,13 @@ pub fn lending_pool_add_bank( fee_vault_authority_bump, ); + log_pool_info(&bank); + + let mut group = ctx.accounts.marginfi_group.load_mut()?; + group.add_bank()?; + bank.config.validate()?; - bank.config.validate_oracle_setup(ctx.remaining_accounts)?; + bank.config.validate_oracle_age()?; emit!(LendingPoolBankCreateEvent { header: GroupEventHeader { @@ -69,179 +94,37 @@ pub fn lending_pool_add_bank( Ok(()) } +/* +. Aligns line spacing for easier comparison against with_seed +. +*/ #[derive(Accounts)] #[instruction(bank_config: BankConfigCompact)] pub struct LendingPoolAddBank<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - #[account( mut, - address = marginfi_group.load()?.admin, + has_one = admin )] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + + #[account(mut)] pub admin: Signer<'info>, + /// Pays to init accounts and pays `fee_state.bank_init_flat_sol_fee` lamports to the protocol #[account(mut)] pub fee_payer: Signer<'info>, - pub bank_mint: Box>, - - #[account( - init, - space = 8 + std::mem::size_of::(), - payer = fee_payer, - )] - pub bank: AccountLoader<'info, Bank>, - - /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ - #[account( - seeds = [ - LIQUIDITY_VAULT_AUTHORITY_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump - )] - pub liquidity_vault_authority: AccountInfo<'info>, - - #[account( - init, - payer = fee_payer, - token::mint = bank_mint, - token::authority = liquidity_vault_authority, - seeds = [ - LIQUIDITY_VAULT_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump, - )] - pub liquidity_vault: Box>, - - /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ - #[account( - seeds = [ - INSURANCE_VAULT_AUTHORITY_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump - )] - pub insurance_vault_authority: AccountInfo<'info>, - - #[account( - init, - payer = fee_payer, - token::mint = bank_mint, - token::authority = insurance_vault_authority, - seeds = [ - INSURANCE_VAULT_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump, - )] - pub insurance_vault: Box>, - - /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ - #[account( - seeds = [ - FEE_VAULT_AUTHORITY_SEED.as_bytes(), - bank.key().as_ref(), - ], - bump - )] - pub fee_vault_authority: AccountInfo<'info>, - + // Note: there is just one FeeState per program, so no further check is required. #[account( - init, - payer = fee_payer, - token::mint = bank_mint, - token::authority = fee_vault_authority, - seeds = [ - FEE_VAULT_SEED.as_bytes(), - bank.key().as_ref(), - ], + seeds = [FEE_STATE_SEED.as_bytes()], bump, + has_one = global_fee_wallet )] - pub fee_vault: Box>, - - pub rent: Sysvar<'info, Rent>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, -} - -/// A copy of lending_pool_add_bank but with an additional bank seed provided. -/// This seed is used by the LendingPoolAddBankWithSeed.bank to generate a -/// PDA account to sign for newly added bank transactions securely. -/// The previous lending_pool_add_bank is preserved for backwards-compatibility. -pub fn lending_pool_add_bank_with_seed( - ctx: Context, - bank_config: BankConfig, - _bank_seed: u64, -) -> MarginfiResult { - let LendingPoolAddBankWithSeed { - bank_mint, - liquidity_vault, - insurance_vault, - fee_vault, - bank: bank_loader, - .. - } = ctx.accounts; - - let mut bank = bank_loader.load_init()?; - - let liquidity_vault_bump = ctx.bumps.liquidity_vault; - let liquidity_vault_authority_bump = ctx.bumps.liquidity_vault_authority; - let insurance_vault_bump = ctx.bumps.insurance_vault; - let insurance_vault_authority_bump = ctx.bumps.insurance_vault_authority; - let fee_vault_bump = ctx.bumps.fee_vault; - let fee_vault_authority_bump = ctx.bumps.fee_vault_authority; - - *bank = Bank::new( - ctx.accounts.marginfi_group.key(), - bank_config, - bank_mint.key(), - bank_mint.decimals, - liquidity_vault.key(), - insurance_vault.key(), - fee_vault.key(), - Clock::get().unwrap().unix_timestamp, - liquidity_vault_bump, - liquidity_vault_authority_bump, - insurance_vault_bump, - insurance_vault_authority_bump, - fee_vault_bump, - fee_vault_authority_bump, - ); - - bank.config.validate()?; - bank.config.validate_oracle_setup(ctx.remaining_accounts)?; - - emit!(LendingPoolBankCreateEvent { - header: GroupEventHeader { - marginfi_group: ctx.accounts.marginfi_group.key(), - signer: Some(*ctx.accounts.admin.key) - }, - bank: bank_loader.key(), - mint: bank_mint.key(), - }); - - Ok(()) -} - -/// A copy of LendingPoolAddBank but with an additional bank seed provided. -/// This seed is used by the LendingPoolAddBankWithSeed.bank to generate a -/// PDA account to sign for newly added bank transactions securely. -/// The previous LendingPoolAddBank is preserved for backwards-compatibility. -#[derive(Accounts)] -#[instruction(bank_config: BankConfigCompact, bank_seed: u64)] -pub struct LendingPoolAddBankWithSeed<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - - #[account( - mut, - address = marginfi_group.load()?.admin, - )] - pub admin: Signer<'info>, + pub fee_state: AccountLoader<'info, FeeState>, + /// CHECK: The fee admin's native SOL wallet, validated against fee state #[account(mut)] - pub fee_payer: Signer<'info>, + pub global_fee_wallet: AccountInfo<'info>, pub bank_mint: Box>, @@ -249,12 +132,12 @@ pub struct LendingPoolAddBankWithSeed<'info> { init, space = 8 + std::mem::size_of::(), payer = fee_payer, - seeds = [ - marginfi_group.key().as_ref(), - bank_mint.key().as_ref(), - &bank_seed.to_le_bytes(), - ], - bump, + /* + In the "with seed" version of this ix, the seed is defined here + . + . + . + */ )] pub bank: AccountLoader<'info, Bank>, @@ -331,3 +214,17 @@ pub struct LendingPoolAddBankWithSeed<'info> { pub token_program: Interface<'info, TokenInterface>, pub system_program: Program<'info, System>, } + +impl<'info> LendingPoolAddBank<'info> { + fn transfer_flat_fee( + &self, + ) -> CpiContext<'_, '_, '_, 'info, anchor_lang::system_program::Transfer<'info>> { + CpiContext::new( + self.system_program.to_account_info(), + anchor_lang::system_program::Transfer { + from: self.fee_payer.to_account_info(), + to: self.global_fee_wallet.to_account_info(), + }, + ) + } +} diff --git a/programs/marginfi/src/instructions/marginfi_group/add_pool_common.rs b/programs/marginfi/src/instructions/marginfi_group/add_pool_common.rs new file mode 100644 index 000000000..8f7dd682c --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/add_pool_common.rs @@ -0,0 +1,39 @@ +use crate::state::marginfi_group::Bank; +use anchor_lang::prelude::*; + +/// Echo the information used to create banks to the log output. Useful for at-a-glance debugging +/// bank creation txes in explorer. Note: costs a lot of CU +pub fn log_pool_info(bank: &Bank) { + let conf = bank.config; + let asset_weight_init = u128::from_le_bytes(conf.asset_weight_init.value); + let asset_weight_maint = u128::from_le_bytes(bank.config.asset_weight_maint.value); + msg!( + "Asset weight init: {:?} maint: {:?}", + asset_weight_init, + asset_weight_maint + ); + let liab_weight_init = u128::from_le_bytes(conf.liability_weight_init.value); + let liab_weight_maint = u128::from_le_bytes(conf.liability_weight_maint.value); + msg!( + "Liab weight init: {:?} maint: {:?}", + liab_weight_init, + liab_weight_maint + ); + msg!( + "deposit limit: {:?} borrow limit: {:?} init val limit: {:?}", + conf.deposit_limit, + conf.borrow_limit, + conf.total_asset_value_init_limit + ); + msg!( + "op state: {:?} risk tier: {:?} asset tag: {:?}", + conf.operational_state as u8, + conf.risk_tier as u8, + conf.asset_tag + ); + msg!( + "oracle age: {:?} flags: {:?}", + conf.oracle_max_age as u8, + bank.flags as u8 + ); +} diff --git a/programs/marginfi/src/instructions/marginfi_group/add_pool_permissionless.rs b/programs/marginfi/src/instructions/marginfi_group/add_pool_permissionless.rs new file mode 100644 index 000000000..e361c5ff2 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/add_pool_permissionless.rs @@ -0,0 +1,251 @@ +// Adds a ASSET_TAG_STAKED type bank to a group with sane defaults. Used by validators to add their +// stake pool to a group so users can borrow SOL against it +use crate::{ + check, + constants::{ + ASSET_TAG_STAKED, FEE_VAULT_AUTHORITY_SEED, FEE_VAULT_SEED, INSURANCE_VAULT_AUTHORITY_SEED, + INSURANCE_VAULT_SEED, LIQUIDITY_VAULT_AUTHORITY_SEED, LIQUIDITY_VAULT_SEED, + SPL_SINGLE_POOL_ID, + }, + events::{GroupEventHeader, LendingPoolBankCreateEvent}, + log_pool_info, + state::{ + marginfi_group::{ + Bank, BankConfigCompact, BankOperationalState, InterestRateConfig, MarginfiGroup, + }, + price::OracleSetup, + staked_settings::StakedSettings, + }, + MarginfiError, MarginfiResult, +}; +use anchor_lang::prelude::*; +use anchor_spl::token_interface::*; +use fixed_macro::types::I80F48; + +pub fn lending_pool_add_bank_permissionless( + ctx: Context, + _bank_seed: u64, +) -> MarginfiResult { + let LendingPoolAddBankPermissionless { + bank_mint, + liquidity_vault, + insurance_vault, + fee_vault, + bank: bank_loader, + stake_pool, + sol_pool, + .. + } = ctx.accounts; + + let mut bank = bank_loader.load_init()?; + let settings = ctx.accounts.staked_settings.load()?; + let mut group = ctx.accounts.marginfi_group.load_mut()?; + + let liquidity_vault_bump = ctx.bumps.liquidity_vault; + let liquidity_vault_authority_bump = ctx.bumps.liquidity_vault_authority; + let insurance_vault_bump = ctx.bumps.insurance_vault; + let insurance_vault_authority_bump = ctx.bumps.insurance_vault_authority; + let fee_vault_bump = ctx.bumps.fee_vault; + let fee_vault_authority_bump = ctx.bumps.fee_vault_authority; + + // These are placeholder values: staked collateral positions do not support borrowing and likely + // never will, thus they will earn no interest. + + // Note: Some placeholder values are non-zero to handle downstream validation checks. + let default_ir_config = InterestRateConfig { + optimal_utilization_rate: I80F48!(0.4).into(), + plateau_interest_rate: I80F48!(0.4).into(), + protocol_fixed_fee_apr: I80F48!(0.01).into(), + max_interest_rate: I80F48!(3).into(), + insurance_ir_fee: I80F48!(0.1).into(), + ..Default::default() + }; + + let default_config: BankConfigCompact = BankConfigCompact { + asset_weight_init: settings.asset_weight_init, + asset_weight_maint: settings.asset_weight_maint, + liability_weight_init: I80F48!(1.5).into(), // placeholder + liability_weight_maint: I80F48!(1.25).into(), // placeholder + deposit_limit: settings.deposit_limit, + interest_rate_config: default_ir_config.into(), // placeholder + operational_state: BankOperationalState::Operational, + borrow_limit: 0, + risk_tier: settings.risk_tier, + asset_tag: ASSET_TAG_STAKED, + _pad0: [0; 6], + total_asset_value_init_limit: settings.total_asset_value_init_limit, + oracle_max_age: settings.oracle_max_age, + }; + + *bank = Bank::new( + ctx.accounts.marginfi_group.key(), + default_config.into(), + bank_mint.key(), + bank_mint.decimals, + liquidity_vault.key(), + insurance_vault.key(), + fee_vault.key(), + Clock::get().unwrap().unix_timestamp, + liquidity_vault_bump, + liquidity_vault_authority_bump, + insurance_vault_bump, + insurance_vault_authority_bump, + fee_vault_bump, + fee_vault_authority_bump, + ); + bank.config.oracle_setup = OracleSetup::StakedWithPythPush; + bank.config.oracle_keys[0] = settings.oracle; + + log_pool_info(&bank); + + group.add_bank()?; + + bank.config.validate()?; + + check!( + stake_pool.owner == &SPL_SINGLE_POOL_ID, + MarginfiError::StakePoolValidationFailed + ); + let lst_mint = bank_mint.key(); + let stake_pool = stake_pool.key(); + let sol_pool = sol_pool.key(); + // The mint (for supply) and stake pool (for sol balance) are recorded for price calculation + bank.config.oracle_keys[1] = lst_mint; + bank.config.oracle_keys[2] = sol_pool; + bank.config.validate_oracle_setup( + ctx.remaining_accounts, + Some(lst_mint), + Some(stake_pool), + Some(sol_pool), + )?; + bank.config.validate_oracle_age()?; + + emit!(LendingPoolBankCreateEvent { + header: GroupEventHeader { + marginfi_group: ctx.accounts.marginfi_group.key(), + signer: Some(group.admin) + }, + bank: bank_loader.key(), + mint: bank_mint.key(), + }); + + Ok(()) +} + +#[derive(Accounts)] +#[instruction(bank_seed: u64)] +pub struct LendingPoolAddBankPermissionless<'info> { + #[account(mut)] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + + #[account( + has_one = marginfi_group + )] + pub staked_settings: AccountLoader<'info, StakedSettings>, + + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// Mint of the spl-single-pool LST (a PDA derived from `stake_pool`) + /// + /// CHECK: passing a mint here that is not actually a staked collateral LST is not possible + /// because the sol_pool and stake_pool will not derive to a valid PDA which is also owned by + /// the staking program and spl-single-pool program. + pub bank_mint: Box>, + + /// CHECK: Validated using `stake_pool` + pub sol_pool: AccountInfo<'info>, + + /// CHECK: We validate this is correct backwards, by deriving the PDA of the `bank_mint` using + /// this key. + /// + /// If derives the same `bank_mint`, then this must be the correct stake pool for that mint, and + /// we can subsequently use it to validate the `sol_pool` + pub stake_pool: AccountInfo<'info>, + + #[account( + init, + space = 8 + std::mem::size_of::(), + payer = fee_payer, + seeds = [ + marginfi_group.key().as_ref(), + bank_mint.key().as_ref(), + &bank_seed.to_le_bytes(), + ], + bump, + )] + pub bank: AccountLoader<'info, Bank>, + + /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ + #[account( + seeds = [ + LIQUIDITY_VAULT_AUTHORITY_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump + )] + pub liquidity_vault_authority: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + token::mint = bank_mint, + token::authority = liquidity_vault_authority, + seeds = [ + LIQUIDITY_VAULT_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump, + )] + pub liquidity_vault: Box>, + + /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ + #[account( + seeds = [ + INSURANCE_VAULT_AUTHORITY_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump + )] + pub insurance_vault_authority: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + token::mint = bank_mint, + token::authority = insurance_vault_authority, + seeds = [ + INSURANCE_VAULT_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump, + )] + pub insurance_vault: Box>, + + /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ + #[account( + seeds = [ + FEE_VAULT_AUTHORITY_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump + )] + pub fee_vault_authority: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + token::mint = bank_mint, + token::authority = fee_vault_authority, + seeds = [ + FEE_VAULT_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump, + )] + pub fee_vault: Box>, + + pub rent: Sysvar<'info, Rent>, + pub token_program: Interface<'info, TokenInterface>, + pub system_program: Program<'info, System>, +} diff --git a/programs/marginfi/src/instructions/marginfi_group/add_pool_with_seed.rs b/programs/marginfi/src/instructions/marginfi_group/add_pool_with_seed.rs new file mode 100644 index 000000000..b3bf93e38 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/add_pool_with_seed.rs @@ -0,0 +1,230 @@ +use crate::{ + check, + constants::{ + ASSET_TAG_STAKED, FEE_STATE_SEED, FEE_VAULT_AUTHORITY_SEED, FEE_VAULT_SEED, + INSURANCE_VAULT_AUTHORITY_SEED, INSURANCE_VAULT_SEED, LIQUIDITY_VAULT_AUTHORITY_SEED, + LIQUIDITY_VAULT_SEED, + }, + events::{GroupEventHeader, LendingPoolBankCreateEvent}, + log_pool_info, + state::{ + fee_state::FeeState, + marginfi_group::{Bank, BankConfigCompact, MarginfiGroup}, + }, + MarginfiError, MarginfiResult, +}; +use anchor_lang::prelude::*; +use anchor_spl::token_interface::*; + +/// A copy of lending_pool_add_bank but with an additional bank seed provided. +/// This seed is used by the LendingPoolAddBankWithSeed.bank to generate a +/// PDA account to sign for newly added bank transactions securely. +/// The previous lending_pool_add_bank is preserved for backwards-compatibility. +pub fn lending_pool_add_bank_with_seed( + ctx: Context, + bank_config: BankConfigCompact, + _bank_seed: u64, +) -> MarginfiResult { + // Transfer the flat sol init fee to the global fee wallet + let fee_state = ctx.accounts.fee_state.load()?; + let bank_init_flat_sol_fee = fee_state.bank_init_flat_sol_fee; + if bank_init_flat_sol_fee > 0 { + anchor_lang::system_program::transfer( + ctx.accounts.transfer_flat_fee(), + bank_init_flat_sol_fee as u64, + )?; + } + + let LendingPoolAddBankWithSeed { + bank_mint, + liquidity_vault, + insurance_vault, + fee_vault, + bank: bank_loader, + .. + } = ctx.accounts; + + let mut bank = bank_loader.load_init()?; + check!( + bank_config.asset_tag != ASSET_TAG_STAKED, + MarginfiError::AddedStakedPoolManually + ); + + let liquidity_vault_bump = ctx.bumps.liquidity_vault; + let liquidity_vault_authority_bump = ctx.bumps.liquidity_vault_authority; + let insurance_vault_bump = ctx.bumps.insurance_vault; + let insurance_vault_authority_bump = ctx.bumps.insurance_vault_authority; + let fee_vault_bump = ctx.bumps.fee_vault; + let fee_vault_authority_bump = ctx.bumps.fee_vault_authority; + + *bank = Bank::new( + ctx.accounts.marginfi_group.key(), + bank_config.into(), + bank_mint.key(), + bank_mint.decimals, + liquidity_vault.key(), + insurance_vault.key(), + fee_vault.key(), + Clock::get().unwrap().unix_timestamp, + liquidity_vault_bump, + liquidity_vault_authority_bump, + insurance_vault_bump, + insurance_vault_authority_bump, + fee_vault_bump, + fee_vault_authority_bump, + ); + + log_pool_info(&bank); + + let mut group = ctx.accounts.marginfi_group.load_mut()?; + group.add_bank()?; + + bank.config.validate()?; + bank.config.validate_oracle_age()?; + + emit!(LendingPoolBankCreateEvent { + header: GroupEventHeader { + marginfi_group: ctx.accounts.marginfi_group.key(), + signer: Some(*ctx.accounts.admin.key) + }, + bank: bank_loader.key(), + mint: bank_mint.key(), + }); + + Ok(()) +} + +/// A copy of LendingPoolAddBank but with an additional bank seed provided. +/// This seed is used by the LendingPoolAddBankWithSeed.bank to generate a +/// PDA account to sign for newly added bank transactions securely. +/// The previous LendingPoolAddBank is preserved for backwards-compatibility. +#[derive(Accounts)] +#[instruction(bank_config: BankConfigCompact, bank_seed: u64)] +pub struct LendingPoolAddBankWithSeed<'info> { + #[account( + mut, + has_one = admin + )] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + + #[account(mut)] + pub admin: Signer<'info>, + + /// Pays to init accounts and pays `fee_state.bank_init_flat_sol_fee` lamports to the protocol + #[account(mut)] + pub fee_payer: Signer<'info>, + + // Note: there is just one FeeState per program, so no further check is required. + #[account( + seeds = [FEE_STATE_SEED.as_bytes()], + bump, + has_one = global_fee_wallet + )] + pub fee_state: AccountLoader<'info, FeeState>, + + /// CHECK: The fee admin's native SOL wallet, validated against fee state + #[account(mut)] + pub global_fee_wallet: AccountInfo<'info>, + + pub bank_mint: Box>, + + #[account( + init, + space = 8 + std::mem::size_of::(), + payer = fee_payer, + seeds = [ + marginfi_group.key().as_ref(), + bank_mint.key().as_ref(), + &bank_seed.to_le_bytes(), + ], + bump, + )] + pub bank: AccountLoader<'info, Bank>, + + /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ + #[account( + seeds = [ + LIQUIDITY_VAULT_AUTHORITY_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump + )] + pub liquidity_vault_authority: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + token::mint = bank_mint, + token::authority = liquidity_vault_authority, + seeds = [ + LIQUIDITY_VAULT_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump, + )] + pub liquidity_vault: Box>, + + /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ + #[account( + seeds = [ + INSURANCE_VAULT_AUTHORITY_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump + )] + pub insurance_vault_authority: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + token::mint = bank_mint, + token::authority = insurance_vault_authority, + seeds = [ + INSURANCE_VAULT_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump, + )] + pub insurance_vault: Box>, + + /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ + #[account( + seeds = [ + FEE_VAULT_AUTHORITY_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump + )] + pub fee_vault_authority: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + token::mint = bank_mint, + token::authority = fee_vault_authority, + seeds = [ + FEE_VAULT_SEED.as_bytes(), + bank.key().as_ref(), + ], + bump, + )] + pub fee_vault: Box>, + + pub rent: Sysvar<'info, Rent>, + pub token_program: Interface<'info, TokenInterface>, + pub system_program: Program<'info, System>, +} + +impl<'info> LendingPoolAddBankWithSeed<'info> { + fn transfer_flat_fee( + &self, + ) -> CpiContext<'_, '_, '_, 'info, anchor_lang::system_program::Transfer<'info>> { + CpiContext::new( + self.system_program.to_account_info(), + anchor_lang::system_program::Transfer { + from: self.fee_payer.to_account_info(), + to: self.global_fee_wallet.to_account_info(), + }, + ) + } +} diff --git a/programs/marginfi/src/instructions/marginfi_group/collect_bank_fees.rs b/programs/marginfi/src/instructions/marginfi_group/collect_bank_fees.rs index aec5bb29a..2c50832d9 100644 --- a/programs/marginfi/src/instructions/marginfi_group/collect_bank_fees.rs +++ b/programs/marginfi/src/instructions/marginfi_group/collect_bank_fees.rs @@ -1,6 +1,6 @@ -use crate::constants::{FEE_VAULT_AUTHORITY_SEED, INSURANCE_VAULT_AUTHORITY_SEED}; +use crate::constants::{FEE_STATE_SEED, FEE_VAULT_AUTHORITY_SEED, INSURANCE_VAULT_AUTHORITY_SEED}; use crate::events::{GroupEventHeader, LendingPoolBankCollectFeesEvent}; -use crate::utils; +use crate::state::fee_state::FeeState; use crate::{ bank_signer, constants::{ @@ -10,7 +10,9 @@ use crate::{ state::marginfi_group::{Bank, BankVaultType, MarginfiGroup}, MarginfiResult, }; +use crate::{check, utils, MarginfiError}; use anchor_lang::prelude::*; +use anchor_spl::associated_token::get_associated_token_address_with_program_id; use anchor_spl::token_interface::{TokenAccount, TokenInterface}; use fixed::types::I80F48; use std::cmp::min; @@ -18,16 +20,32 @@ use std::cmp::min; pub fn lending_pool_collect_bank_fees<'info>( mut ctx: Context<'_, '_, 'info, 'info, LendingPoolCollectBankFees<'info>>, ) -> MarginfiResult { + let mut bank = ctx.accounts.bank.load_mut()?; + + // Validate the program fee ata is correct + { + let mint = &bank.mint; + let global_fee_wallet = &ctx.accounts.fee_state.load()?.global_fee_wallet; + let token_program_id = &ctx.accounts.token_program.key(); + let program_fee_ata = &ctx.accounts.fee_ata.key(); + let ata_expected = + get_associated_token_address_with_program_id(global_fee_wallet, mint, token_program_id); + check!( + program_fee_ata.eq(&ata_expected), + MarginfiError::InvalidFeeAta + ); + } + let LendingPoolCollectBankFees { liquidity_vault_authority, insurance_vault, fee_vault, token_program, liquidity_vault, + fee_ata, .. } = ctx.accounts; - let mut bank = ctx.accounts.bank.load_mut()?; let maybe_bank_mint = utils::maybe_take_bank_mint(&mut ctx.remaining_accounts, &bank, token_program.key)?; @@ -105,9 +123,47 @@ pub fn lending_pool_collect_bank_fees<'info>( ctx.remaining_accounts, )?; + // Transfer the program fee + let (program_fee_transfer_amount, new_outstanding_program_fees) = { + let outstanding = I80F48::from(bank.collected_program_fees_outstanding); + let transfer_amount = min(outstanding, available_liquidity).int(); + + ( + transfer_amount.int(), + outstanding + .checked_sub(transfer_amount) + .ok_or_else(math_error!())?, + ) + }; + + available_liquidity = available_liquidity + .checked_sub(program_fee_transfer_amount) + .ok_or_else(math_error!())?; + + assert!(available_liquidity >= I80F48::ZERO); + + bank.collected_program_fees_outstanding = new_outstanding_program_fees.into(); + + bank.withdraw_spl_transfer( + program_fee_transfer_amount + .checked_to_num() + .ok_or_else(math_error!())?, + liquidity_vault.to_account_info(), + fee_ata.to_account_info(), + liquidity_vault_authority.to_account_info(), + maybe_bank_mint.as_ref(), + token_program.to_account_info(), + bank_signer!( + BankVaultType::Liquidity, + ctx.accounts.bank.key(), + bank.liquidity_vault_authority_bump + ), + ctx.remaining_accounts, + )?; + emit!(LendingPoolBankCollectFeesEvent { header: GroupEventHeader { - marginfi_group: ctx.accounts.marginfi_group.key(), + marginfi_group: ctx.accounts.group.key(), signer: None }, bank: ctx.accounts.bank.key(), @@ -123,11 +179,11 @@ pub fn lending_pool_collect_bank_fees<'info>( #[derive(Accounts)] pub struct LendingPoolCollectBankFees<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group )] pub bank: AccountLoader<'info, Bank>, @@ -174,6 +230,19 @@ pub struct LendingPoolCollectBankFees<'info> { )] pub fee_vault: AccountInfo<'info>, + // Note: there is just one FeeState per program, so no further check is required. + #[account( + seeds = [FEE_STATE_SEED.as_bytes()], + bump, + )] + pub fee_state: AccountLoader<'info, FeeState>, + + /// CHECK: Cannonical ATA of the `FeeState.global_fee_wallet` for the mint used by this bank + /// (validated in handler). Must already exist, may require initializing the ATA if it does not + /// already exist prior to this ix. + #[account(mut)] + pub fee_ata: InterfaceAccount<'info, TokenAccount>, + pub token_program: Interface<'info, TokenInterface>, } @@ -214,16 +283,16 @@ pub fn lending_pool_withdraw_fees<'info>( #[derive(Accounts)] pub struct LendingPoolWithdrawFees<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - #[account( - constraint = bank.load()?.group == marginfi_group.key(), + has_one = admin )] - pub bank: AccountLoader<'info, Bank>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( - address = marginfi_group.load()?.admin, + has_one = group )] + pub bank: AccountLoader<'info, Bank>, + pub admin: Signer<'info>, /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ @@ -291,16 +360,16 @@ pub fn lending_pool_withdraw_insurance<'info>( #[derive(Accounts)] pub struct LendingPoolWithdrawInsurance<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - #[account( - constraint = bank.load()?.group == marginfi_group.key(), + has_one = admin )] - pub bank: AccountLoader<'info, Bank>, + pub group: AccountLoader<'info, MarginfiGroup>, #[account( - address = marginfi_group.load()?.admin, + has_one = group )] + pub bank: AccountLoader<'info, Bank>, + pub admin: Signer<'info>, /// CHECK: ⋐ ͡⋄ ω ͡⋄ ⋑ diff --git a/programs/marginfi/src/instructions/marginfi_group/config_bank_oracle.rs b/programs/marginfi/src/instructions/marginfi_group/config_bank_oracle.rs new file mode 100644 index 000000000..9237d8811 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/config_bank_oracle.rs @@ -0,0 +1,64 @@ +use crate::constants::FREEZE_SETTINGS; +use crate::events::{GroupEventHeader, LendingPoolBankConfigureOracleEvent}; +use crate::state::price::OracleSetup; +use crate::{ + state::marginfi_group::{Bank, MarginfiGroup}, + MarginfiResult, +}; +use anchor_lang::prelude::*; + +pub fn lending_pool_configure_bank_oracle( + ctx: Context, + setup: u8, + oracle: Pubkey, +) -> MarginfiResult { + let mut bank = ctx.accounts.bank.load_mut()?; + + // If settings are frozen, you can only update the deposit and borrow limits, so this ix will fail + if bank.get_flag(FREEZE_SETTINGS) { + panic!("cannot change oracle settings on frozen banks"); + } else { + let setup_type = + OracleSetup::from_u8(setup).unwrap_or_else(|| panic!("unsupported oracle type")); + + bank.config.oracle_setup = setup_type; + bank.config.oracle_keys[0] = oracle; + + msg!( + "setting oracle to type: {:?} key: {:?}", + bank.config.oracle_setup, + bank.config.oracle_keys[0] + ); + + bank.config + .validate_oracle_setup(ctx.remaining_accounts, None, None, None)?; + + emit!(LendingPoolBankConfigureOracleEvent { + header: GroupEventHeader { + marginfi_group: ctx.accounts.group.key(), + signer: Some(*ctx.accounts.admin.key) + }, + bank: ctx.accounts.bank.key(), + oracle_setup: setup, + oracle + }); + } + + Ok(()) +} + +#[derive(Accounts)] +pub struct LendingPoolConfigureBankOracle<'info> { + #[account( + has_one = admin + )] + pub group: AccountLoader<'info, MarginfiGroup>, + + pub admin: Signer<'info>, + + #[account( + mut, + has_one = group, + )] + pub bank: AccountLoader<'info, Bank>, +} diff --git a/programs/marginfi/src/instructions/marginfi_group/config_group_fee.rs b/programs/marginfi/src/instructions/marginfi_group/config_group_fee.rs new file mode 100644 index 000000000..a0570acc2 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/config_group_fee.rs @@ -0,0 +1,34 @@ +use crate::{constants::FEE_STATE_SEED, state::fee_state::FeeState, MarginfiGroup, MarginfiResult}; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct ConfigGroupFee<'info> { + #[account(mut)] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + + /// `global_fee_admin` of the FeeState + pub global_fee_admin: Signer<'info>, + + // Note: there is just one FeeState per program, so no further check is required. + #[account( + seeds = [FEE_STATE_SEED.as_bytes()], + bump, + has_one = global_fee_admin + )] + pub fee_state: AccountLoader<'info, FeeState>, +} + +pub fn config_group_fee(ctx: Context, enable_program_fee: bool) -> MarginfiResult { + let mut marginfi_group = ctx.accounts.marginfi_group.load_mut()?; + let flag_before = marginfi_group.group_flags; + + marginfi_group.set_program_fee_enabled(enable_program_fee); + + msg!( + "flag set to: {:?} was {:?}", + marginfi_group.group_flags, + flag_before + ); + + Ok(()) +} diff --git a/programs/marginfi/src/instructions/marginfi_group/configure.rs b/programs/marginfi/src/instructions/marginfi_group/configure.rs index e4f6e2b94..f45f98d0f 100644 --- a/programs/marginfi/src/instructions/marginfi_group/configure.rs +++ b/programs/marginfi/src/instructions/marginfi_group/configure.rs @@ -1,29 +1,35 @@ use crate::check; use crate::events::{GroupEventHeader, MarginfiGroupConfigureEvent}; use crate::prelude::MarginfiError; -use crate::state::marginfi_account::{ - MarginfiAccount, FLASHLOAN_ENABLED_FLAG, TRANSFER_AUTHORITY_ALLOWED_FLAG, -}; -use crate::{ - state::marginfi_group::{GroupConfig, MarginfiGroup}, - MarginfiResult, -}; +use crate::state::marginfi_account::{MarginfiAccount, ACCOUNT_TRANSFER_AUTHORITY_ALLOWED}; +use crate::{state::marginfi_group::MarginfiGroup, MarginfiResult}; use anchor_lang::prelude::*; -/// Configure margin group +/// Configure margin group. +/// +/// Note: not even the group admin can configure `PROGRAM_FEES_ENABLED`, only the program admin can +/// with `configure_group_fee` /// /// Admin only -pub fn configure(ctx: Context, config: GroupConfig) -> MarginfiResult { +pub fn configure( + ctx: Context, + new_admin: Pubkey, + is_arena_group: bool, +) -> MarginfiResult { let marginfi_group = &mut ctx.accounts.marginfi_group.load_mut()?; - marginfi_group.configure(&config)?; + marginfi_group.update_admin(new_admin); + marginfi_group.set_arena_group(is_arena_group)?; + + msg!("flags set to: {:?}", marginfi_group.group_flags); emit!(MarginfiGroupConfigureEvent { header: GroupEventHeader { marginfi_group: ctx.accounts.marginfi_group.key(), signer: Some(*ctx.accounts.admin.key) }, - config, + admin: new_admin, + flags: marginfi_group.group_flags }); Ok(()) @@ -31,12 +37,12 @@ pub fn configure(ctx: Context, config: GroupConfig) -> M #[derive(Accounts)] pub struct MarginfiGroupConfigure<'info> { - #[account(mut)] - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - #[account( - address = marginfi_group.load()?.admin, + mut, + has_one = admin )] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub admin: Signer<'info>, } @@ -50,7 +56,7 @@ pub struct MarginfiGroupConfigure<'info> { /// 0b1000 is a valid flag /// 0b01100 is a valid flag /// 0b0101 is not a valid flag -const CONFIGURABLE_FLAGS: u64 = FLASHLOAN_ENABLED_FLAG + TRANSFER_AUTHORITY_ALLOWED_FLAG; +const CONFIGURABLE_FLAGS: u64 = ACCOUNT_TRANSFER_AUTHORITY_ALLOWED; fn flag_can_be_set(flag: u64) -> bool { // If bitwise AND operation between flag and its bitwise NOT of CONFIGURABLE_FLAGS is 0, @@ -70,14 +76,17 @@ pub fn set_account_flag(ctx: Context, flag: u64) -> MarginfiResu #[derive(Accounts)] pub struct SetAccountFlag<'info> { - #[account(address = marginfi_account.load()?.group)] - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + #[account( + has_one = admin + )] + pub group: AccountLoader<'info, MarginfiGroup>, - #[account(mut)] + #[account( + mut, + has_one = group + )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - /// Admin only - #[account(address = marginfi_group.load()?.admin)] pub admin: Signer<'info>, } @@ -93,50 +102,51 @@ pub fn unset_account_flag(ctx: Context, flag: u64) -> Marginfi #[derive(Accounts)] pub struct UnsetAccountFlag<'info> { - #[account(address = marginfi_account.load()?.group)] - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + #[account( + has_one = admin + )] + pub group: AccountLoader<'info, MarginfiGroup>, - #[account(mut)] + #[account( + mut, + has_one = group + )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, - /// Admin only - #[account(address = marginfi_group.load()?.admin)] pub admin: Signer<'info>, } #[cfg(test)] mod tests { use crate::state::marginfi_account::{ - DISABLED_FLAG, FLASHLOAN_ENABLED_FLAG, IN_FLASHLOAN_FLAG, TRANSFER_AUTHORITY_ALLOWED_FLAG, + ACCOUNT_DISABLED, ACCOUNT_FLAG_DEPRECATED, ACCOUNT_IN_FLASHLOAN, + ACCOUNT_TRANSFER_AUTHORITY_ALLOWED, }; #[test] - /// - /// 0b0001 is a valid flag - /// 0b0011 is a invalid flag - /// 0b0101 is a invalid flag - /// 0b1000 is a invalid flag fn test_check_flag() { - let flag1 = FLASHLOAN_ENABLED_FLAG; - let flag2 = FLASHLOAN_ENABLED_FLAG + IN_FLASHLOAN_FLAG; - let flag3 = IN_FLASHLOAN_FLAG + DISABLED_FLAG + FLASHLOAN_ENABLED_FLAG; - let flag4 = DISABLED_FLAG + IN_FLASHLOAN_FLAG; - let flag5 = FLASHLOAN_ENABLED_FLAG + TRANSFER_AUTHORITY_ALLOWED_FLAG; - let flag6 = DISABLED_FLAG + FLASHLOAN_ENABLED_FLAG + TRANSFER_AUTHORITY_ALLOWED_FLAG; - let flag7 = DISABLED_FLAG - + FLASHLOAN_ENABLED_FLAG - + IN_FLASHLOAN_FLAG - + TRANSFER_AUTHORITY_ALLOWED_FLAG; + let flag1 = ACCOUNT_FLAG_DEPRECATED; + let flag2 = ACCOUNT_FLAG_DEPRECATED + ACCOUNT_IN_FLASHLOAN; + let flag3 = ACCOUNT_IN_FLASHLOAN + ACCOUNT_DISABLED + ACCOUNT_FLAG_DEPRECATED; + let flag4 = ACCOUNT_DISABLED + ACCOUNT_IN_FLASHLOAN; + let flag5 = ACCOUNT_FLAG_DEPRECATED + ACCOUNT_TRANSFER_AUTHORITY_ALLOWED; + let flag6 = ACCOUNT_DISABLED + ACCOUNT_FLAG_DEPRECATED + ACCOUNT_TRANSFER_AUTHORITY_ALLOWED; + let flag7 = ACCOUNT_DISABLED + + ACCOUNT_FLAG_DEPRECATED + + ACCOUNT_IN_FLASHLOAN + + ACCOUNT_TRANSFER_AUTHORITY_ALLOWED; + let flag8 = ACCOUNT_TRANSFER_AUTHORITY_ALLOWED; // Malformed flags should fail + assert!(!super::flag_can_be_set(flag1)); assert!(!super::flag_can_be_set(flag2)); assert!(!super::flag_can_be_set(flag3)); assert!(!super::flag_can_be_set(flag4)); + assert!(!super::flag_can_be_set(flag5)); assert!(!super::flag_can_be_set(flag6)); assert!(!super::flag_can_be_set(flag7)); // Good flags should succeed - assert!(super::flag_can_be_set(flag1)); - assert!(super::flag_can_be_set(flag5)); + assert!(super::flag_can_be_set(flag8)); } } diff --git a/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs b/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs index 92a834409..5f8d94a6e 100644 --- a/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs +++ b/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs @@ -1,5 +1,7 @@ -use crate::constants::{EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED}; -use crate::events::{GroupEventHeader, LendingPoolBankConfigureEvent}; +use crate::constants::{EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED, FREEZE_SETTINGS}; +use crate::events::{ + GroupEventHeader, LendingPoolBankConfigureEvent, LendingPoolBankConfigureFrozenEvent, +}; use crate::prelude::MarginfiError; use crate::{check, math_error, utils}; use crate::{ @@ -17,37 +19,57 @@ pub fn lending_pool_configure_bank( ) -> MarginfiResult { let mut bank = ctx.accounts.bank.load_mut()?; - bank.configure(&bank_config)?; + // If settings are frozen, you can only update the deposit and borrow limits, everything else is ignored. + if bank.get_flag(FREEZE_SETTINGS) { + bank.configure_unfrozen_fields_only(&bank_config)?; - if bank_config.oracle.is_some() { - bank.config.validate_oracle_setup(ctx.remaining_accounts)?; - } + msg!("WARN: Only deposit+borrow limits updated. Other settings IGNORED for frozen banks!"); - emit!(LendingPoolBankConfigureEvent { - header: GroupEventHeader { - marginfi_group: ctx.accounts.marginfi_group.key(), - signer: Some(*ctx.accounts.admin.key) - }, - bank: ctx.accounts.bank.key(), - mint: bank.mint, - config: bank_config, - }); + emit!(LendingPoolBankConfigureFrozenEvent { + header: GroupEventHeader { + marginfi_group: ctx.accounts.group.key(), + signer: Some(*ctx.accounts.admin.key) + }, + bank: ctx.accounts.bank.key(), + mint: bank.mint, + deposit_limit: bank.config.deposit_limit, + borrow_limit: bank.config.borrow_limit, + }); + } else { + // Settings are not frozen, everything updates + bank.configure(&bank_config)?; + + if bank_config.oracle_max_age.is_some() { + bank.config.validate_oracle_age()?; + } + + emit!(LendingPoolBankConfigureEvent { + header: GroupEventHeader { + marginfi_group: ctx.accounts.group.key(), + signer: Some(*ctx.accounts.admin.key) + }, + bank: ctx.accounts.bank.key(), + mint: bank.mint, + config: bank_config, + }); + } Ok(()) } #[derive(Accounts)] pub struct LendingPoolConfigureBank<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - #[account( - address = marginfi_group.load()?.admin, + mut, + has_one = admin, )] + pub group: AccountLoader<'info, MarginfiGroup>, + pub admin: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group, )] pub bank: AccountLoader<'info, Bank>, } @@ -72,6 +94,14 @@ pub fn lending_pool_setup_emissions( bank.emissions_rate = emissions_rate; bank.emissions_remaining = I80F48::from_num(total_emissions).into(); + msg!("init emissions with mint: {:?}", bank.emissions_mint,); + msg!( + "flags: {:?} rate: {:?} total: {:?}", + emissions_flags, + emissions_rate, + total_emissions + ); + let initial_emissions_amount_pre_fee = utils::calculate_pre_fee_spl_deposit_amount( ctx.accounts.emissions_mint.to_account_info(), total_emissions, @@ -97,22 +127,24 @@ pub fn lending_pool_setup_emissions( #[derive(Accounts)] pub struct LendingPoolSetupEmissions<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - #[account( mut, - address = marginfi_group.load()?.admin, + has_one = admin, )] + pub group: AccountLoader<'info, MarginfiGroup>, + + #[account(mut)] pub admin: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group, )] pub bank: AccountLoader<'info, Bank>, pub emissions_mint: InterfaceAccount<'info, Mint>, + /// CHECK: Asserted by PDA constraints #[account( seeds = [ EMISSIONS_AUTH_SEED.as_bytes(), @@ -121,7 +153,6 @@ pub struct LendingPoolSetupEmissions<'info> { ], bump )] - /// CHECK: Asserted by PDA constraints pub emissions_auth: AccountInfo<'info>, #[account( @@ -138,6 +169,8 @@ pub struct LendingPoolSetupEmissions<'info> { )] pub emissions_token_account: Box>, + /// NOTE: This is a TokenAccount, spl transfer will validate it. + /// /// CHECK: Account provided only for funding rewards #[account(mut)] pub emissions_funding_account: AccountInfo<'info>, @@ -212,17 +245,18 @@ pub fn lending_pool_update_emissions_parameters( #[derive(Accounts)] pub struct LendingPoolUpdateEmissionsParameters<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, - #[account( mut, - address = marginfi_group.load()?.admin, + has_one = admin )] + pub group: AccountLoader<'info, MarginfiGroup>, + + #[account(mut)] pub admin: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group )] pub bank: AccountLoader<'info, Bank>, diff --git a/programs/marginfi/src/instructions/marginfi_group/edit_global_fee.rs b/programs/marginfi/src/instructions/marginfi_group/edit_global_fee.rs new file mode 100644 index 000000000..b214c00e3 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/edit_global_fee.rs @@ -0,0 +1,51 @@ +// Global fee admin calls this to edit the fee rate or the fee wallet. + +use crate::constants::FEE_STATE_SEED; +use crate::state::fee_state; +use crate::state::marginfi_group::WrappedI80F48; +use anchor_lang::prelude::*; +use fee_state::FeeState; + +pub fn edit_fee_state( + ctx: Context, + admin: Pubkey, + fee_wallet: Pubkey, + bank_init_flat_sol_fee: u32, + program_fee_fixed: WrappedI80F48, + program_fee_rate: WrappedI80F48, +) -> Result<()> { + let mut fee_state = ctx.accounts.fee_state.load_mut()?; + fee_state.global_fee_admin = admin; + fee_state.global_fee_wallet = fee_wallet; + fee_state.bank_init_flat_sol_fee = bank_init_flat_sol_fee; + fee_state.program_fee_fixed = program_fee_fixed; + fee_state.program_fee_rate = program_fee_rate; + + let fixed = u128::from_le_bytes(fee_state.program_fee_fixed.value); + let rate = u128::from_le_bytes(fee_state.program_fee_rate.value); + msg!("admin set to: {:?} fee wallet: {:?}", admin, fee_wallet); + msg!( + "flat sol: {:?} fixed: {:?} rate: {:?}", + fee_state.bank_init_flat_sol_fee, + fixed, + rate + ); + + Ok(()) +} + +#[derive(Accounts)] +pub struct EditFeeState<'info> { + /// Admin of the global FeeState + #[account(mut)] + pub global_fee_admin: Signer<'info>, + + // Note: there is just one FeeState per program, so no further check is required. + #[account( + mut, + seeds = [FEE_STATE_SEED.as_bytes()], + bump, + has_one = global_fee_admin + )] + pub fee_state: AccountLoader<'info, FeeState>, +} diff --git a/programs/marginfi/src/instructions/marginfi_group/edit_stake_settings.rs b/programs/marginfi/src/instructions/marginfi_group/edit_stake_settings.rs new file mode 100644 index 000000000..e67609b95 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/edit_stake_settings.rs @@ -0,0 +1,75 @@ +use crate::events::EditStakedSettingsEvent; +// Used by the group admin to edit the default features of staked collateral banks. Remember to +// propagate afterwards. +use crate::state::marginfi_group::{RiskTier, WrappedI80F48}; +use crate::state::staked_settings::StakedSettings; +use crate::{set_if_some, MarginfiGroup}; +use anchor_lang::prelude::*; + +pub fn edit_staked_settings( + ctx: Context, + settings: StakedSettingsEditConfig, +) -> Result<()> { + // let group = ctx.accounts.marginfi_group.load()?; + let mut staked_settings = ctx.accounts.staked_settings.load_mut()?; + // require_keys_eq!(group.admin, ctx.accounts.admin.key()); + + set_if_some!(staked_settings.oracle, settings.oracle); + set_if_some!( + staked_settings.asset_weight_init, + settings.asset_weight_init + ); + set_if_some!( + staked_settings.asset_weight_maint, + settings.asset_weight_maint + ); + set_if_some!(staked_settings.deposit_limit, settings.deposit_limit); + set_if_some!( + staked_settings.total_asset_value_init_limit, + settings.total_asset_value_init_limit + ); + set_if_some!(staked_settings.oracle_max_age, settings.oracle_max_age); + set_if_some!(staked_settings.risk_tier, settings.risk_tier); + + staked_settings.validate()?; + + emit!(EditStakedSettingsEvent { + group: ctx.accounts.marginfi_group.key(), + settings + }); + + Ok(()) +} + +#[derive(Accounts)] +pub struct EditStakedSettings<'info> { + #[account( + has_one = admin + )] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + + pub admin: Signer<'info>, + + #[account( + mut, + has_one = marginfi_group + )] + pub staked_settings: AccountLoader<'info, StakedSettings>, +} + +#[derive(AnchorDeserialize, AnchorSerialize, Default)] +pub struct StakedSettingsEditConfig { + pub oracle: Option, + + pub asset_weight_init: Option, + pub asset_weight_maint: Option, + + pub deposit_limit: Option, + pub total_asset_value_init_limit: Option, + + pub oracle_max_age: Option, + /// WARN: You almost certainly want "Collateral", using Isolated risk tier makes the asset + /// worthless as collateral, making all outstanding accounts eligible to be liquidated, and is + /// generally useful only when creating a staked collateral pool for rewards purposes only. + pub risk_tier: Option, +} diff --git a/programs/marginfi/src/instructions/marginfi_group/handle_bankruptcy.rs b/programs/marginfi/src/instructions/marginfi_group/handle_bankruptcy.rs index accdc428c..58e278f56 100644 --- a/programs/marginfi/src/instructions/marginfi_group/handle_bankruptcy.rs +++ b/programs/marginfi/src/instructions/marginfi_group/handle_bankruptcy.rs @@ -9,7 +9,7 @@ use crate::{ math_error, prelude::MarginfiError, state::{ - marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, DISABLED_FLAG}, + marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, ACCOUNT_DISABLED}, marginfi_group::{Bank, BankVaultType, MarginfiGroup}, }, utils, MarginfiResult, @@ -33,7 +33,7 @@ pub fn lending_pool_handle_bankruptcy<'info>( insurance_vault, token_program, bank: bank_loader, - marginfi_group: marginfi_group_loader, + group: marginfi_group_loader, .. } = ctx.accounts; let bank = bank_loader.load()?; @@ -59,6 +59,7 @@ pub fn lending_pool_handle_bankruptcy<'info>( bank.accrue_interest( clock.unix_timestamp, + &*marginfi_group_loader.load()?, #[cfg(not(feature = "client"))] bank_loader.key(), )?; @@ -67,7 +68,7 @@ pub fn lending_pool_handle_bankruptcy<'info>( .lending_account .balances .iter_mut() - .find(|balance| balance.active && balance.bank_pk == bank_loader.key()); + .find(|balance| balance.is_active() && balance.bank_pk == bank_loader.key()); check!( lending_account_balance.is_some(), @@ -153,7 +154,7 @@ pub fn lending_pool_handle_bankruptcy<'info>( )? .repay(bad_debt)?; - marginfi_account.set_flag(DISABLED_FLAG); + marginfi_account.set_flag(ACCOUNT_DISABLED); emit!(LendingPoolBankHandleBankruptcyEvent { header: AccountEventHeader { @@ -174,20 +175,21 @@ pub fn lending_pool_handle_bankruptcy<'info>( #[derive(Accounts)] pub struct LendingPoolHandleBankruptcy<'info> { - pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + pub group: AccountLoader<'info, MarginfiGroup>, - // #[account(address = marginfi_group.load()?.admin)] + /// CHECK: The admin signer constraint is only validated (in handler) if bank + /// PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG is not set pub signer: Signer<'info>, #[account( mut, - constraint = bank.load()?.group == marginfi_group.key(), + has_one = group )] pub bank: AccountLoader<'info, Bank>, #[account( mut, - constraint = marginfi_account.load()?.group == marginfi_group.key(), + has_one = group )] pub marginfi_account: AccountLoader<'info, MarginfiAccount>, diff --git a/programs/marginfi/src/instructions/marginfi_group/init_global_fee_state.rs b/programs/marginfi/src/instructions/marginfi_group/init_global_fee_state.rs new file mode 100644 index 000000000..050830724 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/init_global_fee_state.rs @@ -0,0 +1,48 @@ +// Runs once per program to init the global fee state. +use crate::constants::FEE_STATE_SEED; +use crate::state::fee_state; +use crate::state::marginfi_group::WrappedI80F48; +use anchor_lang::prelude::*; +use fee_state::FeeState; + +#[allow(unused_variables)] +pub fn initialize_fee_state( + ctx: Context, + admin_key: Pubkey, + fee_wallet: Pubkey, + bank_init_flat_sol_fee: u32, + program_fee_fixed: WrappedI80F48, + program_fee_rate: WrappedI80F48, +) -> Result<()> { + let mut fee_state = ctx.accounts.fee_state.load_init()?; + fee_state.global_fee_admin = admin_key; + fee_state.global_fee_wallet = fee_wallet; + fee_state.key = ctx.accounts.fee_state.key(); + fee_state.bank_init_flat_sol_fee = bank_init_flat_sol_fee; + fee_state.bump_seed = ctx.bumps.fee_state; + fee_state.program_fee_fixed = program_fee_fixed; + fee_state.program_fee_rate = program_fee_rate; + + Ok(()) +} + +#[derive(Accounts)] +pub struct InitFeeState<'info> { + /// Pays the init fee + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + seeds = [ + FEE_STATE_SEED.as_bytes() + ], + bump, + payer = payer, + space = 8 + FeeState::LEN, + )] + pub fee_state: AccountLoader<'info, FeeState>, + + pub rent: Sysvar<'info, Rent>, + pub system_program: Program<'info, System>, +} diff --git a/programs/marginfi/src/instructions/marginfi_group/init_staked_settings.rs b/programs/marginfi/src/instructions/marginfi_group/init_staked_settings.rs new file mode 100644 index 000000000..1ff7e182f --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/init_staked_settings.rs @@ -0,0 +1,90 @@ +// Used by the group admin to enable staked collateral banks and configure their default features +use crate::constants::STAKED_SETTINGS_SEED; +use crate::state::marginfi_group::{RiskTier, WrappedI80F48}; +use crate::state::staked_settings::StakedSettings; +use crate::MarginfiGroup; +use anchor_lang::prelude::*; + +pub fn initialize_staked_settings( + ctx: Context, + settings: StakedSettingsConfig, +) -> Result<()> { + let mut staked_settings = ctx.accounts.staked_settings.load_init()?; + + *staked_settings = StakedSettings::new( + ctx.accounts.staked_settings.key(), + ctx.accounts.marginfi_group.key(), + settings.oracle, + settings.asset_weight_init, + settings.asset_weight_maint, + settings.deposit_limit, + settings.total_asset_value_init_limit, + settings.oracle_max_age, + settings.risk_tier, + ); + + msg!( + "oracle: {:?} max age: {:?}", + staked_settings.oracle, + staked_settings.oracle_max_age + ); + let init = u128::from_le_bytes(staked_settings.asset_weight_init.value); + let maint = u128::from_le_bytes(staked_settings.asset_weight_maint.value); + msg!("asset weight init: {:?} maint: {:?}", init, maint); + msg!( + "deposit limit: {:?} value limit: {:?} risk tier: {:?}", + staked_settings.deposit_limit, + staked_settings.total_asset_value_init_limit, + staked_settings.risk_tier as u8 + ); + + staked_settings.validate()?; + + Ok(()) +} + +#[derive(Accounts)] +pub struct InitStakedSettings<'info> { + #[account( + has_one = admin + )] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + + pub admin: Signer<'info>, + + /// Pays the init fee + #[account(mut)] + pub fee_payer: Signer<'info>, + + #[account( + init, + seeds = [ + STAKED_SETTINGS_SEED.as_bytes(), + marginfi_group.key().as_ref() + ], + bump, + payer = fee_payer, + space = 8 + StakedSettings::LEN, + )] + pub staked_settings: AccountLoader<'info, StakedSettings>, + + pub rent: Sysvar<'info, Rent>, + pub system_program: Program<'info, System>, +} + +#[derive(AnchorDeserialize, AnchorSerialize, Default)] +pub struct StakedSettingsConfig { + pub oracle: Pubkey, + + pub asset_weight_init: WrappedI80F48, + pub asset_weight_maint: WrappedI80F48, + + pub deposit_limit: u64, + pub total_asset_value_init_limit: u64, + + pub oracle_max_age: u16, + /// WARN: You almost certainly want "Collateral", using Isolated risk tier makes the asset + /// worthless as collateral, and is generally useful only when creating a staked collateral pool + /// for rewards purposes only. + pub risk_tier: RiskTier, +} diff --git a/programs/marginfi/src/instructions/marginfi_group/initialize.rs b/programs/marginfi/src/instructions/marginfi_group/initialize.rs index 2399e3ff7..c1e329004 100644 --- a/programs/marginfi/src/instructions/marginfi_group/initialize.rs +++ b/programs/marginfi/src/instructions/marginfi_group/initialize.rs @@ -1,11 +1,38 @@ +use crate::constants::FEE_STATE_SEED; use crate::events::{GroupEventHeader, MarginfiGroupCreateEvent}; +use crate::state::fee_state::FeeState; use crate::{state::marginfi_group::MarginfiGroup, MarginfiResult}; use anchor_lang::prelude::*; -pub fn initialize_group(ctx: Context) -> MarginfiResult { +pub fn initialize_group( + ctx: Context, + is_arena_group: bool, +) -> MarginfiResult { let marginfi_group = &mut ctx.accounts.marginfi_group.load_init()?; marginfi_group.set_initial_configuration(ctx.accounts.admin.key()); + marginfi_group.set_arena_group(is_arena_group)?; + + msg!( + "Group admin: {:?} flags: {:?}", + marginfi_group.admin, + marginfi_group.group_flags + ); + + let fee_state = ctx.accounts.fee_state.load()?; + + marginfi_group.fee_state_cache.global_fee_wallet = fee_state.global_fee_wallet; + marginfi_group.fee_state_cache.program_fee_fixed = fee_state.program_fee_fixed; + marginfi_group.fee_state_cache.program_fee_rate = fee_state.program_fee_rate; + marginfi_group.banks = 0; + + let cache = marginfi_group.fee_state_cache; + msg!( + "global fee wallet: {:?}, fixed fee: {:?}, program free {:?}", + cache.global_fee_wallet, + cache.program_fee_fixed, + cache.program_fee_rate + ); emit!(MarginfiGroupCreateEvent { header: GroupEventHeader { @@ -29,5 +56,11 @@ pub struct MarginfiGroupInitialize<'info> { #[account(mut)] pub admin: Signer<'info>, + #[account( + seeds = [FEE_STATE_SEED.as_bytes()], + bump, + )] + pub fee_state: AccountLoader<'info, FeeState>, + pub system_program: Program<'info, System>, } diff --git a/programs/marginfi/src/instructions/marginfi_group/mod.rs b/programs/marginfi/src/instructions/marginfi_group/mod.rs index 33bc6a914..e8c3ef92d 100644 --- a/programs/marginfi/src/instructions/marginfi_group/mod.rs +++ b/programs/marginfi/src/instructions/marginfi_group/mod.rs @@ -1,15 +1,37 @@ mod accrue_bank_interest; mod add_pool; +mod add_pool_common; +mod add_pool_permissionless; +mod add_pool_with_seed; mod collect_bank_fees; +pub mod config_bank_oracle; +mod config_group_fee; mod configure; mod configure_bank; +mod edit_global_fee; +mod edit_stake_settings; mod handle_bankruptcy; +mod init_global_fee_state; +mod init_staked_settings; mod initialize; +mod propagate_fee_state; +mod propagate_staked_settings; pub use accrue_bank_interest::*; pub use add_pool::*; +pub use add_pool_common::*; +pub use add_pool_permissionless::*; +pub use add_pool_with_seed::*; pub use collect_bank_fees::*; +pub use config_bank_oracle::*; +pub use config_group_fee::*; pub use configure::*; pub use configure_bank::*; +pub use edit_global_fee::*; +pub use edit_stake_settings::*; pub use handle_bankruptcy::*; +pub use init_global_fee_state::*; +pub use init_staked_settings::*; pub use initialize::*; +pub use propagate_fee_state::*; +pub use propagate_staked_settings::*; diff --git a/programs/marginfi/src/instructions/marginfi_group/propagate_fee_state.rs b/programs/marginfi/src/instructions/marginfi_group/propagate_fee_state.rs new file mode 100644 index 000000000..3b71e369d --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/propagate_fee_state.rs @@ -0,0 +1,28 @@ +use anchor_lang::prelude::*; + +use crate::{constants::FEE_STATE_SEED, state::fee_state::FeeState, MarginfiGroup}; + +#[derive(Accounts)] +pub struct PropagateFee<'info> { + // Note: there is just one FeeState per program, so no further check is required. + #[account( + seeds = [FEE_STATE_SEED.as_bytes()], + bump, + )] + pub fee_state: AccountLoader<'info, FeeState>, + + /// Any group, this ix is permisionless and can propogate the fee to any group + #[account(mut)] + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, +} + +pub fn propagate_fee(ctx: Context) -> Result<()> { + let mut group = ctx.accounts.marginfi_group.load_mut()?; + let fee_state = ctx.accounts.fee_state.load()?; + + group.fee_state_cache.global_fee_wallet = fee_state.global_fee_wallet; + group.fee_state_cache.program_fee_fixed = fee_state.program_fee_fixed; + group.fee_state_cache.program_fee_rate = fee_state.program_fee_rate; + + Ok(()) +} diff --git a/programs/marginfi/src/instructions/marginfi_group/propagate_staked_settings.rs b/programs/marginfi/src/instructions/marginfi_group/propagate_staked_settings.rs new file mode 100644 index 000000000..ba8d63f40 --- /dev/null +++ b/programs/marginfi/src/instructions/marginfi_group/propagate_staked_settings.rs @@ -0,0 +1,55 @@ +use crate::constants::ASSET_TAG_STAKED; +// Permissionless ix to propagate a group's staked collateral settings to any bank in that group +use crate::state::marginfi_group::Bank; +use crate::state::staked_settings::StakedSettings; +use crate::MarginfiGroup; +use anchor_lang::prelude::*; + +pub fn propagate_staked_settings(ctx: Context) -> Result<()> { + let settings = ctx.accounts.staked_settings.load()?; + let mut bank = ctx.accounts.bank.load_mut()?; + + let (oracle_before, oracle_after) = (bank.config.oracle_keys[0], settings.oracle); + let (age_before, age_after) = (bank.config.oracle_max_age, settings.oracle_max_age); + + bank.config.oracle_keys[0] = settings.oracle; + bank.config.asset_weight_init = settings.asset_weight_init; + bank.config.asset_weight_maint = settings.asset_weight_maint; + bank.config.deposit_limit = settings.deposit_limit; + bank.config.total_asset_value_init_limit = settings.total_asset_value_init_limit; + bank.config.oracle_max_age = settings.oracle_max_age; + bank.config.risk_tier = settings.risk_tier; + + // Only validate the oracle info if it has changed + if oracle_before != oracle_after { + bank.config + .validate_oracle_setup(ctx.remaining_accounts, None, None, None)?; + } + if age_before != age_after { + bank.config.validate_oracle_age()?; + } + + bank.config.validate()?; + + Ok(()) +} + +#[derive(Accounts)] +pub struct PropagateStakedSettings<'info> { + pub marginfi_group: AccountLoader<'info, MarginfiGroup>, + + #[account( + has_one = marginfi_group + )] + pub staked_settings: AccountLoader<'info, StakedSettings>, + + #[account( + mut, + constraint = { + let bank = bank.load()?; + bank.group == marginfi_group.key() && + bank.config.asset_tag == ASSET_TAG_STAKED + } + )] + pub bank: AccountLoader<'info, Bank>, +} diff --git a/programs/marginfi/src/lib.rs b/programs/marginfi/src/lib.rs index 482daf165..06f816acc 100644 --- a/programs/marginfi/src/lib.rs +++ b/programs/marginfi/src/lib.rs @@ -10,6 +10,7 @@ pub mod utils; use anchor_lang::prelude::*; use instructions::*; use prelude::*; +use state::marginfi_group::WrappedI80F48; use state::marginfi_group::{BankConfigCompact, BankConfigOpt}; #[cfg(not(feature = "sandbox"))] @@ -22,22 +23,26 @@ declare_id!("stag8sTKds2h4KzjUw3zKTsxbqvT4XKHdaR9X9E6Rct"); pub mod marginfi { use super::*; - pub fn marginfi_group_initialize(ctx: Context) -> MarginfiResult { - marginfi_group::initialize_group(ctx) + pub fn marginfi_group_initialize( + ctx: Context, + is_arena_group: bool, + ) -> MarginfiResult { + marginfi_group::initialize_group(ctx, is_arena_group) } pub fn marginfi_group_configure( ctx: Context, - config: GroupConfig, + new_admin: Pubkey, + is_arena_group: bool, ) -> MarginfiResult { - marginfi_group::configure(ctx, config) + marginfi_group::configure(ctx, new_admin, is_arena_group) } pub fn lending_pool_add_bank( ctx: Context, bank_config: BankConfigCompact, ) -> MarginfiResult { - marginfi_group::lending_pool_add_bank(ctx, bank_config.into()) + marginfi_group::lending_pool_add_bank(ctx, bank_config) } /// A copy of lending_pool_add_bank with an additional bank seed. @@ -48,7 +53,14 @@ pub mod marginfi { bank_config: BankConfigCompact, bank_seed: u64, ) -> MarginfiResult { - marginfi_group::lending_pool_add_bank_with_seed(ctx, bank_config.into(), bank_seed) + marginfi_group::lending_pool_add_bank_with_seed(ctx, bank_config, bank_seed) + } + + pub fn lending_pool_add_bank_permissionless( + ctx: Context, + bank_seed: u64, + ) -> MarginfiResult { + marginfi_group::lending_pool_add_bank_permissionless(ctx, bank_seed) } pub fn lending_pool_configure_bank( @@ -58,6 +70,14 @@ pub mod marginfi { marginfi_group::lending_pool_configure_bank(ctx, bank_config_opt) } + pub fn lending_pool_configure_bank_oracle( + ctx: Context, + setup: u8, + oracle: Pubkey, + ) -> MarginfiResult { + marginfi_group::lending_pool_configure_bank_oracle(ctx, setup, oracle) + } + pub fn lending_pool_setup_emissions( ctx: Context, flags: u64, @@ -98,8 +118,9 @@ pub mod marginfi { pub fn lending_account_deposit<'info>( ctx: Context<'_, '_, 'info, 'info, LendingAccountDeposit<'info>>, amount: u64, + deposit_up_to_limit: Option, ) -> MarginfiResult { - marginfi_account::lending_account_deposit(ctx, amount) + marginfi_account::lending_account_deposit(ctx, amount, deposit_up_to_limit) } pub fn lending_account_repay<'info>( @@ -164,6 +185,12 @@ pub mod marginfi { marginfi_account::lending_account_end_flashloan(ctx) } + pub fn marginfi_account_update_emissions_destination_account<'info>( + ctx: Context<'_, '_, 'info, 'info, MarginfiAccountUpdateEmissionsDestinationAccount<'info>>, + ) -> MarginfiResult { + marginfi_account::marginfi_account_update_emissions_destination_account(ctx) + } + // Operational instructions pub fn lending_pool_accrue_bank_interest( ctx: Context, @@ -208,6 +235,99 @@ pub mod marginfi { pub fn marginfi_account_close(ctx: Context) -> MarginfiResult { marginfi_account::close_account(ctx) } + + pub fn lending_account_withdraw_emissions_permissionless<'info>( + ctx: Context<'_, '_, 'info, 'info, LendingAccountWithdrawEmissionsPermissionless<'info>>, + ) -> MarginfiResult { + marginfi_account::lending_account_withdraw_emissions_permissionless(ctx) + } + + /// (Permissionless) Refresh the internal risk engine health cache. Useful for liquidators and + /// other consumers that want to see the internal risk state of a user account. This cache is + /// read-only and serves no purpose except being populated by this ix. + /// * remaining accounts expected in the same order as borrow, etc. I.e., for each balance the + /// user has, pass bank and oracle: + pub fn lending_account_pulse_health<'info>( + ctx: Context<'_, '_, 'info, 'info, PulseHealth<'info>>, + ) -> MarginfiResult { + marginfi_account::lending_account_pulse_health(ctx) + } + + /// (Runs once per program) Configures the fee state account, where the global admin sets fees + /// that are assessed to the protocol + pub fn init_global_fee_state( + ctx: Context, + admin: Pubkey, + fee_wallet: Pubkey, + bank_init_flat_sol_fee: u32, + program_fee_fixed: WrappedI80F48, + program_fee_rate: WrappedI80F48, + ) -> MarginfiResult { + marginfi_group::initialize_fee_state( + ctx, + admin, + fee_wallet, + bank_init_flat_sol_fee, + program_fee_fixed, + program_fee_rate, + ) + } + + /// (global fee admin only) Adjust fees, admin, or the destination wallet + pub fn edit_global_fee_state( + ctx: Context, + admin: Pubkey, + fee_wallet: Pubkey, + bank_init_flat_sol_fee: u32, + program_fee_fixed: WrappedI80F48, + program_fee_rate: WrappedI80F48, + ) -> MarginfiResult { + marginfi_group::edit_fee_state( + ctx, + admin, + fee_wallet, + bank_init_flat_sol_fee, + program_fee_fixed, + program_fee_rate, + ) + } + + /// (Permissionless) Force any group to adopt the current FeeState settings + pub fn propagate_fee_state(ctx: Context) -> MarginfiResult { + marginfi_group::propagate_fee(ctx) + } + + /// (global fee admin only) Enable or disable program fees for any group. Does not require the + /// group admin to sign: the global fee state admin can turn program fees on or off for any + /// group + pub fn config_group_fee( + ctx: Context, + enable_program_fee: bool, + ) -> MarginfiResult { + marginfi_group::config_group_fee(ctx, enable_program_fee) + } + + /// (group admin only) Init the Staked Settings account, which is used to create staked + /// collateral banks, and must run before any staked collateral bank can be created with + /// `add_pool_permissionless`. Running this ix effectively opts the group into the staked + /// collateral feature. + pub fn init_staked_settings( + ctx: Context, + settings: StakedSettingsConfig, + ) -> MarginfiResult { + marginfi_group::initialize_staked_settings(ctx, settings) + } + + pub fn edit_staked_settings( + ctx: Context, + settings: StakedSettingsEditConfig, + ) -> MarginfiResult { + marginfi_group::edit_staked_settings(ctx, settings) + } + + pub fn propagate_staked_settings(ctx: Context) -> MarginfiResult { + marginfi_group::propagate_staked_settings(ctx) + } } #[cfg(not(feature = "no-entrypoint"))] diff --git a/programs/marginfi/src/macros.rs b/programs/marginfi/src/macros.rs index 593dfa2af..db72c8006 100644 --- a/programs/marginfi/src/macros.rs +++ b/programs/marginfi/src/macros.rs @@ -33,6 +33,45 @@ macro_rules! check { }; } +#[macro_export] +/// Checks if two values are equal, emits the error, line number, file name, and the contents of the +/// two values being compared on error. +macro_rules! check_eq { + ($left:expr, $right:expr, $err:expr) => { + if $left != $right { + let err_code: $crate::errors::MarginfiError = $err; + #[cfg(not(feature = "test-bpf"))] + anchor_lang::prelude::msg!( + "Error \"{}\" thrown at {}:{}: left = {:?}, right = {:?}", + err_code, + file!(), + line!(), + $left, + $right + ); + return Err(err_code.into()); + } + }; + + ($left:expr, $right:expr, $err:expr, $($arg:tt)+) => { + if $left != $right { + let err_code: $crate::errors::MarginfiError = $err; + #[cfg(not(feature = "test-bpf"))] + anchor_lang::prelude::msg!( + "Error \"{}\" thrown at {}:{}: left = {:?}, right = {:?}", + err_code, + file!(), + line!(), + $left, + $right + ); + #[cfg(not(feature = "test-bpf"))] + anchor_lang::prelude::msg!($($arg)+); + return Err(err_code.into()); + } + }; +} + #[macro_export] macro_rules! math_error { () => {{ @@ -107,3 +146,14 @@ macro_rules! assert_struct_align { static_assertions::const_assert_eq!(std::mem::align_of::<$struct>(), $align); }; } + +#[macro_export] +macro_rules! live { + () => { + cfg!(any( + feature = "mainnet-beta", + feature = "staging", + feature = "devnet" + )) + }; +} diff --git a/programs/marginfi/src/prelude.rs b/programs/marginfi/src/prelude.rs index dbd09e44b..f80d1ba35 100644 --- a/programs/marginfi/src/prelude.rs +++ b/programs/marginfi/src/prelude.rs @@ -2,7 +2,4 @@ use anchor_lang::prelude::*; pub type MarginfiResult = Result; -pub use crate::{ - errors::MarginfiError, - state::marginfi_group::{GroupConfig, MarginfiGroup}, -}; +pub use crate::{errors::MarginfiError, state::marginfi_group::MarginfiGroup}; diff --git a/programs/marginfi/src/state/fee_state.rs b/programs/marginfi/src/state/fee_state.rs new file mode 100644 index 000000000..0d7f61425 --- /dev/null +++ b/programs/marginfi/src/state/fee_state.rs @@ -0,0 +1,42 @@ +use anchor_lang::prelude::*; + +use crate::{assert_struct_align, assert_struct_size}; + +use super::marginfi_group::WrappedI80F48; + +assert_struct_size!(FeeState, 256); +assert_struct_align!(FeeState, 8); + +/// Unique per-program. The Program Owner uses this account to administrate fees collected by the protocol +#[account(zero_copy)] +#[repr(C)] +pub struct FeeState { + /// The fee state's own key. A PDA derived from just `b"feestate"` + pub key: Pubkey, + /// Can modify fees + pub global_fee_admin: Pubkey, + /// The base wallet for all protocol fees. All SOL fees go to this wallet. All non-SOL fees go + /// to the cannonical ATA of this wallet for that asset. + pub global_fee_wallet: Pubkey, + // Reserved for future use, forces 8-byte alignment + pub placeholder0: u64, + /// Flat fee assessed when a new bank is initialized, in lamports. + /// * In SOL, in native decimals. + pub bank_init_flat_sol_fee: u32, + pub bump_seed: u8, + // Pad to next 8-byte multiple + _padding0: [u8; 4], + // Pad to 128 bytes + _padding1: [u8; 15], + /// Fee collected by the program owner from all groups + pub program_fee_fixed: WrappedI80F48, + /// Fee collected by the program owner from all groups + pub program_fee_rate: WrappedI80F48, + // Reserved for future use + _reserved0: [u8; 32], + _reserved1: [u8; 64], +} + +impl FeeState { + pub const LEN: usize = std::mem::size_of::(); +} diff --git a/programs/marginfi/src/state/health_cache.rs b/programs/marginfi/src/state/health_cache.rs new file mode 100644 index 000000000..6057b95d5 --- /dev/null +++ b/programs/marginfi/src/state/health_cache.rs @@ -0,0 +1,66 @@ +use super::{marginfi_account::MAX_LENDING_ACCOUNT_BALANCES, marginfi_group::WrappedI80F48}; +use crate::{assert_struct_align, assert_struct_size}; +use anchor_lang::prelude::*; +use bytemuck::{Pod, Zeroable}; +use type_layout::TypeLayout; + +pub const HEALTHY: u64 = 1; +pub const ENGINE_OK: u64 = 2; + +assert_struct_size!(HealthCache, 304); +assert_struct_align!(HealthCache, 8); +#[repr(C)] +#[derive( + AnchorDeserialize, AnchorSerialize, Copy, Clone, Zeroable, Pod, PartialEq, Eq, TypeLayout, Debug, +)] +/// A read-only cache of the internal risk engine's information. Only valid in borrow/withdraw if +/// the tx does not fail. To see the state in any context, e.g. to figure out if the risk engine is +/// failing due to some bad price information, use `pulse_health`. +pub struct HealthCache { + pub asset_value: WrappedI80F48, + pub liability_value: WrappedI80F48, + /// Unix timestamp from the system clock when this cache was last updated + pub timestamp: i64, + /// The flags that indicate the state of the health cache This is u64 bitfield, where each bit + /// represents a flag. + /// + /// * HEALTHY = 1 - If set, the account cannot be liquidated. If 0, the account is unhealthy and + /// can be liquidated. + /// * ENGINE STATUS = 2 - If set, the engine did not error during the last health pulse. If 0, + /// the engine would have errored and this cache is likely invalid. + /// * 4, 8, 16, 32, 64, 128, etc - reserved for future use + pub flags: u64, + /// Each price corresponds to that index of Balances in the LendingAccount. Useful for debugging + /// or liquidator consumption, to determine how a user's position is priced internally. + /// * If a price overflows u64, shows u64::MAX + /// * If a price is negative for some reason (as several oracles support), pulse will panic + pub prices: [WrappedI80F48; MAX_LENDING_ACCOUNT_BALANCES], +} + +impl HealthCache { + /// True if account is healthy (cannot be liquidated) + pub fn is_healthy(&self) -> bool { + self.flags & HEALTHY != 0 + } + + pub fn set_healthy(&mut self, healthy: bool) { + if healthy { + self.flags |= HEALTHY; + } else { + self.flags &= !HEALTHY; + } + } + + /// True if the engine did not error during the last health pulse. + pub fn is_engine_ok(&self) -> bool { + self.flags & ENGINE_OK != 0 + } + + pub fn set_engine_ok(&mut self, ok: bool) { + if ok { + self.flags |= ENGINE_OK; + } else { + self.flags &= !ENGINE_OK; + } + } +} diff --git a/programs/marginfi/src/state/marginfi_account.rs b/programs/marginfi/src/state/marginfi_account.rs index ea3944d71..4cd1f0faf 100644 --- a/programs/marginfi/src/state/marginfi_account.rs +++ b/programs/marginfi/src/state/marginfi_account.rs @@ -1,69 +1,105 @@ use super::{ + health_cache::HealthCache, marginfi_group::{Bank, RiskTier, WrappedI80F48}, price::{OraclePriceFeedAdapter, OraclePriceType, PriceAdapter, PriceBias}, }; use crate::{ assert_struct_align, assert_struct_size, check, constants::{ - BANKRUPT_THRESHOLD, EMISSIONS_FLAG_BORROW_ACTIVE, EMISSIONS_FLAG_LENDING_ACTIVE, - EMPTY_BALANCE_THRESHOLD, EXP_10_I80F48, MIN_EMISSIONS_START_TIME, SECONDS_PER_YEAR, - ZERO_AMOUNT_THRESHOLD, + ASSET_TAG_DEFAULT, ASSET_TAG_SOL, ASSET_TAG_STAKED, BANKRUPT_THRESHOLD, + EMISSIONS_FLAG_BORROW_ACTIVE, EMISSIONS_FLAG_LENDING_ACTIVE, EMPTY_BALANCE_THRESHOLD, + EXP_10_I80F48, MIN_EMISSIONS_START_TIME, SECONDS_PER_YEAR, ZERO_AMOUNT_THRESHOLD, }, debug, math_error, prelude::{MarginfiError, MarginfiResult}, utils::NumTraitsWithTolerance, }; -use anchor_lang::prelude::*; +use anchor_lang::{prelude::*, Discriminator}; use anchor_spl::token_interface::Mint; +use bytemuck::{Pod, Zeroable}; use fixed::types::I80F48; use std::{ cmp::{max, min}, ops::Not, }; -#[cfg(any(feature = "test", feature = "client"))] use type_layout::TypeLayout; assert_struct_size!(MarginfiAccount, 2304); assert_struct_align!(MarginfiAccount, 8); -#[account(zero_copy(unsafe))] +#[account(zero_copy)] #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(Debug, PartialEq, Eq, TypeLayout) -)] +#[derive(PartialEq, Eq, TypeLayout)] pub struct MarginfiAccount { pub group: Pubkey, // 32 pub authority: Pubkey, // 32 pub lending_account: LendingAccount, // 1728 - /// The flags that indicate the state of the account. - /// This is u64 bitfield, where each bit represents a flag. + /// The flags that indicate the state of the account. This is u64 bitfield, where each bit + /// represents a flag. /// - /// Flags: - /// - DISABLED_FLAG = 1 << 0 = 1 - This flag indicates that the account is disabled, - /// and no further actions can be taken on it. + /// Flags:MarginfiAccount + /// - 1: `ACCOUNT_DISABLED` - Indicates that the account is disabled and no further actions can + /// be taken on it. + /// - 2: `ACCOUNT_IN_FLASHLOAN` - Only set when an account is within a flash loan, e.g. when + /// start_flashloan is called, then unset when the flashloan ends. + /// - 4: `ACCOUNT_FLAG_DEPRECATED` - Deprecated, available for future use + /// - 8: `ACCOUNT_TRANSFER_AUTHORITY_ALLOWED` - the admin has flagged with account to be moved, + /// original owner can now call `set_account_transfer_authority` pub account_flags: u64, // 8 - pub _padding: [u64; 63], // 504 + /// Set with `update_emissions_destination_account`. Emissions rewards can be withdrawn to the + /// cannonical ATA of this wallet without the user's input (withdraw_emissions_permissionless). + /// If pubkey default, the user has not opted into this feature, and must claim emissions + /// manually (withdraw_emissions). + pub emissions_destination_account: Pubkey, // 32 + pub health_cache: HealthCache, + pub _padding0: [u64; 21], +} + +pub const ACCOUNT_DISABLED: u64 = 1 << 0; +pub const ACCOUNT_IN_FLASHLOAN: u64 = 1 << 1; +pub const ACCOUNT_FLAG_DEPRECATED: u64 = 1 << 2; +pub const ACCOUNT_TRANSFER_AUTHORITY_ALLOWED: u64 = 1 << 3; + +/// 4 for `ASSET_TAG_STAKED` (bank, oracle, lst mint, lst pool), 2 for all others (bank, oracle) +pub fn get_remaining_accounts_per_bank(bank: &Bank) -> MarginfiResult { + get_remaining_accounts_per_asset_tag(bank.config.asset_tag) +} + +/// 4 for `ASSET_TAG_STAKED` (bank, oracle, lst mint, lst pool), 2 for all others (bank, oracle) +fn get_remaining_accounts_per_balance(balance: &Balance) -> MarginfiResult { + get_remaining_accounts_per_asset_tag(balance.bank_asset_tag) } -pub const DISABLED_FLAG: u64 = 1 << 0; -pub const IN_FLASHLOAN_FLAG: u64 = 1 << 1; -pub const FLASHLOAN_ENABLED_FLAG: u64 = 1 << 2; -pub const TRANSFER_AUTHORITY_ALLOWED_FLAG: u64 = 1 << 3; +/// 4 for `ASSET_TAG_STAKED` (bank, oracle, lst mint, lst pool), 2 for all others (bank, oracle) +fn get_remaining_accounts_per_asset_tag(asset_tag: u8) -> MarginfiResult { + match asset_tag { + ASSET_TAG_DEFAULT | ASSET_TAG_SOL => Ok(2), + ASSET_TAG_STAKED => Ok(4), + _ => err!(MarginfiError::AssetTagMismatch), + } +} impl MarginfiAccount { /// Set the initial data for the marginfi account. pub fn initialize(&mut self, group: Pubkey, authority: Pubkey) { self.authority = authority; self.group = group; + self.emissions_destination_account = Pubkey::default(); } - pub fn get_remaining_accounts_len(&self) -> usize { - self.lending_account + /// Expected length of remaining accounts to be passed in borrow/liquidate, INCLUDING the bank + /// key, oracle, and optional accounts like lst mint/pool, etc. + pub fn get_remaining_accounts_len(&self) -> MarginfiResult { + let mut total = 0usize; + for balance in self + .lending_account .balances .iter() - .filter(|b| b.active) - .count() - * 2 // TODO: Make account count oracle setup specific + .filter(|b| b.is_active()) + { + let num_accounts = get_remaining_accounts_per_balance(balance)?; + total += num_accounts; + } + Ok(total) } pub fn set_flag(&mut self, flag: u64) { @@ -82,7 +118,7 @@ impl MarginfiAccount { pub fn set_new_account_authority_checked(&mut self, new_authority: Pubkey) -> MarginfiResult { // check if new account authority flag is set - if !self.get_flag(TRANSFER_AUTHORITY_ALLOWED_FLAG) || self.get_flag(DISABLED_FLAG) { + if !self.get_flag(ACCOUNT_TRANSFER_AUTHORITY_ALLOWED) || self.get_flag(ACCOUNT_DISABLED) { return Err(MarginfiError::IllegalAccountAuthorityTransfer.into()); } @@ -91,7 +127,7 @@ impl MarginfiAccount { self.authority = new_authority; // unset flag after updating the account authority - self.unset_flag(TRANSFER_AUTHORITY_ALLOWED_FLAG); + self.unset_flag(ACCOUNT_TRANSFER_AUTHORITY_ALLOWED); msg!( "Transferred account authority from {:?} to {:?} in group {:?}", @@ -103,7 +139,7 @@ impl MarginfiAccount { } pub fn can_be_closed(&self) -> bool { - let is_disabled = self.get_flag(DISABLED_FLAG); + let is_disabled = self.get_flag(ACCOUNT_DISABLED); let only_has_empty_balances = self .lending_account .balances @@ -166,47 +202,42 @@ impl<'info> BankAccountWithPriceFeed<'_, 'info> { lending_account: &'a LendingAccount, remaining_ais: &'info [AccountInfo<'info>], ) -> MarginfiResult>> { - let active_balances = lending_account - .balances - .iter() - .filter(|balance| balance.active) - .collect::>(); - - debug!("Expecting {} remaining accounts", active_balances.len() * 2); - debug!("Got {} remaining accounts", remaining_ais.len()); - - check!( - active_balances.len() * 2 <= remaining_ais.len(), - MarginfiError::MissingPythOrBankAccount - ); - let clock = Clock::get()?; + let mut account_index = 0; - active_balances + lending_account + .balances .iter() - .enumerate() - .map(|(i, balance)| { - let bank_index = i * 2; - let oracle_ai_idx = bank_index + 1; - - let bank_ai = remaining_ais.get(bank_index).unwrap(); + .filter(|balance| balance.is_active()) + .map(|balance| { + // Get the bank + let bank_ai: Option<&AccountInfo<'info>> = remaining_ais.get(account_index); + if bank_ai.is_none() { + msg!("Ran out of remaining accounts at {:?}", account_index); + return err!(MarginfiError::InvalidBankAccount); + } + let bank_ai = bank_ai.unwrap(); + let bank_al = AccountLoader::::try_from(bank_ai)?; + // Determine number of accounts to process for this balance + let num_accounts = get_remaining_accounts_per_balance(balance)?; check!( balance.bank_pk.eq(bank_ai.key), MarginfiError::InvalidBankAccount ); + let bank = bank_al.load()?; - let price_adapter = { - let oracle_ais = &remaining_ais[oracle_ai_idx..oracle_ai_idx + 1]; - let bank_al = AccountLoader::::try_from(bank_ai)?; - let bank = bank_al.load()?; + // Get the oracle, and the LST mint and sol pool if applicable (staked only) + let oracle_ai_idx = account_index + 1; + let oracle_ais = &remaining_ais[oracle_ai_idx..oracle_ai_idx + num_accounts - 1]; - Box::new(OraclePriceFeedAdapter::try_from_bank_config( - &bank.config, - oracle_ais, - &clock, - )) - }; + let price_adapter = Box::new(OraclePriceFeedAdapter::try_from_bank_config( + &bank.config, + oracle_ais, + &clock, + )); + + account_index += num_accounts; Ok(BankAccountWithPriceFeed { bank: bank_ai.clone(), @@ -218,58 +249,87 @@ impl<'info> BankAccountWithPriceFeed<'_, 'info> { } #[inline(always)] - /// Calculate the value of the assets and liabilities of the account in the form of (assets, liabilities) + /// Calculate the value of the balance, which is either an asset or a liability. If it is an + /// asset, returns (asset_value, 0, price), and if it is a liability, returns (0, liabilty + /// value, price), where price is the actual oracle price used to determine the value after bias + /// adjustments, etc. /// /// Nuances: /// 1. Maintenance requirement is calculated using the real time price feed. /// 2. Initial requirement is calculated using the time weighted price feed, if available. - /// 3. Initial requirement is discounted by the initial discount, if enabled and the usd limit is exceeded. + /// 3. Initial requirement is discounted by the initial discount, if enabled and the usd limit + /// is exceeded. /// 4. Assets are only calculated for collateral risk tier. /// 5. Oracle errors are ignored for deposits in isolated risk tier. - fn calc_weighted_assets_and_liabilities_values<'a>( + fn calc_weighted_value<'a>( &'a self, requirement_type: RequirementType, - ) -> MarginfiResult<(I80F48, I80F48)> + ) -> MarginfiResult<(I80F48, I80F48, I80F48)> where 'info: 'a, { match self.balance.get_side() { Some(side) => { - // SAFETY: We are shortening 'info -> 'a - let shorter_bank: &'a AccountInfo<'a> = unsafe { core::mem::transmute(&self.bank) }; - let bank_al = AccountLoader::::try_from(shorter_bank)?; - let bank = bank_al.load()?; + // We want lifetime <'a> but we have <'info> and it's a pain to modify everything... + // To avoid an unsafe transmuation we just interpret the bank from bytes. Here we + // repeat some of the sanity checks from AccountLoader + if self.bank.owner != &Bank::owner() { + panic!("bank owned by wrong program, this should never happen"); + } + let bank_data = &self.bank.try_borrow_data()?; + if bank_data.len() < Bank::LEN + 8 { + panic!("bank too short, this should never happen"); + } + let bank_discrim: &[u8] = &bank_data[0..8]; + if bank_discrim != Bank::DISCRIMINATOR { + panic!("bad bank discriminator, this should never happen"); + } + let bank_data: &[u8] = &bank_data[8..]; + let bank = *bytemuck::from_bytes(bank_data); + + // Our alternative is this transmute, which is probably fine because we are + // shortening 'info to 'a, but better not to tempt fate with transmute in case + // Anchor messes with lifetimes in a later version. + + // let shorter_bank: &'a AccountInfo<'a> = unsafe { core::mem::transmute(&self.bank) }; + // let bank_al = AccountLoader::::try_from(&shorter_bank)?; + // let bank = bank_al.load()?; + match side { - BalanceSide::Assets => Ok(( - self.calc_weighted_assets(requirement_type, &bank)?, - I80F48::ZERO, - )), - BalanceSide::Liabilities => Ok(( - I80F48::ZERO, - self.calc_weighted_liabs(requirement_type, &bank)?, - )), + BalanceSide::Assets => { + let (value, price) = + self.calc_weighted_asset_value(requirement_type, &bank)?; + Ok((value, I80F48::ZERO, price)) + } + + BalanceSide::Liabilities => { + let (value, price) = + self.calc_weighted_liab_value(requirement_type, &bank)?; + Ok((I80F48::ZERO, value, price)) + } } } - None => Ok((I80F48::ZERO, I80F48::ZERO)), + None => Ok((I80F48::ZERO, I80F48::ZERO, I80F48::ZERO)), } } + /// Returns value, the net asset value in $, and the price used to determine that value. #[inline(always)] - fn calc_weighted_assets<'a>( + fn calc_weighted_asset_value<'a>( &'a self, requirement_type: RequirementType, bank: &'a Bank, - ) -> MarginfiResult { + ) -> MarginfiResult<(I80F48, I80F48)> { match bank.config.risk_tier { RiskTier::Collateral => { let price_feed = self.try_get_price_feed(); if matches!( (&price_feed, requirement_type), - (&Err(PriceFeedError::StaleOracle), RequirementType::Initial) + (&Err(_), RequirementType::Initial) ) { debug!("Skipping stale oracle"); - return Ok(I80F48::ZERO); + return Ok((I80F48::ZERO, I80F48::ZERO)); } let price_feed = price_feed?; @@ -293,23 +353,26 @@ impl<'info> BankAccountWithPriceFeed<'_, 'info> { } } - calc_value( + let value = calc_value( bank.get_asset_amount(self.balance.asset_shares.into())?, lower_price, bank.mint_decimals, Some(asset_weight), - ) + )?; + + Ok((value, lower_price)) } - RiskTier::Isolated => Ok(I80F48::ZERO), + RiskTier::Isolated => Ok((I80F48::ZERO, I80F48::ZERO)), } } + /// Returns value, the net liability value in $, and the price used to determine that value. #[inline(always)] - fn calc_weighted_liabs( + fn calc_weighted_liab_value( &self, requirement_type: RequirementType, bank: &Bank, - ) -> MarginfiResult { + ) -> MarginfiResult<(I80F48, I80F48)> { let price_feed = self.try_get_price_feed()?; let liability_weight = bank .config @@ -320,22 +383,38 @@ impl<'info> BankAccountWithPriceFeed<'_, 'info> { Some(PriceBias::High), )?; - calc_value( + // If `ASSET_TAG_STAKED` assets can ever be borrowed, accomodate for that here... + + let value = calc_value( bank.get_liability_amount(self.balance.liability_shares.into())?, higher_price, bank.mint_decimals, Some(liability_weight), - ) + )?; + + Ok((value, higher_price)) } - fn try_get_price_feed(&self) -> std::result::Result<&OraclePriceFeedAdapter, PriceFeedError> { + fn try_get_price_feed(&self) -> MarginfiResult<&OraclePriceFeedAdapter> { match self.price_feed.as_ref() { Ok(a) => Ok(a), #[allow(unused_variables)] - Err(e) => { - debug!("Price feed error: {:?}", e); - Err(PriceFeedError::StaleOracle) - } + Err(e) => match e { + anchor_lang::error::Error::AnchorError(inner) => { + let error_code = inner.as_ref().error_code_number; + let custom_error = MarginfiError::from(error_code); + Err(error!(custom_error)) + } + anchor_lang::error::Error::ProgramError(inner) => { + match inner.as_ref().program_error { + ProgramError::Custom(error_code) => { + let custom_error = MarginfiError::from(error_code); + Err(error!(custom_error)) + } + _ => Err(error!(MarginfiError::InternalLogicError)), + } + } + }, } } @@ -345,18 +424,6 @@ impl<'info> BankAccountWithPriceFeed<'_, 'info> { } } -enum PriceFeedError { - StaleOracle, -} - -impl From for Error { - fn from(value: PriceFeedError) -> Self { - match value { - PriceFeedError::StaleOracle => error!(MarginfiError::StaleOracle), - } - } -} - /// Calculate the value of an asset, given its quantity with a decimal exponent, and a price with a decimal exponent, and an optional weight. #[inline] pub fn calc_value( @@ -432,7 +499,7 @@ impl<'info> RiskEngine<'_, 'info> { remaining_ais: &'info [AccountInfo<'info>], ) -> MarginfiResult> { check!( - !marginfi_account.get_flag(IN_FLASHLOAN_FLAG), + !marginfi_account.get_flag(ACCOUNT_IN_FLASHLOAN), MarginfiError::AccountInFlashloan ); @@ -456,19 +523,22 @@ impl<'info> RiskEngine<'_, 'info> { /// Checks account is healthy after performing actions that increase risk (removing liquidity). /// - /// `IN_FLASHLOAN_FLAG` behavior. + /// `ACCOUNT_IN_FLASHLOAN` behavior. /// - Health check is skipped. /// - `remaining_ais` can be an empty vec. pub fn check_account_init_health<'a>( marginfi_account: &'a MarginfiAccount, remaining_ais: &'info [AccountInfo<'info>], + health_cache: &mut Option<&mut HealthCache>, ) -> MarginfiResult<()> { - if marginfi_account.get_flag(IN_FLASHLOAN_FLAG) { + if marginfi_account.get_flag(ACCOUNT_IN_FLASHLOAN) { + // Note: The health cache is not applicable to flashloans return Ok(()); } - Self::new_no_flashloan_check(marginfi_account, remaining_ais)? - .check_account_health(RiskRequirementType::Initial)?; + let risk_engine = Self::new_no_flashloan_check(marginfi_account, remaining_ais)?; + let requirement_type = RiskRequirementType::Initial; + risk_engine.check_account_health(requirement_type, health_cache)?; Ok(()) } @@ -477,53 +547,70 @@ impl<'info> RiskEngine<'_, 'info> { pub fn get_account_health_components( &self, requirement_type: RiskRequirementType, + health_cache: &mut Option<&mut HealthCache>, ) -> MarginfiResult<(I80F48, I80F48)> { - let mut total_assets = I80F48::ZERO; - let mut total_liabilities = I80F48::ZERO; + let mut total_assets: I80F48 = I80F48::ZERO; + let mut total_liabilities: I80F48 = I80F48::ZERO; - for a in &self.bank_accounts_with_price { - let (assets, liabilities) = - a.calc_weighted_assets_and_liabilities_values(requirement_type.to_weight_type())?; + for (i, bank_account) in self.bank_accounts_with_price.iter().enumerate() { + let requirement_type = requirement_type.to_weight_type(); + let (asset_val, liab_val, price) = + bank_account.calc_weighted_value(requirement_type)?; + + if let Some(health_cache) = health_cache { + health_cache.prices[i] = price.into(); + } debug!( "Balance {}, assets: {}, liabilities: {}", - a.balance.bank_pk, assets, liabilities + bank_account.balance.bank_pk, asset_val, liab_val ); - total_assets = total_assets.checked_add(assets).ok_or_else(math_error!())?; + total_assets = total_assets + .checked_add(asset_val) + .ok_or_else(math_error!())?; total_liabilities = total_liabilities - .checked_add(liabilities) + .checked_add(liab_val) .ok_or_else(math_error!())?; } + if let Some(health_cache) = health_cache { + health_cache.asset_value = total_assets.into(); + health_cache.liability_value = total_liabilities.into(); + } + Ok((total_assets, total_liabilities)) } - pub fn get_account_health( - &'info self, + /// Errors if risk account's liabilities exceed their assets. + fn check_account_health( + &self, requirement_type: RiskRequirementType, - ) -> MarginfiResult { + health_cache: &mut Option<&mut HealthCache>, + ) -> MarginfiResult<()> { let (total_weighted_assets, total_weighted_liabilities) = - self.get_account_health_components(requirement_type)?; + self.get_account_health_components(requirement_type, health_cache)?; - Ok(total_weighted_assets - .checked_sub(total_weighted_liabilities) - .ok_or_else(math_error!())?) - } + let healthy = total_weighted_assets >= total_weighted_liabilities; - fn check_account_health(&self, requirement_type: RiskRequirementType) -> MarginfiResult { - let (total_weighted_assets, total_weighted_liabilities) = - self.get_account_health_components(requirement_type)?; + if healthy { + debug!( + "check_health: assets {} - liabs: {}", + total_weighted_assets, total_weighted_liabilities + ); + } else { + let assets_f64: f64 = total_weighted_assets.to_num(); + let liabs_f64: f64 = total_weighted_liabilities.to_num(); + msg!("check_health: assets {} - liabs: {}", assets_f64, liabs_f64); + } - debug!( - "check_health: assets {} - liabs: {}", - total_weighted_assets, total_weighted_liabilities - ); + if let Some(cache) = health_cache { + cache.set_healthy(healthy); + } - check!( - total_weighted_assets >= total_weighted_liabilities, - MarginfiError::RiskEngineInitRejected - ); + if !healthy { + return err!(MarginfiError::RiskEngineInitRejected); + } self.check_account_risk_tiers()?; @@ -538,7 +625,7 @@ impl<'info> RiskEngine<'_, 'info> { bank_pk: &Pubkey, ) -> MarginfiResult { check!( - !self.marginfi_account.get_flag(IN_FLASHLOAN_FLAG), + !self.marginfi_account.get_flag(ACCOUNT_IN_FLASHLOAN), MarginfiError::AccountInFlashloan ); @@ -552,16 +639,16 @@ impl<'info> RiskEngine<'_, 'info> { liability_bank_balance .is_empty(BalanceSide::Liabilities) .not(), - MarginfiError::IllegalLiquidation + MarginfiError::NoLiabilitiesInLiabilityBank ); check!( liability_bank_balance.is_empty(BalanceSide::Assets), - MarginfiError::IllegalLiquidation + MarginfiError::AssetsInLiabilityBank ); let (assets, liabs) = - self.get_account_health_components(RiskRequirementType::Maintenance)?; + self.get_account_health_components(RiskRequirementType::Maintenance, &mut None)?; let account_health = assets.checked_sub(liabs).ok_or_else(math_error!())?; @@ -572,8 +659,7 @@ impl<'info> RiskEngine<'_, 'info> { check!( account_health <= I80F48::ZERO, - MarginfiError::IllegalLiquidation, - "Account not unhealthy" + MarginfiError::HealthyAccount ); Ok(account_health) @@ -595,7 +681,7 @@ impl<'info> RiskEngine<'_, 'info> { pre_liquidation_health: I80F48, ) -> MarginfiResult { check!( - !self.marginfi_account.get_flag(IN_FLASHLOAN_FLAG), + !self.marginfi_account.get_flag(ACCOUNT_IN_FLASHLOAN), MarginfiError::AccountInFlashloan ); @@ -609,25 +695,22 @@ impl<'info> RiskEngine<'_, 'info> { liability_bank_balance .is_empty(BalanceSide::Liabilities) .not(), - MarginfiError::IllegalLiquidation, - "Liability payoff too severe, exhausted liability" + MarginfiError::ExhaustedLiability ); check!( liability_bank_balance.is_empty(BalanceSide::Assets), - MarginfiError::IllegalLiquidation, - "Liability payoff too severe, liability balance has assets" + MarginfiError::TooSeverePayoff ); let (assets, liabs) = - self.get_account_health_components(RiskRequirementType::Maintenance)?; + self.get_account_health_components(RiskRequirementType::Maintenance, &mut None)?; let account_health = assets.checked_sub(liabs).ok_or_else(math_error!())?; check!( account_health <= I80F48::ZERO, - MarginfiError::IllegalLiquidation, - "Liquidation too severe, account above maintenance requirement" + MarginfiError::TooSevereLiquidation ); debug!( @@ -637,8 +720,7 @@ impl<'info> RiskEngine<'_, 'info> { check!( account_health > pre_liquidation_health, - MarginfiError::IllegalLiquidation, - "Post liquidation health worse" + MarginfiError::WorseHealthPostLiquidation ); Ok(account_health) @@ -648,10 +730,10 @@ impl<'info> RiskEngine<'_, 'info> { /// Account needs to be insolvent and total value of assets need to be below the bankruptcy threshold. pub fn check_account_bankrupt(&self) -> MarginfiResult { let (total_assets, total_liabilities) = - self.get_account_health_components(RiskRequirementType::Equity)?; + self.get_account_health_components(RiskRequirementType::Equity, &mut None)?; check!( - !self.marginfi_account.get_flag(IN_FLASHLOAN_FLAG), + !self.marginfi_account.get_flag(ACCOUNT_IN_FLASHLOAN), MarginfiError::AccountInFlashloan ); @@ -684,17 +766,27 @@ impl<'info> RiskEngine<'_, 'info> { let n_balances_with_liablities = balances_with_liablities.clone().count(); - let is_in_isolated_risk_tier = balances_with_liablities.clone().any(|a| { - // SAFETY: We are shortening 'info -> 'a - let shorter_bank: &'a AccountInfo<'a> = unsafe { core::mem::transmute(&a.bank) }; - AccountLoader::::try_from(shorter_bank) - .unwrap() - .load() - .unwrap() - .config - .risk_tier - == RiskTier::Isolated - }); + let mut is_in_isolated_risk_tier = false; + + for a in balances_with_liablities { + if a.bank.owner != &Bank::owner() { + panic!("bank owned by wrong program, this should never happen"); + } + let bank_data = a.bank.try_borrow_data()?; + if bank_data.len() < Bank::LEN + 8 { + panic!("bank too short, this should never happen"); + } + let bank_discrim = &bank_data[0..8]; + if bank_discrim != Bank::DISCRIMINATOR { + panic!("bad bank discriminator, this should never happen"); + } + let bank_data = &bank_data[8..]; + let bank: Bank = *bytemuck::from_bytes(bank_data); + if bank.config.risk_tier == RiskTier::Isolated { + is_in_isolated_risk_tier = true; + break; + } + } check!( !is_in_isolated_risk_tier || n_balances_with_liablities == 1, @@ -705,15 +797,13 @@ impl<'info> RiskEngine<'_, 'info> { } } -const MAX_LENDING_ACCOUNT_BALANCES: usize = 16; +pub const MAX_LENDING_ACCOUNT_BALANCES: usize = 16; assert_struct_size!(LendingAccount, 1728); assert_struct_align!(LendingAccount, 8); -#[zero_copy(unsafe)] #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(Debug, PartialEq, Eq, TypeLayout) +#[derive( + AnchorDeserialize, AnchorSerialize, Copy, Clone, Zeroable, Pod, PartialEq, Eq, TypeLayout, )] pub struct LendingAccount { pub balances: [Balance; MAX_LENDING_ACCOUNT_BALANCES], // 104 * 16 = 1664 @@ -722,7 +812,7 @@ pub struct LendingAccount { impl LendingAccount { pub fn get_first_empty_balance(&self) -> Option { - self.balances.iter().position(|b| !b.active) + self.balances.iter().position(|b| !b.is_active()) } } @@ -731,26 +821,36 @@ impl LendingAccount { pub fn get_balance(&self, bank_pk: &Pubkey) -> Option<&Balance> { self.balances .iter() - .find(|balance| balance.active && balance.bank_pk.eq(bank_pk)) + .find(|balance| balance.is_active() && balance.bank_pk.eq(bank_pk)) } pub fn get_active_balances_iter(&self) -> impl Iterator { - self.balances.iter().filter(|b| b.active) + self.balances.iter().filter(|b| b.is_active()) } } assert_struct_size!(Balance, 104); assert_struct_align!(Balance, 8); -#[zero_copy(unsafe)] #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(Debug, PartialEq, Eq, TypeLayout) +#[derive( + AnchorDeserialize, + AnchorSerialize, + Copy, + Clone, + Zeroable, + Pod, + PartialEq, + Eq, + TypeLayout, + Default, )] pub struct Balance { - pub active: bool, + pub active: u8, pub bank_pk: Pubkey, - pub _pad0: [u8; 7], + /// Inherited from the bank when the position is first created and CANNOT BE CHANGED after that. + /// Note that all balances created before the addition of this feature use `ASSET_TAG_DEFAULT` + pub bank_asset_tag: u8, + pub _pad0: [u8; 6], pub asset_shares: WrappedI80F48, pub liability_shares: WrappedI80F48, pub emissions_outstanding: WrappedI80F48, @@ -759,6 +859,14 @@ pub struct Balance { } impl Balance { + pub fn is_active(&self) -> bool { + self.active != 0 + } + + pub fn set_active(&mut self, value: bool) { + self.active = value as u8; + } + /// Check whether a balance is empty while accounting for any rounding errors /// that might have occured during depositing/withdrawing. #[inline] @@ -820,9 +928,10 @@ impl Balance { pub fn empty_deactivated() -> Self { Balance { - active: false, + active: 0, bank_pk: Pubkey::default(), - _pad0: [0; 7], + bank_asset_tag: ASSET_TAG_DEFAULT, + _pad0: [0; 6], asset_shares: WrappedI80F48::from(I80F48::ZERO), liability_shares: WrappedI80F48::from(I80F48::ZERO), emissions_outstanding: WrappedI80F48::from(I80F48::ZERO), @@ -847,7 +956,7 @@ impl<'a> BankAccountWrapper<'a> { let balance = lending_account .balances .iter_mut() - .find(|balance| balance.active && balance.bank_pk.eq(bank_pk)) + .find(|balance| balance.is_active() && balance.bank_pk.eq(bank_pk)) .ok_or_else(|| error!(MarginfiError::BankAccountNotFound))?; Ok(Self { balance, bank }) @@ -863,7 +972,7 @@ impl<'a> BankAccountWrapper<'a> { let balance_index = lending_account .balances .iter() - .position(|balance| balance.active && balance.bank_pk.eq(bank_pk)); + .position(|balance| balance.is_active() && balance.bank_pk.eq(bank_pk)); match balance_index { Some(balance_index) => { @@ -880,9 +989,10 @@ impl<'a> BankAccountWrapper<'a> { .ok_or_else(|| error!(MarginfiError::LendingAccountBalanceSlotsFull))?; lending_account.balances[empty_index] = Balance { - active: true, + active: 1, bank_pk: *bank_pk, - _pad0: [0; 7], + bank_asset_tag: bank.config.asset_tag, + _pad0: [0; 6], asset_shares: I80F48::ZERO.into(), liability_shares: I80F48::ZERO.into(), emissions_outstanding: I80F48::ZERO.into(), @@ -1408,11 +1518,13 @@ mod test { let mut acc = MarginfiAccount { group: group.into(), authority: authority.into(), + emissions_destination_account: Pubkey::default(), lending_account: LendingAccount { balances: [Balance { - active: true, + active: 1, bank_pk: bank_pk.into(), - _pad0: [0; 7], + bank_asset_tag: ASSET_TAG_DEFAULT, + _pad0: [0; 6], asset_shares: WrappedI80F48::default(), liability_shares: WrappedI80F48::default(), emissions_outstanding: WrappedI80F48::default(), @@ -1421,11 +1533,12 @@ mod test { }; 16], _padding: [0; 8], }, - account_flags: TRANSFER_AUTHORITY_ALLOWED_FLAG, - _padding: [0; 63], + account_flags: ACCOUNT_TRANSFER_AUTHORITY_ALLOWED, + health_cache: HealthCache::zeroed(), + _padding0: [0; 21], }; - assert!(acc.get_flag(TRANSFER_AUTHORITY_ALLOWED_FLAG)); + assert!(acc.get_flag(ACCOUNT_TRANSFER_AUTHORITY_ALLOWED)); match acc.set_new_account_authority_checked(new_authority.into()) { Ok(_) => (), diff --git a/programs/marginfi/src/state/marginfi_group.rs b/programs/marginfi/src/state/marginfi_group.rs index ead16d9d7..1f283fc14 100644 --- a/programs/marginfi/src/state/marginfi_group.rs +++ b/programs/marginfi/src/state/marginfi_group.rs @@ -2,7 +2,6 @@ use super::{ marginfi_account::{BalanceSide, RequirementType}, price::{OraclePriceFeedAdapter, OracleSetup}, }; -use crate::borsh::{BorshDeserialize, BorshSerialize}; #[cfg(not(feature = "client"))] use crate::events::{GroupEventHeader, LendingPoolBankAccrueInterestEvent}; use crate::{ @@ -11,7 +10,7 @@ use crate::{ EMISSION_FLAGS, FEE_VAULT_AUTHORITY_SEED, FEE_VAULT_SEED, GROUP_FLAGS, INSURANCE_VAULT_AUTHORITY_SEED, INSURANCE_VAULT_SEED, LIQUIDITY_VAULT_AUTHORITY_SEED, LIQUIDITY_VAULT_SEED, MAX_ORACLE_KEYS, MAX_PYTH_ORACLE_AGE, MAX_SWB_ORACLE_AGE, - PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG, PYTH_ID, SECONDS_PER_YEAR, + ORACLE_MIN_AGE, PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG, SECONDS_PER_YEAR, TOTAL_ASSET_VALUE_INIT_LIMIT_INACTIVE, }, debug, math_error, @@ -20,11 +19,16 @@ use crate::{ state::marginfi_account::calc_value, MarginfiResult, }; +use crate::{ + borsh::{BorshDeserialize, BorshSerialize}, + constants::ASSET_TAG_DEFAULT, + constants::FREEZE_SETTINGS, +}; use anchor_lang::prelude::borsh; use anchor_lang::prelude::*; use anchor_spl::token_interface::*; +use bytemuck::{Pod, Zeroable}; use fixed::types::I80F48; -use pyth_sdk_solana::{state::SolanaPriceAccount, PriceFeed}; use pyth_solana_receiver_sdk::price_update::FeedId; #[cfg(feature = "client")] use std::fmt::Display; @@ -32,61 +36,124 @@ use std::{ fmt::{Debug, Formatter}, ops::Not, }; - -#[cfg(any(feature = "test", feature = "client"))] use type_layout::TypeLayout; +pub const PROGRAM_FEES_ENABLED: u64 = 1; +pub const ARENA_GROUP: u64 = 2; + +assert_struct_size!(MarginfiGroup, 1056); #[account(zero_copy)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(Debug, PartialEq, Eq, TypeLayout) -)] -#[derive(Default)] +#[derive(Default, Debug, PartialEq, Eq, TypeLayout)] pub struct MarginfiGroup { pub admin: Pubkey, - pub _padding_0: [[u64; 2]; 32], + /// Bitmask for group settings flags. + /// * 0: `PROGRAM_FEES_ENABLED` If set, program-level fees are enabled. + /// * 1: `ARENA_GROUP` If set, this is an arena group, which can only have two banks + /// * Bits 1-63: Reserved for future use. + pub group_flags: u64, + /// Caches information from the global `FeeState` so the FeeState can be omitted on certain ixes + pub fee_state_cache: FeeStateCache, + // For groups initialized in versions 0.1.2 or greater (roughly the public launch of Arena), + // this is an authoritative count of the number of banks under this group. For groups + // initialized prior to 0.1.2, a non-authoritative count of the number of banks initiated after + // 0.1.2 went live. + pub banks: u16, + pub pad0: [u8; 6], + + pub _padding_0: [[u64; 2]; 26], pub _padding_1: [[u64; 2]; 32], + pub _padding_3: u64, + pub _padding_4: u64, } -impl MarginfiGroup { - /// Configure the group parameters. - /// This function validates config values so the group remains in a valid state. - /// Any modification of group config should happen through this function. - pub fn configure(&mut self, config: &GroupConfig) -> MarginfiResult { - set_if_some!(self.admin, config.admin); +#[derive( + AnchorSerialize, AnchorDeserialize, Clone, Copy, Default, Zeroable, Pod, Debug, PartialEq, Eq, +)] +#[repr(C)] +pub struct FeeStateCache { + pub global_fee_wallet: Pubkey, + pub program_fee_fixed: WrappedI80F48, + pub program_fee_rate: WrappedI80F48, +} - Ok(()) +impl MarginfiGroup { + pub fn update_admin(&mut self, new_admin: Pubkey) { + if self.admin == new_admin { + msg!("No change to admin: {:?}", new_admin); + // do nothing + } else { + msg!("Set admin from {:?} to {:?}", self.admin, new_admin); + self.admin = new_admin; + } } /// Set the group parameters when initializing a group. /// This should be called only when the group is first initialized. - /// Both margin requirements are initially set to 100% and should be configured before use. #[allow(clippy::too_many_arguments)] pub fn set_initial_configuration(&mut self, admin_pk: Pubkey) { self.admin = admin_pk; + self.set_program_fee_enabled(true); } -} -#[cfg_attr(any(feature = "test", feature = "client"), derive(TypeLayout))] -#[derive(AnchorSerialize, AnchorDeserialize, Default, Debug, Clone)] -pub struct GroupConfig { - pub admin: Option, -} + pub fn get_group_bank_config(&self) -> GroupBankConfig { + GroupBankConfig { + program_fees: self.group_flags == PROGRAM_FEES_ENABLED, + } + } + + pub fn set_program_fee_enabled(&mut self, fee_enabled: bool) { + if fee_enabled { + self.group_flags |= PROGRAM_FEES_ENABLED; + } else { + self.group_flags &= !PROGRAM_FEES_ENABLED; + } + } + + /// Set the `ARENA_GROUP` if `is_arena` is true. If trying to set as arena and the group already + /// has more than two banks, fails. If trying to set an arena bank as non-arena, fails. + pub fn set_arena_group(&mut self, is_arena: bool) -> MarginfiResult { + // If enabling arena mode, ensure the group doesn't already have more than two banks. + if is_arena && self.banks > 2 { + return err!(MarginfiError::ArenaBankLimit); + } + + // If the group is currently marked as arena, disallow switching it back to non-arena. + if self.is_arena_group() && !is_arena { + return err!(MarginfiError::ArenaSettingCannotChange); + } + + if is_arena { + self.group_flags |= ARENA_GROUP; + } else { + self.group_flags &= !ARENA_GROUP; + } + Ok(()) + } + + /// True if program fees are enabled + pub fn program_fees_enabled(&self) -> bool { + (self.group_flags & PROGRAM_FEES_ENABLED) != 0 + } -/// Load and validate a pyth price feed account. -pub fn load_pyth_price_feed(ai: &AccountInfo) -> MarginfiResult { - check!(ai.owner.eq(&PYTH_ID), MarginfiError::InvalidOracleAccount); - let price_feed = SolanaPriceAccount::account_info_to_feed(ai) - .map_err(|_| MarginfiError::InvalidOracleAccount)?; - Ok(price_feed) + /// True if this is an arena group + pub fn is_arena_group(&self) -> bool { + (self.group_flags & ARENA_GROUP) != 0 + } + + // Increment the bank count by 1. If this is an arena group, which only supports two banks, + // errors if trying to add a third bank. If you managed to create 16,000 banks, congrats, does + // nothing. + pub fn add_bank(&mut self) -> MarginfiResult { + if self.is_arena_group() && self.banks >= 2 { + return err!(MarginfiError::ArenaBankLimit); + } + self.banks = self.banks.saturating_add(1); + Ok(()) + } } #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(PartialEq, Eq, TypeLayout) -)] -#[derive(Default, Debug, AnchorDeserialize, AnchorSerialize)] +#[derive(Default, Debug, AnchorDeserialize, AnchorSerialize, PartialEq, Eq)] pub struct InterestRateConfigCompact { // Curve Params pub optimal_utilization_rate: WrappedI80F48, @@ -98,6 +165,7 @@ pub struct InterestRateConfigCompact { pub insurance_ir_fee: WrappedI80F48, pub protocol_fixed_fee_apr: WrappedI80F48, pub protocol_ir_fee: WrappedI80F48, + pub protocol_origination_fee: WrappedI80F48, } impl From for InterestRateConfig { @@ -110,7 +178,9 @@ impl From for InterestRateConfig { insurance_ir_fee: ir_config.insurance_ir_fee, protocol_fixed_fee_apr: ir_config.protocol_fixed_fee_apr, protocol_ir_fee: ir_config.protocol_ir_fee, - _padding: [[0; 2]; 8], + protocol_origination_fee: ir_config.protocol_origination_fee, + _padding0: [0; 16], + _padding1: [[0; 32]; 3], } } } @@ -125,17 +195,26 @@ impl From for InterestRateConfigCompact { insurance_ir_fee: ir_config.insurance_ir_fee, protocol_fixed_fee_apr: ir_config.protocol_fixed_fee_apr, protocol_ir_fee: ir_config.protocol_ir_fee, + protocol_origination_fee: ir_config.protocol_origination_fee, } } } -#[zero_copy] +assert_struct_size!(InterestRateConfig, 240); #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(PartialEq, Eq, TypeLayout) +#[derive( + Default, + Debug, + Copy, + Clone, + AnchorSerialize, + AnchorDeserialize, + Zeroable, + Pod, + PartialEq, + Eq, + TypeLayout, )] -#[derive(Default, Debug)] pub struct InterestRateConfig { // Curve Params pub optimal_utilization_rate: WrappedI80F48, @@ -143,88 +222,38 @@ pub struct InterestRateConfig { pub max_interest_rate: WrappedI80F48, // Fees + /// Goes to insurance, funds `collected_insurance_fees_outstanding` pub insurance_fee_fixed_apr: WrappedI80F48, + /// Goes to insurance, funds `collected_insurance_fees_outstanding` pub insurance_ir_fee: WrappedI80F48, + /// Earned by the group, goes to `collected_group_fees_outstanding` pub protocol_fixed_fee_apr: WrappedI80F48, + /// Earned by the group, goes to `collected_group_fees_outstanding` pub protocol_ir_fee: WrappedI80F48, + pub protocol_origination_fee: WrappedI80F48, - pub _padding: [[u64; 2]; 8], // 16 * 8 = 128 bytes + pub _padding0: [u8; 16], + pub _padding1: [[u8; 32]; 3], } impl InterestRateConfig { - /// Return interest rate charged to borrowers and to depositors. - /// Rate is denominated in APR (0-). - /// - /// Return (`lending_rate`, `borrowing_rate`, `group_fees_apr`, `insurance_fees_apr`) - pub fn calc_interest_rate( - &self, - utilization_ratio: I80F48, - ) -> Option<(I80F48, I80F48, I80F48, I80F48)> { - let protocol_ir_fee = I80F48::from(self.protocol_ir_fee); - let insurance_ir_fee = I80F48::from(self.insurance_ir_fee); - - let protocol_fixed_fee_apr = I80F48::from(self.protocol_fixed_fee_apr); - let insurance_fee_fixed_apr = I80F48::from(self.insurance_fee_fixed_apr); - - let rate_fee = protocol_ir_fee + insurance_ir_fee; - let total_fixed_fee_apr = protocol_fixed_fee_apr + insurance_fee_fixed_apr; - - let base_rate = self.interest_rate_curve(utilization_ratio)?; - - // Lending rate is adjusted for utilization ratio to symmetrize payments between borrowers and depositors. - let lending_rate = base_rate.checked_mul(utilization_ratio)?; - - // Borrowing rate is adjusted for fees. - // borrowing_rate = base_rate + base_rate * rate_fee + total_fixed_fee_apr - let borrowing_rate = base_rate - .checked_mul(I80F48::ONE.checked_add(rate_fee)?)? - .checked_add(total_fixed_fee_apr)?; - - let group_fees_apr = calc_fee_rate( - base_rate, - self.protocol_ir_fee.into(), - self.protocol_fixed_fee_apr.into(), - )?; - - let insurance_fees_apr = calc_fee_rate( - base_rate, - self.insurance_ir_fee.into(), - self.insurance_fee_fixed_apr.into(), - )?; - - assert!(lending_rate >= I80F48::ZERO); - assert!(borrowing_rate >= I80F48::ZERO); - assert!(group_fees_apr >= I80F48::ZERO); - assert!(insurance_fees_apr >= I80F48::ZERO); - - // TODO: Add liquidation discount check - - Some(( - lending_rate, - borrowing_rate, - group_fees_apr, - insurance_fees_apr, - )) - } - - /// Piecewise linear interest rate function. - /// The curves approaches the `plateau_interest_rate` as the utilization ratio approaches the `optimal_utilization_rate`, - /// once the utilization ratio exceeds the `optimal_utilization_rate`, the curve approaches the `max_interest_rate`. - /// - /// To be clear we don't particularly appreciate the piecewise linear nature of this "curve", but it is what it is. - #[inline] - fn interest_rate_curve(&self, ur: I80F48) -> Option { - let optimal_ur = self.optimal_utilization_rate.into(); - let plateau_ir = self.plateau_interest_rate.into(); - let max_ir: I80F48 = self.max_interest_rate.into(); - - if ur <= optimal_ur { - ur.checked_div(optimal_ur)?.checked_mul(plateau_ir) - } else { - (ur - optimal_ur) - .checked_div(I80F48::ONE - optimal_ur)? - .checked_mul(max_ir - plateau_ir)? - .checked_add(plateau_ir) + pub fn create_interest_rate_calculator(&self, group: &MarginfiGroup) -> InterestRateCalc { + let group_bank_config = &group.get_group_bank_config(); + debug!( + "Creating interest rate calculator with protocol fees: {}", + group_bank_config.program_fees + ); + InterestRateCalc { + optimal_utilization_rate: self.optimal_utilization_rate.into(), + plateau_interest_rate: self.plateau_interest_rate.into(), + max_interest_rate: self.max_interest_rate.into(), + insurance_fixed_fee: self.insurance_fee_fixed_apr.into(), + insurance_rate_fee: self.insurance_ir_fee.into(), + protocol_fixed_fee: self.protocol_fixed_fee_apr.into(), + protocol_rate_fee: self.protocol_ir_fee.into(), + add_program_fees: group_bank_config.program_fees, + program_fee_fixed: group.fee_state_cache.program_fee_fixed.into(), + program_fee_rate: group.fee_state_cache.program_fee_rate.into(), } } @@ -261,14 +290,142 @@ impl InterestRateConfig { ir_config.protocol_fixed_fee_apr ); set_if_some!(self.protocol_ir_fee, ir_config.protocol_ir_fee); + set_if_some!( + self.protocol_origination_fee, + ir_config.protocol_origination_fee + ); } } -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(Debug, PartialEq, Eq, TypeLayout) -)] -#[derive(AnchorDeserialize, AnchorSerialize, Default, Clone)] +#[derive(Debug, Clone)] +/// Short for calculator +pub struct InterestRateCalc { + optimal_utilization_rate: I80F48, + plateau_interest_rate: I80F48, + max_interest_rate: I80F48, + + // Fees + insurance_fixed_fee: I80F48, + insurance_rate_fee: I80F48, + /// AKA group fixed fee + protocol_fixed_fee: I80F48, + /// AKA group rate fee + protocol_rate_fee: I80F48, + + program_fee_fixed: I80F48, + program_fee_rate: I80F48, + + add_program_fees: bool, +} + +impl InterestRateCalc { + /// Return interest rate charged to borrowers and to depositors. + /// Rate is denominated in APR (0-). + /// + /// Return ComputedInterestRates + pub fn calc_interest_rate(&self, utilization_ratio: I80F48) -> Option { + let Fees { + insurance_fee_rate, + insurance_fee_fixed, + group_fee_rate, + group_fee_fixed, + protocol_fee_rate, + protocol_fee_fixed, + } = self.get_fees(); + + let fee_ir = insurance_fee_rate + group_fee_rate + protocol_fee_rate; + let fee_fixed = insurance_fee_fixed + group_fee_fixed + protocol_fee_fixed; + + let base_rate = self.interest_rate_curve(utilization_ratio)?; + + // Lending rate is adjusted for utilization ratio to symmetrize payments between borrowers and depositors. + let lending_rate_apr = base_rate.checked_mul(utilization_ratio)?; + + // Borrowing rate is adjusted for fees. + // borrowing_rate = base_rate + base_rate * rate_fee + total_fixed_fee_apr + let borrowing_rate_apr = base_rate + .checked_mul(I80F48::ONE.checked_add(fee_ir)?)? + .checked_add(fee_fixed)?; + + let group_fee_apr = calc_fee_rate(base_rate, group_fee_rate, group_fee_fixed)?; + let insurance_fee_apr = calc_fee_rate(base_rate, insurance_fee_rate, insurance_fee_fixed)?; + let protocol_fee_apr = calc_fee_rate(base_rate, protocol_fee_rate, protocol_fee_fixed)?; + + assert!(lending_rate_apr >= I80F48::ZERO); + assert!(borrowing_rate_apr >= I80F48::ZERO); + assert!(group_fee_apr >= I80F48::ZERO); + assert!(insurance_fee_apr >= I80F48::ZERO); + assert!(protocol_fee_apr >= I80F48::ZERO); + + // TODO: Add liquidation discount check + Some(ComputedInterestRates { + lending_rate_apr, + borrowing_rate_apr, + group_fee_apr, + insurance_fee_apr, + protocol_fee_apr, + }) + } + + /// Piecewise linear interest rate function. + /// The curves approaches the `plateau_interest_rate` as the utilization ratio approaches the `optimal_utilization_rate`, + /// once the utilization ratio exceeds the `optimal_utilization_rate`, the curve approaches the `max_interest_rate`. + /// + /// To be clear we don't particularly appreciate the piecewise linear nature of this "curve", but it is what it is. + #[inline] + fn interest_rate_curve(&self, ur: I80F48) -> Option { + let optimal_ur: I80F48 = self.optimal_utilization_rate; + let plateau_ir: I80F48 = self.plateau_interest_rate; + let max_ir: I80F48 = self.max_interest_rate; + + if ur <= optimal_ur { + ur.checked_div(optimal_ur)?.checked_mul(plateau_ir) + } else { + (ur - optimal_ur) + .checked_div(I80F48::ONE - optimal_ur)? + .checked_mul(max_ir - plateau_ir)? + .checked_add(plateau_ir) + } + } + + pub fn get_fees(&self) -> Fees { + let (protocol_fee_rate, protocol_fee_fixed) = if self.add_program_fees { + (self.program_fee_rate, self.program_fee_fixed) + } else { + (I80F48::ZERO, I80F48::ZERO) + }; + + Fees { + insurance_fee_rate: self.insurance_rate_fee, + insurance_fee_fixed: self.insurance_fixed_fee, + group_fee_rate: self.protocol_rate_fee, + group_fee_fixed: self.protocol_fixed_fee, + protocol_fee_rate, + protocol_fee_fixed, + } + } +} + +#[derive(Debug, Clone)] +pub struct Fees { + pub insurance_fee_rate: I80F48, + pub insurance_fee_fixed: I80F48, + pub group_fee_rate: I80F48, + pub group_fee_fixed: I80F48, + pub protocol_fee_rate: I80F48, + pub protocol_fee_fixed: I80F48, +} + +#[derive(Debug, Clone)] +pub struct ComputedInterestRates { + pub lending_rate_apr: I80F48, + pub borrowing_rate_apr: I80F48, + pub group_fee_apr: I80F48, + pub insurance_fee_apr: I80F48, + pub protocol_fee_apr: I80F48, +} + +#[derive(AnchorDeserialize, AnchorSerialize, Default, Clone, Debug, PartialEq, Eq, TypeLayout)] pub struct InterestRateConfigOpt { pub optimal_utilization_rate: Option, pub plateau_interest_rate: Option, @@ -278,17 +435,20 @@ pub struct InterestRateConfigOpt { pub insurance_ir_fee: Option, pub protocol_fixed_fee_apr: Option, pub protocol_ir_fee: Option, + pub protocol_origination_fee: Option, +} + +/// Group level configuration to be used in bank accounts. +#[derive(Clone, Debug)] +pub struct GroupBankConfig { + pub program_fees: bool, } assert_struct_size!(Bank, 1856); assert_struct_align!(Bank, 8); -#[account(zero_copy(unsafe))] +#[account(zero_copy)] #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(Debug, PartialEq, Eq, TypeLayout) -)] -#[derive(Default)] +#[derive(Default, Debug, PartialEq, Eq, TypeLayout)] pub struct Bank { pub mint: Pubkey, pub mint_decimals: u8, @@ -312,6 +472,7 @@ pub struct Bank { pub _pad1: [u8; 4], // 4x u8 + 4 = 8 + /// Fees collected and pending withdraw for the `insurance_vault` pub collected_insurance_fees_outstanding: WrappedI80F48, pub fee_vault: Pubkey, @@ -320,6 +481,7 @@ pub struct Bank { pub _pad2: [u8; 6], // 2x u8 + 6 = 8 + /// Fees collected and pending withdraw for the `fee_vault` pub collected_group_fees_outstanding: WrappedI80F48, pub total_liability_shares: WrappedI80F48, @@ -334,6 +496,7 @@ pub struct Bank { /// - EMISSIONS_FLAG_BORROW_ACTIVE: 1 /// - EMISSIONS_FLAG_LENDING_ACTIVE: 2 /// - PERMISSIONLESS_BAD_DEBT_SETTLEMENT: 4 + /// - FREEZE_SETTINGS: 8 /// pub flags: u64, /// Emissions APR. @@ -342,11 +505,16 @@ pub struct Bank { pub emissions_remaining: WrappedI80F48, pub emissions_mint: Pubkey, - pub _padding_0: [[u64; 2]; 28], + /// Fees collected and pending withdraw for the `FeeState.global_fee_wallet`'s cannonical ATA for `mint` + pub collected_program_fees_outstanding: WrappedI80F48, + + pub _padding_0: [[u64; 2]; 27], pub _padding_1: [[u64; 2]; 32], // 16 * 2 * 32 = 1024B } impl Bank { + pub const LEN: usize = std::mem::size_of::(); + #[allow(clippy::too_many_arguments)] pub fn new( marginfi_group_pk: Pubkey, @@ -389,6 +557,7 @@ impl Bank { emissions_rate: 0, emissions_remaining: I80F48::ZERO.into(), emissions_mint: Pubkey::default(), + collected_program_fees_outstanding: I80F48::ZERO.into(), ..Default::default() } } @@ -537,16 +706,14 @@ impl Bank { set_if_some!(self.config.operational_state, config.operational_state); - set_if_some!(self.config.oracle_setup, config.oracle.map(|o| o.setup)); - - set_if_some!(self.config.oracle_keys, config.oracle.map(|o| o.keys)); - if let Some(ir_config) = &config.interest_rate_config { self.config.interest_rate_config.update(ir_config); } set_if_some!(self.config.risk_tier, config.risk_tier); + set_if_some!(self.config.asset_tag, config.asset_tag); + set_if_some!( self.config.total_asset_value_init_limit, config.total_asset_value_init_limit @@ -555,14 +722,34 @@ impl Bank { set_if_some!(self.config.oracle_max_age, config.oracle_max_age); if let Some(flag) = config.permissionless_bad_debt_settlement { + msg!( + "setting bad debt settlement: {:?}", + config.permissionless_bad_debt_settlement.unwrap() + ); self.update_flag(flag, PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG); } + if let Some(flag) = config.freeze_settings { + msg!( + "setting freeze settings: {:?}", + config.freeze_settings.unwrap() + ); + self.update_flag(flag, FREEZE_SETTINGS); + } + self.config.validate()?; Ok(()) } + /// Configures just the borrow and deposit limits, ignoring all other values + pub fn configure_unfrozen_fields_only(&mut self, config: &BankConfigOpt) -> MarginfiResult { + set_if_some!(self.config.deposit_limit, config.deposit_limit); + set_if_some!(self.config.borrow_limit, config.borrow_limit); + // weights didn't change so no validation is needed + Ok(()) + } + /// Calculate the interest rate accrual state changes for a given time period /// /// Collected protocol and insurance fees are stored in state. @@ -570,13 +757,13 @@ impl Bank { pub fn accrue_interest( &mut self, current_timestamp: i64, + group: &MarginfiGroup, #[cfg(not(feature = "client"))] bank: Pubkey, ) -> MarginfiResult<()> { #[cfg(all(not(feature = "client"), feature = "debug"))] solana_program::log::sol_log_compute_units(); let time_delta: u64 = (current_timestamp - self.last_update).try_into().unwrap(); - if time_delta == 0 { return Ok(()); } @@ -602,37 +789,58 @@ impl Bank { return Ok(()); } - - let (asset_share_value, liability_share_value, fees_collected, insurance_collected) = - calc_interest_rate_accrual_state_changes( - time_delta, - total_assets, - total_liabilities, - &self.config.interest_rate_config, - self.asset_share_value.into(), - self.liability_share_value.into(), - ) - .ok_or_else(math_error!())?; + let ir_calc = self + .config + .interest_rate_config + .create_interest_rate_calculator(group); + + let InterestRateStateChanges { + new_asset_share_value: asset_share_value, + new_liability_share_value: liability_share_value, + insurance_fees_collected, + group_fees_collected, + protocol_fees_collected, + } = calc_interest_rate_accrual_state_changes( + time_delta, + total_assets, + total_liabilities, + &ir_calc, + self.asset_share_value.into(), + self.liability_share_value.into(), + ) + .ok_or_else(math_error!())?; debug!("deposit share value: {}\nliability share value: {}\nfees collected: {}\ninsurance collected: {}", - asset_share_value, liability_share_value, fees_collected, insurance_collected); + asset_share_value, liability_share_value, group_fees_collected, insurance_fees_collected); self.asset_share_value = asset_share_value.into(); self.liability_share_value = liability_share_value.into(); - self.collected_group_fees_outstanding = { - fees_collected - .checked_add(self.collected_group_fees_outstanding.into()) - .ok_or_else(math_error!())? - .into() - }; + if group_fees_collected > I80F48::ZERO { + self.collected_group_fees_outstanding = { + group_fees_collected + .checked_add(self.collected_group_fees_outstanding.into()) + .ok_or_else(math_error!())? + .into() + }; + } - self.collected_insurance_fees_outstanding = { - insurance_collected - .checked_add(self.collected_insurance_fees_outstanding.into()) - .ok_or_else(math_error!())? - .into() - }; + if insurance_fees_collected > I80F48::ZERO { + self.collected_insurance_fees_outstanding = { + insurance_fees_collected + .checked_add(self.collected_insurance_fees_outstanding.into()) + .ok_or_else(math_error!())? + .into() + }; + } + if protocol_fees_collected > I80F48::ZERO { + self.collected_program_fees_outstanding = { + protocol_fees_collected + .checked_add(self.collected_program_fees_outstanding.into()) + .ok_or_else(math_error!())? + .into() + }; + } #[cfg(not(feature = "client"))] { @@ -647,8 +855,8 @@ impl Bank { bank, mint: self.mint, delta: time_delta, - fees_collected: fees_collected.to_num::(), - insurance_collected: insurance_collected.to_num::(), + fees_collected: group_fees_collected.to_num::(), + insurance_collected: insurance_fees_collected.to_num::(), }); } @@ -858,30 +1066,62 @@ fn calc_interest_rate_accrual_state_changes( time_delta: u64, total_assets_amount: I80F48, total_liabilities_amount: I80F48, - interest_rate_config: &InterestRateConfig, + interest_rate_calc: &InterestRateCalc, asset_share_value: I80F48, liability_share_value: I80F48, -) -> Option<(I80F48, I80F48, I80F48, I80F48)> { +) -> Option { let utilization_rate = total_liabilities_amount.checked_div(total_assets_amount)?; - let (lending_apr, borrowing_apr, group_fee_apr, insurance_fee_apr) = - interest_rate_config.calc_interest_rate(utilization_rate)?; + let computed_rates = interest_rate_calc.calc_interest_rate(utilization_rate)?; debug!( - "Accruing interest for {} seconds. Utilization rate: {}. Lending APR: {}. Borrowing APR: {}. Group fee APR: {}. Insurance fee APR: {}.", - time_delta, - utilization_rate, - lending_apr, - borrowing_apr, - group_fee_apr, - insurance_fee_apr + "Utilization rate: {}, time delta {}s", + utilization_rate, time_delta ); + debug!("{:#?}", computed_rates); + + let ComputedInterestRates { + lending_rate_apr, + borrowing_rate_apr, + group_fee_apr, + insurance_fee_apr, + protocol_fee_apr, + } = computed_rates; + + Some(InterestRateStateChanges { + new_asset_share_value: calc_accrued_interest_payment_per_period( + lending_rate_apr, + time_delta, + asset_share_value, + )?, + new_liability_share_value: calc_accrued_interest_payment_per_period( + borrowing_rate_apr, + time_delta, + liability_share_value, + )?, + insurance_fees_collected: calc_interest_payment_for_period( + insurance_fee_apr, + time_delta, + total_liabilities_amount, + )?, + group_fees_collected: calc_interest_payment_for_period( + group_fee_apr, + time_delta, + total_liabilities_amount, + )?, + protocol_fees_collected: calc_interest_payment_for_period( + protocol_fee_apr, + time_delta, + total_liabilities_amount, + )?, + }) +} - Some(( - calc_accrued_interest_payment_per_period(lending_apr, time_delta, asset_share_value)?, - calc_accrued_interest_payment_per_period(borrowing_apr, time_delta, liability_share_value)?, - calc_interest_payment_for_period(group_fee_apr, time_delta, total_liabilities_amount)?, - calc_interest_payment_for_period(insurance_fee_apr, time_delta, total_liabilities_amount)?, - )) +struct InterestRateStateChanges { + new_asset_share_value: I80F48, + new_liability_share_value: I80F48, + insurance_fees_collected: I80F48, + group_fees_collected: I80F48, + protocol_fees_collected: I80F48, } /// Calculates the fee rate for a given base rate and fees specified. @@ -889,6 +1129,10 @@ fn calc_interest_rate_accrual_state_changes( /// /// Used for calculating the fees charged to the borrowers. fn calc_fee_rate(base_rate: I80F48, rate_fees: I80F48, fixed_fees: I80F48) -> Option { + if rate_fees.is_zero() { + return Some(fixed_fees); + } + base_rate.checked_mul(rate_fees)?.checked_add(fixed_fees) } @@ -911,6 +1155,10 @@ fn calc_accrued_interest_payment_per_period( /// Calculates the interest payment for a given period `time_delta` in a principal value `value` for interest rate (in APR) `arp`. /// Result is the interest payment. fn calc_interest_payment_for_period(apr: I80F48, time_delta: u64, value: I80F48) -> Option { + if apr.is_zero() { + return Some(I80F48::ZERO); + } + let interest_payment = value .checked_mul(apr)? .checked_mul(time_delta.into())? @@ -920,13 +1168,14 @@ fn calc_interest_payment_for_period(apr: I80F48, time_delta: u64, value: I80F48) } #[repr(u8)] -#[cfg_attr(any(feature = "test", feature = "client"), derive(PartialEq, Eq))] -#[derive(Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize)] +#[derive(Debug, Clone, Copy, AnchorDeserialize, AnchorSerialize, PartialEq, Eq)] pub enum BankOperationalState { Paused, Operational, ReduceOnly, } +unsafe impl Zeroable for BankOperationalState {} +unsafe impl Pod for BankOperationalState {} #[cfg(feature = "client")] impl Display for BankOperationalState { @@ -940,24 +1189,23 @@ impl Display for BankOperationalState { } #[repr(u8)] -#[derive(Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq, Default)] pub enum RiskTier { - Collateral, + #[default] + Collateral = 0, /// ## Isolated Risk /// Assets in this trance can be borrowed only in isolation. /// They can't be borrowed together with other assets. /// /// For example, if users has USDC, and wants to borrow XYZ which is isolated, /// they can't borrow XYZ together with SOL, only XYZ alone. - Isolated, + Isolated = 1, } +unsafe impl Zeroable for RiskTier {} +unsafe impl Pod for RiskTier {} #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(PartialEq, Eq, TypeLayout) -)] -#[derive(AnchorDeserialize, AnchorSerialize, Debug)] +#[derive(AnchorDeserialize, AnchorSerialize, Debug, PartialEq, Eq)] /// TODO: Convert weights to (u64, u64) to avoid precision loss (maybe?) pub struct BankConfigCompact { pub asset_weight_init: WrappedI80F48, @@ -971,14 +1219,21 @@ pub struct BankConfigCompact { pub interest_rate_config: InterestRateConfigCompact, pub operational_state: BankOperationalState, - pub oracle_setup: OracleSetup, - pub oracle_key: Pubkey, - pub borrow_limit: u64, pub risk_tier: RiskTier, - pub _pad0: [u8; 7], + /// Determines what kinds of assets users of this bank can interact with. + /// Options: + /// * ASSET_TAG_DEFAULT (0) - A regular asset that can be comingled with any other regular asset + /// or with `ASSET_TAG_SOL` + /// * ASSET_TAG_SOL (1) - Accounts with a SOL position can comingle with **either** + /// `ASSET_TAG_DEFAULT` or `ASSET_TAG_STAKED` positions, but not both + /// * ASSET_TAG_STAKED (2) - Staked SOL assets. Accounts with a STAKED position can only deposit + /// other STAKED assets or SOL (`ASSET_TAG_SOL`) and can only borrow SOL + pub asset_tag: u8, + + pub _pad0: [u8; 6], /// USD denominated limit for calculating asset value for initialization margin requirements. /// Example, if total SOL deposits are equal to $1M and the limit it set to $500K, @@ -994,10 +1249,30 @@ pub struct BankConfigCompact { pub oracle_max_age: u16, } +impl Default for BankConfigCompact { + fn default() -> Self { + Self { + asset_weight_init: I80F48::ZERO.into(), + asset_weight_maint: I80F48::ZERO.into(), + liability_weight_init: I80F48::ONE.into(), + liability_weight_maint: I80F48::ONE.into(), + deposit_limit: 0, + borrow_limit: 0, + interest_rate_config: InterestRateConfigCompact::default(), + operational_state: BankOperationalState::Paused, + _pad0: [0; 6], + risk_tier: RiskTier::Isolated, + asset_tag: ASSET_TAG_DEFAULT, + total_asset_value_init_limit: TOTAL_ASSET_VALUE_INIT_LIMIT_INACTIVE, + oracle_max_age: 0, + } + } +} + impl From for BankConfig { fn from(config: BankConfigCompact) -> Self { let keys = [ - config.oracle_key, + Pubkey::default(), Pubkey::default(), Pubkey::default(), Pubkey::default(), @@ -1011,15 +1286,17 @@ impl From for BankConfig { deposit_limit: config.deposit_limit, interest_rate_config: config.interest_rate_config.into(), operational_state: config.operational_state, - oracle_setup: config.oracle_setup, + oracle_setup: OracleSetup::None, oracle_keys: keys, _pad0: [0; 6], borrow_limit: config.borrow_limit, risk_tier: config.risk_tier, - _pad1: [0; 7], + asset_tag: config.asset_tag, + _pad1: [0; 6], total_asset_value_init_limit: config.total_asset_value_init_limit, oracle_max_age: config.oracle_max_age, - _padding: [0; 38], + _padding0: [0; 6], + _padding1: [0; 32], } } } @@ -1034,11 +1311,10 @@ impl From for BankConfigCompact { deposit_limit: config.deposit_limit, interest_rate_config: config.interest_rate_config.into(), operational_state: config.operational_state, - oracle_setup: config.oracle_setup, - oracle_key: config.oracle_keys[0], borrow_limit: config.borrow_limit, risk_tier: config.risk_tier, - _pad0: [0; 7], + asset_tag: config.asset_tag, + _pad0: [0; 6], total_asset_value_init_limit: config.total_asset_value_init_limit, oracle_max_age: config.oracle_max_age, } @@ -1047,13 +1323,10 @@ impl From for BankConfigCompact { assert_struct_size!(BankConfig, 544); assert_struct_align!(BankConfig, 8); -#[zero_copy(unsafe)] #[repr(C)] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(PartialEq, Eq, TypeLayout) +#[derive( + Debug, Clone, Copy, AnchorDeserialize, AnchorSerialize, Zeroable, Pod, PartialEq, Eq, TypeLayout, )] -#[derive(Debug)] /// TODO: Convert weights to (u64, u64) to avoid precision loss (maybe?) pub struct BankConfig { pub asset_weight_init: WrappedI80F48, @@ -1077,7 +1350,17 @@ pub struct BankConfig { pub risk_tier: RiskTier, - pub _pad1: [u8; 7], + /// Determines what kinds of assets users of this bank can interact with. + /// Options: + /// * ASSET_TAG_DEFAULT (0) - A regular asset that can be comingled with any other regular asset + /// or with `ASSET_TAG_SOL` + /// * ASSET_TAG_SOL (1) - Accounts with a SOL position can comingle with **either** + /// `ASSET_TAG_DEFAULT` or `ASSET_TAG_STAKED` positions, but not both + /// * ASSET_TAG_STAKED (2) - Staked SOL assets. Accounts with a STAKED position can only deposit + /// other STAKED assets or SOL (`ASSET_TAG_SOL`) and can only borrow SOL + pub asset_tag: u8, + + pub _pad1: [u8; 6], /// USD denominated limit for calculating asset value for initialization margin requirements. /// Example, if total SOL deposits are equal to $1M and the limit it set to $500K, @@ -1092,7 +1375,9 @@ pub struct BankConfig { /// Time window in seconds for the oracle price feed to be considered live. pub oracle_max_age: u16, - pub _padding: [u8; 38], + // Note: 6 bytes of padding to next 8 byte alignment, then end padding + pub _padding0: [u8; 6], + pub _padding1: [u8; 32], } impl Default for BankConfig { @@ -1110,10 +1395,12 @@ impl Default for BankConfig { oracle_keys: [Pubkey::default(); MAX_ORACLE_KEYS], _pad0: [0; 6], risk_tier: RiskTier::Isolated, - _pad1: [0; 7], + asset_tag: ASSET_TAG_DEFAULT, + _pad1: [0; 6], total_asset_value_init_limit: TOTAL_ASSET_VALUE_INIT_LIMIT_INACTIVE, oracle_max_age: 0, - _padding: [0; 38], + _padding0: [0; 6], + _padding1: [0; 32], } } } @@ -1192,8 +1479,25 @@ impl BankConfig { self.borrow_limit != u64::MAX } - pub fn validate_oracle_setup(&self, ais: &[AccountInfo]) -> MarginfiResult { - OraclePriceFeedAdapter::validate_bank_config(self, ais)?; + /// * lst_mint, stake_pool, sol_pool - required only if configuring + /// `OracleSetup::StakedWithPythPush` on initial setup. If configuring a staked bank after + /// initial setup, can be omitted + pub fn validate_oracle_setup( + &self, + ais: &[AccountInfo], + lst_mint: Option, + stake_pool: Option, + sol_pool: Option, + ) -> MarginfiResult { + OraclePriceFeedAdapter::validate_bank_config(self, ais, lst_mint, stake_pool, sol_pool)?; + Ok(()) + } + + pub fn validate_oracle_age(&self) -> MarginfiResult { + check!( + self.oracle_max_age >= ORACLE_MIN_AGE, + MarginfiError::InvalidOracleSetup + ); Ok(()) } @@ -1211,7 +1515,10 @@ impl BankConfig { } pub fn get_pyth_push_oracle_feed_id(&self) -> Option<&FeedId> { - if matches!(self.oracle_setup, OracleSetup::PythPushOracle) { + if matches!( + self.oracle_setup, + OracleSetup::PythPushOracle | OracleSetup::StakedWithPythPush + ) { let bytes: &[u8; 32] = self.oracle_keys[0].as_ref().try_into().unwrap(); Some(bytes) } else { @@ -1222,11 +1529,7 @@ impl BankConfig { #[zero_copy] #[repr(C, align(8))] -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(PartialEq, Eq, TypeLayout) -)] -#[derive(Default, BorshDeserialize, BorshSerialize)] +#[derive(Default, BorshDeserialize, BorshSerialize, TypeLayout)] pub struct WrappedI80F48 { pub value: [u8; 16], } @@ -1251,11 +1554,15 @@ impl From for I80F48 { } } -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(Clone, PartialEq, Eq, TypeLayout) -)] -#[derive(AnchorDeserialize, AnchorSerialize, Default)] +impl PartialEq for WrappedI80F48 { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl Eq for WrappedI80F48 {} + +#[derive(AnchorDeserialize, AnchorSerialize, Default, Clone, PartialEq, Eq, TypeLayout)] pub struct BankConfigOpt { pub asset_weight_init: Option, pub asset_weight_maint: Option, @@ -1268,27 +1575,19 @@ pub struct BankConfigOpt { pub operational_state: Option, - pub oracle: Option, - pub interest_rate_config: Option, pub risk_tier: Option, + pub asset_tag: Option, + pub total_asset_value_init_limit: Option, pub oracle_max_age: Option, pub permissionless_bad_debt_settlement: Option, -} -#[cfg_attr( - any(feature = "test", feature = "client"), - derive(PartialEq, Eq, TypeLayout) -)] -#[derive(Clone, Copy, AnchorDeserialize, AnchorSerialize, Debug)] -pub struct OracleConfig { - pub setup: OracleSetup, - pub keys: [Pubkey; MAX_ORACLE_KEYS], + pub freeze_settings: Option, } #[derive(Debug, Clone)] @@ -1333,6 +1632,8 @@ macro_rules! assert_eq_with_tolerance { mod tests { use std::time::{SystemTime, UNIX_EPOCH}; + use crate::constants::{PROTOCOL_FEE_FIXED_DEFAULT, PROTOCOL_FEE_RATE_DEFAULT}; + use super::*; use fixed_macro::types::I80F48; @@ -1429,13 +1730,22 @@ mod tests { ..Default::default() }; - let (lending_apr, borrow_apr, group_fees_apr, insurance_apr) = - config.calc_interest_rate(I80F48!(0)).unwrap(); + let ComputedInterestRates { + lending_rate_apr: lending_apr, + borrowing_rate_apr: borrow_apr, + group_fee_apr: group_fees_apr, + insurance_fee_apr: insurance_apr, + protocol_fee_apr, + } = config + .create_interest_rate_calculator(&MarginfiGroup::default()) + .calc_interest_rate(I80F48!(0.6)) + .unwrap(); - assert_eq_with_tolerance!(lending_apr, I80F48!(0), I80F48!(0.001)); - assert_eq_with_tolerance!(borrow_apr, I80F48!(0.01), I80F48!(0.001)); + assert_eq_with_tolerance!(lending_apr, I80F48!(0.24), I80F48!(0.001)); + assert_eq_with_tolerance!(borrow_apr, I80F48!(0.41), I80F48!(0.001)); assert_eq_with_tolerance!(group_fees_apr, I80F48!(0.01), I80F48!(0.001)); assert_eq_with_tolerance!(insurance_apr, I80F48!(0), I80F48!(0.001)); + assert_eq_with_tolerance!(protocol_fee_apr, I80F48!(0), I80F48!(0.001)); } #[test] @@ -1452,8 +1762,16 @@ mod tests { ..Default::default() }; - let (lending_apr, borrow_apr, group_fees_apr, insurance_apr) = - config.calc_interest_rate(I80F48!(0.5)).unwrap(); + let ComputedInterestRates { + lending_rate_apr: lending_apr, + borrowing_rate_apr: borrow_apr, + group_fee_apr: group_fees_apr, + insurance_fee_apr: insurance_apr, + protocol_fee_apr: _, + } = config + .create_interest_rate_calculator(&MarginfiGroup::default()) + .calc_interest_rate(I80F48!(0.5)) + .unwrap(); assert_eq_with_tolerance!(lending_apr, I80F48!(0.2), I80F48!(0.001)); assert_eq_with_tolerance!(borrow_apr, I80F48!(0.45), I80F48!(0.001)); @@ -1461,6 +1779,18 @@ mod tests { assert_eq_with_tolerance!(insurance_apr, I80F48!(0.04), I80F48!(0.001)); } + #[test] + fn calc_fee_rate_1() { + let rate = I80F48!(0.4); + let fee_ir = I80F48!(0.05); + let fee_fixed = I80F48!(0.01); + + assert_eq!( + calc_fee_rate(rate, fee_ir, fee_fixed).unwrap(), + I80F48!(0.03) + ); + } + /// ur: 0.8 /// protocol_fixed_fee: 0.01 /// optimal_utilization_rate: 0.5 @@ -1478,8 +1808,16 @@ mod tests { ..Default::default() }; - let (lending_apr, borrow_apr, group_fees_apr, insurance_apr) = - config.calc_interest_rate(I80F48!(0.7)).unwrap(); + let ComputedInterestRates { + lending_rate_apr: lending_apr, + borrowing_rate_apr: borrow_apr, + group_fee_apr: group_fees_apr, + insurance_fee_apr: insurance_apr, + protocol_fee_apr: _, + } = config + .create_interest_rate_calculator(&MarginfiGroup::default()) + .calc_interest_rate(I80F48!(0.7)) + .unwrap(); assert_eq_with_tolerance!(lending_apr, I80F48!(1.19), I80F48!(0.001)); assert_eq_with_tolerance!(borrow_apr, I80F48!(1.88), I80F48!(0.001)); @@ -1531,6 +1869,7 @@ mod tests { bank.accrue_interest( current_timestamp, + &MarginfiGroup::default(), #[cfg(not(feature = "client"))] Pubkey::default(), ) @@ -1560,20 +1899,72 @@ mod tests { }; let ur = I80F48!(207_112_621_602) / I80F48!(10_000_000_000_000); + let mut group = MarginfiGroup::default(); + group.group_flags = 1; + group.fee_state_cache.program_fee_fixed = PROTOCOL_FEE_FIXED_DEFAULT.into(); + group.fee_state_cache.program_fee_rate = PROTOCOL_FEE_RATE_DEFAULT.into(); + + let ComputedInterestRates { + lending_rate_apr: lending_apr, + borrowing_rate_apr: borrow_apr, + group_fee_apr, + insurance_fee_apr, + protocol_fee_apr, + } = ir_config + .create_interest_rate_calculator(&group) + .calc_interest_rate(ur) + .expect("interest rate calculation failed"); - let (lending_apr, borrow_apr, fees_apr, insurance_apr) = ir_config + println!("ur: {}", ur); + println!("lending_apr: {}", lending_apr); + println!("borrow_apr: {}", borrow_apr); + println!("group_fee_apr: {}", group_fee_apr); + println!("insurance_fee_apr: {}", insurance_fee_apr); + + assert_eq_with_tolerance!( + borrow_apr, + (lending_apr / ur) + group_fee_apr + insurance_fee_apr + protocol_fee_apr, + I80F48!(0.001) + ); + + Ok(()) + } + + #[test] + fn interest_rate_accrual_test_0_no_protocol_fees() -> anyhow::Result<()> { + let ir_config = InterestRateConfig { + optimal_utilization_rate: I80F48!(0.4).into(), + plateau_interest_rate: I80F48!(0.4).into(), + protocol_fixed_fee_apr: I80F48!(0.01).into(), + max_interest_rate: I80F48!(3).into(), + insurance_ir_fee: I80F48!(0.1).into(), + ..Default::default() + }; + + let ur = I80F48!(207_112_621_602) / I80F48!(10_000_000_000_000); + + let ComputedInterestRates { + lending_rate_apr: lending_apr, + borrowing_rate_apr: borrow_apr, + group_fee_apr, + insurance_fee_apr, + protocol_fee_apr, + } = ir_config + .create_interest_rate_calculator(&MarginfiGroup::default()) .calc_interest_rate(ur) .expect("interest rate calculation failed"); println!("ur: {}", ur); println!("lending_apr: {}", lending_apr); println!("borrow_apr: {}", borrow_apr); - println!("fees_apr: {}", fees_apr); - println!("insurance_apr: {}", insurance_apr); + println!("group_fee_apr: {}", group_fee_apr); + println!("insurance_fee_apr: {}", insurance_fee_apr); + + assert!(protocol_fee_apr.is_zero()); assert_eq_with_tolerance!( borrow_apr, - (lending_apr / ur) + fees_apr + insurance_apr, + (lending_apr / ur) + group_fee_apr + insurance_fee_apr, I80F48!(0.001) ); @@ -1591,6 +1982,11 @@ mod tests { ..Default::default() }; + let mut group = MarginfiGroup::default(); + group.group_flags = 1; + group.fee_state_cache.program_fee_fixed = PROTOCOL_FEE_FIXED_DEFAULT.into(); + group.fee_state_cache.program_fee_rate = PROTOCOL_FEE_RATE_DEFAULT.into(); + let liab_share_value = I80F48!(1.0); let asset_share_value = I80F48!(1.0); @@ -1600,24 +1996,30 @@ mod tests { let old_total_liability_amount = liab_share_value * total_liability_shares; let old_total_asset_amount = asset_share_value * total_asset_shares; - let (new_asset_share_value, new_liab_share_value, fees_collected, insurance_collected) = - calc_interest_rate_accrual_state_changes( - 3600, - total_asset_shares, - total_liability_shares, - &ir_config, - asset_share_value, - liab_share_value, - ) - .unwrap(); + let InterestRateStateChanges { + new_asset_share_value, + new_liability_share_value: new_liab_share_value, + insurance_fees_collected: insurance_collected, + group_fees_collected, + protocol_fees_collected, + } = calc_interest_rate_accrual_state_changes( + 3600, + total_asset_shares, + total_liability_shares, + &ir_config.create_interest_rate_calculator(&group), + asset_share_value, + liab_share_value, + ) + .unwrap(); let new_total_liability_amount = total_liability_shares * new_liab_share_value; let new_total_asset_amount = total_asset_shares * new_asset_share_value; println!("new_asset_share_value: {}", new_asset_share_value); println!("new_liab_share_value: {}", new_liab_share_value); - println!("fees_collected: {}", fees_collected); + println!("group_fees_collected: {}", group_fees_collected); println!("insurance_collected: {}", insurance_collected); + println!("protocol_fees_collected: {}", protocol_fees_collected); println!("new_total_liability_amount: {}", new_total_liability_amount); println!("new_total_asset_amount: {}", new_total_asset_amount); @@ -1625,23 +2027,19 @@ mod tests { println!("old_total_liability_amount: {}", old_total_liability_amount); println!("old_total_asset_amount: {}", old_total_asset_amount); - println!( - "total_fee_collected: {}", - fees_collected + insurance_collected - ); + let total_fees_collected = + group_fees_collected + insurance_collected + protocol_fees_collected; + + println!("total_fee_collected: {}", total_fees_collected); println!( "diff: {}", - ((new_total_asset_amount - new_total_liability_amount) - + fees_collected - + insurance_collected) + ((new_total_asset_amount - new_total_liability_amount) + total_fees_collected) - (old_total_asset_amount - old_total_liability_amount) ); assert_eq_with_tolerance!( - (new_total_asset_amount - new_total_liability_amount) - + fees_collected - + insurance_collected, + (new_total_asset_amount - new_total_liability_amount) + total_fees_collected, old_total_asset_amount - old_total_liability_amount, I80F48::ONE ); diff --git a/programs/marginfi/src/state/mod.rs b/programs/marginfi/src/state/mod.rs index 710755626..66865081b 100644 --- a/programs/marginfi/src/state/mod.rs +++ b/programs/marginfi/src/state/mod.rs @@ -1,3 +1,6 @@ +pub mod fee_state; +pub mod health_cache; pub mod marginfi_account; pub mod marginfi_group; pub mod price; +pub mod staked_settings; diff --git a/programs/marginfi/src/state/price.rs b/programs/marginfi/src/state/price.rs index 7a55ede95..bdf26f7cd 100644 --- a/programs/marginfi/src/state/price.rs +++ b/programs/marginfi/src/state/price.rs @@ -1,19 +1,23 @@ -use std::{cell::Ref, cmp::min}; - use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use bytemuck::{Pod, Zeroable}; use enum_dispatch::enum_dispatch; use fixed::types::I80F48; pub use pyth_sdk_solana; use pyth_sdk_solana::{state::SolanaPriceAccount, Price, PriceFeed}; use pyth_solana_receiver_sdk::price_update::{self, FeedId, PriceUpdateV2}; +use solana_program::borsh::try_from_slice_unchecked; +use solana_program::stake::state::StakeStateV2; +use std::cmp::min; use crate::{ - check, + check, check_eq, constants::{ CONF_INTERVAL_MULTIPLE, EXP_10, EXP_10_I80F48, MAX_CONF_INTERVAL, - MIN_PYTH_PUSH_VERIFICATION_LEVEL, PYTH_ID, STD_DEV_MULTIPLE, SWITCHBOARD_PULL_ID, + MIN_PYTH_PUSH_VERIFICATION_LEVEL, NATIVE_STAKE_ID, PYTH_ID, SPL_SINGLE_POOL_ID, + STD_DEV_MULTIPLE, SWITCHBOARD_PULL_ID, }, - debug, math_error, + debug, live, math_error, prelude::*, }; @@ -26,7 +30,8 @@ use pyth_solana_receiver_sdk::PYTH_PUSH_ORACLE_ID; // ============================================================================ /// Switchboard V2 program ID -pub const SWITCHBOARD_PROGRAM_ID: Pubkey = solana_program::pubkey!("SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"); +pub const SWITCHBOARD_PROGRAM_ID: Pubkey = + solana_program::pubkey!("SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"); /// Switchboard decimal representation #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] @@ -94,7 +99,7 @@ impl AggregatorAccountData { pub fn new_from_bytes(data: &[u8]) -> Result { // Basic validation - switchboard v2 aggregator accounts are typically ~3kb if data.len() < 300 { - return Err(MarginfiError::InvalidOracleAccount.into()); + return Err(MarginfiError::InvalidBankAccount.into()); } // Resolution mode is at offset 8 @@ -102,35 +107,27 @@ impl AggregatorAccountData { let resolution_mode = match resolution_mode_byte { 0 => AggregatorResolutionMode::ModeRoundResolution, 1 => AggregatorResolutionMode::ModeSlidingResolution, - _ => return Err(MarginfiError::InvalidOracleAccount.into()), + _ => return Err(MarginfiError::InvalidBankAccount.into()), }; // min_oracle_results: u32 at offset 98 - let min_oracle_results = u32::from_le_bytes([ - data[98], data[99], data[100], data[101] - ]); + let min_oracle_results = u32::from_le_bytes([data[98], data[99], data[100], data[101]]); // Latest confirmed round data // num_success: u32 at offset 226 - let num_success = u32::from_le_bytes([ - data[226], data[227], data[228], data[229] - ]); + let num_success = u32::from_le_bytes([data[226], data[227], data[228], data[229]]); // std_deviation: SwitchboardDecimal (mantissa: i128, scale: u32) // mantissa at offset 246, scale at offset 262 let std_mantissa = i128::from_le_bytes([ - data[246], data[247], data[248], data[249], - data[250], data[251], data[252], data[253], - data[254], data[255], data[256], data[257], - data[258], data[259], data[260], data[261], - ]); - let std_scale = u32::from_le_bytes([ - data[262], data[263], data[264], data[265] + data[246], data[247], data[248], data[249], data[250], data[251], data[252], data[253], + data[254], data[255], data[256], data[257], data[258], data[259], data[260], data[261], ]); + let std_scale = u32::from_le_bytes([data[262], data[263], data[264], data[265]]); let std_deviation = SwitchboardDecimal { mantissa: std_mantissa, - scale: std_scale + scale: std_scale, }; Ok(Self { @@ -156,14 +153,30 @@ impl AggregatorAccountData { // ============================================================================ #[repr(u8)] -#[cfg_attr(any(feature = "test", feature = "client"), derive(PartialEq, Eq))] -#[derive(Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize)] +#[derive(Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] pub enum OracleSetup { None, PythLegacy, SwitchboardV2, PythPushOracle, SwitchboardPull, + StakedWithPythPush, +} +unsafe impl Zeroable for OracleSetup {} +unsafe impl Pod for OracleSetup {} + +impl OracleSetup { + pub fn from_u8(value: u8) -> Option { + match value { + 0 => Some(Self::None), + 1 => Some(Self::PythLegacy), + 2 => Some(Self::SwitchboardV2), + 3 => Some(Self::PythPushOracle), + 4 => Some(Self::SwitchboardPull), + 5 => Some(Self::StakedWithPythPush), + _ => None, + } + } } #[derive(Copy, Clone, Debug)] @@ -200,9 +213,9 @@ pub enum OraclePriceFeedAdapter { } impl OraclePriceFeedAdapter { - pub fn try_from_bank_config( + pub fn try_from_bank_config<'info>( bank_config: &BankConfig, - ais: &[AccountInfo], + ais: &'info [AccountInfo<'info>], clock: &Clock, ) -> MarginfiResult { Self::try_from_bank_config_with_max_age( @@ -213,20 +226,24 @@ impl OraclePriceFeedAdapter { ) } - pub fn try_from_bank_config_with_max_age( + pub fn try_from_bank_config_with_max_age<'info>( bank_config: &BankConfig, - ais: &[AccountInfo], + ais: &'info [AccountInfo<'info>], clock: &Clock, max_age: u64, ) -> MarginfiResult { match bank_config.oracle_setup { OracleSetup::None => Err(MarginfiError::OracleNotSetup.into()), OracleSetup::PythLegacy => { - check!(ais.len() == 1, MarginfiError::InvalidOracleAccount); - check!( - ais[0].key == &bank_config.oracle_keys[0], - MarginfiError::InvalidOracleAccount - ); + check!(ais.len() == 1, MarginfiError::WrongNumberOfOracleAccounts); + if ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } let account_info = &ais[0]; @@ -235,25 +252,37 @@ impl OraclePriceFeedAdapter { )) } OracleSetup::SwitchboardV2 => { - check!(ais.len() == 1, MarginfiError::InvalidOracleAccount); - check!( - ais[0].key == &bank_config.oracle_keys[0], - MarginfiError::InvalidOracleAccount - ); + check!(ais.len() == 1, MarginfiError::WrongNumberOfOracleAccounts); + if ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } Ok(OraclePriceFeedAdapter::SwitchboardV2( SwitchboardV2PriceFeed::load_checked(&ais[0], clock.unix_timestamp, max_age)?, )) } OracleSetup::PythPushOracle => { - check!(ais.len() == 1, MarginfiError::InvalidOracleAccount); + check!(ais.len() == 1, MarginfiError::WrongNumberOfOracleAccounts); let account_info = &ais[0]; - check!( - account_info.owner == &pyth_solana_receiver_sdk::id(), - MarginfiError::InvalidOracleAccount - ); + if live!() { + check_eq!( + *account_info.owner, + pyth_solana_receiver_sdk::id(), + MarginfiError::PythPushWrongAccountOwner + ); + } else { + // On localnet, allow the mock program ID -OR- the real one + let owner_ok = account_info.owner.eq(&PYTH_ID) + || account_info.owner.eq(&pyth_solana_receiver_sdk::id()); + check!(owner_ok, MarginfiError::PythPushWrongAccountOwner); + } let price_feed_id = bank_config.get_pyth_push_oracle_feed_id().unwrap(); @@ -267,49 +296,187 @@ impl OraclePriceFeedAdapter { )) } OracleSetup::SwitchboardPull => { - check!(ais.len() == 1, MarginfiError::InvalidOracleAccount); - check!( - ais[0].key == &bank_config.oracle_keys[0], - MarginfiError::InvalidOracleAccount - ); + check!(ais.len() == 1, MarginfiError::WrongNumberOfOracleAccounts); + if ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } Ok(OraclePriceFeedAdapter::SwitchboardPull( SwitchboardPullPriceFeed::load_checked(&ais[0], clock.unix_timestamp, max_age)?, )) } + OracleSetup::StakedWithPythPush => { + check!(ais.len() == 3, MarginfiError::WrongNumberOfOracleAccounts); + + if ais[1].key != &bank_config.oracle_keys[1] + || ais[2].key != &bank_config.oracle_keys[2] + { + msg!( + "Expected oracle keys: [1] {:?}, [2] {:?}, got: [1] {:?}, [2] {:?}", + bank_config.oracle_keys[1], + bank_config.oracle_keys[2], + ais[1].key, + ais[2].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } + + let lst_mint = Account::<'info, Mint>::try_from(&ais[1]).unwrap(); + let lst_supply = lst_mint.supply; + let stake_state = try_from_slice_unchecked::(&ais[2].data.borrow())?; + let (_, stake) = match stake_state { + StakeStateV2::Stake(meta, stake, _) => (meta, stake), + _ => panic!("unsupported stake state"), // TODO emit more specific error + }; + let sol_pool_balance = stake.delegation.stake; + // Note: When the pool is fresh, it has 1 SOL in it (an initial and non-refundable + // balance that will stay in the pool forever). We don't want to include that + // balance when reading the quantity of SOL that has been staked from actual + // depositors (i.e. the amount that can actually be redeemed again). + let lamports_per_sol: u64 = 1_000_000_000; + let sol_pool_adjusted_balance = sol_pool_balance + .checked_sub(lamports_per_sol) + .ok_or_else(math_error!())?; + // Note: exchange rate is `sol_pool_balance / lst_supply`, but we will do the + // division last to avoid precision loss. Division does not need to be + // decimal-adjusted because both SOL and stake positions use 9 decimals + + // Note: mainnet/staging/devnet use "push" oracles, localnet uses legacy + if cfg!(any( + feature = "mainnet-beta", + feature = "staging", + feature = "devnet" + )) { + let account_info = &ais[0]; + + check_eq!( + account_info.owner, + &pyth_solana_receiver_sdk::id(), + MarginfiError::StakedPythPushWrongAccountOwner + ); + + let price_feed_id = bank_config.get_pyth_push_oracle_feed_id().unwrap(); + let mut feed = PythPushOraclePriceFeed::load_checked( + account_info, + price_feed_id, + clock, + max_age, + )?; + let adjusted_price = (feed.price.price as i128) + .checked_mul(sol_pool_adjusted_balance as i128) + .ok_or_else(math_error!())? + .checked_div(lst_supply as i128) + .ok_or_else(math_error!())?; + feed.price.price = adjusted_price.try_into().unwrap(); + + let adjusted_ema_price = (feed.ema_price.price as i128) + .checked_mul(sol_pool_adjusted_balance as i128) + .ok_or_else(math_error!())? + .checked_div(lst_supply as i128) + .ok_or_else(math_error!())?; + feed.ema_price.price = adjusted_ema_price.try_into().unwrap(); + + let price = OraclePriceFeedAdapter::PythPushOracle(feed); + Ok(price) + } else { + // Localnet only + + if ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } + + let account_info = &ais[0]; + let mut feed = PythLegacyPriceFeed::load_checked( + account_info, + clock.unix_timestamp, + max_age, + )?; + + let adjusted_price = (feed.price.price as i128) + .checked_mul(sol_pool_adjusted_balance as i128) + .ok_or_else(math_error!())? + .checked_div(lst_supply as i128) + .ok_or_else(math_error!())?; + feed.price.price = adjusted_price.try_into().unwrap(); + + let adjusted_ema_price = (feed.ema_price.price as i128) + .checked_mul(sol_pool_adjusted_balance as i128) + .ok_or_else(math_error!())? + .checked_div(lst_supply as i128) + .ok_or_else(math_error!())?; + feed.ema_price.price = adjusted_ema_price.try_into().unwrap(); + + let price = OraclePriceFeedAdapter::PythLegacy(feed); + Ok(price) + } + } } } + /// * lst_mint, stake_pool, sol_pool - required only if configuring + /// `OracleSetup::StakedWithPythPush` initially. (subsequent validations of staked banks can + /// omit these) pub fn validate_bank_config( bank_config: &BankConfig, oracle_ais: &[AccountInfo], + lst_mint: Option, + stake_pool: Option, + sol_pool: Option, ) -> MarginfiResult { match bank_config.oracle_setup { OracleSetup::None => Err(MarginfiError::OracleNotSetup.into()), OracleSetup::PythLegacy => { - check!(oracle_ais.len() == 1, MarginfiError::InvalidOracleAccount); check!( - oracle_ais[0].key == &bank_config.oracle_keys[0], - MarginfiError::InvalidOracleAccount + oracle_ais.len() == 1, + MarginfiError::WrongNumberOfOracleAccounts ); + if oracle_ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + oracle_ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } + PythLegacyPriceFeed::check_ais(&oracle_ais[0])?; Ok(()) } OracleSetup::SwitchboardV2 => { - check!(oracle_ais.len() == 1, MarginfiError::InvalidOracleAccount); check!( - oracle_ais[0].key == &bank_config.oracle_keys[0], - MarginfiError::InvalidOracleAccount + oracle_ais.len() == 1, + MarginfiError::WrongNumberOfOracleAccounts ); + if oracle_ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + oracle_ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } SwitchboardV2PriceFeed::check_ais(&oracle_ais[0])?; Ok(()) } OracleSetup::PythPushOracle => { - check!(oracle_ais.len() == 1, MarginfiError::InvalidOracleAccount); + check!( + oracle_ais.len() == 1, + MarginfiError::WrongNumberOfOracleAccounts + ); PythPushOraclePriceFeed::check_ai_and_feed_id( &oracle_ais[0], @@ -319,16 +486,108 @@ impl OraclePriceFeedAdapter { Ok(()) } OracleSetup::SwitchboardPull => { - check!(oracle_ais.len() == 1, MarginfiError::InvalidOracleAccount); check!( - oracle_ais[0].key == &bank_config.oracle_keys[0], - MarginfiError::InvalidOracleAccount + oracle_ais.len() == 1, + MarginfiError::WrongNumberOfOracleAccounts ); + if oracle_ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + oracle_ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } SwitchboardPullPriceFeed::check_ais(&oracle_ais[0])?; Ok(()) } + OracleSetup::StakedWithPythPush => { + if lst_mint.is_some() && stake_pool.is_some() && sol_pool.is_some() { + check!( + oracle_ais.len() == 3, + MarginfiError::WrongNumberOfOracleAccounts + ); + + // Note: mainnet/staging/devnet use "push" oracles, localnet uses legacy + if live!() { + PythPushOraclePriceFeed::check_ai_and_feed_id( + &oracle_ais[0], + bank_config.get_pyth_push_oracle_feed_id().unwrap(), + )?; + } else { + // Localnet only + if oracle_ais[0].key != &bank_config.oracle_keys[0] { + msg!( + "Expected oracle key: {:?}, got: {:?}", + bank_config.oracle_keys[0], + oracle_ais[0].key + ); + return Err(error!(MarginfiError::WrongOracleAccountKeys)); + } + + PythLegacyPriceFeed::check_ais(&oracle_ais[0])?; + } + + let lst_mint = lst_mint.unwrap(); + let stake_pool = stake_pool.unwrap(); + let sol_pool = sol_pool.unwrap(); + + let program_id = &SPL_SINGLE_POOL_ID; + let stake_pool_bytes = &stake_pool.to_bytes(); + // Validate the given stake_pool derives the same lst_mint, proving stake_pool is correct + let (exp_mint, _) = + Pubkey::find_program_address(&[b"mint", stake_pool_bytes], program_id); + check_eq!(exp_mint, lst_mint, MarginfiError::StakePoolValidationFailed); + // Validate the now-proven stake_pool derives the given sol_pool + let (exp_pool, _) = + Pubkey::find_program_address(&[b"stake", stake_pool_bytes], program_id); + check_eq!(exp_pool, sol_pool, MarginfiError::StakePoolValidationFailed); + + // Sanity check the mint. Note: spl-single-pool uses a classic Token, never Token22 + check!( + oracle_ais[1].owner == &anchor_spl::token::spl_token::ID, + MarginfiError::StakePoolValidationFailed + ); + check_eq!( + oracle_ais[1].key(), + lst_mint, + MarginfiError::StakePoolValidationFailed + ); + // Sanity check the pool is a native stake pool. Note: the native staking program is + // written in vanilla Solana and has no Anchor discriminator. + check!( + oracle_ais[2].owner == &NATIVE_STAKE_ID, + MarginfiError::StakePoolValidationFailed + ); + check_eq!( + oracle_ais[2].key(), + sol_pool, + MarginfiError::StakePoolValidationFailed + ); + + Ok(()) + } else { + // light validation (after initial setup, only the Pyth oracle needs to be validated) + check!( + oracle_ais.len() == 1, + MarginfiError::WrongNumberOfOracleAccounts + ); + // Note: mainnet/staging/devnet use push oracles, localnet uses legacy push + if live!() { + PythPushOraclePriceFeed::check_ai_and_feed_id( + &oracle_ais[0], + bank_config.get_pyth_push_oracle_feed_id().unwrap(), + )?; + } else { + // Localnet only + PythLegacyPriceFeed::check_ais(&oracle_ais[0])?; + } + + Ok(()) + } + } } } } @@ -343,13 +602,22 @@ impl PythLegacyPriceFeed { pub fn load_checked(ai: &AccountInfo, current_time: i64, max_age: u64) -> MarginfiResult { let price_feed = load_pyth_price_feed(ai)?; - let ema_price = price_feed - .get_ema_price_no_older_than(current_time, max_age) - .ok_or(MarginfiError::StaleOracle)?; + // Note: mainnet/staging/devnet use oracle age, localnet ignores oracle age + let ema_price = if live!() { + price_feed + .get_ema_price_no_older_than(current_time, max_age) + .ok_or(MarginfiError::InternalLogicError)? + } else { + price_feed.get_ema_price_unchecked() + }; - let price = price_feed - .get_price_no_older_than(current_time, max_age) - .ok_or(MarginfiError::StaleOracle)?; + let price = if live!() { + price_feed + .get_price_no_older_than(current_time, max_age) + .ok_or(MarginfiError::InternalLogicError)? + } else { + price_feed.get_price_unchecked() + }; Ok(Self { ema_price: Box::new(ema_price), @@ -437,7 +705,7 @@ impl PriceAdapter for PythLegacyPriceFeed { #[cfg_attr(feature = "client", derive(Clone, Debug))] pub struct SwitchboardPullPriceFeed { - feed: Box, + pub feed: Box, } impl SwitchboardPullPriceFeed { @@ -450,7 +718,7 @@ impl SwitchboardPullPriceFeed { check!( ai.owner.eq(&SWITCHBOARD_PULL_ID), - MarginfiError::InvalidOracleAccount + MarginfiError::SwitchboardWrongAccountOwner ); // let feed = @@ -468,7 +736,7 @@ impl SwitchboardPullPriceFeed { check!( ai.owner.eq(&SWITCHBOARD_PULL_ID), - MarginfiError::InvalidOracleAccount + MarginfiError::SwitchboardWrongAccountOwner ); // PullFeedAccountData::parse(ai_data).map_err(|_| MarginfiError::InvalidOracleAccount)?; @@ -556,15 +824,15 @@ impl SwitchboardV2PriceFeed { check!( ai.owner.eq(&SWITCHBOARD_PROGRAM_ID), - MarginfiError::InvalidOracleAccount + MarginfiError::InternalLogicError ); let aggregator_account = AggregatorAccountData::new_from_bytes(&ai_data) - .map_err(|_| MarginfiError::InvalidOracleAccount)?; + .map_err(|_| MarginfiError::InternalLogicError)?; aggregator_account .check_staleness(current_timestamp, max_age as i64) - .map_err(|_| MarginfiError::StaleOracle)?; + .map_err(|_| MarginfiError::InternalLogicError)?; Ok(Self { aggregator_account: Box::new((&aggregator_account).into()), @@ -576,11 +844,11 @@ impl SwitchboardV2PriceFeed { check!( ai.owner.eq(&SWITCHBOARD_PROGRAM_ID), - MarginfiError::InvalidOracleAccount + MarginfiError::InternalLogicError ); AggregatorAccountData::new_from_bytes(&ai_data) - .map_err(|_| MarginfiError::InvalidOracleAccount)?; + .map_err(|_| MarginfiError::InternalLogicError)?; Ok(()) } @@ -651,10 +919,21 @@ impl PriceAdapter for SwitchboardV2PriceFeed { } pub fn load_price_update_v2_checked(ai: &AccountInfo) -> MarginfiResult { - check!( - ai.owner.eq(&pyth_solana_receiver_sdk::id()), - MarginfiError::InvalidOracleAccount - ); + if live!() { + check_eq!( + *ai.owner, + pyth_solana_receiver_sdk::id(), + MarginfiError::PythPushWrongAccountOwner + ); + } else { + // On localnet, allow the mock program ID OR the real one (for regression tests against + // actual mainnet accounts). + // * Note: Typically price updates are owned by `pyth_solana_receiver_sdk` and the oracle + // feed account itself is owned by PYTH ID. On localnet, the mock program may own both for + // simplicity. + let owner_ok = ai.owner.eq(&PYTH_ID) || ai.owner.eq(&pyth_solana_receiver_sdk::id()); + check!(owner_ok, MarginfiError::PythPushWrongAccountOwner); + } let price_feed_data = ai.try_borrow_data()?; let discriminator = &price_feed_data[0..8]; @@ -721,13 +1000,8 @@ impl PythPushOraclePriceFeed { ) .map_err(|e| { debug!("Pyth push oracle error: {:?}", e); - - match e { - pyth_solana_receiver_sdk::error::GetPriceError::PriceTooOld => { - MarginfiError::StaleOracle - } - _ => MarginfiError::InvalidOracleAccount, - } + let error: MarginfiError = e.into(); + error })?; let ema_price = { @@ -761,13 +1035,8 @@ impl PythPushOraclePriceFeed { .get_price_unchecked(&price_feed_account.price_message.feed_id) .map_err(|e| { println!("Pyth push oracle error: {:?}", e); - - match e { - pyth_solana_receiver_sdk::error::GetPriceError::PriceTooOld => { - MarginfiError::StaleOracle - } - _ => MarginfiError::InvalidOracleAccount, - } + let error: MarginfiError = e.into(); + error })?; let ema_price = { @@ -805,7 +1074,7 @@ impl PythPushOraclePriceFeed { check!( &price_feed_account.price_message.feed_id.eq(feed_id), - MarginfiError::InvalidOracleAccount + MarginfiError::PythPushMismatchedFeedId ); Ok(()) @@ -954,7 +1223,7 @@ impl LiteAggregatorAccountData { let min_oracle_results = self.min_oracle_results; let latest_confirmed_round_num_success = self.latest_confirmed_round_num_success; if min_oracle_results > latest_confirmed_round_num_success { - return Err(MarginfiError::InvalidOracleAccount.into()); + return Err(MarginfiError::SwitchboardInvalidAccount.into()); } Ok(self.latest_confirmed_round_result) } @@ -981,9 +1250,9 @@ fn pyth_price_components_to_i80f48(price: I80F48, exponent: i32) -> MarginfiResu /// Load and validate a pyth price feed account. fn load_pyth_price_feed(ai: &AccountInfo) -> MarginfiResult { - check!(ai.owner.eq(&PYTH_ID), MarginfiError::InvalidOracleAccount); + check!(ai.owner.eq(&PYTH_ID), MarginfiError::InternalLogicError); let price_feed = SolanaPriceAccount::account_info_to_feed(ai) - .map_err(|_| MarginfiError::InvalidOracleAccount)?; + .map_err(|_| MarginfiError::InternalLogicError)?; Ok(price_feed) } diff --git a/programs/marginfi/src/state/staked_settings.rs b/programs/marginfi/src/state/staked_settings.rs new file mode 100644 index 000000000..1c50420c4 --- /dev/null +++ b/programs/marginfi/src/state/staked_settings.rs @@ -0,0 +1,116 @@ +use anchor_lang::prelude::*; +use fixed::types::I80F48; +use fixed_macro::types::I80F48; + +use crate::{assert_struct_align, assert_struct_size, check, MarginfiError, MarginfiResult}; + +use super::marginfi_group::{RiskTier, WrappedI80F48}; + +assert_struct_size!(StakedSettings, 256); +assert_struct_align!(StakedSettings, 8); + +/// Unique per-group. Staked Collateral banks created under a group automatically use these +/// settings. Groups that have not created this struct cannot create staked collateral banks. When +/// this struct updates, changes must be permissionlessly propogated to staked collateral banks. +/// Administrators can also edit the bank manually, i.e. with configure_bank, to temporarily make +/// changes such as raising the deposit limit for a single bank. +#[account(zero_copy)] +#[repr(C)] +pub struct StakedSettings { + /// This account's own key. A PDA derived from `marginfi_group` and `STAKED_SETTINGS_SEED` + pub key: Pubkey, + /// Group for which these settings apply + pub marginfi_group: Pubkey, + /// Generally, the Pyth push oracle for SOL + pub oracle: Pubkey, + + pub asset_weight_init: WrappedI80F48, + pub asset_weight_maint: WrappedI80F48, + + pub deposit_limit: u64, + pub total_asset_value_init_limit: u64, + + pub oracle_max_age: u16, + pub risk_tier: RiskTier, + _pad0: [u8; 5], + + /// The following values are irrelevant because staked collateral positions do not support + /// borrowing. + // * interest_config, + // * liability_weight_init + // * liability_weight_maint + // * borrow_limit + _reserved0: [u8; 8], + _reserved1: [u8; 32], + _reserved2: [u8; 64], +} + +impl StakedSettings { + pub const LEN: usize = std::mem::size_of::(); + + pub fn new( + key: Pubkey, + marginfi_group: Pubkey, + oracle: Pubkey, + asset_weight_init: WrappedI80F48, + asset_weight_maint: WrappedI80F48, + deposit_limit: u64, + total_asset_value_init_limit: u64, + oracle_max_age: u16, + risk_tier: RiskTier, + ) -> Self { + StakedSettings { + key, + marginfi_group, + oracle, + asset_weight_init, + asset_weight_maint, + deposit_limit, + total_asset_value_init_limit, + oracle_max_age, + risk_tier, + ..Default::default() + } + } + + /// Same as `bank.validate()`, except that liability rates and interest rates do not exist in + /// this context (since Staked Collateral accounts cannot be borrowed against and such Banks + /// will use placeholders for those values) + pub fn validate(&self) -> MarginfiResult { + let asset_init_w = I80F48::from(self.asset_weight_init); + let asset_maint_w = I80F48::from(self.asset_weight_maint); + + check!( + asset_init_w >= I80F48::ZERO && asset_init_w <= I80F48::ONE, + MarginfiError::InvalidConfig + ); + check!(asset_maint_w >= asset_init_w, MarginfiError::InvalidConfig); + + if self.risk_tier == RiskTier::Isolated { + check!(asset_init_w == I80F48::ZERO, MarginfiError::InvalidConfig); + check!(asset_maint_w == I80F48::ZERO, MarginfiError::InvalidConfig); + } + + Ok(()) + } +} + +impl Default for StakedSettings { + fn default() -> Self { + StakedSettings { + key: Pubkey::default(), + marginfi_group: Pubkey::default(), + oracle: Pubkey::default(), + asset_weight_init: I80F48!(0.8).into(), + asset_weight_maint: I80F48!(0.9).into(), + deposit_limit: 1_000_000, + total_asset_value_init_limit: 1_000_000, + oracle_max_age: 10, + risk_tier: RiskTier::Collateral, + _pad0: [0; 5], + _reserved0: [0; 8], + _reserved1: [0; 32], + _reserved2: [0; 64], + } + } +} diff --git a/programs/marginfi/src/utils.rs b/programs/marginfi/src/utils.rs index fc79d68b7..b4a772815 100644 --- a/programs/marginfi/src/utils.rs +++ b/programs/marginfi/src/utils.rs @@ -1,6 +1,10 @@ use crate::{ bank_authority_seed, bank_seed, - state::marginfi_group::{Bank, BankVaultType}, + constants::{ASSET_TAG_DEFAULT, ASSET_TAG_SOL, ASSET_TAG_STAKED}, + state::{ + marginfi_account::MarginfiAccount, + marginfi_group::{Bank, BankVaultType}, + }, MarginfiError, MarginfiResult, }; use anchor_lang::prelude::*; @@ -192,3 +196,63 @@ pub fn hex_to_bytes(hex: &str) -> Vec { }) .collect() } + +/// Validate that after a deposit to Bank, the users's account contains either all Default/SOL +/// balances, or all Staked/Sol balances. Default and Staked assets cannot mix. +pub fn validate_asset_tags(bank: &Bank, marginfi_account: &MarginfiAccount) -> MarginfiResult { + let mut has_default_asset = false; + let mut has_staked_asset = false; + + for balance in marginfi_account.lending_account.balances.iter() { + if balance.is_active() { + match balance.bank_asset_tag { + ASSET_TAG_DEFAULT => has_default_asset = true, + ASSET_TAG_SOL => { /* Do nothing, SOL can mix with any asset type */ } + ASSET_TAG_STAKED => has_staked_asset = true, + _ => panic!("unsupported asset tag"), + } + } + } + + // 1. Regular assets (DEFAULT) cannot mix with Staked assets + if bank.config.asset_tag == ASSET_TAG_DEFAULT && has_staked_asset { + return err!(MarginfiError::AssetTagMismatch); + } + + // 2. Staked SOL cannot mix with Regular asset (DEFAULT) + if bank.config.asset_tag == ASSET_TAG_STAKED && has_default_asset { + return err!(MarginfiError::AssetTagMismatch); + } + + Ok(()) +} + +/// Validate that two banks are compatible based on their asset tags. See the following combinations +/// (* is wildcard, e.g. any tag): +/// +/// Allowed: +/// 1) Default/Default +/// 2) Sol/* +/// 3) Staked/Staked +/// +/// Forbidden: +/// 1) Default/Staked +/// +/// Returns an error if the two banks have mismatching asset tags according to the above. +pub fn validate_bank_asset_tags(bank_a: &Bank, bank_b: &Bank) -> MarginfiResult { + let is_bank_a_default = bank_a.config.asset_tag == ASSET_TAG_DEFAULT; + let is_bank_a_staked = bank_a.config.asset_tag == ASSET_TAG_STAKED; + let is_bank_b_default = bank_b.config.asset_tag == ASSET_TAG_DEFAULT; + let is_bank_b_staked = bank_b.config.asset_tag == ASSET_TAG_STAKED; + // Note: Sol is compatible with all other tags and doesn't matter... + + // 1. Default assets cannot mix with Staked assets + if is_bank_a_default && is_bank_b_staked { + return err!(MarginfiError::AssetTagMismatch); + } + if is_bank_a_staked && is_bank_b_default { + return err!(MarginfiError::AssetTagMismatch); + } + + Ok(()) +} diff --git a/programs/mocks/Cargo.toml b/programs/mocks/Cargo.toml deleted file mode 100644 index e1a5ff097..000000000 --- a/programs/mocks/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "mocks" -version = "0.1.0" -description = "External program mocks" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] -name = "mocks" - -[features] -no-entrypoint = [] -no-idl = [] -no-log-ix-name = [] -cpi = ["no-entrypoint"] -default = ["mainnet-beta"] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] -test-bpf = ["test", "debug"] -test = [] -client = [] -devnet = [] -mainnet-beta = [] -debug = [] -staging = [] - -[dependencies] -anchor-lang = { workspace = true } -anchor-spl = { workspace = true } - -bytemuck = { version = "1.9.1", features = ["derive"] } -static_assertions = "1.1.0" \ No newline at end of file diff --git a/programs/mocks/src/errors.rs b/programs/mocks/src/errors.rs deleted file mode 100644 index 5f5b7b38c..000000000 --- a/programs/mocks/src/errors.rs +++ /dev/null @@ -1,7 +0,0 @@ -use anchor_lang::error_code; - -#[error_code] -pub enum ErrorCode { - #[msg("This is an error.")] - SomeError, // 6000 -} diff --git a/programs/mocks/src/instructions/do_nothing.rs b/programs/mocks/src/instructions/do_nothing.rs deleted file mode 100644 index 76a6d2b46..000000000 --- a/programs/mocks/src/instructions/do_nothing.rs +++ /dev/null @@ -1,14 +0,0 @@ -use anchor_lang::prelude::*; - -#[derive(Accounts)] -pub struct DoNothing<'info> { - pub payer: Signer<'info>, -} - -pub fn do_nothing(ctx: Context) -> Result<()> { - msg!( - "Nothing was done. Signed by: {:?}", - ctx.accounts.payer.key() - ); - Ok(()) -} diff --git a/programs/mocks/src/instructions/init_pool_auth.rs b/programs/mocks/src/instructions/init_pool_auth.rs deleted file mode 100644 index 11cdf96cf..000000000 --- a/programs/mocks/src/instructions/init_pool_auth.rs +++ /dev/null @@ -1,71 +0,0 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{Mint, Token, TokenAccount}; - -use crate::state::PoolAuth; - -#[derive(Accounts)] -#[instruction( - nonce: u16, -)] -pub struct InitPoolAuth<'info> { - /// Pays the init fee - #[account(mut)] - pub payer: Signer<'info>, - - #[account( - init, - seeds = [ - &nonce.to_le_bytes(), - b"pool_auth".as_ref(), - ], - bump, - payer = payer, - space = 8 + PoolAuth::LEN, - )] - pub pool_auth: Account<'info, PoolAuth>, - - pub mint_a: Account<'info, Mint>, - pub mint_b: Account<'info, Mint>, - - #[account( - init, - seeds = [ - mint_a.key().as_ref(), - pool_auth.key().as_ref(), - b"pools", - ], - bump, - token::mint = mint_a, - token::authority = pool_auth, - payer = payer - )] - pub pool_a: Account<'info, TokenAccount>, - - #[account( - init, - seeds = [ - mint_b.key().as_ref(), - pool_auth.key().as_ref(), - b"pools", - ], - bump, - token::mint = mint_b, - token::authority = pool_auth, - payer = payer - )] - pub pool_b: Account<'info, TokenAccount>, - - pub token_program: Program<'info, Token>, - pub rent: Sysvar<'info, Rent>, - pub system_program: Program<'info, System>, -} - -pub fn init_pool_auth(ctx: Context, nonce: u16) -> Result<()> { - let pool_auth = &mut ctx.accounts.pool_auth; - pool_auth.nonce = nonce; - pool_auth.pool_a = ctx.accounts.pool_a.key(); - pool_auth.pool_b = ctx.accounts.pool_b.key(); - pool_auth.bump_seed = ctx.bumps.pool_auth; - - Ok(()) -} diff --git a/programs/mocks/src/instructions/mod.rs b/programs/mocks/src/instructions/mod.rs deleted file mode 100644 index e98adaaf6..000000000 --- a/programs/mocks/src/instructions/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod do_nothing; -pub mod init_pool_auth; -pub mod swap_like_jupiter; - -pub use do_nothing::*; -pub use init_pool_auth::*; -pub use swap_like_jupiter::*; diff --git a/programs/mocks/src/instructions/swap_like_jupiter.rs b/programs/mocks/src/instructions/swap_like_jupiter.rs deleted file mode 100644 index 0c5b17818..000000000 --- a/programs/mocks/src/instructions/swap_like_jupiter.rs +++ /dev/null @@ -1,75 +0,0 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{self, Token, TokenAccount, Transfer}; - -use crate::{pool_auth_signer_seeds, state::PoolAuth}; - -#[derive(Accounts)] -pub struct SwapLikeJupiter<'info> { - pub user_authority: Signer<'info>, - - /// PDA authority of the pools - /// CHECK: this is a mock program, security doesn't matter - pub pool_auth: Account<'info, PoolAuth>, - - #[account(mut)] - pub pool_a: Account<'info, TokenAccount>, - - #[account(mut)] - pub pool_b: Account<'info, TokenAccount>, - - #[account(mut)] - pub source_a: Account<'info, TokenAccount>, - - #[account(mut)] - pub destination_b: Account<'info, TokenAccount>, - - pub token_program: Program<'info, Token>, -} - -impl<'info> SwapLikeJupiter<'info> { - pub fn swap_like_jup( - ctx: Context<'_, '_, '_, 'info, SwapLikeJupiter<'info>>, - amt_a: u64, - amt_b: u64, - ) -> Result<()> { - let pool_auth = &ctx.accounts.pool_auth; - - let a_before = ctx.accounts.source_a.amount; - msg!("User a before: {:?} transfer {:?}", a_before, amt_a); - token::transfer(ctx.accounts.transfer_a_to_pool(), amt_a)?; - - let b_before = ctx.accounts.pool_b.amount; - msg!("Pool b before: {:?} transfer {:?}", b_before, amt_b); - token::transfer( - ctx.accounts - .transfer_b_to_user() - .with_signer(&[pool_auth_signer_seeds!(pool_auth)]), - amt_b, - )?; - - Ok(()) - } -} - -impl<'info> SwapLikeJupiter<'info> { - fn transfer_a_to_pool(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> { - CpiContext::new( - self.token_program.to_account_info(), - Transfer { - from: self.source_a.to_account_info(), - to: self.pool_a.to_account_info(), - authority: self.user_authority.to_account_info(), - }, - ) - } - fn transfer_b_to_user(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> { - CpiContext::new( - self.token_program.to_account_info(), - Transfer { - from: self.pool_b.to_account_info(), - to: self.destination_b.to_account_info(), - authority: self.pool_auth.to_account_info(), - }, - ) - } -} diff --git a/programs/mocks/src/lib.rs b/programs/mocks/src/lib.rs deleted file mode 100644 index 026cf9f23..000000000 --- a/programs/mocks/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -use anchor_lang::prelude::*; - -pub mod errors; -pub mod instructions; -pub mod macros; -pub mod state; -// pub mod utils; - -use crate::instructions::*; -// use crate::state::*; -// use errors::*; - -declare_id!("5XaaR94jBubdbrRrNW7DtRvZeWvLhSHkEGU3jHTEXV3C"); - -#[program] -pub mod mocks { - use super::*; - use std::io::Write as IoWrite; - - /// Do nothing - pub fn do_nothing(ctx: Context) -> Result<()> { - instructions::do_nothing::do_nothing(ctx) - } - - /// Init authority for fake jupiter-like swap pools - pub fn init_pool_auth(ctx: Context, nonce: u16) -> Result<()> { - instructions::init_pool_auth::init_pool_auth(ctx, nonce) - } - - /// Execute an exchange of a:b like-jupiter. You set the amount a sent and b received. - pub fn swap_like_jupiter<'info>( - ctx: Context<'_, '_, '_, 'info, SwapLikeJupiter<'info>>, - amt_a: u64, - amt_b: u64, - ) -> Result<()> { - instructions::swap_like_jupiter::SwapLikeJupiter::swap_like_jup(ctx, amt_a, amt_b) - } - - #[derive(Accounts)] - pub struct Write<'info> { - #[account(mut)] - target: Signer<'info>, - } - - /// Write arbitrary bytes to an arbitrary account. YOLO. - pub fn write(ctx: Context, offset: u64, data: Vec) -> Result<()> { - let account_data = ctx.accounts.target.to_account_info().data; - let borrow_data = &mut *account_data.borrow_mut(); - let offset = offset as usize; - - Ok((&mut borrow_data[offset..]).write_all(&data[..])?) - } -} diff --git a/programs/mocks/src/macros.rs b/programs/mocks/src/macros.rs deleted file mode 100644 index c9db378d1..000000000 --- a/programs/mocks/src/macros.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[macro_export] -macro_rules! pool_auth_signer_seeds { - ($pool_auth:expr) => { - &[ - &$pool_auth.nonce.to_le_bytes(), - b"pool_auth".as_ref(), - &[$pool_auth.bump_seed], - ] - }; -} diff --git a/programs/mocks/src/state/mod.rs b/programs/mocks/src/state/mod.rs deleted file mode 100644 index e3077eccc..000000000 --- a/programs/mocks/src/state/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod pool_auth; - -pub use pool_auth::*; diff --git a/programs/mocks/src/state/pool_auth.rs b/programs/mocks/src/state/pool_auth.rs deleted file mode 100644 index 634abcaf7..000000000 --- a/programs/mocks/src/state/pool_auth.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anchor_lang::prelude::*; - -#[account()] -pub struct PoolAuth { - /// The account's own key - pub key: Pubkey, - pub pool_a: Pubkey, - pub pool_b: Pubkey, - pub bump_seed: u8, - pub nonce: u16, -} - -impl PoolAuth { - pub const LEN: usize = std::mem::size_of::(); -} diff --git a/scripts/build-program-verifiable.sh b/scripts/build-program-verifiable.sh deleted file mode 100755 index 25d86c12d..000000000 --- a/scripts/build-program-verifiable.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env sh -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -verify_bin=$(which solana-verify) -if [ "$?" != "0" ]; then - echo "solana-verify not found. Please run: cargo install solana-verify." - exit 1 -fi - -program_lib_name=$1 -deployment=$2 - -if [ -z "$program_lib_name" ] || [ -z "$deployment" ]; then - echo "Usage: $0 " - exit 1 -fi - -if [ "$deployment" = "mainnet" ]; then - features="--features mainnet-beta" -elif [ "$deployment" = "devnet" ]; then - features="--features devnet --no-default-features" -elif [ "$deployment" = "staging" ]; then - features="--features staging --no-default-features" -else - echo "Error: Unknown deployment: $deployment" - exit 1 -fi - -cmd="sudo $verify_bin build --library-name $program_lib_name -- $features" -echo "Running: $cmd" -eval "$cmd" diff --git a/scripts/build-program.sh b/scripts/build-program.sh deleted file mode 100755 index 1ca3556f1..000000000 --- a/scripts/build-program.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env sh -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -program_lib_name=$1 -cluster=$2 - -if [ -z "$program_lib_name" ] || [ -z "$cluster" ]; then - echo "Usage: $0 " - exit 1 -fi - -if [ "$cluster" = "mainnet" ]; then - features="--features mainnet-beta" -elif [ "$cluster" = "devnet" ]; then - features="--features devnet --no-default-features" -elif [ "$cluster" = "staging" ]; then - features="--features staging --no-default-features" -else - echo "Error: Unknown cluster: $cluster" - exit 1 -fi - -cmd="anchor build -p $program_lib_name -- $features" -echo "Running: $cmd" -eval "$cmd" diff --git a/scripts/build-workspace.sh b/scripts/build-workspace.sh deleted file mode 100755 index 77055eada..000000000 --- a/scripts/build-workspace.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -cmd="anchor build --no-idl" -echo "Running: $cmd" -eval "$cmd" diff --git a/scripts/deploy-buffer.sh b/scripts/deploy-buffer.sh deleted file mode 100755 index 74c4e1373..000000000 --- a/scripts/deploy-buffer.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env sh -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -program_lib_name=$1 -rpc_url=$2 -keypair=$3 - -if [ -z "$program_lib_name" ] || [ -z "$rpc_url" ] || [ -z "$keypair" ]; then - echo "Usage: $0 " - exit 1 -fi - -cmd="solana --url $rpc_url program write-buffer "$ROOT/target/deploy/$program_lib_name.so" -k $keypair" -echo "Running: $cmd" -eval "$cmd" diff --git a/scripts/deploy-staging-program.sh b/scripts/deploy-staging-program.sh deleted file mode 100644 index 581f5c67b..000000000 --- a/scripts/deploy-staging-program.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env sh - -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -set -e - -ask_confirmation() { - while true; do - read -p "Are you sure you want to proceed? (y/n): " yn - case $yn in - [Yy]* ) return 0;; - [Nn]* ) return 1;; - * ) echo "Please answer yes (y) or no (n).";; - esac - done -} - -deployer_key_path=$1 -[ -z "$deployer_key_path" ] && echo "Missing deployer_key_path argument" && exit 1 -[ ! -f "$deployer_key_path" ] && echo "$deployer_key_path is not a file" && exit 1 - -program_address_or_keypair=$2 -[ -z "$program_address_or_keypair" ] && echo "Missing program_address_or_keypair argument" && exit 1 - -rpc_url=$3 -[ -z "$rpc_url" ] && echo "Missing rpc_url argument" && exit 1 - -deployer_pk=$(solana-keygen pubkey $deployer_key_path) -deployer_balance=$(solana balance $deployer_key_path) -set +e -exist_result=$(solana account $program_address_or_keypair 2>&1) -set -e - -if [ "$exist_result" = *"Error: AccountNotFound:"* ]; then - is_upgrade=0 -else - is_upgrade=1 -fi - -if [ -f "$program_address_or_keypair" ]; then - program_id=$(solana-keygen pubkey $program_address_or_keypair) -else - if [ "$is_upgrade" = "0" ]; then - echo "You need to provide a private key path for a first deploy." - exit 1 - else - program_id=$program_address_or_keypair - fi -fi - -echo "=========================================================================================" -echo "Deployer: $deployer_pk" -echo "Balance: $deployer_balance" -printf "Deploying to: $program_id" -if [ "$is_upgrade" = "0" ]; then - echo " (first deployment)" -else - echo " (already deployed)" -fi - -if ! ask_confirmation; then - echo "Cancelled." - exit 0 -fi - -if [ "$is_upgrade" = "0" ]; then - echo "Deploying..." -else - echo "Upgrading..." -fi - -solana program deploy \ - --use-rpc \ - --url $rpc_url \ - --fee-payer $deployer_key_path \ - --keypair $deployer_key_path \ - --program-id $program_address_or_keypair \ - "$ROOT/target/deploy/marginfi.so" diff --git a/scripts/fuzz-program.sh b/scripts/fuzz-program.sh deleted file mode 100755 index 9b4b2f45b..000000000 --- a/scripts/fuzz-program.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -e - -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -cd $ROOT/programs/marginfi/fuzz -cmd="cargo +nightly fuzz run lend -Zbuild-std --strip-dead-code --no-cfg-fuzzing -- -max_total_time=300 -verbosity=0" -echo "Running: $cmd" -eval "$cmd" diff --git a/scripts/lint.sh b/scripts/lint.sh deleted file mode 100755 index cf5c83b4d..000000000 --- a/scripts/lint.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env sh - -cargo clippy --features=test,test-bpf -- -D warnings -A clippy::await_holding_refcell_ref -A clippy::comparison_chain -A clippy::too_many_arguments diff --git a/scripts/setup-devnet.sh b/scripts/setup-devnet.sh deleted file mode 100755 index 6390ff089..000000000 --- a/scripts/setup-devnet.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -RPC_ENDPOINT=https://devnet.genesysgo.net/ -KEYPAIR_PATH=~/.config/solana/id.json - -[ -z "$PROGRAM_ID" ] && echo "PROGRAM_ID must be specified. Exiting." && exit 1 -[ -z "$NEW_PROFILE_NAME" ] && echo "NEW_PROFILE_NAME must be specified. Exiting." && exit 1 - -mfi profile create \ - --cluster devnet \ - --name "$NEW_PROFILE_NAME" \ - --keypair-path "$KEYPAIR_PATH" \ - --rpc-url "$RPC_ENDPOINT" \ - --program-id "$PROGRAM_ID" - -mfi profile set "$NEW_PROFILE_NAME" - -mfi group create "$@" -y - -# Add USDC bank -mfi group add-bank \ - --mint F9jRT1xL7PCRepBuey5cQG5vWHFSbnvdWxJWKqtzMDsd \ - --asset-weight-init 0.85 \ - --asset-weight-maint 0.9 \ - --liability-weight-maint 1.1 \ - --liability-weight-init 1.15 \ - --deposit-limit 1000000000000000\ - --borrow-limit 1000000000000000\ - --pyth-oracle 5SSkXsEKQepHHAewytPVwdej4epN1nxgLVM84L4KXgy7 \ - --optimal-utilization-rate 0.9 \ - --plateau-interest-rate 1 \ - --max-interest-rate 10 \ - --insurance-fee-fixed-apr 0.01 \ - --insurance-ir-fee 0.1 \ - --protocol-fixed-fee-apr 0.01 \ - --protocol-ir-fee 0.1 \ - -y \ - "$@" - -# Add SOL bank -mfi group add-bank \ - --mint 4Bn9Wn1sgaD5KfMRZjxwKFcrUy6NKdyqLPtzddazYc4x \ - --asset-weight-init 0.75 \ - --asset-weight-maint 0.8 \ - --liability-weight-maint 1.2 \ - --liability-weight-init 1.25 \ - --deposit-limit 1000000000000000\ - --borrow-limit 1000000000000000\ - --pyth-oracle J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix \ - --optimal-utilization-rate 0.8 \ - --plateau-interest-rate 1 \ - --max-interest-rate 20 \ - --insurance-fee-fixed-apr 0.01 \ - --insurance-ir-fee 0.1 \ - --protocol-fixed-fee-apr 0.01 \ - --protocol-ir-fee 0.1 \ - -y \ - "$@" diff --git a/scripts/single-test.sh b/scripts/single-test.sh deleted file mode 100755 index f3174c35d..000000000 --- a/scripts/single-test.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$#" -lt 2 ]; then - echo "Usage: $0 [--verbose]" - exit 1 -fi - -program_name=$1 -test_name=$2 -verbose=false - -if [ "$#" -eq 3 ] && [ "$3" == "--verbose" ]; then - verbose=true -fi - -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -SBF_OUT_DIR="$ROOT/target/deploy" -RUST_LOG="solana_runtime::message_processor::stable_log=debug" -CARGO_CMD="SBF_OUT_DIR=$SBF_OUT_DIR RUST_LOG=$RUST_LOG cargo nextest run --package $program_name --features=test,test-bpf --test-threads=1 -- $test_name" - -echo "Running: $CARGO_CMD" - -if [ "$verbose" == true ]; then - eval $CARGO_CMD -else - eval $CARGO_CMD 2>&1 | awk '/PASS/ {print "\033[32m" $0 "\033[39m"} /FAIL/ {print "\033[31m" $0 "\033[39m"}' -fi diff --git a/scripts/test-program.sh b/scripts/test-program.sh deleted file mode 100755 index 6595e253a..000000000 --- a/scripts/test-program.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -set -e - -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -program_lib_name=$1 -loglevel=$2 - -if [ -z "$program_lib_name" ]; then - echo "Usage: $0 " - exit 1 -fi - -if [ "$loglevel" == "--sane" ]; then - loglevel=warn - nocapture="--test-threads=1" -else - loglevel=debug - nocapture="--nocapture" -fi - -if [ "$program_lib_name" == "all" ]; then - package_filter="" -else - package_filter="--package $program_lib_name" -fi - -extra_params="${@:3}" - -cmd="SBF_OUT_DIR=$ROOT/target/deploy RUST_LOG=solana_runtime::message_processor::stable_log=$loglevel cargo nextest run --no-fail-fast $package_filter --features=test,test-bpf $nocapture -- $extra_params" -echo "Running: $cmd" -eval "$cmd" diff --git a/scripts/verify.sh b/scripts/verify.sh deleted file mode 100755 index 4d190fd9e..000000000 --- a/scripts/verify.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env sh -ROOT=$(git rev-parse --show-toplevel) -cd $ROOT - -VERIFY_BIN=$(which solana-verify) -if [ "$?" != "0" ]; then - echo "solana-verify not found. Please run: cargo install solana-verify." - exit 1 -fi - -program_lib_name=$1 -cluster=$2 - -if [ -z "$program_lib_name" ] || [ -z "$cluster" ]; then - echo "Usage: $0 " - exit 1 -fi - -if [ "$cluster" = "mainnet" ]; then - cluster_feature="mainnet-beta" - program_id="MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA" - rpc_moniker="main" -elif [ "$cluster" = "devnet" ]; then - cluster_feature=" devnet" - program_id="LipsxuAkFkwa4RKNzn51wAsW7Dedzt1RNHMkTkDEZUW" - rpc_moniker="dev" -else - echo "Error: Unknown cluster: $cluster" - exit 1 -fi - -cmd="sudo $VERIFY_BIN verify-from-repo --url $rpc_moniker --program-id $program_id https://github.com/mrgnlabs/marginfi-v2 --library-name $program_lib_name -- --features $cluster_feature" -echo "Running: $cmd" -eval "$cmd" diff --git a/yarn.lock b/yarn.lock index 7590eb8dd..f4362e60c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,13 +2,41 @@ # yarn lockfile v1 -"@babel/runtime@^7.12.5", "@babel/runtime@^7.24.8": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" - integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== +"@babel/runtime@^7.12.5", "@babel/runtime@^7.25.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== dependencies: regenerator-runtime "^0.14.0" +"@brokerloop/ttlcache@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@brokerloop/ttlcache/-/ttlcache-3.2.3.tgz#bc3c79bb381f7b43f83745eb96e86673f75d3d11" + integrity sha512-kZWoyJGBYTv1cL5oHBYEixlJysJBf2RVnub3gbclD+dwaW9aKubbHzbZ9q1q6bONosxaOqMsoBorOrZKzBDiqg== + dependencies: + "@soncodi/signal" "~2.0.7" + +"@coral-xyz/anchor-30@npm:@coral-xyz/anchor@0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" + integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== + dependencies: + "@coral-xyz/anchor-errors" "^0.30.1" + "@coral-xyz/borsh" "^0.30.1" + "@noble/hashes" "^1.3.1" + "@solana/web3.js" "^1.68.0" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^6.3.0" + cross-fetch "^3.1.5" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + pako "^2.0.3" + snake-case "^3.0.4" + superstruct "^0.15.4" + toml "^3.0.0" + "@coral-xyz/anchor-errors@^0.30.1": version "0.30.1" resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz#bdfd3a353131345244546876eb4afc0e125bec30" @@ -87,9 +115,9 @@ "@jridgewell/trace-mapping" "0.3.9" "@grpc/grpc-js@^1.8.13": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.11.1.tgz#a92f33e98f1959feffcd1b25a33b113d2c977b70" - integrity sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw== + version "1.12.4" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.12.4.tgz#3208808435ebf1e495f9a5c5c5a0bc3dc8c9e891" + integrity sha512-NBhrxEWnFh0FxeA0d//YP95lRFsSx2TNLEUQg4/W+5f/BMxcCjgOOIT24iD+ZB/tZw057j44DaIxja7w4XMrhg== dependencies: "@grpc/proto-loader" "^0.7.13" "@js-sdsl/ordered-map" "^4.4.2" @@ -127,26 +155,69 @@ resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== -"@mrgnlabs/marginfi-client-v2@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@mrgnlabs/marginfi-client-v2/-/marginfi-client-v2-3.1.0.tgz#f380b63aa5ec4fe9f467e3711c5410970c907403" - integrity sha512-XXq+7iltsMe1BvboG3SQb1xjDoJq1W24eNZd1r3xor54GoeGxjVIvvrSNAKlU0CyzlqzuN1EosBBLmnKSuC+8A== +"@metaplex-foundation/umi-options@^0.8.9": + version "0.8.9" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/umi-options/-/umi-options-0.8.9.tgz#9c9e269d9eee7d055ad6831dcb30a30127dcb0c5" + integrity sha512-jSQ61sZMPSAk/TXn8v8fPqtz3x8d0/blVZXLLbpVbo2/T5XobiI6/MfmlUosAjAUaQl6bHRF8aIIqZEFkJiy4A== + +"@metaplex-foundation/umi-public-keys@^0.8.9": + version "0.8.9" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/umi-public-keys/-/umi-public-keys-0.8.9.tgz#ca7a927c924ed8e28d0f8bb3dc0f2adc1f9011ec" + integrity sha512-CxMzN7dgVGOq9OcNCJe2casKUpJ3RmTVoOvDFyeoTQuK+vkZ1YSSahbqC1iGuHEtKTLSjtWjKvUU6O7zWFTw3Q== + dependencies: + "@metaplex-foundation/umi-serializers-encodings" "^0.8.9" + +"@metaplex-foundation/umi-serializers-core@^0.8.9": + version "0.8.9" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/umi-serializers-core/-/umi-serializers-core-0.8.9.tgz#cd5ae763a59e54dd01f1284f4a6bf4e78e4aab9c" + integrity sha512-WT82tkiYJ0Qmscp7uTj1Hz6aWQPETwaKLAENAUN5DeWghkuBKtuxyBKVvEOuoXerJSdhiAk0e8DWA4cxcTTQ/w== + +"@metaplex-foundation/umi-serializers-encodings@^0.8.9": + version "0.8.9" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/umi-serializers-encodings/-/umi-serializers-encodings-0.8.9.tgz#0f02605ee3e6fbeac1abc4fb267a7cc96ecb4410" + integrity sha512-N3VWLDTJ0bzzMKcJDL08U3FaqRmwlN79FyE4BHj6bbAaJ9LEHjDQ9RJijZyWqTm0jE7I750fU7Ow5EZL38Xi6Q== + dependencies: + "@metaplex-foundation/umi-serializers-core" "^0.8.9" + +"@metaplex-foundation/umi-serializers-numbers@^0.8.9": + version "0.8.9" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/umi-serializers-numbers/-/umi-serializers-numbers-0.8.9.tgz#28c10367f6aebac0276ec1bce81d0d8db54b05de" + integrity sha512-NtBf1fnVNQJHFQjLFzRu2i9GGnigb9hOm/Gfrk628d0q0tRJB7BOM3bs5C61VAs7kJs4yd+pDNVAERJkknQ7Lg== + dependencies: + "@metaplex-foundation/umi-serializers-core" "^0.8.9" + +"@metaplex-foundation/umi-serializers@^0.8.9": + version "0.8.9" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/umi-serializers/-/umi-serializers-0.8.9.tgz#af6c5bb1a3276cbe252fd08e359b305ed80a3343" + integrity sha512-Sve8Etm3zqvLSUfza+MYRkjTnCpiaAFT7VWdqeHzA3n58P0AfT3p74RrZwVt/UFkxI+ln8BslwBDJmwzcPkuHw== + dependencies: + "@metaplex-foundation/umi-options" "^0.8.9" + "@metaplex-foundation/umi-public-keys" "^0.8.9" + "@metaplex-foundation/umi-serializers-core" "^0.8.9" + "@metaplex-foundation/umi-serializers-encodings" "^0.8.9" + "@metaplex-foundation/umi-serializers-numbers" "^0.8.9" + +"@mrgnlabs/marginfi-client-v2@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@mrgnlabs/marginfi-client-v2/-/marginfi-client-v2-4.0.0.tgz#50676767dc9a06b5ffaccb25f3dc8f7a24b6f52d" + integrity sha512-GnXdGdPgU54w517iDXfKoti1i8jrVyOwhxPKS2Lpqac3QH4NL3b4sSbz+pCBDmjk60gveHzu3IAIGscUpbo1yQ== dependencies: "@coral-xyz/anchor" "^0.30.1" "@mrgnlabs/mrgn-common" "*" "@pythnetwork/pyth-solana-receiver" "^0.8.0" "@solana/wallet-adapter-base" "^0.9.23" "@solana/web3.js" "^1.93.2" + "@switchboard-xyz/on-demand" "^1.2.36" bignumber.js "^9.1.2" borsh "^2.0.0" bs58 "^6.0.0" decimal.js "^10.4.3" superstruct "^1.0.4" -"@mrgnlabs/mrgn-common@*", "@mrgnlabs/mrgn-common@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@mrgnlabs/mrgn-common/-/mrgn-common-1.7.0.tgz#0a40b7696057ee4119f4fe3950ead11623085545" - integrity sha512-vDeVmcSRB4tAXDZPNTrzlOIhWQFk+CD5akuBl6vaULwRrxMwkSmOxvkgnXHdxXvKgTH9CfU/0exkevOc4VXslQ== +"@mrgnlabs/mrgn-common@*", "@mrgnlabs/mrgn-common@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@mrgnlabs/mrgn-common/-/mrgn-common-1.8.0.tgz#76df1104b3a6b04054c56c297b4dc6a27236d8fe" + integrity sha512-6VQ/2Ob8alyI1jsY3RETsWJ5W/myEp+h2yu1mFI/yzKHLffQMlt50FWPSDmCe2TBmkBioW48HOl+PqQf3+Wfbg== dependencies: "@coral-xyz/anchor" "^0.30.1" "@solana/buffer-layout-utils" "^0.2.0" @@ -166,29 +237,27 @@ "@solana/buffer-layout" "=4.0.0" "@solana/buffer-layout-utils" "=0.2.0" -"@noble/curves@^1.0.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" - integrity sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A== - dependencies: - "@noble/hashes" "1.4.0" - -"@noble/curves@^1.4.2": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" - integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== +"@noble/curves@^1.0.0", "@noble/curves@^1.4.2": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== dependencies: - "@noble/hashes" "1.4.0" + "@noble/hashes" "1.6.0" "@noble/ed25519@^1.7.1": version "1.7.3" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.3.tgz#57e1677bf6885354b466c38e2b620c62f45a7123" integrity sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ== -"@noble/hashes@1.4.0", "@noble/hashes@^1.3.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" - integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@noble/hashes@^1.3.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -251,17 +320,17 @@ bn.js "^5.2.1" "@pythnetwork/pyth-solana-receiver@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@pythnetwork/pyth-solana-receiver/-/pyth-solana-receiver-0.8.0.tgz#d7bf3c5c97a0f0eab8ac19f53b11664117e1152d" - integrity sha512-5lhLtggAqsiHtffTPM8vcKJmhBdxzidBmiNNUlqPyg9XmhZ4Z+roY0dfzluEoX5xer9rEA1ThsBpX0bG1DRIGA== + version "0.8.2" + resolved "https://registry.yarnpkg.com/@pythnetwork/pyth-solana-receiver/-/pyth-solana-receiver-0.8.2.tgz#e1c54de017bad6c321c22245fe240e020e9c853d" + integrity sha512-WrrdwwhSYvvB5vJEL+SfPnfuxgkRKMeKdZvGFFwe6ENrMhrQCM05oDkvNNYfXATLcpQGRAyBu9l1xIxUxixpqw== dependencies: "@coral-xyz/anchor" "^0.29.0" "@noble/hashes" "^1.4.0" "@pythnetwork/price-service-sdk" ">=1.6.0" - "@pythnetwork/solana-utils" "*" + "@pythnetwork/solana-utils" "0.4.2" "@solana/web3.js" "^1.90.0" -"@pythnetwork/solana-utils@*": +"@pythnetwork/solana-utils@0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@pythnetwork/solana-utils/-/solana-utils-0.4.2.tgz#3e220eed518c02ad702ebb023488afd7c5649a87" integrity sha512-hKo7Bcs/kDWA5Fnqhg9zJSB94NMoUDIDjHjSi/uvZOzwizISUQI6oY3LWd2CXzNh4f8djjY2BS5iNHaM4cm8Bw== @@ -271,6 +340,19 @@ bs58 "^5.0.0" jito-ts "^3.0.1" +"@solana/addresses@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/addresses/-/addresses-2.0.0-experimental.21e994f.tgz#51509c4e48c3feae573f30a0ad7736d2054b1bdf" + integrity sha512-zmg+ALhjxZApKJKSjeGK7EgMT9NywdvGKlAjyNL2fieiFWp0lRTBmWyjPBCQQGdJjBkayCscq3GQkDF2MhC6fg== + dependencies: + "@metaplex-foundation/umi-serializers" "^0.8.9" + "@solana/assertions" "2.0.0-experimental.21e994f" + +"@solana/assertions@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/assertions/-/assertions-2.0.0-experimental.21e994f.tgz#a67143b41aaf1d810176b943a203f1508f4095df" + integrity sha512-iGOUpOqkqxzQ/xi4Q3YLiBQPASiQ43NYTalmQm99hmOhySRA4+yyQTmMW1PJ8FAm7Zf86cCiYTf19Exa7+DxoQ== + "@solana/buffer-layout-utils@=0.2.0", "@solana/buffer-layout-utils@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca" @@ -295,163 +377,157 @@ dependencies: buffer "~6.0.3" -"@solana/codecs-core@2.0.0-preview.2": - version "2.0.0-preview.2" - resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-preview.2.tgz#689784d032fbc1fedbde40bb25d76cdcecf6553b" - integrity sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg== - dependencies: - "@solana/errors" "2.0.0-preview.2" - -"@solana/codecs-core@2.0.0-preview.4": - version "2.0.0-preview.4" - resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-preview.4.tgz#770826105f2f884110a21662573e7a2014654324" - integrity sha512-A0VVuDDA5kNKZUinOqHxJQK32aKTucaVbvn31YenGzHX1gPqq+SOnFwgaEY6pq4XEopSmaK16w938ZQS8IvCnw== - dependencies: - "@solana/errors" "2.0.0-preview.4" - -"@solana/codecs-data-structures@2.0.0-preview.2": - version "2.0.0-preview.2" - resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-preview.2.tgz#e82cb1b6d154fa636cd5c8953ff3f32959cc0370" - integrity sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg== - dependencies: - "@solana/codecs-core" "2.0.0-preview.2" - "@solana/codecs-numbers" "2.0.0-preview.2" - "@solana/errors" "2.0.0-preview.2" - -"@solana/codecs-data-structures@2.0.0-preview.4": - version "2.0.0-preview.4" - resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-preview.4.tgz#f8a2470982a9792334737ea64000ccbdff287247" - integrity sha512-nt2k2eTeyzlI/ccutPcG36M/J8NAYfxBPI9h/nQjgJ+M+IgOKi31JV8StDDlG/1XvY0zyqugV3I0r3KAbZRJpA== - dependencies: - "@solana/codecs-core" "2.0.0-preview.4" - "@solana/codecs-numbers" "2.0.0-preview.4" - "@solana/errors" "2.0.0-preview.4" - -"@solana/codecs-numbers@2.0.0-preview.2": - version "2.0.0-preview.2" - resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-preview.2.tgz#56995c27396cd8ee3bae8bd055363891b630bbd0" - integrity sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw== - dependencies: - "@solana/codecs-core" "2.0.0-preview.2" - "@solana/errors" "2.0.0-preview.2" - -"@solana/codecs-numbers@2.0.0-preview.4": - version "2.0.0-preview.4" - resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-preview.4.tgz#6a53b456bb7866f252d8c032c81a92651e150f66" - integrity sha512-Q061rLtMadsO7uxpguT+Z7G4UHnjQ6moVIxAQxR58nLxDPCC7MB1Pk106/Z7NDhDLHTcd18uO6DZ7ajHZEn2XQ== - dependencies: - "@solana/codecs-core" "2.0.0-preview.4" - "@solana/errors" "2.0.0-preview.4" - -"@solana/codecs-strings@2.0.0-preview.2": - version "2.0.0-preview.2" - resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-preview.2.tgz#8bd01a4e48614d5289d72d743c3e81305d445c46" - integrity sha512-EgBwY+lIaHHgMJIqVOGHfIfpdmmUDNoNO/GAUGeFPf+q0dF+DtwhJPEMShhzh64X2MeCZcmSO6Kinx0Bvmmz2g== - dependencies: - "@solana/codecs-core" "2.0.0-preview.2" - "@solana/codecs-numbers" "2.0.0-preview.2" - "@solana/errors" "2.0.0-preview.2" - -"@solana/codecs-strings@2.0.0-preview.4": - version "2.0.0-preview.4" - resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-preview.4.tgz#4d06bb722a55a5d04598d362021bfab4bd446760" - integrity sha512-YDbsQePRWm+xnrfS64losSGRg8Wb76cjK1K6qfR8LPmdwIC3787x9uW5/E4icl/k+9nwgbIRXZ65lpF+ucZUnw== - dependencies: - "@solana/codecs-core" "2.0.0-preview.4" - "@solana/codecs-numbers" "2.0.0-preview.4" - "@solana/errors" "2.0.0-preview.4" - -"@solana/codecs@2.0.0-preview.2": - version "2.0.0-preview.2" - resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-preview.2.tgz#d6615fec98f423166fb89409f9a4ad5b74c10935" - integrity sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA== - dependencies: - "@solana/codecs-core" "2.0.0-preview.2" - "@solana/codecs-data-structures" "2.0.0-preview.2" - "@solana/codecs-numbers" "2.0.0-preview.2" - "@solana/codecs-strings" "2.0.0-preview.2" - "@solana/options" "2.0.0-preview.2" - -"@solana/codecs@2.0.0-preview.4": - version "2.0.0-preview.4" - resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-preview.4.tgz#a1923cc78a6f64ebe656c7ec6335eb6b70405b22" - integrity sha512-gLMupqI4i+G4uPi2SGF/Tc1aXcviZF2ybC81x7Q/fARamNSgNOCUUoSCg9nWu1Gid6+UhA7LH80sWI8XjKaRog== - dependencies: - "@solana/codecs-core" "2.0.0-preview.4" - "@solana/codecs-data-structures" "2.0.0-preview.4" - "@solana/codecs-numbers" "2.0.0-preview.4" - "@solana/codecs-strings" "2.0.0-preview.4" - "@solana/options" "2.0.0-preview.4" - -"@solana/errors@2.0.0-preview.2": - version "2.0.0-preview.2" - resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-preview.2.tgz#e0ea8b008c5c02528d5855bc1903e5e9bbec322e" - integrity sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA== +"@solana/codecs-core@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz#1a2d76b9c7b9e7b7aeb3bd78be81c2ba21e3ce22" + integrity sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ== dependencies: - chalk "^5.3.0" - commander "^12.0.0" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-data-structures@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz#d47b2363d99fb3d643f5677c97d64a812982b888" + integrity sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-numbers@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz#f34978ddf7ea4016af3aaed5f7577c1d9869a614" + integrity sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" -"@solana/errors@2.0.0-preview.4": - version "2.0.0-preview.4" - resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-preview.4.tgz#056ba76b6dd900dafa70117311bec3aef0f5250b" - integrity sha512-kadtlbRv2LCWr8A9V22On15Us7Nn8BvqNaOB4hXsTB3O0fU40D1ru2l+cReqLcRPij4znqlRzW9Xi0m6J5DIhA== +"@solana/codecs-strings@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz#e1d9167075b8c5b0b60849f8add69c0f24307018" + integrity sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-rc.1.tgz#146dc5db58bd3c28e04b4c805e6096c2d2a0a875" + integrity sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/options" "2.0.0-rc.1" + +"@solana/errors@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-rc.1.tgz#3882120886eab98a37a595b85f81558861b29d62" + integrity sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ== dependencies: chalk "^5.3.0" commander "^12.1.0" -"@solana/options@2.0.0-preview.2": - version "2.0.0-preview.2" - resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-preview.2.tgz#13ff008bf43a5056ef9a091dc7bb3f39321e867e" - integrity sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w== +"@solana/functional@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/functional/-/functional-2.0.0-experimental.21e994f.tgz#e7ebdc8fcb14a0a2bc7d0f7df8667d171f54a10b" + integrity sha512-FMXFiTA+hsc9FCv0r47oF7njq/K9x7zh0H+To7tpeqwN65LtJPu5BMG7xZY3rn5TrudgKw6XPuIr3ARbI8+IWA== + +"@solana/instructions@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/instructions/-/instructions-2.0.0-experimental.21e994f.tgz#f308fdb671252ff52fcf08366fe7a1800b0d54b1" + integrity sha512-PuJJzvT7wtwE5UcGavUppnfVWnoxL8CPhZBb96HpOaQhQ2JuyhN445bfav5KkaUMCE6ubrVzOEqzrbtygD3aBg== + +"@solana/keys@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/keys/-/keys-2.0.0-experimental.21e994f.tgz#52e9307a0a0055f2bbff23e76b38cb4ba7f75da3" + integrity sha512-Qsm7ARy69PdIuis7TZy8ELyhq0pcRFPXtaZ8vLFUvsukrcWRowiJ8JJs6Q3tA+gQK5vUn9ABp7a7Qs0FHzgbyw== dependencies: - "@solana/codecs-core" "2.0.0-preview.2" - "@solana/codecs-numbers" "2.0.0-preview.2" + "@solana/assertions" "2.0.0-experimental.21e994f" -"@solana/options@2.0.0-preview.4": - version "2.0.0-preview.4" - resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-preview.4.tgz#212d35d1da87c7efb13de4d3569ad9eb070f013d" - integrity sha512-tv2O/Frxql/wSe3jbzi5nVicIWIus/BftH+5ZR+r9r3FO0/htEllZS5Q9XdbmSboHu+St87584JXeDx3xm4jaA== +"@solana/options@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-rc.1.tgz#06924ba316dc85791fc46726a51403144a85fc4d" + integrity sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA== dependencies: - "@solana/codecs-core" "2.0.0-preview.4" - "@solana/codecs-data-structures" "2.0.0-preview.4" - "@solana/codecs-numbers" "2.0.0-preview.4" - "@solana/codecs-strings" "2.0.0-preview.4" - "@solana/errors" "2.0.0-preview.4" + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" -"@solana/spl-token-group@^0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@solana/spl-token-group/-/spl-token-group-0.0.5.tgz#f955dcca782031c85e862b2b46878d1bb02db6c2" - integrity sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ== +"@solana/rpc-core@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/rpc-core/-/rpc-core-2.0.0-experimental.21e994f.tgz#294c0ea4d99c1bd6b11bb0c0cc67847adb6f3c3a" + integrity sha512-T7VcTLRi4dsqmpFYdnvcHZFS8Vcgdi6funMUrXcM7ofQqb8vWGJnlX6AX0eIZiVsmoYk5Ki8wW4D6Ul6bXZyZg== dependencies: - "@solana/codecs" "2.0.0-preview.4" - "@solana/spl-type-length-value" "0.1.0" + "@metaplex-foundation/umi-serializers" "^0.8.9" + +"@solana/rpc-transport@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/rpc-transport/-/rpc-transport-2.0.0-experimental.21e994f.tgz#2b8c3f97f4853711daaeed03ff0700a1d44aca4a" + integrity sha512-PfGPzRuEodhfLyOD8ZneYQ389SWYgmj1Q/HWQZo8yZMsiAaW/lqCygoW88lecxXKlZF5gJYrBX154kgvGqEM7g== -"@solana/spl-token-metadata@^0.1.3": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.4.tgz#5cdc3b857a8c4a6877df24e24a8648c4132d22ba" - integrity sha512-N3gZ8DlW6NWDV28+vCCDJoTqaCZiF/jDUnk3o8GRkAFzHObiR60Bs1gXHBa8zCPdvOwiG6Z3dg5pg7+RW6XNsQ== +"@solana/spl-single-pool-classic@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@solana/spl-single-pool-classic/-/spl-single-pool-classic-1.0.2.tgz#f675cdf39037cd42a3a4690a030cc77c0837e40c" + integrity sha512-kh2D3KElYsJWZIoksCd5dlC9jsKict7WTS+lZvhaGXTarZbjMqhIaiiMTe5oqKgHSNwavoP05VJ8YlTmbTxTLg== dependencies: - "@solana/codecs" "2.0.0-preview.2" - "@solana/spl-type-length-value" "0.1.0" + "@solana/spl-single-pool" "1.0.0" + "@solana/web3.js" "^1.91.6" -"@solana/spl-token@^0.4.8": - version "0.4.8" - resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.8.tgz#a84e4131af957fa9fbd2727e5fc45dfbf9083586" - integrity sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA== +"@solana/spl-single-pool@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@solana/spl-single-pool/-/spl-single-pool-1.0.0.tgz#eec9ca109ad63936b60cab6f2f6f9566e0cd0eeb" + integrity sha512-m2zNzRXcYXibd2n514TQhWM7WkWuCqddNHmxgPDcpSFpwbP9hNUigjGoJeR1khvKjRj5jV+PdiiwBEWi3pExfw== + dependencies: + "@solana/web3.js" "=2.0.0-experimental.21e994f" + +"@solana/spl-token-group@^0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz#83c00f0cd0bda33115468cd28b89d94f8ec1fee4" + integrity sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug== + dependencies: + "@solana/codecs" "2.0.0-rc.1" + +"@solana/spl-token-metadata@^0.1.2", "@solana/spl-token-metadata@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz#d240947aed6e7318d637238022a7b0981b32ae80" + integrity sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA== + dependencies: + "@solana/codecs" "2.0.0-rc.1" + +"@solana/spl-token@^0.3.4": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.11.tgz#cdc10f9472b29b39c8983c92592cadd06627fb9a" + integrity sha512-bvohO3rIMSVL24Pb+I4EYTJ6cL82eFpInEXD/I8K8upOGjpqHsKUoAempR/RnUlI1qSFNyFlWJfu6MNUgfbCQQ== dependencies: "@solana/buffer-layout" "^4.0.0" "@solana/buffer-layout-utils" "^0.2.0" - "@solana/spl-token-group" "^0.0.5" - "@solana/spl-token-metadata" "^0.1.3" + "@solana/spl-token-metadata" "^0.1.2" buffer "^6.0.3" -"@solana/spl-type-length-value@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@solana/spl-type-length-value/-/spl-type-length-value-0.1.0.tgz#b5930cf6c6d8f50c7ff2a70463728a4637a2f26b" - integrity sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA== +"@solana/spl-token@^0.4.8": + version "0.4.9" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.9.tgz#24d032d2935f237925c3b058ba6bb1e1ece5428c" + integrity sha512-g3wbj4F4gq82YQlwqhPB0gHFXfgsC6UmyGMxtSLf/BozT/oKd59465DbnlUK8L8EcimKMavxsVAMoLcEdeCicg== dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + "@solana/spl-token-group" "^0.0.7" + "@solana/spl-token-metadata" "^0.1.6" buffer "^6.0.3" +"@solana/transactions@2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/transactions/-/transactions-2.0.0-experimental.21e994f.tgz#48dc6483a1d57e85cd23c88e854239b2ac0bd097" + integrity sha512-DunbTMBzlC7jmTzkFsRm5DhGe+MjaZ8m+SJ7V520mQq+kxrbPrRmI3ikfUVdejg0WaEV4Dy+RwQ5xllsrJ47kA== + dependencies: + "@metaplex-foundation/umi-serializers" "^0.8.9" + "@solana/addresses" "2.0.0-experimental.21e994f" + "@solana/keys" "2.0.0-experimental.21e994f" + "@solana/wallet-adapter-base@^0.9.23": version "0.9.23" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.23.tgz#3b17c28afd44e173f44f658bf9700fd637e12a11" @@ -470,12 +546,27 @@ "@wallet-standard/base" "^1.0.1" "@wallet-standard/features" "^1.0.3" -"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.90.0", "@solana/web3.js@^1.93.2", "@solana/web3.js@^1.95.2": - version "1.95.2" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.2.tgz#6f8a0362fa75886a21550dbec49aad54481463a6" - integrity sha512-SjlHp0G4qhuhkQQc+YXdGkI8EerCqwxvgytMgBpzMUQTafrkNant3e7pgilBGgjy/iM40ICvWBLgASTPMrQU7w== +"@solana/web3.js@=2.0.0-experimental.21e994f": + version "2.0.0-experimental.21e994f" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-2.0.0-experimental.21e994f.tgz#c5568d88903f63c85de700c03b2acef2217d059f" + integrity sha512-Yy0D57nlNTDm0BhBRIM85Sn52T6vjxpBRRdwE/FOJJmN92n0Qpc4mTAwOPfEqoVpiTcluUBZ4l8FAWxjGCFMgQ== + dependencies: + "@metaplex-foundation/umi-serializers" "^0.8.9" + "@solana/addresses" "2.0.0-experimental.21e994f" + "@solana/functional" "2.0.0-experimental.21e994f" + "@solana/instructions" "2.0.0-experimental.21e994f" + "@solana/keys" "2.0.0-experimental.21e994f" + "@solana/rpc-core" "2.0.0-experimental.21e994f" + "@solana/rpc-transport" "2.0.0-experimental.21e994f" + "@solana/transactions" "2.0.0-experimental.21e994f" + fast-stable-stringify "^1.0.0" + +"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.90.0", "@solana/web3.js@^1.91.6", "@solana/web3.js@^1.93.2", "@solana/web3.js@^1.95.2", "@solana/web3.js@^1.95.3", "@solana/web3.js@^1.95.8": + version "1.95.8" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.8.tgz#2d49abda23f7a79a3cc499ab6680f7be11786ee1" + integrity sha512-sBHzNh7dHMrmNS5xPD1d0Xa2QffW/RXaxu/OysRXBfwTp+LYqGGmMtCYYwrHPrN5rjAmJCsQRNAwv4FM0t3B6g== dependencies: - "@babel/runtime" "^7.24.8" + "@babel/runtime" "^7.25.0" "@noble/curves" "^1.4.2" "@noble/hashes" "^1.4.0" "@solana/buffer-layout" "^4.0.1" @@ -512,12 +603,66 @@ rpc-websockets "^7.5.1" superstruct "^0.14.2" +"@solworks/soltoolkit-sdk@^0.0.37": + version "0.0.37" + resolved "https://registry.yarnpkg.com/@solworks/soltoolkit-sdk/-/soltoolkit-sdk-0.0.37.tgz#53800f0e43c56962194b130e02c713d8d7fb6a7c" + integrity sha512-3+mNv9ymup0LTOmZRhIWvqGmf9Col1TKuZ2I9dqrnbSveOBnjCgNDUkfge4qJ2FcWNZdVEvHzUd3UkV902INAg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/spl-token" "^0.3.4" + "@solana/web3.js" "^1.95.3" + "@types/bn.js" "^5.1.0" + "@types/node" "^18.7.13" + "@types/node-fetch" "^2.6.2" + bn.js "^5.2.1" + bs58 "^5.0.0" + decimal.js "^10.4.0" + typescript "^4.8.2" + +"@soncodi/signal@~2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@soncodi/signal/-/signal-2.0.7.tgz#0a2c361b02dbfdbcf4e66b78e5f711e0a13d6e83" + integrity sha512-zA2oZluZmVvgZEDjF243KWD1S2J+1SH1MVynI0O1KRgDt1lU8nqk7AK3oQfW/WpwT51L5waGSU0xKF/9BTP5Cw== + "@swc/helpers@^0.5.11": - version "0.5.12" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.12.tgz#37aaca95284019eb5d2207101249435659709f4b" - integrity sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g== + version "0.5.15" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== dependencies: - tslib "^2.4.0" + tslib "^2.8.0" + +"@switchboard-xyz/common@^2.5.7": + version "2.5.7" + resolved "https://registry.yarnpkg.com/@switchboard-xyz/common/-/common-2.5.7.tgz#8b781a882318d7e2e661bab3bc3695575c3da20e" + integrity sha512-xUThQ2Zuf+2/nO1J459DC3BrQn1OxytbrU84mxuGZKw4APhtm7hKKCaIdQ1uzdASj9v3NRZ+/KIPeMaQfk+ZZA== + dependencies: + "@solana/web3.js" "^1.95.8" + axios "^1.7.8" + big.js "^6.2.2" + bn.js "^5.2.1" + bs58 "^6.0.0" + cron-validator "^1.3.1" + decimal.js "^10.4.3" + js-sha256 "^0.11.0" + lodash "^4.17.21" + protobufjs "^7.4.0" + yaml "^2.6.1" + +"@switchboard-xyz/on-demand@^1.2.36": + version "1.2.54" + resolved "https://registry.yarnpkg.com/@switchboard-xyz/on-demand/-/on-demand-1.2.54.tgz#ddf88d8bbc9525c2a447505f6ef22323da34db28" + integrity sha512-R7f0LmtV/XEbWhPVTKCWFIzGnbBgu8caP9eOlUapgcR+07oChU2SIyFtoG/bjNbAwKgx8TNVOxEk5RaZe5EyiA== + dependencies: + "@brokerloop/ttlcache" "^3.2.3" + "@coral-xyz/anchor-30" "npm:@coral-xyz/anchor@0.30.1" + "@solana/web3.js" "^1.95.8" + "@solworks/soltoolkit-sdk" "^0.0.37" + "@switchboard-xyz/common" "^2.5.7" + axios "^1.7.8" + big.js "^6.2.2" + bs58 "^6.0.0" + js-yaml "^4.1.0" + protobufjs "^7.4.0" "@tsconfig/node10@^1.0.7": version "1.0.11" @@ -540,21 +685,21 @@ integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@types/bn.js@^5.1.0": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" - integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== + version "5.1.6" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.6.tgz#9ba818eec0c85e4d3c679518428afdf611d03203" + integrity sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w== dependencies: "@types/node" "*" "@types/chai@^4.3.0": - version "4.3.4" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4" - integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== + version "4.3.20" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.20.tgz#cb291577ed342ca92600430841a00329ba05cecc" + integrity sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ== "@types/connect@^3.4.33": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== dependencies: "@types/node" "*" @@ -563,28 +708,38 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/mocha@^9.0.0": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" - integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== +"@types/mocha@^10.0.10": + version "10.0.10" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.10.tgz#91f62905e8d23cbd66225312f239454a23bebfa0" + integrity sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q== -"@types/node@*": - version "18.11.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.13.tgz#dff34f226ec1ac0432ae3b136ec5552bd3b9c0fe" - integrity sha512-IASpMGVcWpUsx5xBOrxMj7Bl8lqfuTY7FKAnPmu5cHkfQVWF8GulWS1jbRqA934qZL35xh5xN/+Xe/i26Bod4w== +"@types/node-fetch@^2.6.2": + version "2.6.12" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" + integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== + dependencies: + "@types/node" "*" + form-data "^4.0.0" -"@types/node@>=13.7.0": - version "22.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.3.0.tgz#7f8da0e2b72c27c4f9bd3cb5ef805209d04d4f9e" - integrity sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g== +"@types/node@*", "@types/node@>=13.7.0": + version "22.10.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" + integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== dependencies: - undici-types "~6.18.2" + undici-types "~6.20.0" "@types/node@^12.12.54": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== +"@types/node@^18.7.13": + version "18.19.68" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.68.tgz#f4f10d9927a7eaf3568c46a6d739cc0967ccb701" + integrity sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw== + dependencies: + undici-types "~5.26.4" + "@types/uuid@^8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" @@ -598,23 +753,23 @@ "@types/node" "*" "@types/ws@^8.2.2": - version "8.5.12" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" - integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== + version "8.5.13" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== dependencies: "@types/node" "*" -"@wallet-standard/base@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@wallet-standard/base/-/base-1.0.1.tgz#860dd94d47c9e3c5c43b79d91c6afdbd7a36264e" - integrity sha512-1To3ekMfzhYxe0Yhkpri+Fedq0SYcfrOfJi3vbLjMwF2qiKPjTGLwZkf2C9ftdQmxES+hmxhBzTwF4KgcOwf8w== +"@wallet-standard/base@^1.0.1", "@wallet-standard/base@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@wallet-standard/base/-/base-1.1.0.tgz#214093c0597a1e724ee6dbacd84191dfec62bb33" + integrity sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ== "@wallet-standard/features@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@wallet-standard/features/-/features-1.0.3.tgz#c992876c5e4f7a0672f8869c4146c87e0dfe48c8" - integrity sha512-m8475I6W5LTatTZuUz5JJNK42wFRgkJTB0I9tkruMwfqBF2UN2eomkYNVf9RbrsROelCRzSFmugqjKZBFaubsA== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@wallet-standard/features/-/features-1.1.0.tgz#f256d7b18940c8d134f66164330db358a8f5200e" + integrity sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg== dependencies: - "@wallet-standard/base" "^1.0.1" + "@wallet-standard/base" "^1.1.0" JSONStream@^1.3.5: version "1.3.5" @@ -625,16 +780,16 @@ JSONStream@^1.3.5: through ">=2.2.7 <3" acorn-walk@^8.1.1: - version "8.3.3" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" - integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== dependencies: acorn "^8.11.0" acorn@^8.11.0, acorn@^8.4.1: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== agentkeepalive@^4.2.1, agentkeepalive@^4.3.0, agentkeepalive@^4.5.0: version "4.5.0" @@ -643,6 +798,11 @@ agentkeepalive@^4.2.1, agentkeepalive@^4.3.0, agentkeepalive@^4.5.0: dependencies: humanize-ms "^1.2.1" +anchor-bankrun@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/anchor-bankrun/-/anchor-bankrun-0.4.1.tgz#6fbbf824673f5fcdf353b1f1003d561c14a67c79" + integrity sha512-ryCT84tw+lP4AqRpBsZJbt/KTRoVVKufkxFGd77gnx9iHkbwA5G/9cALk/eqLQm4xeUWTrJSJdEVyg2e74iP9A== + ansi-colors@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -688,15 +848,29 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.7.8: + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base-x@^3.0.2: - version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" - integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + version "3.0.10" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.10.tgz#62de58653f8762b5d6f8d9fe30fa75f7b2585a75" + integrity sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ== dependencies: safe-buffer "^5.0.1" @@ -715,6 +889,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +big.js@^6.2.1, big.js@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.2.tgz#be3bb9ac834558b53b099deef2a1d06ac6368e1a" + integrity sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ== + bigint-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" @@ -728,9 +907,9 @@ bignumber.js@^9.0.1, bignumber.js@^9.1.2: integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== bindings@^1.3.0: version "1.5.0" @@ -766,11 +945,11 @@ brace-expansion@^2.0.1: balanced-match "^1.0.0" braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browser-stdout@^1.3.1: version "1.3.1" @@ -817,9 +996,9 @@ buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: ieee754 "^1.2.1" bufferutil@^4.0.1: - version "4.0.7" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" - integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== dependencies: node-gyp-build "^4.3.0" @@ -829,17 +1008,17 @@ camelcase@^6.0.0, camelcase@^6.3.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== chai@^4.3.4: - version "4.3.7" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" - integrity sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A== + version "4.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" + integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== dependencies: assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^4.1.2" - get-func-name "^2.0.0" - loupe "^2.3.1" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" pathval "^1.1.1" - type-detect "^4.0.5" + type-detect "^4.1.0" chalk@^4.1.0: version "4.1.2" @@ -854,10 +1033,12 @@ chalk@^5.3.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" chokidar@^3.5.3: version "3.6.0" @@ -904,7 +1085,14 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -commander@^12.0.0, commander@^12.1.0: +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^12.1.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== @@ -919,12 +1107,17 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cron-validator@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cron-validator/-/cron-validator-1.3.1.tgz#8f2fe430f92140df77f91178ae31fc1e3a48a20e" + integrity sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A== + cross-fetch@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== dependencies: - node-fetch "2.6.7" + node-fetch "^2.6.12" crypto-hash@^1.3.0: version "1.3.0" @@ -932,26 +1125,26 @@ crypto-hash@^1.3.0: integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== debug@^4.3.5: - version "4.3.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" - integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== dependencies: - ms "2.1.2" + ms "^2.1.3" decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decimal.js@^10.4.3: +decimal.js@^10.4.0, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -deep-eql@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" - integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== +deep-eql@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" + integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== dependencies: type-detect "^4.0.0" @@ -960,6 +1153,11 @@ delay@^5.0.0: resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + diff@^3.1.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -984,9 +1182,9 @@ dot-case@^3.0.4: tslib "^2.0.3" dotenv@^16.0.3: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== emoji-regex@^8.0.0: version "8.0.0" @@ -1006,9 +1204,9 @@ es6-promisify@^5.0.0: es6-promise "^4.0.3" escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-string-regexp@^4.0.0: version "4.0.0" @@ -1040,10 +1238,10 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -1060,25 +1258,39 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== glob-parent@~5.1.2: version "5.1.2" @@ -1178,9 +1390,9 @@ isomorphic-ws@^4.0.1: integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== jayson@^4.0.0, jayson@^4.1.0, jayson@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.1.tgz#282ff13d3cea09776db684b7eeca98c47b2fa99a" - integrity sha512-5ZWm4Q/0DHPyeMfAsrwViwUS2DMVsQgWh8bEEIVTkfb3DzHZ2L3G5WUnF+AKmGjjM9r1uAv73SaqC1/U4RL45w== + version "4.1.3" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.3.tgz#db9be2e4287d9fef4fc05b5fe367abe792c2eee8" + integrity sha512-LtXh5aYZodBZ9Fc3j6f2w+MTNcnxteMOrb+QgIouguGOulWi0lieEkOUg+HkjjFs0DGoWDds6bi4E9hpNFLulQ== dependencies: "@types/connect" "^3.4.33" "@types/node" "^12.12.54" @@ -1209,6 +1421,11 @@ jito-ts@^3.0.1: node-fetch "^2.6.7" superstruct "^1.0.3" +js-sha256@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.11.0.tgz#256a921d9292f7fe98905face82e367abaca9576" + integrity sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q== + js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -1221,7 +1438,7 @@ json-stringify-safe@^5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@^1.0.1: +json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== @@ -1245,6 +1462,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -1258,12 +1480,12 @@ long@^5.0.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== -loupe@^2.3.1: - version "2.3.6" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.6.tgz#76e4af498103c532d1ecc9be102036a21f787b53" - integrity sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA== +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== dependencies: - get-func-name "^2.0.0" + get-func-name "^2.0.1" lower-case@^2.0.2: version "2.0.2" @@ -1277,6 +1499,18 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimatch@^5.0.1, minimatch@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" @@ -1285,9 +1519,9 @@ minimatch@^5.0.1, minimatch@^5.1.6: brace-expansion "^2.0.1" minimist@^1.2.0, minimist@^1.2.6: - version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mkdirp@^0.5.1: version "0.5.6" @@ -1297,9 +1531,9 @@ mkdirp@^0.5.1: minimist "^1.2.6" mocha@^10.2.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.0.tgz#9e5cbed8fa9b37537a25bd1f7fb4f6fc45458b9a" - integrity sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA== + version "10.8.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.8.2.tgz#8d8342d016ed411b12a429eb731b825f961afb96" + integrity sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg== dependencies: ansi-colors "^4.1.3" browser-stdout "^1.3.1" @@ -1322,11 +1556,6 @@ mocha@^10.2.0: yargs-parser "^20.2.9" yargs-unparser "^2.0.0" -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - ms@^2.0.0, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -1340,14 +1569,7 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^2.6.7, node-fetch@^2.7.0: +node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -1355,9 +1577,9 @@ node-fetch@^2.6.7, node-fetch@^2.7.0: whatwg-url "^5.0.0" node-gyp-build@^4.3.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" - integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== + version "4.8.4" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -1411,14 +1633,14 @@ picomatch@^2.0.4, picomatch@^2.2.1: integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== prettier@^2.6.2: - version "2.8.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc" - integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg== + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -protobufjs@^7.2.5: - version "7.3.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.2.tgz#60f3b7624968868f6f739430cfbc8c9370e26df4" - integrity sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg== +protobufjs@^7.2.5, protobufjs@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" + integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -1433,6 +1655,11 @@ protobufjs@^7.2.5: "@types/node" ">=13.7.0" long "^5.0.0" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -1470,9 +1697,9 @@ rpc-websockets@^7.5.1: utf-8-validate "^5.0.2" rpc-websockets@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.2.tgz#4c1568d00b8100f997379a363478f41f8f4b242c" - integrity sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw== + version "9.0.4" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.4.tgz#9d8ee82533b5d1e13d9ded729e3e38d0d8fa083f" + integrity sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ== dependencies: "@swc/helpers" "^0.5.11" "@types/uuid" "^8.3.4" @@ -1505,6 +1732,45 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +solana-bankrun-darwin-arm64@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/solana-bankrun-darwin-arm64/-/solana-bankrun-darwin-arm64-0.3.1.tgz#65ab6cd2e74eef260c38251f4c53721cf5b9030f" + integrity sha512-9LWtH/3/WR9fs8Ve/srdo41mpSqVHmRqDoo69Dv1Cupi+o1zMU6HiEPUHEvH2Tn/6TDbPEDf18MYNfReLUqE6A== + +solana-bankrun-darwin-universal@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/solana-bankrun-darwin-universal/-/solana-bankrun-darwin-universal-0.3.1.tgz#bf691457cf046e8739c021ca11e48de5b4fefd45" + integrity sha512-muGHpVYWT7xCd8ZxEjs/bmsbMp8XBqroYGbE4lQPMDUuLvsJEIrjGqs3MbxEFr71sa58VpyvgywWd5ifI7sGIg== + +solana-bankrun-darwin-x64@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/solana-bankrun-darwin-x64/-/solana-bankrun-darwin-x64-0.3.1.tgz#c6f30c0a6bc3e1621ed90ce7562f26e93bf5303f" + integrity sha512-oCaxfHyt7RC3ZMldrh5AbKfy4EH3YRMl8h6fSlMZpxvjQx7nK7PxlRwMeflMnVdkKKp7U8WIDak1lilIPd3/lg== + +solana-bankrun-linux-x64-gnu@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/solana-bankrun-linux-x64-gnu/-/solana-bankrun-linux-x64-gnu-0.3.1.tgz#78b522f1a581955a48f43a8fb560709c11301cfd" + integrity sha512-PfRFhr7igGFNt2Ecfdzh3li9eFPB3Xhmk0Eib17EFIB62YgNUg3ItRnQQFaf0spazFjjJLnglY1TRKTuYlgSVA== + +solana-bankrun-linux-x64-musl@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/solana-bankrun-linux-x64-musl/-/solana-bankrun-linux-x64-musl-0.3.1.tgz#1a044a132138a0084e82406ec7bf4939f06bed68" + integrity sha512-6r8i0NuXg3CGURql8ISMIUqhE7Hx/O7MlIworK4oN08jYrP0CXdLeB/hywNn7Z8d1NXrox/NpYUgvRm2yIzAsQ== + +solana-bankrun@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/solana-bankrun/-/solana-bankrun-0.3.1.tgz#13665ab7c1c15ec2b3354aae56980d0ded514998" + integrity sha512-inRwON7fBU5lPC36HdEqPeDg15FXJYcf77+o0iz9amvkUMJepcwnRwEfTNyMVpVYdgjTOBW5vg+596/3fi1kGA== + dependencies: + "@solana/web3.js" "^1.68.0" + bs58 "^4.0.1" + optionalDependencies: + solana-bankrun-darwin-arm64 "0.3.1" + solana-bankrun-darwin-universal "0.3.1" + solana-bankrun-darwin-x64 "0.3.1" + solana-bankrun-linux-x64-gnu "0.3.1" + solana-bankrun-linux-x64-musl "0.3.1" + source-map-support@^0.5.6: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -1648,39 +1914,44 @@ ts-node@^10.9.1: yn "3.1.1" tsconfig-paths@^3.5.0: - version "3.14.1" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" - integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== dependencies: "@types/json5" "^0.0.29" - json5 "^1.0.1" + json5 "^1.0.2" minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.3: - version "2.4.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" - integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== +tslib@^2.0.3, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tslib@^2.4.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +type-detect@^4.0.0, type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +typescript@^4.8.2: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -typescript@^4.3.5: - version "4.9.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" - integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== +typescript@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== -undici-types@~6.18.2: - version "6.18.2" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.18.2.tgz#8b678cf939d4fc9ec56be3c68ed69c619dee28b0" - integrity sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== utf-8-validate@^5.0.2: version "5.0.10" @@ -1737,15 +2008,20 @@ ws@^7.5.10: integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.5.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" - integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yaml@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== + yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"