diff --git a/.github/workflows/build-upload-binaries.yaml b/.github/workflows/build-upload-binaries.yaml new file mode 100644 index 00000000..5006c910 --- /dev/null +++ b/.github/workflows/build-upload-binaries.yaml @@ -0,0 +1,203 @@ +name: Build and Upload Binaries to Release + +on: + workflow_dispatch: + inputs: + release_tag: + description: 'Release tag to upload binaries to (e.g. jito-restaking-cli-v0.1.0)' + required: true + type: string + package_path: + description: 'Path to the package' + required: true + default: 'cli' + type: choice + options: + - cli + publish_release: + description: 'Publish the release after uploading binaries' + required: true + default: true + type: boolean + +jobs: + build-binaries: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + name: x86_64-unknown-linux-gnu + - os: macos-latest + target: x86_64-apple-darwin + name: x86_64-apple-darwin + - os: windows-latest + target: x86_64-pc-windows-msvc + name: x86_64-pc-windows-msvc + steps: + - name: Git Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + ref: refs/tags/${{ github.event.inputs.release_tag }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: 1.84.1 + targets: ${{ matrix.target }} + override: true + + - name: Install system dependencies (Linux only) + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update && sudo apt-get install -y libudev-dev + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + with: + key: "build-${{ matrix.target }}-${{ inputs.package_path }}" + + - name: Extract crate name and version + id: crate_info + shell: bash + run: | + CRATE_NAME=$(grep -m1 '^name =' ${{ inputs.package_path }}/Cargo.toml | cut -d '"' -f2) + VERSION=$(grep -m1 '^version =' ${{ inputs.package_path }}/Cargo.toml | cut -d '"' -f2) + echo "crate_name=$CRATE_NAME" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building $CRATE_NAME version $VERSION for ${{ matrix.target }}" + + - name: Build + working-directory: ${{ inputs.package_path }} + run: cargo build --release --target ${{ matrix.target }} + + - name: Prepare binary (Unix) + if: matrix.os != 'windows-latest' + run: | + CRATE_NAME="${{ steps.crate_info.outputs.crate_name }}" + VERSION="${{ steps.crate_info.outputs.version }}" + BINARY_NAME="${CRATE_NAME}-v${VERSION}-${{ matrix.target }}" + + echo "Building binary with new version: $VERSION" + + # Copy binary to root with appropriate name + cp ./target/${{ matrix.target }}/release/${CRATE_NAME} ${BINARY_NAME} + + # Create checksum + shasum -a 256 ${BINARY_NAME} > ${BINARY_NAME}.sha256 + + - name: Prepare binary (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + $CRATE_NAME = "${{ steps.crate_info.outputs.crate_name }}" + $VERSION = "${{ steps.crate_info.outputs.version }}" + $BINARY_NAME = "${CRATE_NAME}-v${VERSION}-${{ matrix.target }}.exe" + + Write-Host "Building binary with new version: $VERSION" + + # Copy binary to root with appropriate name + Copy-Item "./target/${{ matrix.target }}/release/${CRATE_NAME}.exe" -Destination $BINARY_NAME + + # Create checksum + $hash = Get-FileHash -Path $BINARY_NAME -Algorithm SHA256 + $hash.Hash | Out-File -FilePath "${BINARY_NAME}.sha256" + + - name: Upload binary artifacts (Unix) + if: matrix.os != 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }} + path: | + ${{ steps.crate_info.outputs.crate_name }}-v${{ steps.crate_info.outputs.version }}-${{ matrix.target }} + ${{ steps.crate_info.outputs.crate_name }}-v${{ steps.crate_info.outputs.version }}-${{ matrix.target }}.sha256 + retention-days: 7 + + - name: Upload binary artifacts (Windows) + if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }} + path: | + ${{ steps.crate_info.outputs.crate_name }}-v${{ steps.crate_info.outputs.version }}-${{ matrix.target }}.exe + ${{ steps.crate_info.outputs.crate_name }}-v${{ steps.crate_info.outputs.version }}-${{ matrix.target }}.exe.sha256 + retention-days: 7 + + publish-release: + name: Publish Release + needs: build-binaries + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Git Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.release_tag }} + + - name: Extract release information + id: release_info + shell: bash + run: | + CRATE_NAME=$(basename "${{ inputs.package_path }}") + VERSION=$(grep -m1 'version =' ${{ inputs.package_path }}/Cargo.toml | cut -d '"' -f2) + echo "crate_name=$CRATE_NAME" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create release directory + run: mkdir -p release-binaries + + - name: Download Linux binary + uses: actions/download-artifact@v4 + with: + name: x86_64-unknown-linux-gnu + path: release-binaries + + - name: Download macOS binary + uses: actions/download-artifact@v4 + with: + name: x86_64-apple-darwin + path: release-binaries + + - name: Download Windows binary + uses: actions/download-artifact@v4 + with: + name: x86_64-pc-windows-msvc + path: release-binaries + + - name: Generate release notes + id: release_notes + run: | + echo "" >> RELEASE_NOTES.md + echo "## Binaries" >> RELEASE_NOTES.md + echo "- Linux (x86_64-unknown-linux-gnu)" >> RELEASE_NOTES.md + echo "- macOS (x86_64-apple-darwin)" >> RELEASE_NOTES.md + echo "- Windows (x86_64-pc-windows-msvc)" >> RELEASE_NOTES.md + + if [ -f "CHANGELOG.md" ]; then + echo "" >> RELEASE_NOTES.md + echo "## Changelog" >> RELEASE_NOTES.md + # Extract the relevant section from CHANGELOG.md if it exists + grep -A 50 "^## ${{ steps.release_info.outputs.version }}" CHANGELOG.md | grep -B 50 -m 2 "^## " | head -n -1 >> RELEASE_NOTES.md || true + fi + + - name: Update release with binaries + uses: ncipollo/release-action@v1 + with: + tag: ${{ github.event.inputs.release_tag }} + artifacts: "./release-binaries/*" + artifactErrorsFailBuild: false + allowUpdates: true + omitBody: true # Don't update the release body/notes + omitName: true # Don't update the release name + draft: ${{ github.event.inputs.publish_release != 'true' }} + + - name: Publish Release + if: github.event.inputs.publish_release == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release edit "${{ github.event.inputs.release_tag }}" --draft=false \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1b7ba3ed..87c7e846 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,62 +28,34 @@ jobs: - run: cargo audit --ignore RUSTSEC-2022-0093 --ignore RUSTSEC-2024-0344 --ignore RUSTSEC-2024-0421 --ignore RUSTSEC-2025-0022 code_gen: - # cargo b && ./target/debug/jito-restaking-cli --markdown-help > ./docs/_tools/00_cli.md && ./target/debug/jito-shank-cli && yarn generate-clients && cargo b + # cargo b && ./target/debug/jito-shank-cli && yarn generate-clients && cargo b name: code generation runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: components: rustfmt, clippy toolchain: 1.84.1 - name: Install system dependencies run: sudo apt-get update && sudo apt-get install -y libudev-dev - - name: cargo build - run: cargo b -r - - name: Generate the CLI markdown - run: ./target/release/jito-restaking-cli --markdown-help > ./docs/_tools/00_cli.md - - name: Verify no CLI files changes - uses: tj-actions/verify-changed-files@v20 - with: - fail-if-changed: true - fail-message: 'Unexpected changes in the CLI files. Please run `./target/release/jito-restaking-cli --markdown-help > ./docs/_tools/00_cli.md` to regenerate the files.' - - name: Regenerate Shank IDL files - run: ./target/release/jito-shank-cli - - name: Verify no changed files - uses: tj-actions/verify-changed-files@v20 - with: - fail-if-changed: true - fail-message: 'Unexpected changes in the shank IDL files. Please run `./target/release/jito-shank-cli` to regenerate the files.' + - name: Set Node.js 22.x uses: actions/setup-node@v3 with: - node-version: 22.x - - name: Run install - uses: borales/actions-yarn@v4 - with: - cmd: install - - name: Generate kinobi IDL files - uses: borales/actions-yarn@v4 - with: - cmd: generate-clients - - name: Update dependencies - uses: borales/actions-yarn@v4 - with: - cmd: update-dependencies - - name: Verify no changed files + node-version: '22.x' + + - name: Generate all code + run: make generate-code + + - name: Verify no file changes uses: tj-actions/verify-changed-files@v20 - id: verify-changed-kinobi-files - - name: Run step only when any of the above files change. - if: steps.verify-changed-kinobi-files.outputs.files_changed == 'true' - env: - CHANGED_FILES: ${{ steps.verify-changed-kinobi-files.outputs.changed_files }} - run: | - echo "Changed files: $CHANGED_FILES" - echo "Unexpected changes in the client files. Please run `yarn generate-clients` to regenerate the files." - exit 1 + with: + fail-if-changed: true + fail-message: 'Unexpected changes in generated files. Please run `make generate-code` locally to regenerate the files.' lint: name: lint diff --git a/.github/workflows/jekyll-gh-pages.yaml b/.github/workflows/jekyll-gh-pages.yaml deleted file mode 100644 index 8546b224..00000000 --- a/.github/workflows/jekyll-gh-pages.yaml +++ /dev/null @@ -1,46 +0,0 @@ -name: Build Jekyll site -on: - push: - branches: [ "master" ] - -permissions: - contents: read - pages: write - id-token: write - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y libudev-dev - # Build the cargo docs - - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: nightly-2024-07-25 - - name: Generate Cargo Docs - run: cargo doc --no-deps - - name: Copy Cargo Docs to Jekyll source - run: cp -R target/doc docs/api - - # Setup Github Pages - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Build - uses: actions/jekyll-build-pages@v1.0.13 - with: - source: ./docs - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - deploy: - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} \ No newline at end of file diff --git a/.github/workflows/publish-cli.yaml b/.github/workflows/publish-cli.yaml index 462ea8de..264c85da 100644 --- a/.github/workflows/publish-cli.yaml +++ b/.github/workflows/publish-cli.yaml @@ -3,6 +3,14 @@ name: Publish CLI on: workflow_dispatch: inputs: + crate: + description: Crate to publish + required: true + default: cli + type: choice + options: + - cli + - shank-cli level: description: Version increment level required: true @@ -80,10 +88,12 @@ jobs: env: SBF_OUT_DIR: ${{ github.workspace }}/target/sbf-solana-solana/release - publish_cli: - name: Publish CLI + publish_crate: + name: Publish ${{ inputs.crate }} Crate runs-on: ubuntu-latest needs: test_sbf + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} permissions: contents: write steps: @@ -101,7 +111,10 @@ jobs: - name: Rust Cache uses: Swatinem/rust-cache@v2 with: - key: "restaking-publish-${{ inputs.package_path }}" + key: "restaking-publish-${{ inputs.crate }}" + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y libudev-dev - name: Install Cargo Release uses: baptiste0928/cargo-install@v3 @@ -113,9 +126,22 @@ jobs: git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" - - name: Publish CLI Crate + - name: Set crate directory + id: crate_info + run: | + if [ "${{ inputs.crate }}" == "cli" ]; then + echo "directory=cli" >> $GITHUB_OUTPUT + echo "package_name=jito-restaking-cli" >> $GITHUB_OUTPUT + echo "release_name=Jito Restaking CLI" >> $GITHUB_OUTPUT + elif [ "${{ inputs.crate }}" == "shank-cli" ]; then + echo "directory=shank-cli" >> $GITHUB_OUTPUT + echo "package_name=jito-shank-cli" >> $GITHUB_OUTPUT + echo "release_name=Jito Shank CLI" >> $GITHUB_OUTPUT + fi + + - name: Publish Crate id: publish - working-directory: cli + working-directory: ${{ steps.crate_info.outputs.directory }} run: | OLD_VERSION=$(grep -m1 'version =' Cargo.toml | cut -d '"' -f2) @@ -129,28 +155,19 @@ jobs: echo "old_version=${OLD_VERSION}" >> $GITHUB_OUTPUT echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT - echo "git_tag=v${NEW_VERSION}-cli" >> $GITHUB_OUTPUT - - # We will add it later - # Optional: Build binaries for different platforms - # - name: Build CLI binaries - # if: inputs.create_binaries == 'true' - # run: | - # # Build for different targets - # cargo build --release --bin your-cli-name - # # You could add cross-compilation here + echo "git_tag=v${NEW_VERSION}-${{ inputs.crate }}" >> $GITHUB_OUTPUT - - name: Create CLI Release + - name: Create Release if: inputs.dry_run != 'true' uses: ncipollo/release-action@v1 with: tag: ${{ steps.publish.outputs.git_tag }} - name: "Jito Restaking CLI v${{ steps.publish.outputs.new_version }}" + name: "${{ steps.crate_info.outputs.release_name }} v${{ steps.publish.outputs.new_version }}" body: | - ## CLI Release v${{ steps.publish.outputs.new_version }} + ## ${{ steps.crate_info.outputs.release_name }} Release v${{ steps.publish.outputs.new_version }} ### Installation ```bash - cargo install jito-restaking-cli --version ${{ steps.publish.outputs.new_version }} + cargo install ${{ steps.crate_info.outputs.package_name }} --version ${{ steps.publish.outputs.new_version }} ``` - makeLatest: true + makeLatest: true \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c3557edb..09445173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,15 +360,6 @@ 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 = "anstream" version = "0.6.18" @@ -1058,21 +1049,6 @@ dependencies = [ "inout", ] -[[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", - "unicode-width 0.1.14", - "vec_map", -] - [[package]] name = "clap" version = "4.5.28" @@ -1083,15 +1059,6 @@ dependencies = [ "clap_derive", ] -[[package]] -name = "clap-markdown" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ebc67e6266e14f8b31541c2f204724fa2ac7ad5c17d6f5908fbb92a60f42cff" -dependencies = [ - "clap 4.5.28", -] - [[package]] name = "clap_builder" version = "4.5.27" @@ -1101,7 +1068,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -1169,7 +1136,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width", "windows-sys 0.59.0", ] @@ -1365,7 +1332,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.98", ] @@ -1441,18 +1408,6 @@ dependencies = [ "syn 1.0.109", ] -[[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" @@ -1497,16 +1452,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.3.7" @@ -1518,17 +1463,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -2142,19 +2076,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hidapi" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" -dependencies = [ - "cc", - "cfg-if", - "libc", - "pkg-config", - "windows-sys 0.48.0", -] - [[package]] name = "histogram" version = "0.6.9" @@ -2509,7 +2430,7 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width", "web-time", ] @@ -2605,43 +2526,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "jito-restaking-cli" -version = "1.0.0" -dependencies = [ - "anyhow", - "base64 0.22.1", - "borsh 0.10.4", - "bytemuck", - "chrono", - "clap 4.5.28", - "clap-markdown", - "env_logger 0.10.2", - "jito-bytemuck", - "jito-jsm-core", - "jito-restaking-client", - "jito-restaking-client-common", - "jito-restaking-core", - "jito-vault-client", - "jito-vault-core", - "jito-vault-sdk", - "log", - "serde", - "serde_json", - "solana-account-decoder", - "solana-cli-config", - "solana-program", - "solana-remote-wallet", - "solana-rpc-client", - "solana-rpc-client-api", - "solana-sdk", - "spl-associated-token-account", - "spl-token", - "thiserror 1.0.69", - "tokio", - "uriparse", -] - [[package]] name = "jito-restaking-client" version = "0.0.5" @@ -2744,7 +2628,7 @@ name = "jito-shank-cli" version = "0.0.5" dependencies = [ "anyhow", - "clap 4.5.28", + "clap", "env_logger 0.10.2", "envfile", "log", @@ -2792,7 +2676,7 @@ version = "0.0.5" dependencies = [ "anyhow", "base64 0.22.1", - "clap 4.5.28", + "clap", "dotenvy", "env_logger 0.10.2", "futures", @@ -3489,15 +3373,6 @@ 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" @@ -3725,7 +3600,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", + "rustc-hash", "rustls 0.23.22", "socket2", "thiserror 2.0.11", @@ -3743,7 +3618,7 @@ dependencies = [ "getrandom 0.2.15", "rand 0.8.5", "ring", - "rustc-hash 2.1.0", + "rustc-hash", "rustls 0.23.22", "rustls-pki-types", "rustls-platform-verifier", @@ -4008,39 +3883,12 @@ dependencies = [ "windows-sys 0.52.0", ] -[[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 = "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-hash" version = "2.1.0" @@ -4321,7 +4169,6 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.7.1", "itoa", "memchr", "ryu", @@ -4379,19 +4226,6 @@ dependencies = [ "syn 2.0.98", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.7.1", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha1" version = "0.10.6" @@ -4507,12 +4341,6 @@ 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 = "shellexpand" version = "2.1.2" @@ -5016,51 +4844,6 @@ dependencies = [ "solana-vote-program", ] -[[package]] -name = "solana-clap-utils" -version = "2.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3c210e89742f6c661eb4e7549eb779dbc56cab4b700a1fd761cd7c9b2de6e6" -dependencies = [ - "chrono", - "clap 2.34.0", - "rpassword", - "solana-clock", - "solana-cluster-type", - "solana-commitment-config", - "solana-derivation-path", - "solana-hash", - "solana-keypair", - "solana-message", - "solana-native-token", - "solana-presigner", - "solana-pubkey", - "solana-remote-wallet", - "solana-seed-phrase", - "solana-signature", - "solana-signer", - "thiserror 2.0.11", - "tiny-bip39", - "uriparse", - "url", -] - -[[package]] -name = "solana-cli-config" -version = "2.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cdb4a08bb852494082cd115e3b654b5505af4d2c0e9d24e602553d36dc2f1f5" -dependencies = [ - "dirs-next", - "lazy_static", - "serde", - "serde_derive", - "serde_yaml", - "solana-clap-utils", - "solana-commitment-config", - "url", -] - [[package]] name = "solana-client" version = "2.2.7" @@ -6324,30 +6107,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "solana-remote-wallet" -version = "2.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e71f9dfc6f2a5df04c3fed2b90b0fbf0da3939f3383a9bf24a5c0bcf994f2b10" -dependencies = [ - "console", - "dialoguer", - "hidapi", - "log", - "num-derive", - "num-traits", - "parking_lot", - "qstring", - "semver", - "solana-derivation-path", - "solana-offchain-message", - "solana-pubkey", - "solana-signature", - "solana-signer", - "thiserror 2.0.11", - "uriparse", -] - [[package]] name = "solana-rent" version = "2.2.1" @@ -6787,7 +6546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" dependencies = [ "hmac 0.12.1", - "pbkdf2 0.11.0", + "pbkdf2", "sha2 0.10.8", ] @@ -8016,12 +7775,6 @@ 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.11.1" @@ -8251,15 +8004,6 @@ dependencies = [ "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 0.1.14", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -8341,25 +8085,6 @@ dependencies = [ "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 1.1.0", - "sha2 0.9.9", - "thiserror 1.0.69", - "unicode-normalization", - "wasm-bindgen", - "zeroize", -] - [[package]] name = "tinystr" version = "0.7.6" @@ -8645,27 +8370,12 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.0" @@ -8703,12 +8413,6 @@ dependencies = [ "void", ] -[[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.9.0" @@ -8772,12 +8476,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[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.5" diff --git a/Cargo.toml b/Cargo.toml index 19416a65..8b4e71e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ members = [ "account_traits_derive", "bytemuck", - "cli", "clients/rust/common", "clients/rust/restaking_client", "clients/rust/vault_client", @@ -15,20 +14,11 @@ members = [ "shank-cli", "vault_core", "vault_program", - "vault_sdk"] + "vault_sdk", +] resolver = "2" -[profile.release] -overflow-checks = true -lto = "fat" -codegen-units = 1 - -[profile.release.build-override] -opt-level = 3 -incremental = false -codegen-units = 1 - [workspace.package] version = "0.0.5" authors = ["Jito Network Maintainers "] @@ -50,11 +40,11 @@ chrono = "0.4.38" clap = { version = "4.5.16", features = ["derive", "env"] } clap-markdown = { version = "0.1.4" } dotenvy = "0.15.7" -envfile = "0.2.1" env_logger = "0.10.2" +envfile = "0.2.1" futures = "0.3.31" -jito-bytemuck = { path = "bytemuck", version = "=0.0.5" } jito-account-traits-derive = { path = "account_traits_derive", version = "=0.0.5" } +jito-bytemuck = { path = "bytemuck", version = "=0.0.5" } jito-jsm-core = { path = "core", version = "=0.0.5" } jito-restaking-client = { path = "clients/rust/restaking_client", version = "=0.0.5" } jito-restaking-client-common = { path = "clients/rust/common", version = "=0.0.5" } @@ -82,10 +72,10 @@ solana-decode-error = "~2.2" solana-metrics = "~2.2" solana-program = "~2.2" solana-program-test = "~2.2" -solana-sdk = "~2.2" solana-remote-wallet = "~2.2" solana-rpc-client = "~2.2" solana-rpc-client-api = "~2.2" +solana-sdk = "~2.2" solana-security-txt = "1.1.1" spl-associated-token-account = { version = "6.0.0", features = ["no-entrypoint"] } spl-token = { version = "7.0.0", features = ["no-entrypoint"] } @@ -102,3 +92,13 @@ check-cfg = [ 'cfg(target_os, values("solana"))', 'cfg(feature, values("frozen-abi", "no-entrypoint"))', ] + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 + +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/README.md b/README.md index 7e6a74c8..35472161 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,12 @@ Jito Restaking is a next-generation restaking platform for Solana and SVM enviro ## Table of Contents - [Features](#features) +- [Documentation](#documentation) +- [SDKs](#sdks) - [Installation](#installation) - [Usage](#usage) - [Running Tests](#running-tests) +- [Releasing](#releasing) - [Contributing](#contributing) - [License](#license) @@ -22,6 +25,11 @@ Jito Restaking is a next-generation restaking platform for Solana and SVM enviro - VRT construction and management - Flexible NCN and operator management +## Documentation + +The comprehensive documentation for Stakenet has moved to [jito.network/docs/restaking/jito-restaking-overview](https://www.jito.network/docs/restaking/jito-restaking-overview/). +The source files are maintained in the [Jito Omnidocs repository](https://github.com/jito-foundation/jito-omnidocs/tree/master/restaking). + ## SDKs We provide TypeScript SDKs for interacting with the Jito Restaking system: diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 13b7ebcf..f6d3126c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "jito-restaking-cli" description = "Jito Restaking CLI" -version = "1.0.0" +version = "1.0.1" authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } license = { workspace = true } edition = { workspace = true } -readme = { workspace = true } +readme = "./README.md" + +[[bin]] +name = "jito-restaking-cli" +path = "src/bin/main.rs" [dependencies] anyhow = { workspace = true } @@ -41,7 +45,3 @@ spl-token = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } uriparse = { workspace = true } - -[[bin]] -name = "jito-restaking-cli" -path = "src/bin/main.rs" diff --git a/cli/src/bin/main.rs b/cli/src/bin/main.rs index 874ea897..c82821df 100644 --- a/cli/src/bin/main.rs +++ b/cli/src/bin/main.rs @@ -62,9 +62,17 @@ pub fn get_cli_config(args: &Cli) -> Result { } } else { let signer = match args.signer.as_ref() { - Some(keypair_path) => Some(CliSigner::new_keypair_from_path(keypair_path)?), + Some(keypair_path) => { + let signer = if keypair_path.starts_with("usb://") { + CliSigner::new_ledger(keypair_path) + } else { + CliSigner::new_keypair_from_path(keypair_path)? + }; + Some(signer) + } None => None, }; + CliConfig { rpc_url: args .rpc_url diff --git a/cli/src/cli_config.rs b/cli/src/cli_config.rs index a6551b01..b8fa0c2b 100644 --- a/cli/src/cli_config.rs +++ b/cli/src/cli_config.rs @@ -3,9 +3,12 @@ use solana_sdk::commitment_config::CommitmentConfig; use crate::cli_signer::CliSigner; pub struct CliConfig { + /// The RPC endpoint URL pub rpc_url: String, + /// The commitment level pub commitment: CommitmentConfig, + /// Optional signer pub signer: Option, } diff --git a/cli/src/cli_signer.rs b/cli/src/cli_signer.rs index fb16d52b..0dec01a6 100644 --- a/cli/src/cli_signer.rs +++ b/cli/src/cli_signer.rs @@ -31,6 +31,7 @@ impl CliSigner { Self::new(Some(keypair), None) } + /// Creates a signer from a path pub fn new_keypair_from_path(keypair_path: &str) -> anyhow::Result { match read_keypair_file(keypair_path) { Ok(keypair) => Ok(Self::new(Some(keypair), None)), diff --git a/cli/src/vault.rs b/cli/src/vault.rs index 0b1b8832..7559b762 100644 --- a/cli/src/vault.rs +++ b/cli/src/vault.rs @@ -31,6 +31,15 @@ pub enum ConfigActions { /// The new admin's pubkey new_admin: Pubkey, }, + /// Set the config admin + SetSecondaryAdmin { + /// The new admin's pubkey + new_admin: Pubkey, + + /// Set fee_admin + #[arg(long)] + set_fee_admin: bool, + }, /// Set the program fee SetProgramFee { /// The program fee diff --git a/cli/src/vault_handler.rs b/cli/src/vault_handler.rs index eaff3699..dffeebca 100644 --- a/cli/src/vault_handler.rs +++ b/cli/src/vault_handler.rs @@ -16,12 +16,12 @@ use jito_vault_client::{ CreateTokenMetadataBuilder, DelegateTokenAccountBuilder, EnqueueWithdrawalBuilder, InitializeConfigBuilder, InitializeVaultBuilder, InitializeVaultNcnTicketBuilder, InitializeVaultOperatorDelegationBuilder, InitializeVaultUpdateStateTrackerBuilder, - MintToBuilder, SetAdminBuilder, SetConfigAdminBuilder, SetDepositCapacityBuilder, - SetFeesBuilder, SetIsPausedBuilder, SetProgramFeeBuilder, SetProgramFeeWalletBuilder, - SetSecondaryAdminBuilder, UpdateTokenMetadataBuilder, UpdateVaultBalanceBuilder, - WarmupVaultNcnTicketBuilder, + MintToBuilder, SetAdminBuilder, SetConfigAdminBuilder, SetConfigSecondaryAdminBuilder, + SetDepositCapacityBuilder, SetFeesBuilder, SetIsPausedBuilder, SetProgramFeeBuilder, + SetProgramFeeWalletBuilder, SetSecondaryAdminBuilder, UpdateTokenMetadataBuilder, + UpdateVaultBalanceBuilder, WarmupVaultNcnTicketBuilder, }, - types::{VaultAdminRole, WithdrawalAllocationMethod}, + types::{ConfigAdminRole, VaultAdminRole, WithdrawalAllocationMethod}, }; use jito_vault_core::{ burn_vault::BurnVault, config::Config, vault::Vault, vault_ncn_ticket::VaultNcnTicket, @@ -125,6 +125,16 @@ impl VaultCliHandler { VaultCommands::Config { action: ConfigActions::SetAdmin { new_admin }, } => self.set_config_admin(new_admin).await, + VaultCommands::Config { + action: + ConfigActions::SetSecondaryAdmin { + new_admin, + set_fee_admin, + }, + } => { + self.set_config_secondary_admin(&new_admin, set_fee_admin) + .await + } VaultCommands::Config { action: ConfigActions::SetProgramFee { new_fee_bps }, } => self.set_program_fee(new_fee_bps).await, @@ -1787,6 +1797,52 @@ impl VaultCliHandler { Ok(()) } + /// Sets the secondary admin roles for Config + /// + /// This function allows assigning a new administrator to various administrative roles + /// for Config. Multiple roles can be assigned in a single call by enabling the + /// corresponding boolean flags. + #[allow(clippy::future_not_send)] + async fn set_config_secondary_admin( + &self, + new_admin: &Pubkey, + set_fee_admin: bool, + ) -> Result<()> { + let signer = self.signer()?; + + let config_address = Config::find_program_address(&self.vault_program_id).0; + + let mut roles: Vec = vec![]; + if set_fee_admin { + roles.push(ConfigAdminRole::FeeAdmin); + } + + for role in roles.iter() { + let mut ix_builder = SetConfigSecondaryAdminBuilder::new(); + ix_builder + .config(config_address) + .admin(signer.pubkey()) + .new_admin(*new_admin) + .config_admin_role(*role); + let mut ix = ix_builder.instruction(); + ix.program_id = self.vault_program_id; + + info!("Setting {:?} Admin to {} for Config", role, new_admin); + + self.process_transaction(&[ix], &signer.pubkey(), &[signer]) + .await?; + } + + if !self.print_tx { + let account = self + .get_account::(&config_address) + .await?; + info!("{}", account.pretty_display()); + } + + Ok(()) + } + /// Set the fees for Vault /// /// Updates one or more fee parameters for a specific vault. Each fee type diff --git a/clients/js/vault_client/instructions/addDelegation.ts b/clients/js/vault_client/instructions/addDelegation.ts index b517a3d8..7df52ce0 100644 --- a/clients/js/vault_client/instructions/addDelegation.ts +++ b/clients/js/vault_client/instructions/addDelegation.ts @@ -32,7 +32,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const ADD_DELEGATION_DISCRIMINATOR = 23; +export const ADD_DELEGATION_DISCRIMINATOR = 24; export function getAddDelegationDiscriminatorBytes() { return getU8Encoder().encode(ADD_DELEGATION_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/closeVaultUpdateStateTracker.ts b/clients/js/vault_client/instructions/closeVaultUpdateStateTracker.ts index e3a40036..cce2ef54 100644 --- a/clients/js/vault_client/instructions/closeVaultUpdateStateTracker.ts +++ b/clients/js/vault_client/instructions/closeVaultUpdateStateTracker.ts @@ -32,7 +32,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const CLOSE_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR = 28; +export const CLOSE_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR = 29; export function getCloseVaultUpdateStateTrackerDiscriminatorBytes() { return getU8Encoder().encode(CLOSE_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/cooldownDelegation.ts b/clients/js/vault_client/instructions/cooldownDelegation.ts index 4d9d087c..568420e2 100644 --- a/clients/js/vault_client/instructions/cooldownDelegation.ts +++ b/clients/js/vault_client/instructions/cooldownDelegation.ts @@ -32,7 +32,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const COOLDOWN_DELEGATION_DISCRIMINATOR = 24; +export const COOLDOWN_DELEGATION_DISCRIMINATOR = 25; export function getCooldownDelegationDiscriminatorBytes() { return getU8Encoder().encode(COOLDOWN_DELEGATION_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/crankVaultUpdateStateTracker.ts b/clients/js/vault_client/instructions/crankVaultUpdateStateTracker.ts index 1f8f37bb..70d9061a 100644 --- a/clients/js/vault_client/instructions/crankVaultUpdateStateTracker.ts +++ b/clients/js/vault_client/instructions/crankVaultUpdateStateTracker.ts @@ -27,7 +27,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const CRANK_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR = 27; +export const CRANK_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR = 28; export function getCrankVaultUpdateStateTrackerDiscriminatorBytes() { return getU8Encoder().encode(CRANK_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/createTokenMetadata.ts b/clients/js/vault_client/instructions/createTokenMetadata.ts index 404ad283..174eccaa 100644 --- a/clients/js/vault_client/instructions/createTokenMetadata.ts +++ b/clients/js/vault_client/instructions/createTokenMetadata.ts @@ -37,7 +37,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const CREATE_TOKEN_METADATA_DISCRIMINATOR = 29; +export const CREATE_TOKEN_METADATA_DISCRIMINATOR = 30; export function getCreateTokenMetadataDiscriminatorBytes() { return getU8Encoder().encode(CREATE_TOKEN_METADATA_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/index.ts b/clients/js/vault_client/instructions/index.ts index 7bc28cbe..8c7050f9 100644 --- a/clients/js/vault_client/instructions/index.ts +++ b/clients/js/vault_client/instructions/index.ts @@ -26,8 +26,10 @@ export * from './initializeVaultOperatorDelegation'; export * from './initializeVaultUpdateStateTracker'; export * from './initializeVaultWithMint'; export * from './mintTo'; +export * from './revokeDelegateTokenAccount'; export * from './setAdmin'; export * from './setConfigAdmin'; +export * from './setConfigSecondaryAdmin'; export * from './setDepositCapacity'; export * from './setFees'; export * from './setIsPaused'; diff --git a/clients/js/vault_client/instructions/initializeVaultUpdateStateTracker.ts b/clients/js/vault_client/instructions/initializeVaultUpdateStateTracker.ts index e031988a..17fb9a0d 100644 --- a/clients/js/vault_client/instructions/initializeVaultUpdateStateTracker.ts +++ b/clients/js/vault_client/instructions/initializeVaultUpdateStateTracker.ts @@ -33,7 +33,7 @@ import { type WithdrawalAllocationMethodArgs, } from '../types'; -export const INITIALIZE_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR = 26; +export const INITIALIZE_VAULT_UPDATE_STATE_TRACKER_DISCRIMINATOR = 27; export function getInitializeVaultUpdateStateTrackerDiscriminatorBytes() { return getU8Encoder().encode( diff --git a/clients/js/vault_client/instructions/revokeDelegateTokenAccount.ts b/clients/js/vault_client/instructions/revokeDelegateTokenAccount.ts new file mode 100644 index 00000000..ac78d1e3 --- /dev/null +++ b/clients/js/vault_client/instructions/revokeDelegateTokenAccount.ts @@ -0,0 +1,249 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const REVOKE_DELEGATE_TOKEN_ACCOUNT_DISCRIMINATOR = 21; + +export function getRevokeDelegateTokenAccountDiscriminatorBytes() { + return getU8Encoder().encode(REVOKE_DELEGATE_TOKEN_ACCOUNT_DISCRIMINATOR); +} + +export type RevokeDelegateTokenAccountInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountVault extends string | IAccountMeta = string, + TAccountDelegateAssetAdmin extends string | IAccountMeta = string, + TAccountTokenMint extends string | IAccountMeta = string, + TAccountTokenAccount extends string | IAccountMeta = string, + TAccountTokenProgram extends + | string + | IAccountMeta = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountVault extends string + ? ReadonlyAccount + : TAccountVault, + TAccountDelegateAssetAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountDelegateAssetAdmin, + TAccountTokenMint extends string + ? ReadonlyAccount + : TAccountTokenMint, + TAccountTokenAccount extends string + ? WritableAccount + : TAccountTokenAccount, + TAccountTokenProgram extends string + ? ReadonlyAccount + : TAccountTokenProgram, + ...TRemainingAccounts, + ] + >; + +export type RevokeDelegateTokenAccountInstructionData = { + discriminator: number; +}; + +export type RevokeDelegateTokenAccountInstructionDataArgs = {}; + +export function getRevokeDelegateTokenAccountInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([['discriminator', getU8Encoder()]]), + (value) => ({ + ...value, + discriminator: REVOKE_DELEGATE_TOKEN_ACCOUNT_DISCRIMINATOR, + }) + ); +} + +export function getRevokeDelegateTokenAccountInstructionDataDecoder(): Decoder { + return getStructDecoder([['discriminator', getU8Decoder()]]); +} + +export function getRevokeDelegateTokenAccountInstructionDataCodec(): Codec< + RevokeDelegateTokenAccountInstructionDataArgs, + RevokeDelegateTokenAccountInstructionData +> { + return combineCodec( + getRevokeDelegateTokenAccountInstructionDataEncoder(), + getRevokeDelegateTokenAccountInstructionDataDecoder() + ); +} + +export type RevokeDelegateTokenAccountInput< + TAccountConfig extends string = string, + TAccountVault extends string = string, + TAccountDelegateAssetAdmin extends string = string, + TAccountTokenMint extends string = string, + TAccountTokenAccount extends string = string, + TAccountTokenProgram extends string = string, +> = { + config: Address; + vault: Address; + delegateAssetAdmin: TransactionSigner; + tokenMint: Address; + tokenAccount: Address; + tokenProgram?: Address; +}; + +export function getRevokeDelegateTokenAccountInstruction< + TAccountConfig extends string, + TAccountVault extends string, + TAccountDelegateAssetAdmin extends string, + TAccountTokenMint extends string, + TAccountTokenAccount extends string, + TAccountTokenProgram extends string, + TProgramAddress extends Address = typeof JITO_VAULT_PROGRAM_ADDRESS, +>( + input: RevokeDelegateTokenAccountInput< + TAccountConfig, + TAccountVault, + TAccountDelegateAssetAdmin, + TAccountTokenMint, + TAccountTokenAccount, + TAccountTokenProgram + >, + config?: { programAddress?: TProgramAddress } +): RevokeDelegateTokenAccountInstruction< + TProgramAddress, + TAccountConfig, + TAccountVault, + TAccountDelegateAssetAdmin, + TAccountTokenMint, + TAccountTokenAccount, + TAccountTokenProgram +> { + // Program address. + const programAddress = config?.programAddress ?? JITO_VAULT_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: false }, + vault: { value: input.vault ?? null, isWritable: false }, + delegateAssetAdmin: { + value: input.delegateAssetAdmin ?? null, + isWritable: false, + }, + tokenMint: { value: input.tokenMint ?? null, isWritable: false }, + tokenAccount: { value: input.tokenAccount ?? null, isWritable: true }, + tokenProgram: { value: input.tokenProgram ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Resolve default values. + if (!accounts.tokenProgram.value) { + accounts.tokenProgram.value = + 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.vault), + getAccountMeta(accounts.delegateAssetAdmin), + getAccountMeta(accounts.tokenMint), + getAccountMeta(accounts.tokenAccount), + getAccountMeta(accounts.tokenProgram), + ], + programAddress, + data: getRevokeDelegateTokenAccountInstructionDataEncoder().encode({}), + } as RevokeDelegateTokenAccountInstruction< + TProgramAddress, + TAccountConfig, + TAccountVault, + TAccountDelegateAssetAdmin, + TAccountTokenMint, + TAccountTokenAccount, + TAccountTokenProgram + >; + + return instruction; +} + +export type ParsedRevokeDelegateTokenAccountInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + vault: TAccountMetas[1]; + delegateAssetAdmin: TAccountMetas[2]; + tokenMint: TAccountMetas[3]; + tokenAccount: TAccountMetas[4]; + tokenProgram: TAccountMetas[5]; + }; + data: RevokeDelegateTokenAccountInstructionData; +}; + +export function parseRevokeDelegateTokenAccountInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedRevokeDelegateTokenAccountInstruction { + if (instruction.accounts.length < 6) { + // TODO: Coded error. + throw new Error('Not enough accounts'); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = instruction.accounts![accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + config: getNextAccount(), + vault: getNextAccount(), + delegateAssetAdmin: getNextAccount(), + tokenMint: getNextAccount(), + tokenAccount: getNextAccount(), + tokenProgram: getNextAccount(), + }, + data: getRevokeDelegateTokenAccountInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/vault_client/instructions/setAdmin.ts b/clients/js/vault_client/instructions/setAdmin.ts index 6f8da505..cd3da3a3 100644 --- a/clients/js/vault_client/instructions/setAdmin.ts +++ b/clients/js/vault_client/instructions/setAdmin.ts @@ -30,7 +30,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const SET_ADMIN_DISCRIMINATOR = 21; +export const SET_ADMIN_DISCRIMINATOR = 22; export function getSetAdminDiscriminatorBytes() { return getU8Encoder().encode(SET_ADMIN_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/setConfigAdmin.ts b/clients/js/vault_client/instructions/setConfigAdmin.ts index 107ad3ba..2cc27ac4 100644 --- a/clients/js/vault_client/instructions/setConfigAdmin.ts +++ b/clients/js/vault_client/instructions/setConfigAdmin.ts @@ -30,7 +30,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const SET_CONFIG_ADMIN_DISCRIMINATOR = 31; +export const SET_CONFIG_ADMIN_DISCRIMINATOR = 32; export function getSetConfigAdminDiscriminatorBytes() { return getU8Encoder().encode(SET_CONFIG_ADMIN_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/setConfigSecondaryAdmin.ts b/clients/js/vault_client/instructions/setConfigSecondaryAdmin.ts new file mode 100644 index 00000000..1db2782e --- /dev/null +++ b/clients/js/vault_client/instructions/setConfigSecondaryAdmin.ts @@ -0,0 +1,216 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type TransactionSigner, +} from '@solana/web3.js'; +import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; +import { + getConfigAdminRoleDecoder, + getConfigAdminRoleEncoder, + type ConfigAdminRole, + type ConfigAdminRoleArgs, +} from '../types'; + +export const SET_CONFIG_SECONDARY_ADMIN_DISCRIMINATOR = 33; + +export function getSetConfigSecondaryAdminDiscriminatorBytes() { + return getU8Encoder().encode(SET_CONFIG_SECONDARY_ADMIN_DISCRIMINATOR); +} + +export type SetConfigSecondaryAdminInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountAdmin extends string | IAccountMeta = string, + TAccountNewAdmin extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountAdmin, + TAccountNewAdmin extends string + ? ReadonlyAccount + : TAccountNewAdmin, + ...TRemainingAccounts, + ] + >; + +export type SetConfigSecondaryAdminInstructionData = { + discriminator: number; + configAdminRole: ConfigAdminRole; +}; + +export type SetConfigSecondaryAdminInstructionDataArgs = { + configAdminRole: ConfigAdminRoleArgs; +}; + +export function getSetConfigSecondaryAdminInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['configAdminRole', getConfigAdminRoleEncoder()], + ]), + (value) => ({ + ...value, + discriminator: SET_CONFIG_SECONDARY_ADMIN_DISCRIMINATOR, + }) + ); +} + +export function getSetConfigSecondaryAdminInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['configAdminRole', getConfigAdminRoleDecoder()], + ]); +} + +export function getSetConfigSecondaryAdminInstructionDataCodec(): Codec< + SetConfigSecondaryAdminInstructionDataArgs, + SetConfigSecondaryAdminInstructionData +> { + return combineCodec( + getSetConfigSecondaryAdminInstructionDataEncoder(), + getSetConfigSecondaryAdminInstructionDataDecoder() + ); +} + +export type SetConfigSecondaryAdminInput< + TAccountConfig extends string = string, + TAccountAdmin extends string = string, + TAccountNewAdmin extends string = string, +> = { + config: Address; + admin: TransactionSigner; + newAdmin: Address; + configAdminRole: SetConfigSecondaryAdminInstructionDataArgs['configAdminRole']; +}; + +export function getSetConfigSecondaryAdminInstruction< + TAccountConfig extends string, + TAccountAdmin extends string, + TAccountNewAdmin extends string, + TProgramAddress extends Address = typeof JITO_VAULT_PROGRAM_ADDRESS, +>( + input: SetConfigSecondaryAdminInput< + TAccountConfig, + TAccountAdmin, + TAccountNewAdmin + >, + config?: { programAddress?: TProgramAddress } +): SetConfigSecondaryAdminInstruction< + TProgramAddress, + TAccountConfig, + TAccountAdmin, + TAccountNewAdmin +> { + // Program address. + const programAddress = config?.programAddress ?? JITO_VAULT_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: false }, + admin: { value: input.admin ?? null, isWritable: false }, + newAdmin: { value: input.newAdmin ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.admin), + getAccountMeta(accounts.newAdmin), + ], + programAddress, + data: getSetConfigSecondaryAdminInstructionDataEncoder().encode( + args as SetConfigSecondaryAdminInstructionDataArgs + ), + } as SetConfigSecondaryAdminInstruction< + TProgramAddress, + TAccountConfig, + TAccountAdmin, + TAccountNewAdmin + >; + + return instruction; +} + +export type ParsedSetConfigSecondaryAdminInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + admin: TAccountMetas[1]; + newAdmin: TAccountMetas[2]; + }; + data: SetConfigSecondaryAdminInstructionData; +}; + +export function parseSetConfigSecondaryAdminInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedSetConfigSecondaryAdminInstruction { + if (instruction.accounts.length < 3) { + // TODO: Coded error. + throw new Error('Not enough accounts'); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = instruction.accounts![accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + config: getNextAccount(), + admin: getNextAccount(), + newAdmin: getNextAccount(), + }, + data: getSetConfigSecondaryAdminInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/vault_client/instructions/setSecondaryAdmin.ts b/clients/js/vault_client/instructions/setSecondaryAdmin.ts index 49aad6c9..08990dd9 100644 --- a/clients/js/vault_client/instructions/setSecondaryAdmin.ts +++ b/clients/js/vault_client/instructions/setSecondaryAdmin.ts @@ -36,7 +36,7 @@ import { type VaultAdminRoleArgs, } from '../types'; -export const SET_SECONDARY_ADMIN_DISCRIMINATOR = 22; +export const SET_SECONDARY_ADMIN_DISCRIMINATOR = 23; export function getSetSecondaryAdminDiscriminatorBytes() { return getU8Encoder().encode(SET_SECONDARY_ADMIN_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/updateTokenMetadata.ts b/clients/js/vault_client/instructions/updateTokenMetadata.ts index d3d64a9e..355c8304 100644 --- a/clients/js/vault_client/instructions/updateTokenMetadata.ts +++ b/clients/js/vault_client/instructions/updateTokenMetadata.ts @@ -36,7 +36,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const UPDATE_TOKEN_METADATA_DISCRIMINATOR = 30; +export const UPDATE_TOKEN_METADATA_DISCRIMINATOR = 31; export function getUpdateTokenMetadataDiscriminatorBytes() { return getU8Encoder().encode(UPDATE_TOKEN_METADATA_DISCRIMINATOR); diff --git a/clients/js/vault_client/instructions/updateVaultBalance.ts b/clients/js/vault_client/instructions/updateVaultBalance.ts index 9fe40f21..1e1af9e7 100644 --- a/clients/js/vault_client/instructions/updateVaultBalance.ts +++ b/clients/js/vault_client/instructions/updateVaultBalance.ts @@ -27,7 +27,7 @@ import { import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const UPDATE_VAULT_BALANCE_DISCRIMINATOR = 25; +export const UPDATE_VAULT_BALANCE_DISCRIMINATOR = 26; export function getUpdateVaultBalanceDiscriminatorBytes() { return getU8Encoder().encode(UPDATE_VAULT_BALANCE_DISCRIMINATOR); diff --git a/clients/js/vault_client/programs/jitoVault.ts b/clients/js/vault_client/programs/jitoVault.ts index 83cd3290..44a1e635 100644 --- a/clients/js/vault_client/programs/jitoVault.ts +++ b/clients/js/vault_client/programs/jitoVault.ts @@ -33,8 +33,10 @@ import { type ParsedInitializeVaultUpdateStateTrackerInstruction, type ParsedInitializeVaultWithMintInstruction, type ParsedMintToInstruction, + type ParsedRevokeDelegateTokenAccountInstruction, type ParsedSetAdminInstruction, type ParsedSetConfigAdminInstruction, + type ParsedSetConfigSecondaryAdminInstruction, type ParsedSetDepositCapacityInstruction, type ParsedSetFeesInstruction, type ParsedSetIsPausedInstruction, @@ -83,6 +85,7 @@ export enum JitoVaultInstruction { SetProgramFeeWallet, SetIsPaused, DelegateTokenAccount, + RevokeDelegateTokenAccount, SetAdmin, SetSecondaryAdmin, AddDelegation, @@ -94,6 +97,7 @@ export enum JitoVaultInstruction { CreateTokenMetadata, UpdateTokenMetadata, SetConfigAdmin, + SetConfigSecondaryAdmin, } export function identifyJitoVaultInstruction( @@ -164,38 +168,44 @@ export function identifyJitoVaultInstruction( return JitoVaultInstruction.DelegateTokenAccount; } if (containsBytes(data, getU8Encoder().encode(21), 0)) { - return JitoVaultInstruction.SetAdmin; + return JitoVaultInstruction.RevokeDelegateTokenAccount; } if (containsBytes(data, getU8Encoder().encode(22), 0)) { - return JitoVaultInstruction.SetSecondaryAdmin; + return JitoVaultInstruction.SetAdmin; } if (containsBytes(data, getU8Encoder().encode(23), 0)) { - return JitoVaultInstruction.AddDelegation; + return JitoVaultInstruction.SetSecondaryAdmin; } if (containsBytes(data, getU8Encoder().encode(24), 0)) { - return JitoVaultInstruction.CooldownDelegation; + return JitoVaultInstruction.AddDelegation; } if (containsBytes(data, getU8Encoder().encode(25), 0)) { - return JitoVaultInstruction.UpdateVaultBalance; + return JitoVaultInstruction.CooldownDelegation; } if (containsBytes(data, getU8Encoder().encode(26), 0)) { - return JitoVaultInstruction.InitializeVaultUpdateStateTracker; + return JitoVaultInstruction.UpdateVaultBalance; } if (containsBytes(data, getU8Encoder().encode(27), 0)) { - return JitoVaultInstruction.CrankVaultUpdateStateTracker; + return JitoVaultInstruction.InitializeVaultUpdateStateTracker; } if (containsBytes(data, getU8Encoder().encode(28), 0)) { - return JitoVaultInstruction.CloseVaultUpdateStateTracker; + return JitoVaultInstruction.CrankVaultUpdateStateTracker; } if (containsBytes(data, getU8Encoder().encode(29), 0)) { - return JitoVaultInstruction.CreateTokenMetadata; + return JitoVaultInstruction.CloseVaultUpdateStateTracker; } if (containsBytes(data, getU8Encoder().encode(30), 0)) { - return JitoVaultInstruction.UpdateTokenMetadata; + return JitoVaultInstruction.CreateTokenMetadata; } if (containsBytes(data, getU8Encoder().encode(31), 0)) { + return JitoVaultInstruction.UpdateTokenMetadata; + } + if (containsBytes(data, getU8Encoder().encode(32), 0)) { return JitoVaultInstruction.SetConfigAdmin; } + if (containsBytes(data, getU8Encoder().encode(33), 0)) { + return JitoVaultInstruction.SetConfigSecondaryAdmin; + } throw new Error( 'The provided instruction could not be identified as a jitoVault instruction.' ); @@ -267,6 +277,9 @@ export type ParsedJitoVaultInstruction< | ({ instructionType: JitoVaultInstruction.DelegateTokenAccount; } & ParsedDelegateTokenAccountInstruction) + | ({ + instructionType: JitoVaultInstruction.RevokeDelegateTokenAccount; + } & ParsedRevokeDelegateTokenAccountInstruction) | ({ instructionType: JitoVaultInstruction.SetAdmin; } & ParsedSetAdminInstruction) @@ -299,4 +312,7 @@ export type ParsedJitoVaultInstruction< } & ParsedUpdateTokenMetadataInstruction) | ({ instructionType: JitoVaultInstruction.SetConfigAdmin; - } & ParsedSetConfigAdminInstruction); + } & ParsedSetConfigAdminInstruction) + | ({ + instructionType: JitoVaultInstruction.SetConfigSecondaryAdmin; + } & ParsedSetConfigSecondaryAdminInstruction); diff --git a/clients/js/vault_client/types/configAdminRole.ts b/clients/js/vault_client/types/configAdminRole.ts new file mode 100644 index 00000000..87bf0228 --- /dev/null +++ b/clients/js/vault_client/types/configAdminRole.ts @@ -0,0 +1,37 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getEnumDecoder, + getEnumEncoder, + type Codec, + type Decoder, + type Encoder, +} from '@solana/web3.js'; + +export enum ConfigAdminRole { + FeeAdmin, +} + +export type ConfigAdminRoleArgs = ConfigAdminRole; + +export function getConfigAdminRoleEncoder(): Encoder { + return getEnumEncoder(ConfigAdminRole); +} + +export function getConfigAdminRoleDecoder(): Decoder { + return getEnumDecoder(ConfigAdminRole); +} + +export function getConfigAdminRoleCodec(): Codec< + ConfigAdminRoleArgs, + ConfigAdminRole +> { + return combineCodec(getConfigAdminRoleEncoder(), getConfigAdminRoleDecoder()); +} diff --git a/clients/js/vault_client/types/index.ts b/clients/js/vault_client/types/index.ts index b0e6f2af..5a490756 100644 --- a/clients/js/vault_client/types/index.ts +++ b/clients/js/vault_client/types/index.ts @@ -6,6 +6,7 @@ * @see https://github.com/kinobi-so/kinobi */ +export * from './configAdminRole'; export * from './createMetadataAccountArgsV3'; export * from './dataV2'; export * from './delegationState'; diff --git a/clients/rust/vault_client/src/generated/instructions/add_delegation.rs b/clients/rust/vault_client/src/generated/instructions/add_delegation.rs index 38fa63e1..1a21ded0 100644 --- a/clients/rust/vault_client/src/generated/instructions/add_delegation.rs +++ b/clients/rust/vault_client/src/generated/instructions/add_delegation.rs @@ -73,7 +73,7 @@ pub struct AddDelegationInstructionData { impl AddDelegationInstructionData { pub fn new() -> Self { - Self { discriminator: 23 } + Self { discriminator: 24 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/close_vault_update_state_tracker.rs b/clients/rust/vault_client/src/generated/instructions/close_vault_update_state_tracker.rs index af6fdbdc..c4b34223 100644 --- a/clients/rust/vault_client/src/generated/instructions/close_vault_update_state_tracker.rs +++ b/clients/rust/vault_client/src/generated/instructions/close_vault_update_state_tracker.rs @@ -69,7 +69,7 @@ pub struct CloseVaultUpdateStateTrackerInstructionData { impl CloseVaultUpdateStateTrackerInstructionData { pub fn new() -> Self { - Self { discriminator: 28 } + Self { discriminator: 29 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/cooldown_delegation.rs b/clients/rust/vault_client/src/generated/instructions/cooldown_delegation.rs index e29bcef2..715548a2 100644 --- a/clients/rust/vault_client/src/generated/instructions/cooldown_delegation.rs +++ b/clients/rust/vault_client/src/generated/instructions/cooldown_delegation.rs @@ -75,7 +75,7 @@ pub struct CooldownDelegationInstructionData { impl CooldownDelegationInstructionData { pub fn new() -> Self { - Self { discriminator: 24 } + Self { discriminator: 25 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/crank_vault_update_state_tracker.rs b/clients/rust/vault_client/src/generated/instructions/crank_vault_update_state_tracker.rs index 14f21edd..e224395e 100644 --- a/clients/rust/vault_client/src/generated/instructions/crank_vault_update_state_tracker.rs +++ b/clients/rust/vault_client/src/generated/instructions/crank_vault_update_state_tracker.rs @@ -70,7 +70,7 @@ pub struct CrankVaultUpdateStateTrackerInstructionData { impl CrankVaultUpdateStateTrackerInstructionData { pub fn new() -> Self { - Self { discriminator: 27 } + Self { discriminator: 28 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/create_token_metadata.rs b/clients/rust/vault_client/src/generated/instructions/create_token_metadata.rs index 38b6947e..7a9afce7 100644 --- a/clients/rust/vault_client/src/generated/instructions/create_token_metadata.rs +++ b/clients/rust/vault_client/src/generated/instructions/create_token_metadata.rs @@ -86,7 +86,7 @@ pub struct CreateTokenMetadataInstructionData { impl CreateTokenMetadataInstructionData { pub fn new() -> Self { - Self { discriminator: 29 } + Self { discriminator: 30 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/initialize_vault_update_state_tracker.rs b/clients/rust/vault_client/src/generated/instructions/initialize_vault_update_state_tracker.rs index e2e16b16..49a6d566 100644 --- a/clients/rust/vault_client/src/generated/instructions/initialize_vault_update_state_tracker.rs +++ b/clients/rust/vault_client/src/generated/instructions/initialize_vault_update_state_tracker.rs @@ -76,7 +76,7 @@ pub struct InitializeVaultUpdateStateTrackerInstructionData { impl InitializeVaultUpdateStateTrackerInstructionData { pub fn new() -> Self { - Self { discriminator: 26 } + Self { discriminator: 27 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/mod.rs b/clients/rust/vault_client/src/generated/instructions/mod.rs index 3529db29..57f6404a 100644 --- a/clients/rust/vault_client/src/generated/instructions/mod.rs +++ b/clients/rust/vault_client/src/generated/instructions/mod.rs @@ -25,8 +25,10 @@ pub(crate) mod r#initialize_vault_operator_delegation; pub(crate) mod r#initialize_vault_update_state_tracker; pub(crate) mod r#initialize_vault_with_mint; pub(crate) mod r#mint_to; +pub(crate) mod r#revoke_delegate_token_account; pub(crate) mod r#set_admin; pub(crate) mod r#set_config_admin; +pub(crate) mod r#set_config_secondary_admin; pub(crate) mod r#set_deposit_capacity; pub(crate) mod r#set_fees; pub(crate) mod r#set_is_paused; @@ -58,8 +60,10 @@ pub use self::r#initialize_vault_operator_delegation::*; pub use self::r#initialize_vault_update_state_tracker::*; pub use self::r#initialize_vault_with_mint::*; pub use self::r#mint_to::*; +pub use self::r#revoke_delegate_token_account::*; pub use self::r#set_admin::*; pub use self::r#set_config_admin::*; +pub use self::r#set_config_secondary_admin::*; pub use self::r#set_deposit_capacity::*; pub use self::r#set_fees::*; pub use self::r#set_is_paused::*; diff --git a/clients/rust/vault_client/src/generated/instructions/revoke_delegate_token_account.rs b/clients/rust/vault_client/src/generated/instructions/revoke_delegate_token_account.rs new file mode 100644 index 00000000..e1e10b83 --- /dev/null +++ b/clients/rust/vault_client/src/generated/instructions/revoke_delegate_token_account.rs @@ -0,0 +1,488 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct RevokeDelegateTokenAccount { + pub config: solana_program::pubkey::Pubkey, + + pub vault: solana_program::pubkey::Pubkey, + + pub delegate_asset_admin: solana_program::pubkey::Pubkey, + + pub token_mint: solana_program::pubkey::Pubkey, + + pub token_account: solana_program::pubkey::Pubkey, + + pub token_program: solana_program::pubkey::Pubkey, +} + +impl RevokeDelegateTokenAccount { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.vault, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.delegate_asset_admin, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.token_mint, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.token_account, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.token_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let data = RevokeDelegateTokenAccountInstructionData::new() + .try_to_vec() + .unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::JITO_VAULT_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct RevokeDelegateTokenAccountInstructionData { + discriminator: u8, +} + +impl RevokeDelegateTokenAccountInstructionData { + pub fn new() -> Self { + Self { discriminator: 21 } + } +} + +impl Default for RevokeDelegateTokenAccountInstructionData { + fn default() -> Self { + Self::new() + } +} + +/// Instruction builder for `RevokeDelegateTokenAccount`. +/// +/// ### Accounts: +/// +/// 0. `[]` config +/// 1. `[]` vault +/// 2. `[signer]` delegate_asset_admin +/// 3. `[]` token_mint +/// 4. `[writable]` token_account +/// 5. `[optional]` token_program (default to `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) +#[derive(Clone, Debug, Default)] +pub struct RevokeDelegateTokenAccountBuilder { + config: Option, + vault: Option, + delegate_asset_admin: Option, + token_mint: Option, + token_account: Option, + token_program: Option, + __remaining_accounts: Vec, +} + +impl RevokeDelegateTokenAccountBuilder { + pub fn new() -> Self { + Self::default() + } + #[inline(always)] + pub fn config(&mut self, config: solana_program::pubkey::Pubkey) -> &mut Self { + self.config = Some(config); + self + } + #[inline(always)] + pub fn vault(&mut self, vault: solana_program::pubkey::Pubkey) -> &mut Self { + self.vault = Some(vault); + self + } + #[inline(always)] + pub fn delegate_asset_admin( + &mut self, + delegate_asset_admin: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.delegate_asset_admin = Some(delegate_asset_admin); + self + } + #[inline(always)] + pub fn token_mint(&mut self, token_mint: solana_program::pubkey::Pubkey) -> &mut Self { + self.token_mint = Some(token_mint); + self + } + #[inline(always)] + pub fn token_account(&mut self, token_account: solana_program::pubkey::Pubkey) -> &mut Self { + self.token_account = Some(token_account); + self + } + /// `[optional account, default to 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA']` + #[inline(always)] + pub fn token_program(&mut self, token_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.token_program = Some(token_program); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = RevokeDelegateTokenAccount { + config: self.config.expect("config is not set"), + vault: self.vault.expect("vault is not set"), + delegate_asset_admin: self + .delegate_asset_admin + .expect("delegate_asset_admin is not set"), + token_mint: self.token_mint.expect("token_mint is not set"), + token_account: self.token_account.expect("token_account is not set"), + token_program: self.token_program.unwrap_or(solana_program::pubkey!( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + )), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `revoke_delegate_token_account` CPI accounts. +pub struct RevokeDelegateTokenAccountCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub vault: &'b solana_program::account_info::AccountInfo<'a>, + + pub delegate_asset_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_mint: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_account: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `revoke_delegate_token_account` CPI instruction. +pub struct RevokeDelegateTokenAccountCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub vault: &'b solana_program::account_info::AccountInfo<'a>, + + pub delegate_asset_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_mint: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_account: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> RevokeDelegateTokenAccountCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RevokeDelegateTokenAccountCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + config: accounts.config, + vault: accounts.vault, + delegate_asset_admin: accounts.delegate_asset_admin, + token_mint: accounts.token_mint, + token_account: accounts.token_account, + token_program: accounts.token_program, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.vault.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.delegate_asset_admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.token_mint.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.token_account.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.token_program.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = RevokeDelegateTokenAccountInstructionData::new() + .try_to_vec() + .unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::JITO_VAULT_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.vault.clone()); + account_infos.push(self.delegate_asset_admin.clone()); + account_infos.push(self.token_mint.clone()); + account_infos.push(self.token_account.clone()); + account_infos.push(self.token_program.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `RevokeDelegateTokenAccount` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[]` config +/// 1. `[]` vault +/// 2. `[signer]` delegate_asset_admin +/// 3. `[]` token_mint +/// 4. `[writable]` token_account +/// 5. `[]` token_program +#[derive(Clone, Debug)] +pub struct RevokeDelegateTokenAccountCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RevokeDelegateTokenAccountCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RevokeDelegateTokenAccountCpiBuilderInstruction { + __program: program, + config: None, + vault: None, + delegate_asset_admin: None, + token_mint: None, + token_account: None, + token_program: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + #[inline(always)] + pub fn config( + &mut self, + config: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.config = Some(config); + self + } + #[inline(always)] + pub fn vault(&mut self, vault: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.vault = Some(vault); + self + } + #[inline(always)] + pub fn delegate_asset_admin( + &mut self, + delegate_asset_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.delegate_asset_admin = Some(delegate_asset_admin); + self + } + #[inline(always)] + pub fn token_mint( + &mut self, + token_mint: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.token_mint = Some(token_mint); + self + } + #[inline(always)] + pub fn token_account( + &mut self, + token_account: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.token_account = Some(token_account); + self + } + #[inline(always)] + pub fn token_program( + &mut self, + token_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.token_program = Some(token_program); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = RevokeDelegateTokenAccountCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + vault: self.instruction.vault.expect("vault is not set"), + + delegate_asset_admin: self + .instruction + .delegate_asset_admin + .expect("delegate_asset_admin is not set"), + + token_mint: self.instruction.token_mint.expect("token_mint is not set"), + + token_account: self + .instruction + .token_account + .expect("token_account is not set"), + + token_program: self + .instruction + .token_program + .expect("token_program is not set"), + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct RevokeDelegateTokenAccountCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + vault: Option<&'b solana_program::account_info::AccountInfo<'a>>, + delegate_asset_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + token_mint: Option<&'b solana_program::account_info::AccountInfo<'a>>, + token_account: Option<&'b solana_program::account_info::AccountInfo<'a>>, + token_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/vault_client/src/generated/instructions/set_admin.rs b/clients/rust/vault_client/src/generated/instructions/set_admin.rs index 0bf2b7d6..661585b0 100644 --- a/clients/rust/vault_client/src/generated/instructions/set_admin.rs +++ b/clients/rust/vault_client/src/generated/instructions/set_admin.rs @@ -62,7 +62,7 @@ pub struct SetAdminInstructionData { impl SetAdminInstructionData { pub fn new() -> Self { - Self { discriminator: 21 } + Self { discriminator: 22 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/set_config_admin.rs b/clients/rust/vault_client/src/generated/instructions/set_config_admin.rs index 0675719d..1bb3ee0c 100644 --- a/clients/rust/vault_client/src/generated/instructions/set_config_admin.rs +++ b/clients/rust/vault_client/src/generated/instructions/set_config_admin.rs @@ -57,7 +57,7 @@ pub struct SetConfigAdminInstructionData { impl SetConfigAdminInstructionData { pub fn new() -> Self { - Self { discriminator: 31 } + Self { discriminator: 32 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/set_config_secondary_admin.rs b/clients/rust/vault_client/src/generated/instructions/set_config_secondary_admin.rs new file mode 100644 index 00000000..00cd38f4 --- /dev/null +++ b/clients/rust/vault_client/src/generated/instructions/set_config_secondary_admin.rs @@ -0,0 +1,406 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! +//! + +use crate::generated::types::ConfigAdminRole; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct SetConfigSecondaryAdmin { + pub config: solana_program::pubkey::Pubkey, + + pub admin: solana_program::pubkey::Pubkey, + + pub new_admin: solana_program::pubkey::Pubkey, +} + +impl SetConfigSecondaryAdmin { + pub fn instruction( + &self, + args: SetConfigSecondaryAdminInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: SetConfigSecondaryAdminInstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.admin, true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.new_admin, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = SetConfigSecondaryAdminInstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::JITO_VAULT_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct SetConfigSecondaryAdminInstructionData { + discriminator: u8, +} + +impl SetConfigSecondaryAdminInstructionData { + pub fn new() -> Self { + Self { discriminator: 33 } + } +} + +impl Default for SetConfigSecondaryAdminInstructionData { + fn default() -> Self { + Self::new() + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SetConfigSecondaryAdminInstructionArgs { + pub config_admin_role: ConfigAdminRole, +} + +/// Instruction builder for `SetConfigSecondaryAdmin`. +/// +/// ### Accounts: +/// +/// 0. `[]` config +/// 1. `[signer]` admin +/// 2. `[]` new_admin +#[derive(Clone, Debug, Default)] +pub struct SetConfigSecondaryAdminBuilder { + config: Option, + admin: Option, + new_admin: Option, + config_admin_role: Option, + __remaining_accounts: Vec, +} + +impl SetConfigSecondaryAdminBuilder { + pub fn new() -> Self { + Self::default() + } + #[inline(always)] + pub fn config(&mut self, config: solana_program::pubkey::Pubkey) -> &mut Self { + self.config = Some(config); + self + } + #[inline(always)] + pub fn admin(&mut self, admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.admin = Some(admin); + self + } + #[inline(always)] + pub fn new_admin(&mut self, new_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.new_admin = Some(new_admin); + self + } + #[inline(always)] + pub fn config_admin_role(&mut self, config_admin_role: ConfigAdminRole) -> &mut Self { + self.config_admin_role = Some(config_admin_role); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = SetConfigSecondaryAdmin { + config: self.config.expect("config is not set"), + admin: self.admin.expect("admin is not set"), + new_admin: self.new_admin.expect("new_admin is not set"), + }; + let args = SetConfigSecondaryAdminInstructionArgs { + config_admin_role: self + .config_admin_role + .clone() + .expect("config_admin_role is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `set_config_secondary_admin` CPI accounts. +pub struct SetConfigSecondaryAdminCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `set_config_secondary_admin` CPI instruction. +pub struct SetConfigSecondaryAdminCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: SetConfigSecondaryAdminInstructionArgs, +} + +impl<'a, 'b> SetConfigSecondaryAdminCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: SetConfigSecondaryAdminCpiAccounts<'a, 'b>, + args: SetConfigSecondaryAdminInstructionArgs, + ) -> Self { + Self { + __program: program, + config: accounts.config, + admin: accounts.admin, + new_admin: accounts.new_admin, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.new_admin.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = SetConfigSecondaryAdminInstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::JITO_VAULT_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.admin.clone()); + account_infos.push(self.new_admin.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `SetConfigSecondaryAdmin` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[]` config +/// 1. `[signer]` admin +/// 2. `[]` new_admin +#[derive(Clone, Debug)] +pub struct SetConfigSecondaryAdminCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> SetConfigSecondaryAdminCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(SetConfigSecondaryAdminCpiBuilderInstruction { + __program: program, + config: None, + admin: None, + new_admin: None, + config_admin_role: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + #[inline(always)] + pub fn config( + &mut self, + config: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.config = Some(config); + self + } + #[inline(always)] + pub fn admin(&mut self, admin: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.admin = Some(admin); + self + } + #[inline(always)] + pub fn new_admin( + &mut self, + new_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.new_admin = Some(new_admin); + self + } + #[inline(always)] + pub fn config_admin_role(&mut self, config_admin_role: ConfigAdminRole) -> &mut Self { + self.instruction.config_admin_role = Some(config_admin_role); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = SetConfigSecondaryAdminInstructionArgs { + config_admin_role: self + .instruction + .config_admin_role + .clone() + .expect("config_admin_role is not set"), + }; + let instruction = SetConfigSecondaryAdminCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + admin: self.instruction.admin.expect("admin is not set"), + + new_admin: self.instruction.new_admin.expect("new_admin is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct SetConfigSecondaryAdminCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + config_admin_role: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/vault_client/src/generated/instructions/set_secondary_admin.rs b/clients/rust/vault_client/src/generated/instructions/set_secondary_admin.rs index 6b5aef2d..bfc17fb6 100644 --- a/clients/rust/vault_client/src/generated/instructions/set_secondary_admin.rs +++ b/clients/rust/vault_client/src/generated/instructions/set_secondary_admin.rs @@ -70,7 +70,7 @@ pub struct SetSecondaryAdminInstructionData { impl SetSecondaryAdminInstructionData { pub fn new() -> Self { - Self { discriminator: 22 } + Self { discriminator: 23 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/update_token_metadata.rs b/clients/rust/vault_client/src/generated/instructions/update_token_metadata.rs index 4bd036fd..205b09bc 100644 --- a/clients/rust/vault_client/src/generated/instructions/update_token_metadata.rs +++ b/clients/rust/vault_client/src/generated/instructions/update_token_metadata.rs @@ -75,7 +75,7 @@ pub struct UpdateTokenMetadataInstructionData { impl UpdateTokenMetadataInstructionData { pub fn new() -> Self { - Self { discriminator: 30 } + Self { discriminator: 31 } } } diff --git a/clients/rust/vault_client/src/generated/instructions/update_vault_balance.rs b/clients/rust/vault_client/src/generated/instructions/update_vault_balance.rs index f29ad959..59e0a6d8 100644 --- a/clients/rust/vault_client/src/generated/instructions/update_vault_balance.rs +++ b/clients/rust/vault_client/src/generated/instructions/update_vault_balance.rs @@ -76,7 +76,7 @@ pub struct UpdateVaultBalanceInstructionData { impl UpdateVaultBalanceInstructionData { pub fn new() -> Self { - Self { discriminator: 25 } + Self { discriminator: 26 } } } diff --git a/clients/rust/vault_client/src/generated/types/config_admin_role.rs b/clients/rust/vault_client/src/generated/types/config_admin_role.rs new file mode 100644 index 00000000..19f1a05f --- /dev/null +++ b/clients/rust/vault_client/src/generated/types/config_admin_role.rs @@ -0,0 +1,27 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; +use num_derive::FromPrimitive; + +#[derive( + BorshSerialize, + BorshDeserialize, + Clone, + Debug, + Eq, + PartialEq, + Copy, + PartialOrd, + Hash, + FromPrimitive, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ConfigAdminRole { + FeeAdmin, +} diff --git a/clients/rust/vault_client/src/generated/types/mod.rs b/clients/rust/vault_client/src/generated/types/mod.rs index 70be7c3c..3c077437 100644 --- a/clients/rust/vault_client/src/generated/types/mod.rs +++ b/clients/rust/vault_client/src/generated/types/mod.rs @@ -5,6 +5,7 @@ //! //! +pub(crate) mod r#config_admin_role; pub(crate) mod r#create_metadata_account_args_v3; pub(crate) mod r#data_v2; pub(crate) mod r#delegation_state; @@ -13,6 +14,7 @@ pub(crate) mod r#update_metadata_account_args_v2; pub(crate) mod r#vault_admin_role; pub(crate) mod r#withdrawal_allocation_method; +pub use self::r#config_admin_role::*; pub use self::r#create_metadata_account_args_v3::*; pub use self::r#data_v2::*; pub use self::r#delegation_state::*; diff --git a/crankers/src/bin/main.rs b/crankers/src/bin/main.rs index 8d0315be..fd9d95a1 100644 --- a/crankers/src/bin/main.rs +++ b/crankers/src/bin/main.rs @@ -1,11 +1,19 @@ -use std::{collections::HashMap, fmt, path::PathBuf, process::Command, sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + fmt, + path::PathBuf, + process::Command, + sync::Arc, + time::{Duration, Instant}, +}; use anyhow::{anyhow, Context}; use clap::{arg, Parser, ValueEnum}; use dotenvy::dotenv; -use jito_bytemuck::AccountDeserialize; use jito_jsm_core::get_epoch; -use jito_vault_core::{vault::Vault, vault_operator_delegation::VaultOperatorDelegation}; +use jito_vault_core::{ + config::Config, vault::Vault, vault_operator_delegation::VaultOperatorDelegation, +}; use jito_vault_cranker::{metrics::emit_vault_metrics, vault_handler::VaultHandler}; use log::{error, info}; use solana_metrics::set_host_id; @@ -125,15 +133,7 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { )); let rpc_client = RpcClient::new_with_timeout(args.rpc_url.clone(), Duration::from_secs(60)); - let config_address = - jito_vault_core::config::Config::find_program_address(&args.vault_program_id).0; - - let account = rpc_client - .get_account(&config_address) - .await - .context("Failed to read Jito vault config address")?; - let config = jito_vault_core::config::Config::try_from_slice_unchecked(&account.data) - .context("Failed to deserialize Jito vault config")?; + let config_address = Config::find_program_address(&args.vault_program_id).0; let vault_handler = Arc::new(VaultHandler::new( &args.rpc_url, @@ -144,11 +144,17 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { // Track vault metrics in separate thread tokio::spawn({ + let config: Config = vault_handler + .get_vault_program_account(&config_address) + .await?; let epoch_length = config.epoch_length(); async move { let metrics_client = RpcClient::new_with_timeout(args.rpc_url, Duration::from_secs(60)); loop { - if let Err(e) = emit_vault_metrics(&metrics_client, epoch_length).await { + if let Err(e) = + emit_vault_metrics(&metrics_client, epoch_length, &args.cluster.to_string()) + .await + { error!("Failed to emit metrics: {}", e); } tokio::time::sleep(Duration::from_secs(args.metrics_interval)).await; @@ -157,6 +163,10 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { }); loop { + let config: Config = vault_handler + .get_vault_program_account(&config_address) + .await?; + let slot = rpc_client.get_slot().await.context("get slot")?; let epoch = get_epoch(slot, config.epoch_length()).unwrap(); @@ -191,6 +201,8 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { info!("Updating {} vaults", vaults_need_update.len()); + let start = Instant::now(); + let tasks: Vec<_> = grouped_delegations .into_iter() .map(|(vault, mut delegations)| { @@ -209,7 +221,7 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { .unwrap(); async move { match vault_handler - .do_vault_update(&payer, epoch, &vault, &operators) + .do_vault_update(slot, &config, &payer, &vault, &operators) .await { Ok(_) => { @@ -230,6 +242,8 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { } } + log::info!("Time elapsed: {:.2}s", start.elapsed().as_secs_f64()); + info!("Sleeping for {} seconds", args.crank_interval); // ---------- SLEEP (crank_interval)---------- tokio::time::sleep(Duration::from_secs(args.crank_interval)).await; diff --git a/crankers/src/metrics.rs b/crankers/src/metrics.rs index b3e941dd..c7ecfd3d 100644 --- a/crankers/src/metrics.rs +++ b/crankers/src/metrics.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use jito_jsm_core::get_epoch; use jito_vault_core::config::Config; +use log::error; use solana_metrics::datapoint_info; use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{program_pack::Pack, pubkey::Pubkey}; @@ -13,6 +14,7 @@ use crate::vault_handler::VaultHandler; pub async fn emit_vault_metrics( rpc_client: &RpcClient, config_epoch_length: u64, + cluster_name: &str, ) -> anyhow::Result<()> { let slot = rpc_client.get_slot().await?; let epoch = slot / config_epoch_length; @@ -90,12 +92,23 @@ pub async fn emit_vault_metrics( .get(&vault.vrt_mint) .ok_or_else(|| anyhow::anyhow!("Mint not found in map"))?; - let st_deposit_account: &TokenAccount = st_ata_map + let try_st_deposit_account = st_ata_map .get(&get_associated_token_address( address, &vault.supported_mint, )) - .ok_or_else(|| anyhow::anyhow!("ST deposit account not found in map"))?; + .ok_or_else(|| anyhow::anyhow!("ST deposit account not found in map")); + + if try_st_deposit_account.is_err() { + error!( + "Failed to get ST deposit account for vault {}: {}", + address, + try_st_deposit_account.unwrap_err() + ); + continue; + } + + let st_deposit_account = try_st_deposit_account.unwrap(); datapoint_info!( "restaking-vault-supply", @@ -107,6 +120,7 @@ pub async fn emit_vault_metrics( ("vrt_supply_external", vrt_mint.supply as i64, i64), ("st_supply_internal", vault.tokens_deposited() as i64, i64), ("st_supply_external", st_deposit_account.amount as i64, i64), + "cluster" => cluster_name, ); } @@ -126,6 +140,7 @@ pub async fn emit_vault_metrics( num_vault_operator_delegations_updated, i64 ), + "cluster" => cluster_name, ); Ok(()) diff --git a/crankers/src/vault_handler.rs b/crankers/src/vault_handler.rs index 52dcb95d..5b07e99b 100644 --- a/crankers/src/vault_handler.rs +++ b/crankers/src/vault_handler.rs @@ -3,6 +3,7 @@ use std::time::Duration; use anyhow::Context; use base64::{engine::general_purpose, Engine}; use jito_bytemuck::AccountDeserialize; +use jito_jsm_core::get_epoch; use jito_vault_client::{ instructions::{ CloseVaultUpdateStateTrackerBuilder, CrankVaultUpdateStateTrackerBuilder, @@ -11,10 +12,10 @@ use jito_vault_client::{ types::WithdrawalAllocationMethod, }; use jito_vault_core::{ - vault::Vault, vault_operator_delegation::VaultOperatorDelegation, + config::Config, vault::Vault, vault_operator_delegation::VaultOperatorDelegation, vault_update_state_tracker::VaultUpdateStateTracker, }; -use log::error; +use log::{error, info}; use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_rpc_client_api::{ @@ -30,6 +31,8 @@ use tokio::time::sleep; use crate::core::get_latest_blockhash_with_retry; +const MAX_RETRIES: u8 = 10; + pub struct VaultHandler { rpc_url: String, vault_program_id: Pubkey, @@ -101,6 +104,7 @@ impl VaultHandler { /// Sends and confirms a transaction with retries, priority fees, and blockhash refresh /// /// # Arguments + /// * `payer` - Keypair of payer /// * `instructions` - Vector of instructions to include in the transaction /// /// # Returns @@ -112,7 +116,6 @@ impl VaultHandler { ) -> anyhow::Result<()> { let rpc_client = self.get_rpc_client(); let mut retries = 0; - const MAX_RETRIES: u8 = 10; instructions.insert( 0, @@ -159,6 +162,101 @@ impl VaultHandler { )) } + /// Splits a vector of instructions into multiple transactions to stay within Solana's + /// transaction size limit of 1232 bytes. + /// + /// This function dynamically batches instructions by testing the actual transaction size + /// rather than using fixed batch sizes. Each transaction will include a compute budget + /// instruction at the beginning. + async fn split_instructions_by_size( + &self, + instructions: &[Instruction], + payer: &Keypair, + max_size: usize, + ) -> anyhow::Result> { + let mut transactions = Vec::new(); + let mut current_batch = Vec::new(); + + let compute_budget_ix = + ComputeBudgetInstruction::set_compute_unit_price(self.priority_fees); + + for instruction in instructions { + // Create a test transaction with current batch + new instruction + let mut test_batch = vec![compute_budget_ix.clone()]; + test_batch.extend(current_batch.clone()); + test_batch.push(instruction.clone()); + + let blockhash = get_latest_blockhash_with_retry(&self.get_rpc_client()).await?; + let test_tx = Transaction::new_signed_with_payer( + &test_batch, + Some(&payer.pubkey()), + &[payer], + blockhash, + ); + + let tx_size = test_tx.signatures.len() + test_tx.message_data().len(); + + if tx_size > max_size && !current_batch.is_empty() { + // Finalize current batch + let mut final_batch = vec![compute_budget_ix.clone()]; + final_batch.extend(current_batch.clone()); + + let blockhash = get_latest_blockhash_with_retry(&self.get_rpc_client()).await?; + let tx = Transaction::new_signed_with_payer( + &final_batch, + Some(&payer.pubkey()), + &[payer], + blockhash, + ); + transactions.push(tx); + + // Start new batch with current instruction + current_batch = vec![instruction.clone()]; + } else { + current_batch.push(instruction.clone()); + } + } + + // Handle remaining instructions + if !current_batch.is_empty() { + let mut final_batch = vec![compute_budget_ix]; + final_batch.extend(current_batch); + + let blockhash = get_latest_blockhash_with_retry(&self.get_rpc_client()).await?; + let tx = Transaction::new_signed_with_payer( + &final_batch, + Some(&payer.pubkey()), + &[payer], + blockhash, + ); + transactions.push(tx); + } + + Ok(transactions) + } + + /// Retrieves Jito Vault Program account + pub async fn get_vault_program_account( + &self, + pubkey: &Pubkey, + ) -> anyhow::Result { + let rpc_client = self.get_rpc_client(); + + match rpc_client.get_account(pubkey).await { + Ok(account) => match T::try_from_slice_unchecked(&account.data) { + Ok(vault_operator_delegation) => Ok(*vault_operator_delegation), + Err(e) => { + let context = format!("Failed deserializing: {pubkey}"); + Err(anyhow::Error::new(e).context(context)) + } + }, + Err(e) => { + let context = format!("Error: Failed to get account: {pubkey}"); + Err(anyhow::Error::new(e).context(context)) + } + } + } + /// Retrieves all existing vaults /// /// # Returns @@ -256,11 +354,13 @@ impl VaultHandler { /// Returns `anyhow::Result<()>` indicating success or failure of the update operation. pub async fn do_vault_update( &self, + slot: u64, + config: &jito_vault_core::config::Config, payer: &Keypair, - epoch: u64, vault: &Pubkey, operators: &[Pubkey], ) -> anyhow::Result<()> { + let epoch = get_epoch(slot, config.epoch_length())?; let tracker_pubkey = VaultUpdateStateTracker::find_program_address(&self.vault_program_id, vault, epoch).0; @@ -276,7 +376,7 @@ impl VaultHandler { log::info!("Initialized tracker for vault: {vault}, tracker: {tracker_pubkey}"); // Crank - self.crank(payer, epoch, vault, operators, tracker_pubkey) + self.crank(slot, config, payer, vault, operators, tracker_pubkey) .await?; log::info!("Cranked vault: {vault}"); @@ -324,20 +424,83 @@ impl VaultHandler { Ok(()) } + /// Retrieves operators that need to be updated and builds their crank instructions. + /// + /// # Arguments + /// * `operators_iter` - Iterator of operator public keys to check + /// * `slot` - Current slot number + /// * `config` - Configuration containing epoch length + /// * `vault` - Vault public key + /// * `tracker_pubkey` - Vault update state tracker public key + /// + /// # Returns + /// * `Vec` - Vector of crank instructions for operators that need updates + async fn retrieve_non_updated_operators( + &self, + operators_iter: &[&Pubkey], + slot: u64, + config: &Config, + vault: &Pubkey, + tracker_pubkey: Pubkey, + ) -> anyhow::Result> { + let mut instructions = Vec::with_capacity(operators_iter.len()); + + for operator in operators_iter { + let vault_operator_delegation_pubkey = VaultOperatorDelegation::find_program_address( + &self.vault_program_id, + vault, + operator, + ) + .0; + + let vault_operator_delegation: VaultOperatorDelegation = self + .get_vault_program_account(&vault_operator_delegation_pubkey) + .await?; + + // Check if operator is NOT already updated (inverted logic) + if vault_operator_delegation + .check_is_already_updated(slot, config.epoch_length()) + .is_ok() + { + let mut ix_builder = CrankVaultUpdateStateTrackerBuilder::new(); + ix_builder + .config(self.config_address) + .vault(*vault) + .operator(**operator) + .vault_operator_delegation(vault_operator_delegation_pubkey) + .vault_update_state_tracker(tracker_pubkey); + + let mut ix = ix_builder.instruction(); + ix.program_id = self.vault_program_id; + + instructions.push(ix); + } + } + + Ok(instructions) + } + /// Cranks the [`VaultUpdateStateTracker`] for a specific epoch and list of operators. /// + /// - Try to crank maximum 10 times + /// - Batch multiple operator cranks per one transaction + /// - Cycle send transaction, check `is_already_updated`, then retry + /// /// # Returns /// /// This method returns an `anyhow::Result<()>` that indicates whether the crank operation /// was successful or not. pub async fn crank( &self, + slot: u64, + config: &Config, payer: &Keypair, - epoch: u64, vault: &Pubkey, operators: &[Pubkey], tracker_pubkey: Pubkey, ) -> anyhow::Result<()> { + let rpc_client = self.get_rpc_client(); + let epoch = get_epoch(slot, config.epoch_length())?; let tracker = self.get_update_state_tracker(vault, epoch).await?; if operators.is_empty() || tracker.all_operators_updated(operators.len() as u64)? { @@ -362,7 +525,6 @@ impl VaultHandler { .take(end_index) .skip(start_index) .collect::>() - .into_iter() } else { // Crank through operators from start index to operators.len() and then 0 to end_index operators @@ -370,32 +532,74 @@ impl VaultHandler { .skip(start_index) .chain(operators.iter().take(end_index)) .collect::>() - .into_iter() }; // Need to send each transaction in serial since strict sequence is required - for operator in operators_iter { - let vault_operator_delegation = VaultOperatorDelegation::find_program_address( - &self.vault_program_id, - vault, - operator, - ) - .0; + let instructions = self + .retrieve_non_updated_operators(&operators_iter, slot, config, vault, tracker_pubkey) + .await?; - let mut ix_builder = CrankVaultUpdateStateTrackerBuilder::new(); - ix_builder - .config(self.config_address) - .vault(*vault) - .operator(*operator) - .vault_operator_delegation(vault_operator_delegation) - .vault_update_state_tracker(tracker_pubkey); - let mut ix = ix_builder.instruction(); - ix.program_id = self.vault_program_id; - - self.send_and_confirm_transaction_with_retry(payer, vec![ix]) - .await?; + if instructions.is_empty() { + return Ok(()); + } + + let txs = self + .split_instructions_by_size(&instructions, payer, 1232) + .await?; + + for (i, tx) in txs.iter().enumerate() { + let mut retries = 0; + + // Retry loop for current transaction + loop { + match rpc_client + .send_and_confirm_transaction_with_spinner_and_commitment( + tx, + CommitmentConfig::confirmed(), + ) + .await + { + Ok(_) => { + info!( + "✅ Transaction {}/{} completed successfully", + i + 1, + txs.len() + ); + break; // Success - move to next transaction + } + Err(err) => { + retries += 1; + + if retries <= MAX_RETRIES { + info!( + "⚠️ Transaction {}/{} failed (attempt {}/{}), retrying in 1s: {:?}", + i + 1, txs.len(), retries, MAX_RETRIES, err + ); + sleep(Duration::from_secs(1)).await; + } else { + error!( + "❌ Transaction {}/{} failed permanently after {} retries: {:?}", + i + 1, + txs.len(), + MAX_RETRIES, + err + ); + return Err(anyhow::anyhow!( + "Transaction {} failed after {} retries: {}", + i + 1, + MAX_RETRIES, + err + )); + } + } + } + } } + info!( + "🎉 All {} transactions completed successfully for vault cranking!", + txs.len() + ); Ok(()) } diff --git a/docs/_about/01_high_level.md b/docs/_about/01_high_level.md deleted file mode 100644 index d1637956..00000000 --- a/docs/_about/01_high_level.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: About -category: Jekyll -layout: post ---- - -Jito (Re)staking is a multi-asset staking protocol for node consensus networks. The system is made of two programs: the -restaking program and the vault program. - -The restaking program acts as a node consensus network and operator registry. The program leverages a flexible system of -admins so NCNs can customize the operators and vaults supported and operators can customize the NCNs they stake to and vaults -they can receive delegations from. - -The vault program manages the minting and burning of vault receipt tokens (VRTs). VRTs are SPL tokens that represent -a pro-rata stake of assets in the vault. VRTs provide enhanced liquidity, composability, and interoperability with other -Solana programs. The program also leverages a flexible system of admins so vaults can customize the capacity, operators -that can receive delegations from the vault, the NCNs supported by the vault, and the fee structure for staking and unstaking. - -### Key Features - -- Universal framework for (re)staking SPL tokens to node consensus networks on Solana and SVM chains. -- Staked assets are tokenized into Vault Receipt Tokens (VRT) -- Flexible opt-in from node consensus networks, operators, and vaults for staking. - -### Entity Opt-in - -The restaking and vault programs are designed to be flexible and allow for easy opt-in from node consensus networks, -operators, and vaults. The following diagram shows the opt-in process for the Jito Restaking Ecosystem: - -![alt text](/assets/images/opt_in.png) -*Figure: Overview of the Jito Restaking Ecosystem* - -When a NCN, operator, and vault have all opted-in to each other and the vault has staked assets to the operator, those -assets are considered staked to the NCN. The operator will then be able to participate in the NCN's consensus protocol. -Assuming the vault has opted-in to the slasher. - -### Vault Interactions - -The following diagram shows the interactions between users, admins, and the vault: - -![Vault interactions](/assets/images/vault_interactions.png) -*Figure: Overview of the Vault Interactions* diff --git a/docs/_config.yaml b/docs/_config.yaml deleted file mode 100644 index 533f0d27..00000000 --- a/docs/_config.yaml +++ /dev/null @@ -1,46 +0,0 @@ -title: Jito (Re)Staking Overview -longtitle: Jito (Re)Staking Overview -description: Jito Restaking Documentation -url: "https://docs.restaking.jito.network" - -remote_theme: sighingnow/jekyll-gitbook - -markdown: kramdown -syntax_highlighter_style: colorful -toc: - enabled: true - h_min: 1 - h_max: 3 - -assets: - dirname: assets - baseurl: /assets/ - sources: - - assets - -collections: - terminology: - output: true - permalink: /:collection/:title/ - sort_by: weight - restaking: - output: true - permalink: /:collection/:title/ - sort_by: weight - vault: - output: true - permalink: /:collection/:title/ - sort_by: weight - tools: - output: true - permalink: /:collection/:title/ - ncn: - output: true - permalink: /:collection/:title/ - -ordered_collections: - - terminology - - restaking - - vault - - tools - - ncn diff --git a/docs/_ncn/00_implementation.md b/docs/_ncn/00_implementation.md deleted file mode 100644 index 79fc81e8..00000000 --- a/docs/_ncn/00_implementation.md +++ /dev/null @@ -1,1615 +0,0 @@ ---- -title: NCN Implementation Guide -category: Jekyll -layout: post -weight: 1 ---- - -## Introduction - -The Node Consensus Network (NCN) is a robust blockchain consensus system built on Solana. It enables network participants to agree on critical network decisions using a secure, stake-weighted voting mechanism. This system utilizes Jito's restaking infrastructure, allowing operators with delegated tokens to vote on network parameters and states. - -This tutorial will focus on a [pre-built NCN program](https://github.com/jito-foundation/ncn-template) that acts like a template or base that you can use to create your own NCN program. To help you understand how it works, we will walk through building a simulation test that covers the majority of its setup and functionality. We do not recommend most NCN developers build an NCN from scratch. Rather, we suggest using this prebuilt program as a starting point and customizing it according to your needs. - -By following the simulation test setup in this guide, you will gain hands-on experience with the entire NCN lifecycle: initializing vaults and operators using Jito's restaking and vault programs, configuring the NCN program, and executing the full voting process. - -### The purpose of NCNs - -Decentralized networks require reliable mechanisms for participants to reach consensus without central authorities. The NCN addresses this need by: - -1. Providing a secure voting framework where influence is proportional to the amount of stake held. -2. Supporting multiple token types with configurable voting weights, allowing flexibility in how voting power is assigned. -3. Creating verifiable and immutable records of consensus decisions on the blockchain. -4. Establishing a solid foundation for network governance and parameter setting. - -### NCN components - -To run an NCN, you need one or more of each of the following three components, which interact with each other: Vaults, Operators, and the NCN Program itself. - -#### 1. Vaults - -Vaults are accounts that hold tokens and delegate them to operators. They play a crucial role in the NCN by: - -1. Holding the tokens used for staking. -2. Delegating stake (voting power) to chosen operators. -3. Enabling stake-weighted participation in the network's governance. - -#### 2. Operators - -Operators are accounts that receive delegated stake from vaults and actively participate in the voting process. Their key functions are: - -1. Receiving stake delegations from one or more vaults. -2. Casting votes on behalf of the delegated stake during consensus rounds. -3. Forming the network of active participants who drive the consensus process. - -#### 3. NCN program - -The NCN Program is the core on-chain component of the system. It's the smart contract that NCN developers build and deploy. Its main responsibilities are: - -1. Storing the global configuration parameters for the NCN instance. -2. Maintaining the registry of participating vaults and supported token types. -3. Managing the state for each voting epoch (consensus cycle). - -### NCN Lifecycle - -The Node Consensus Network operates in a well-defined lifecycle that consists of three main phases: - -1. **Initial Setup (One-time)**: This phase involves establishing the foundational infrastructure of the NCN. It includes: - - - Configuring the NCN parameters - - Initializing the vault registry - - Registering supported token types and assigning weights - - The initial setup is performed only once when the NCN is first deployed, with occasional administrative updates as needed (such as adjusting token weights or adding new supported tokens). - -2. **Snapshotting (Recurring)**: At the beginning of each consensus cycle (epoch), the system captures the current state of all participants: - - - Creating epoch state and weight tables - - Taking snapshots of operator stake weights - - Recording vault-operator delegations - - Calculating total voting power distribution - - This phase ensures that voting is based on a consistent, point-in-time view of the network, preventing manipulation during the voting process. - -3. **Voting (Recurring)**: After snapshotting is complete, operators can cast their votes: - - Operators submit their choices (e.g., weather status) - - Votes are weighted according to the operator's stake - -## Get to know the program template - -Our example NCN Program facilitates consensus on a simple "weather status" using a stake-weighted voting mechanism. It operates in distinct time periods called epochs (your NCN's epochs do not have to be equivalent to a Solana epoch). The program uses a weight-based system to determine the influence (voting power) of different operators. Consensus is achieved when votes representing at least 66% of the total participating stake weight agree on the same outcome (ballot). - -### Key components - -The program uses several types of accounts: - -1. **Global Accounts**: Initialized once at the start and updated infrequently. - - **[`Config`](#config)**: Stores global settings like epoch timing parameters (`epochs_before_stall`, `epochs_after_consensus_before_close`) and voting validity periods (`valid_slots_after_consensus`). - - **[`VaultRegistry`](#vaultregistry)**: Manages the list of registered vaults and the different types of stake tokens (mints) the NCN supports. - - **[`AccountPayer`](#accountpayer)**: An empty PDA account used to hold SOL temporarily for paying rent during account creation or reallocation. -2. **Per-Consensus Cycle Accounts**: Initialized at the beginning of each epoch and usually closed shortly after the cycle ends. - - **[`WeightTable`](#weighttable)**: Stores the specific voting weights assigned to different stake tokens for the current epoch. - - **[`EpochState`](#epochaccountstatus)**: Tracks the status and progress of the current epoch's consensus cycle. - - **[`BallotBox`](#ballotbox)**: Handles the collection and stake-weighted tallying of votes for the current epoch's decision (e.g., weather status). - - **[`EpochSnapshot`](#epochsnapshot)**: Captures the state of stake delegations at the beginning of the epoch to ensure consistent voting weights throughout the cycle. - - **[`OperatorSnapshot`](#operatorsnapshot)**: Records each operator's total stake weight and delegation breakdown for the current epoch. - - **[`ConsensusResult`](#consensusresult)**: Stores the final outcome (the winning ballot and associated details) for the completed epoch. - - **[`EpochMarker`](#epochmarker)**: A marker account created when all temporary accounts for an epoch have been successfully closed. -3. **Component Structures**: These are not separate accounts but important data structures used within the accounts above. - - **[`Ballot`](#ballot)**: Represents a single potential outcome in the consensus process. - - **[`BallotTally`](#ballottally)**: Aggregates votes and stake weight for a specific ballot. - - **[`OperatorVote`](#operatorvote)**: Records a vote cast by a single operator. - - **[`VaultOperatorStakeWeight`](#vaultoperatorstakeweight)**: Tracks the weighted stake from a specific vault to an operator. - - **[`StMintEntry`](#stmintentry)**: Represents a supported token mint and its voting weight in the VaultRegistry. - - **[`VaultEntry`](#vaultentry)**: Represents a registered vault in the VaultRegistry. - -### Weather status system - -The goal of the NCN program is to come to consensus on the weather in Solana Beach. For the purposes of keeping this tutorial simple, our weather statuses are as follows: - -1. **Sunny (0)**: Represents clear, sunny weather. -2. **Cloudy (1)**: Represents cloudy weather conditions. -3. **Rainy (2)**: Represents rainy weather conditions. - -Operators vote on these status values. The program tallies the votes, weighting each vote by the operator's associated stake weight, to determine the final consensus result. Leveraging the final result of this NCN, we can build onchain programs whose behavior is dependent on the weather in Solana Beach. - -### Consensus mechanism - -The consensus process follows these steps: - -1. Operators cast votes, choosing a specific weather status (Sunny, Cloudy, or Rainy). -2. Each vote's influence is determined by the operator's total stake weight, calculated from delegations received. -3. Votes are collected and tallied within the `BallotBox` account for the current epoch. -4. Consensus is reached when one weather status receives votes representing ≥66% of the total stake weight participating in that epoch. -5. The final consensus result (winning status, total weight supporting it, etc.) is recorded in the `ConsensusResult` account. - -### Onchain program overview - -The onchain program is written in Rust (without using the Anchor framework) and consists of several instructions that can be called to perform various actions within the NCN. The instruction logic resides in the `/program` directory, while shared core logic is located in the `/core` directory. - -The instructions are broadly categorized: - -1. **Admin Instructions**: These require administrator privileges and are used for initial setup and configuration. - - `admin_initialize_config`: Initializes the main `Config` account. - - `admin_register_st_mint`: Registers a new type of stake token (ST) the NCN will support. - - `admin_set_new_admin`: Transfers administrative control to a new keypair. - - `admin_set_parameters`: Updates parameters within the `Config` account. - - `admin_set_st_mint`: Updates details for an existing supported token mint (Deprecated/Redundant? Check `admin_register_st_mint` and `admin_set_weight`). - - `admin_set_tie_breaker`: Configures the tie-breaking mechanism or authority. - - `admin_set_weight`: Sets or updates the voting weight for a specific supported token mint. -2. **Permissionless Keeper Instructions**: These are permissionless instructions, meaning anyone can call them to advance the state of the NCN, typically moving between epoch phases. They ensure the NCN progresses correctly. - - `initialize_epoch_state`: Creates the `EpochState` account for a new epoch. - - `initialize_vault_registry`: Creates the initial `VaultRegistry` account. - - `realloc_vault_registry`: Increases the size of the `VaultRegistry` account, to reach the desired size. Solana has a limitation when it comes to the size of the account that you can allocate in one call, so when you have a larger account, you will need to call realloc on it multiple times to reach the desired size. - - `initialize_weight_table`: Creates the `WeightTable` account for an epoch. - - `realloc_weight_table`: Increases the size of the `WeightTable` account. - - `initialize_epoch_snapshot`: Creates the main `EpochSnapshot` account. - - `initialize_operator_snapshot`: Creates an `OperatorSnapshot` account for a specific operator within an epoch. - - `set_epoch_weights`: Populates the `WeightTable` with weights from the `VaultRegistry`. - - `snapshot_vault_operator_delegation`: Records the weighted stake from a specific vault delegation into the relevant `OperatorSnapshot`. - - `initialize_ballot_box`: Creates the `BallotBox` account for voting in an epoch. - - `realloc_ballot_box`: Increases the size of the `BallotBox` account. - - `register_vault`: Registers a vault (that has already been approved via Jito handshake) with the NCN program's `VaultRegistry`. - - `close_epoch_account`: Closes temporary epoch-specific accounts (like `EpochState`, `BallotBox`, etc.) after they are no longer needed, reclaiming rent. -3. **Operator Instruction**: This is the primary action taken by participants during a consensus cycle. - - `cast_vote`: Allows an operator (using their admin key) to submit their vote for the current epoch. - -For more details, you can always check the source code or the API documentation [here](https://github.com/jito-foundation/ncn-template). - -## Build and run the simulation test - -This section will walk through building a simulation test of our example NCN program. The test represents a comprehensive scenario designed to mimic a complete NCN system. It involves multiple operators, vaults, and different types of tokens. The test covers the entire workflow, from the initial setup of participants and the NCN program itself, through the voting process, and finally to reaching and verifying consensus. It heavily utilizes Jito's restaking and vault infrastructure alongside the custom NCN voting logic. - -The NCN program used can be found [here](https://github.com/jito-foundation/ncn-template). By creating a simulation test of this NCN, you'll be better prepared to use it as a template or base that you can adapt to create your own NCN program. Just a reminder: we do not recommend most NCN developers build their NCN from scratch. Rather, we suggest using this prebuilt program as a starting point and customizing it according to your needs. - -The simulation test we'll be creating below can also be found in the [example NCN repository](https://github.com/jito-foundation/ncn-template). However, you'll understand the system better if you write the test along with us, so feel free to clone the repository, delete the test file `./integration_tests/test/ncn_program/simulation_test.rs`, and follow along. This will give you hands-on experience with the entire NCN lifecycle: initializing vaults and operators using Jito's restaking and vault programs, configuring the NCN program, and executing the full voting process. - -### Prerequisites - -Before running the simulation test, ensure you have completed the following setup steps: - -1. Build the NCN onchain program using Cargo: `cargo build-sbf --manifest-path program/Cargo.toml --sbf-out-dir integration_tests/tests/fixtures` -2. Ensure you have the correct versions installed: - - Solana CLI: 2.2.6 (recommended) - - Rust/Cargo: 1.81 or newer - -### Building the Simulation Test - -Let's build the simulation test step by step. - -#### 1. Create a new file - -You can start with a blank file. Create a new file named `simulation_test.rs` inside the `integration_tests/tests` folder. Copy and paste the following boilerplate code at the bottom of your test function: - -```rust -#[cfg(test)] -mod tests { - use crate::fixtures::{test_builder::TestBuilder, TestResult}; - use jito_restaking_core::{config::Config, ncn_vault_ticket::NcnVaultTicket}; - use ncn_program_core::{ballot_box::WeatherStatus, constants::WEIGHT}; - use solana_sdk::{msg, signature::Keypair, signer::Signer}; - - #[tokio::test] - async fn simulation_test() -> TestResult<()> { - // YOUR TEST CODE WILL GO HERE - // 2. ENVIRONMENT SETUP - - // 3. NCN SETUP - - // 4. OPERATORS AND VAULTS SETUP - - // 5. NCN PROGRAM CONFIGURATION - - // 6. Epoch Snapshot and Voting Preparation - - // 7. VOTING - - // 8. VERIFICATION - - // 9. CLEANUP - - Ok(()) - } -} -``` - -Unless otherwise specified, all of the code snippets provided in this guide represent code that should go inside the `simulation_test` test function, in the order provided. - -Next, you need to make this new test discoverable. Copy and paste the following line into the `integration_tests/tests/mod.rs` file to declare the new module: - -```rust -// integration_tests/tests/mod.rs -mod simulation_test; -``` - -Now, you can run this specific test using the following command: - -```bash -SBF_OUT_DIR=integration_tests/tests/fixtures cargo test -p ncn-program-integration-tests --test tests simulation_test -``` - -This command targets the `ncn-program-integration-tests` package and runs only the `simulation_test` test function. If you want to run all tests in the suite, simply remove the test name filter (`-p ncn-program-integration-tests --test tests simulation_test`) from the command. - -Currently, the test will pass because it doesn't contain any logic yet. You should see output similar to this: - -```bash -running 1 test -test ncn_program::simulation_test::tests::simulation_test ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 54 filtered out; finished in 0.00s -``` - -#### 2. Environment Setup - -The first step within our test function is to set up the testing environment using the `TestBuilder`. Copy and paste the following code at the bottom of your test function: - -```rust -let mut fixture = TestBuilder::new().await; -``` - -The `TestBuilder` is a test utility that encapsulates and simplifies the setup process for NCN program testing. It provides: - -1. A local test validator environment with pre-loaded programs -2. Clients for interacting with the NCN, Vault, and Restaking programs -3. Helper methods for common operations (creating operators, vaults, advancing clock time) -4. Management of test accounts, keypairs, and token mints - -This and other utility functions (like `add_operators_to_test_ncn`, `add_vaults_to_test_ncn`) abstract away much of the complex, repetitive setup code, allowing tests to focus on the specific behaviors being verified rather than boilerplate infrastructure. - -Since we are running this test locally against a test ledger, we need to initialize the Jito Restaking and Vault programs on the ledger. In a real network environment (devnet, mainnet), these programs would already be deployed and configured. - -Copy and paste the following code at the bottom of your test function: - -```rust -fixture.initialize_restaking_and_vault_programs().await?; -``` - -Finally, let's prepare some client objects and configuration variables we'll use throughout the test. - -Copy and paste the following code at the bottom of your test function: - -```rust -let ncn_program_client = fixture.ncn_program_client(); -let vault_program_client = fixture.vault_client(); -let restaking_client = fixture.restaking_program_client(); - -// Define test parameters -const OPERATOR_COUNT: usize = 13; // Number of operators to simulate -let mints = vec![ - (Keypair::new(), WEIGHT), // Alice: Base weight - (Keypair::new(), WEIGHT * 2), // Bob: Double weight - (Keypair::new(), WEIGHT * 3), // Charlie: Triple weight - (Keypair::new(), WEIGHT * 4), // Dave: Quadruple weight -]; -let delegations = [ - 1, // Minimum delegation amount (e.g., 1 lamport) - 10_000_000_000, // 10 tokens (assuming 9 decimals) - 100_000_000_000, // 100 tokens - 1_000_000_000_000, // 1,000 tokens - 10_000_000_000_000, // 10,000 tokens -]; -``` - -This code does the following: - -1. Gets client handles for interacting with the NCN, Vault, and Restaking programs. -2. Defines `OPERATOR_COUNT` to specify how many operators we'll create. -3. Sets up `mints`: a list of keypairs representing different SPL token mints and their corresponding voting weights. We use different weights to test the stake-weighting mechanism. `WEIGHT` is likely a constant representing the base unit of weight. -4. Defines `delegations`: an array of different token amounts (in lamports, assuming 9 decimals for typical SPL tokens) that vaults will delegate to operators. - -#### 3. NCN Setup - -Now, let's create the NCN account using the Jito Restaking program. The `create_test_ncn` helper function handles the necessary instruction calls. - -Copy and paste the following code at the bottom of your test function: - -```rust -let mut test_ncn = fixture.create_test_ncn().await?; -let ncn_pubkey = test_ncn.ncn_root.ncn_pubkey; -``` - -This step: - -- Calls the Jito Restaking program to create a new Node Consensus Network (NCN) account and its associated administrative structures. -- Stores the public key (`ncn_pubkey`) of the newly created NCN, which we'll need to interact with it later. - -If you run the test at this point (`cargo test ... simulation_test`), you should see transaction logs in the output, indicating that the NCN creation instructions were executed successfully. - -#### 4. Operators and Vaults Setup - -This phase is crucial for simulating a realistic network. We will create the operators who vote and the vaults that provide the stake (voting power). - -##### 4.1 Operator Creation and NCN Connection - -We'll add the specified number of operators (`OPERATOR_COUNT`) to our NCN using another helper function. - -Copy and paste the following code at the bottom of your test function: - -```rust -fixture - .add_operators_to_test_ncn(&mut test_ncn, OPERATOR_COUNT, Some(100)) - .await?; -``` - -This `add_operators_to_test_ncn` function performs several actions by calling instructions in the Jito Restaking program: - -- Creates `OPERATOR_COUNT` (13 in our case) separate operator accounts. -- Sets an optional operator fee (here, 100 basis points = 1%). -- Establishes a secure, bidirectional "handshake" between each newly created operator and the NCN. - -The handshake process involves multiple steps: - -1. Creating the operator account itself, managed by its unique admin keypair. -2. Initializing the state that tracks the relationship between the NCN and the operator (`do_initialize_ncn_operator_state`). -3. Warming up the connection from the NCN's perspective (`do_ncn_warmup_operator`). -4. Warming up the connection from the operator's perspective (`do_operator_warmup_ncn`). - -For more information on this, please read the guide [here](/) - -This handshake is essential for security. It ensures that operators must explicitly connect to the NCN (and vice-versa) and potentially wait through an activation period before they can participate in voting. - -##### 4.2 Vault Creation - -Next, we create vaults to hold the different types of tokens we defined earlier. We'll distribute them across the token types. -Note that you can have more than one vault with the same ST Mint (Support Token Mint). - -Copy and paste the following code at the bottom of your test function: - -```rust -// Create vaults associated with different token mints -{ - // Create 3 vaults for Alice (base weight) - fixture - .add_vaults_to_test_ncn(&mut test_ncn, 3, Some(mints[0].0.insecure_clone())) - .await?; - // Create 2 vaults for Bob (double weight) - fixture - .add_vaults_to_test_ncn(&mut test_ncn, 2, Some(mints[1].0.insecure_clone())) - .await?; - // Create 1 vault for Charlie (triple weight) - fixture - .add_vaults_to_test_ncn(&mut test_ncn, 1, Some(mints[2].0.insecure_clone())) - .await?; - // Create 1 vault for Dave (quadruple weight) - fixture - .add_vaults_to_test_ncn(&mut test_ncn, 1, Some(mints[3].0.insecure_clone())) - .await?; -} -``` - -The `add_vaults_to_test_ncn` helper function orchestrates calls to both the Jito Vault and Jito Restaking programs to: - -- Create a total of 7 vaults (3 + 2 + 1 + 1). -- Associate each group of vaults with one of our predefined token mints (`mints[0]`, `mints[1]`, etc.). -- Initialize the vault accounts using the Jito Vault program (setting zero fees, which is common for testing). -- Mint tokens for the vaults if needed (though here we provide the mints). -- Establish bidirectional handshakes "Tickets" between each vault and the NCN using specific Jito Restaking instructions (`do_initialize_ncn_vault_ticket`, `do_warmup_ncn_vault_ticket`). -- Establish corresponding handshakes "Tickets" using Jito Vault program instructions (`do_initialize_vault_ncn_ticket`, `do_warmup_vault_ncn_ticket`). -- Establish bidirectional handshakes "Tickets" between each new vault and _all_ existing operators using Jito Restaking (`do_initialize_operator_vault_ticket`, `do_warmup_operator_vault_ticket`) and Jito Vault (`do_initialize_vault_operator_delegation`) instructions. Note that `do_initialize_vault_operator_delegation` only sets up the _potential_ for delegation; no actual tokens are delegated yet. -- Advance the simulated clock (`fixture.advance_slots`) after handshakes "Tickets" to ensure the relationships become active, simulating the necessary waiting period. - -Creating vaults with different token types allows us to test how the NCN handles varying voting power based on token weights. - -##### 4.3 Delegation Setup - -This is where vaults actually delegate their tokens (stake) to operators, granting them voting power. We'll iterate through operators and vaults to create delegations. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Vaults delegate stake to operators -{ - // Iterate through all operators except the last one - for (index, operator_root) in test_ncn - .operators - .iter() - .take(OPERATOR_COUNT - 1) - .enumerate() - { - // Each vault delegates to this operator - for vault_root in test_ncn.vaults.iter() { - // Cycle through the predefined delegation amounts - let delegation_amount = delegations[index % delegations.len()]; - - if delegation_amount > 0 { - // Call the Vault program to add the delegation - vault_program_client - .do_add_delegation( - vault_root, // The vault delegating - &operator_root.operator_pubkey, // The operator receiving - delegation_amount, // The amount to delegate - ) - .await - .unwrap(); - } - } - } -} -``` - -The delegation process is where voting power is established. Each vault delegates tokens to operators, which determines: - -1. How much voting power each operator has -2. How token weights multiply that power -3. The distribution of influence across the network - -Key aspects of the delegation setup: - -- Every vault delegates to every operator (except the last one for this example) - -* Note that vaults can choose whom to delegate to, they don't have to delegate to all operators - -- Delegation amounts cycle through the `delegations` array to test different scenarios -- The last operator intentionally receives zero delegation to test the system's handling of operators without stake -- The delegation is performed directly through the vault program using `do_add_delegation` which will call a specific instruction in the vault program to do that - -Each operator accumulates voting power from all the different delegations they receive. The total voting power for an operator is the sum of the weighted values of each delegation. - -**Example:** - -- Vault A (holding Alice, weight W) delegates 100 tokens to Operator X. Power contribution: 100 \* W. -- Vault B (holding Bob, weight 2W) delegates 50 tokens to Operator X. Power contribution: 50 _ 2W = 100 _ W. -- Operator X's total voting power would be (100 _ W) + (50 _ 2W) = 200 \* W. - -This distributed delegation model enables testing complex scenarios where: - -- Operators have vastly different amounts of influence. -- Tokens with higher weights contribute disproportionately more voting power. -- The distribution of delegations affects consensus outcomes. - -The deliberate omission of delegation to the last operator creates a control case to verify that operators with zero stake cannot influence the voting process, which is a critical security feature. - -You can run the test now and see the output. - -##### 4.4 Delegation Architecture and Voting Power Calculation - -The delegation architecture follows a multiplication relationship: - -- **Operator Voting Power = Sum of (Delegation Amount × Delegated Token's Weight)** - -Each operator accumulates voting power from all the different delegations they receive. The total voting power for an operator is the sum of the weighted values of each delegation. - -**Example:** - -- Vault A (holding TKN1, weight W) delegates 100 tokens to Operator X. Power contribution: 100 \* W. -- Vault B (holding TKN2, weight 2W) delegates 50 tokens to Operator X. Power contribution: 50 _ 2W = 100 _ W. -- Operator X's total voting power would be (100 _ W) + (50 _ 2W) = 200 \* W. - -This distributed delegation model enables testing complex scenarios where: - -- Operators have vastly different amounts of influence. -- Tokens with higher weights contribute disproportionately more voting power. -- The distribution of delegations affects consensus outcomes. - -The deliberate omission of delegation to the last operator creates a control case to verify that operators with zero stake cannot influence the voting process, which is a critical security feature. - -You can run the test now and see the output. - -#### 5. NCN Program Configuration - -Until now, all the code we've written uses the Jito restaking program and Jito vault program. Now we will start using the example NCN program that you will have to deploy. - -The NCN Program Configuration phase establishes the on-chain infrastructure necessary for the voting and consensus mechanisms. This includes setting up configuration parameters, creating data structures, and registering the token types and vaults that will participate in the system. - -##### 5.1 Program Configuration Initialization - -First, we initialize the main configuration account for our NCN instance. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Initialize the main Config account for the NCN program -ncn_program_client - .do_initialize_config(test_ncn.ncn_root.ncn_pubkey, &test_ncn.ncn_root.ncn_admin) - .await?; -``` - -This step initializes the core configuration for the NCN program with critical parameters: - -- **NCN Admin**: The authority that can modify configuration settings, this admin has to be the same admin for the NCN account from Jito restaking program side. -- **Epochs Before Stall**: How many epochs before a non-completed consensus cycle is considered stalled (default: 3) -- **Epochs After Consensus Before Close**: How long to wait after consensus before closing epoch data (default: 10) -- **Valid Slots After Consensus**: How many slots votes are still accepted after consensus is reached (default: 10000) - -Under the hood, this creates an `NcnConfig` account that stores these parameters and serves as the authoritative configuration for this NCN instance. - -##### 5.2 Vault Registry Initialization - -The vault registry account is a large one, so it is not possible to initialize it in one call due to Solana network limitations. We will have to call the NCN program multiple times to get to the full size. The first call will be an init call to the instruction `admin_initialize_vault_registry`. After that, we will call a realloc instruction `admin_realloc_vault_registry` to increase the size of the account. This will be done in a loop until the account is the correct size. - -The realloc will take care of assigning the default values to the vault registry account once the desirable size is reached. In our example, we will do that by calling one function `do_full_initialize_vault_registry`. If you want to learn more about this, you can check the [source code](https://github.com/jito-foundation/ncn-template). - -Copy and paste the following code at the bottom of your test function: - -```rust -// Initialize the VaultRegistry account (handles potential reallocations) -ncn_program_client - .do_full_initialize_vault_registry(test_ncn.ncn_root.ncn_pubkey) - .await?; -``` - -The vault registry is a critical data structure that: - -- Tracks all supported vault accounts -- Maintains the list of supported token mints (token types) -- Records the weight assigned to each token type -- Serves as the source of truth for vault and token configurations - -Note that this is only initializing the vault registry. The vaults and the supported tokens will be registered in the next steps. - -Check out the vault registry struct [here](#vaultregistry) - -##### 5.3 Activating Relationships with Time Advancement - -Next, we advance the simulation clock to ensure that all previously established handshake relationships (NCN-Operator, NCN-Vault, Operator-Vault) become active, as Jito's restaking infrastructure often includes activation periods. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Fast-forward time to simulate a full epoch passing -// This is needed for all the relationships to get activated -let restaking_config_address = - Config::find_program_address(&jito_restaking_program::id()).0; -let restaking_config = restaking_client - .get_config(&restaking_config_address) - .await?; -let epoch_length = restaking_config.epoch_length(); -fixture - .warp_slot_incremental(epoch_length * 2) - .await - .unwrap(); -``` - -This section: - -1. Retrieves the epoch length from the restaking program configuration -2. Advances the simulation time by two full epochs -3. Ensures all handshake relationships between NCN, operators, and vaults become active - -The time advancement is necessary because Jito's restaking infrastructure uses an activation period for security. This prevents malicious actors from quickly creating and voting with fake operators or vaults by enforcing a waiting period before they can participate. - -Now it is time to register the supported tokens with the NCN program and assign weights to each mint for voting power calculations. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Register each Supported Token (ST) mint and its weight in the NCN's VaultRegistry -for (mint, weight) in mints.iter() { - ncn_program_client - .do_admin_register_st_mint(ncn_pubkey, mint.pubkey(), *weight) - .await?; -} -``` - -This step registers each Supported Token (ST) mint with the NCN program and assigns the appropriate weight: - -- Each token mint (Alice, Bob, Charlie, Dave) is registered with its corresponding weight -- The weights determine the voting power multiplier for delegations in that token -- Only the NCN admin has the authority to register tokens, ensuring trust in the system -- Registration involves updating the vault registry with each token's data -- The NCN admin can update the weights of the tokens at any time, which will affect the voting power of the delegations in the next consensus cycle - -The weight assignment is fundamental to the design, allowing different tokens to have varying influence on the voting process based on their economic significance or other criteria determined by the NCN administrators. - -It's good to know that in real-life examples, NCNs will probably want to set the token weights based on the token's price or market cap. To do so, you will have to use an oracle to get the price of the token and then set the weight based on that. In this case, you will have to store the feed of the price in this step instead of the weight. - -##### 5.5 Vault Registration - -Registering a vault is a permissionless operation. The reason is the admin has already given permission to the vault to be part of the NCN in the vault registration step earlier, so this step is just to register the vault in the NCN program. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Register all the vaults in the ncn program -for vault in test_ncn.vaults.iter() { - let vault = vault.vault_pubkey; - let (ncn_vault_ticket, _, _) = NcnVaultTicket::find_program_address( - &jito_restaking_program::id(), - &ncn_pubkey, - &vault, - ); - - ncn_program_client - .do_register_vault(ncn_pubkey, vault, ncn_vault_ticket) - .await?; -} -``` - -The final configuration step registers each vault with the NCN program: - -1. For each vault created earlier, the system finds its NCN vault ticket PDA (Program Derived Address) -2. The vault is registered in the NCN program's vault registry -3. This creates the association between the vault and its supported token type -4. The registration enables the NCN program to track vault delegations for voting power calculation - -This registration process establishes the complete set of vaults that can contribute to the voting system, creating a closed ecosystem of verified participants. - -##### 5.6 NCN Architecture and Security Considerations - -##### 5.5 Architecture Considerations - -The NCN program configuration establishes a multi-layered security model: - -1. **Authentication Layer**: Only the NCN admin can initialize configuration and register tokens -2. **Relationship Layer**: Only vaults and operators with established, active handshakes can participate -3. **Time Security Layer**: Enforced waiting periods prevent quick creation and use of malicious actors -4. **Registry Layer**: All participants must be registered and tracked in on-chain registries - -This layered approach ensures the integrity of the voting system by validating the identity and relationships of all participants before they can influence the consensus process. - -The configuration phase completes the preparation of the system's infrastructure, setting the stage for the actual voting mechanics to begin in subsequent phases. - -#### 6. Epoch Snapshot and Voting Preparation - -The Epoch Snapshot and Voting Preparation phase is where the system captures the current state of all participants and prepares the infrastructure for voting. This is an essential component of the architecture as it ensures voting is based on a consistent, verifiable snapshot of the network state at a specific moment in time. - -The upcoming section is a keeper task (with the exception of the voting). This means that it is permissionless and can be done by anyone. - -##### 6.1 Epoch State Initialization - -To begin a new consensus cycle (epoch), we first initialize an `EpochState` account for our NCN, which will track the progress of this epoch. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Initialize the epoch state for the current epoch -fixture.add_epoch_state_for_test_ncn(&test_ncn).await?; -``` - -This step initializes the **Epoch State** for the current consensus cycle: - -- It creates an `EpochState` account tied to the specific NCN and epoch. -- This account tracks the progress through each stage of the consensus cycle. -- It maintains flags for each phase (weight setting, snapshot taking, voting, closing). -- The epoch state provides protection against out-of-sequence operations. -- It stores metadata like the current epoch, slot information, and participant counts. - -Once initialized, the `EpochState` account becomes the authoritative record of where the system is in the voting process, preventing operations from happening out of order or in duplicate. - -You can take a look at the epoch state struct [here](#epochaccountstatus). - -##### 6.2 Weight Table Initialization and Population - -For the current epoch, we initialize a `WeightTable` and populate it by copying the token weights from the `VaultRegistry`, effectively freezing these weights for the duration of this consensus cycle. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Initialize the weight table to track voting weights -let clock = fixture.clock().await; -let epoch = clock.epoch; -ncn_program_client - .do_full_initialize_weight_table(test_ncn.ncn_root.ncn_pubkey, epoch) - .await?; - -// Take a snapshot of weights for each token mint -ncn_program_client - .do_set_epoch_weights(test_ncn.ncn_root.ncn_pubkey, epoch) - .await?; -``` - -The weight table mechanism handles the token weights for the current epoch in two stages: - -1. **Weight Table Initialization**: - - - Creates a [`WeightTable`](#weighttable) account for the specific epoch using `do_full_initialize_weight_table`. This may involve multiple calls internally to allocate sufficient space. - - Allocates space based on the number of supported tokens registered in the [`VaultRegistry`](#vaultregistry). - - Links the table to the NCN and current epoch. - - Initializes the table structure with empty entries. - -2. **Weight Setting**: - - Populates the [`WeightTable`](#weighttable) by calling `do_set_epoch_weights` - - Copies the current weights from the [`VaultRegistry`](#vaultregistry) to the epoch-specific `WeightTable`. - - "Freezes" these weights for the duration of the consensus cycle. - - Updates the [`EpochState`](#epochaccountstatus) to mark weight setting as complete. - - Creates an immutable record of token weights that will be used for voting. - -This two-step process is critical for the integrity of the system as it: - -- Creates a permanent record of weights at the time voting begins. -- Prevents weight changes during a consensus cycle from affecting ongoing votes. -- Allows transparent verification of the weights used for a particular vote. -- Enables historical auditing of how weights changed over time. - -##### 6.3 Epoch Snapshot Creation - -We then create an `EpochSnapshot` account to record the overall state for this epoch, such as total operator and vault counts, and to accumulate total stake weight. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Take the epoch snapshot -fixture.add_epoch_snapshot_to_test_ncn(&test_ncn).await?; -``` - -The epoch snapshot captures the aggregate state of the entire system: - -- Creates an [`EpochSnapshot`](#epochsnapshot) account for the NCN and epoch. -- Records the total number of operators and vaults expected to participate. -- Captures the total potential stake weight across all participants (initialized to zero). -- Stores important metadata like the snapshot creation slot. -- Serves as the reference point for total voting power calculations, acting as the denominator for consensus thresholds. - -##### 6.4 Operator Snapshots - -Next, individual `OperatorSnapshot` accounts are created for each participating operator, capturing their state and expected delegations for the epoch. - -Copy and paste the following code at the bottom of your test function: - -```rust -// 2.b. Initialize the operators using the Jito Restaking program, and initiate the -// handshake relationship between the NCN <> operators -{ - for _ in 0..OPERATOR_COUNT { - // Set operator fee to 100 basis points (1%) - let operator_fees_bps: Option = Some(100); - - // Initialize a new operator account with the specified fee - let operator_root = restaking_client - .do_initialize_operator(operator_fees_bps) - .await?; - - // Establish bidirectional handshake between NCN and operator: - // 1. Initialize the NCN's state tracking (the NCN operator ticket) for this operator - restaking_client - .do_initialize_ncn_operator_state( - &test_ncn.ncn_root, - &operator_root.operator_pubkey, - ) - .await?; - - // 2. Advance slot to satisfy timing requirements - fixture.warp_slot_incremental(1).await.unwrap(); - - // 3. NCN warms up to operator - creates NCN's half of the handshake - restaking_client - .do_ncn_warmup_operator(&test_ncn.ncn_root, &operator_root.operator_pubkey) - .await?; - - // 4. Operator warms up to NCN - completes operator's half of the handshake - restaking_client - .do_operator_warmup_ncn(&operator_root, &test_ncn.ncn_root.ncn_pubkey) - .await?; - - // Add the initialized operator to our test NCN's operator list - test_ncn.operators.push(operator_root); - } -} -``` - -This step creates an individual snapshot for each operator in the system: - -- For each operator, it creates an [`OperatorSnapshot`](#operatorsnapshot) account linked to the operator, NCN, and epoch. -- Records the operator's total delegated stake weight at this moment (initialized to zero). -- Captures the expected number of vault delegations for the operator. -- Verifies the operator has active handshakes with the NCN. -- Validates the operator's eligibility to participate in voting. - -These snapshots establish each operator's baseline for the current epoch. The actual voting power will be populated in the next step based on individual delegations. This ensures that later delegation changes cannot alter voting weight once the snapshot phase is complete. - -##### 6.5 Vault-Operator Delegation Snapshots - -With operator snapshots ready, we now record the weighted stake from each specific vault-to-operator delegation into the relevant `OperatorSnapshot` and update the total stake in the `EpochSnapshot`. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Record all vault-to-operator delegations -fixture - .add_vault_operator_delegation_snapshots_to_test_ncn(&test_ncn) - .await?; -``` - -This crucial step iterates through each active vault-to-operator delegation and records its contribution to the operator's voting power: - -- For each valid delegation found in the Jito Vault program: - - Retrieves the corresponding token weight from the epoch's [`WeightTable`](#weighttable). - - Calculates the weighted stake for that delegation (delegation amount \* token weight). - - Updates the relevant [`OperatorSnapshot`](#operatorsnapshot) by adding the calculated stake weight. - - Stores detailed information about the weighted delegation within the [`OperatorSnapshot`](#operatorsnapshot)'s `vault_operator_stake_weight` array. - - Increments the total stake weight in the global [`EpochSnapshot`](#epochsnapshot). - - Creates a [`VaultOperatorDelegationSnapshot`](#vaultoperatordelegationsnapshot) account for detailed auditing. - -These granular snapshots serve multiple purposes: - -- They populate the [`OperatorSnapshot`](#operatorsnapshot) accounts with the actual stake weights used for voting. -- They update the [`EpochSnapshot`](#epochsnapshot) with the total voting power present in the system for this epoch. -- They provide detailed audit trails of exactly where each operator's voting power originates. -- They enable verification of correct weight calculation for each delegation. -- They prevent retroactive manipulation of the voting power distribution. - -##### 6.6 Ballot Box Initialization - -To prepare for voting, we initialize a `BallotBox` account for the current epoch, which will collect and tally all operator votes. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Initialize the ballot box for collecting votes -fixture.add_ballot_box_to_test_ncn(&test_ncn).await?; -``` - -The final preparation step creates the ballot box: - -- Initializes a [`BallotBox`](#ballotbox) account linked to the NCN and epoch using `do_full_initialize_ballot_box`. Similar to the weight table, this may require multiple allocation calls internally. -- Creates arrays to track operator votes ([`OperatorVote`](#operatorvote)) and ballot tallies ([`BallotTally`](#ballottally)). -- Sets up the data structures for recording and counting votes. -- Prepares the consensus tracking mechanism. -- Links the ballot box to the [`EpochState`](#epochaccountstatus) for progress tracking. - -The [`BallotBox`](#ballotbox) becomes the central repository where all votes are recorded and tallied during the voting process. It is designed to efficiently track: - -- Which operators have voted and what they voted for. -- The cumulative stake weight behind each voting option (ballot). -- The current winning ballot (if any). -- Whether consensus has been reached. - -##### 6.7 Snapshot Architecture and Security Considerations - -The snapshot system implements several key architectural principles: - -1. **Point-in-Time Consistency**: All snapshots capture the system state relative to the start of the epoch, creating a consistent view based on frozen weights and delegations present at that time. -2. **Immutability**: Once taken and populated, snapshots cannot be modified, ensuring the integrity of the voting weights used. -3. **Layered Verification**: The system enables verification at multiple levels: - - Aggregate level (`EpochSnapshot`) - - Participant level (`OperatorSnapshot`) - - Relationship level (individual weighted delegations within `OperatorSnapshot`, optionally `VaultOperatorDelegationSnapshot`) -4. **Defense Against Time-Based Attacks**: By freezing the state (weights and relevant delegations) before voting begins, the system prevents: - - Late stake additions influencing outcomes within the _current_ epoch. - - Strategic withdrawals affecting voting power _after_ the snapshot. - - Any form of "stake voting power front-running" within the epoch. -5. **Separation of State and Process**: - - The state (snapshots, weights) is captured separately from the process (voting). - - This clear separation simplifies reasoning about the system. - - It enables more effective testing and verification. - -The comprehensive snapshot approach ensures that voting occurs on a well-defined, verifiable view of the network's state, establishing a solid foundation for the actual voting process to follow. - -#### 7. Voting Process - -The Voting Process is the core functionality of the NCN system, where operators express their preferences on the network state (represented by the "weather status" in this simulation). This process leverages the infrastructure and snapshots created in previous steps to ensure secure, verifiable, and stake-weighted consensus. - -##### 7.1 Setting the Expected Outcome - -In our simulation, we'll predefine an expected winning outcome for verification purposes. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Define the expected winning weather status -let winning_weather_status = WeatherStatus::Sunny as u8; -``` - -For testing purposes, the system defines an expected outcome (`WeatherStatus::Sunny`). In a production environment, the winning outcome would be determined organically through actual operator votes based on real-world data or criteria. The weather status enum (`Sunny`, `Cloudy`, `Rainy`) serves as a simplified proxy for any on-chain decision that requires consensus. - -##### 7.2 Casting Votes from Different Operators - -Operators now cast their votes. We'll simulate a few operators voting, some for the expected outcome and some against, to test the tallying logic. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Cast votes from operators -{ - let epoch = fixture.clock().await.epoch; - - let first_operator = &test_ncn.operators[0]; - let second_operator = &test_ncn.operators[1]; - let third_operator = &test_ncn.operators[2]; - - // First operator votes for Cloudy - ncn_program_client - .do_cast_vote( - ncn_pubkey, - first_operator.operator_pubkey, - &first_operator.operator_admin, - WeatherStatus::Cloudy as u8, - epoch, - ) - .await?; - - // Second and third operators vote for Sunny (expected winner) - ncn_program_client - .do_cast_vote( - ncn_pubkey, - second_operator.operator_pubkey, - &second_operator.operator_admin, - winning_weather_status, - epoch, - ) - .await?; - ncn_program_client - .do_cast_vote( - ncn_pubkey, - third_operator.operator_pubkey, - &third_operator.operator_admin, - winning_weather_status, - epoch, - ) - .await?; -} -``` - -This section demonstrates the system's ability to handle diverse voting preferences using the `do_cast_vote` helper, which calls the `cast_vote` instruction: - -- The first operator votes for "Cloudy" (representing a minority view). -- The second and third operators vote for "Sunny" (the presumed majority view). -- Each `do_cast_vote` call invokes the NCN program with the operator's choice and admin signature. - -Under the hood, each vote triggers several key operations within the `cast_vote` instruction: - -- **Verification**: - - Verifies the operator admin's signature. - - Checks that the operator hasn't already voted in this epoch using the [`BallotBox`](#ballotbox). - - Retrieves the operator's [`OperatorSnapshot`](#operatorsnapshot) to confirm eligibility and get its total stake weight. - - Ensures the [`EpochState`](#epochaccountstatus) indicates voting is currently allowed. -- **Recording**: - - Records the vote details (operator, slot, stake weight, ballot choice) in the `operator_votes` array within the [`BallotBox`](#ballotbox). - - Marks the operator as having voted. -- **Tallying**: - - Finds or creates a [`BallotTally`](#ballottally) for the chosen weather status in the `ballot_tallies` array. - - Adds the operator's full stake weight (from the snapshot) to this tally. - - Increments the raw vote count for this tally. -- **Consensus Check**: - - Compares the updated tally's stake weight against the total stake weight recorded in the [`EpochSnapshot`](#epochsnapshot). - - If the tally now exceeds the consensus threshold (e.g., 66%), it marks consensus as reached in the [`BallotBox`](#ballotbox) and records the current slot. - -##### 7.3 Establishing Consensus Through Majority Voting - -To ensure consensus is reached for our test, the remaining eligible operators will now vote for the predefined winning weather status. - -Copy and paste the following code at the bottom of your test function: - -```rust -// All remaining operators vote for Sunny to form a majority -for operator_root in test_ncn.operators.iter().take(OPERATOR_COUNT).skip(3) { - ncn_program_client - .do_cast_vote( - ncn_pubkey, - operator_root.operator_pubkey, - &operator_root.operator_admin, - winning_weather_status, - epoch, - ) - .await?; -} -``` - -The consensus mechanism works as follows: - -- The system maintains a running [`BallotTally`](#ballottally) for each unique option voted on. -- After each vote, it recalculates the total stake weight supporting the voted option. -- It compares this stake weight to the total stake weight available in the [`EpochSnapshot`](#epochsnapshot). -- If an option's stake weight reaches the consensus threshold (e.g., >= 66%), the system: - - Marks that `Ballot` as the `winning_ballot` in the [`BallotBox`](#ballotbox). - - Records the current `slot` in `slot_consensus_reached`. - - Updates the `EpochState`. - - Creates a persistent [`ConsensusResult`](#consensusresult) account (discussed in Verification). -- Consensus requires a supermajority to ensure decisions have strong, verifiable support across the network's weighted stake. - -##### 7.4 Vote Processing Architecture - -When an operator casts a vote via the `cast_vote` instruction, the system performs several critical operations: - -- **Authentication**: Verifies the transaction is signed by the correct `operator_admin` keypair associated with the `operator` account. -- **Authorization & Preconditions**: Confirms that: - - The operator exists, is registered with the NCN, and has an active [`OperatorSnapshot`](#operatorsnapshot) for the current `epoch`. - - The operator has not already voted in this epoch (checked via [`BallotBox`](#ballotbox)). - - The operator has non-zero stake weight in their [`OperatorSnapshot`](#operatorsnapshot). - - The [`EpochState`](#epochaccountstatus) confirms that the snapshotting phase is complete and voting is open. -- **Vote Recording**: - - Locates an empty slot or confirms the operator hasn't voted in the `operator_votes` array within the [`BallotBox`](#ballotbox). - - Stores the `operator` pubkey, current `slot`, the operator's total `stake_weights` (from [`OperatorSnapshot`](#operatorsnapshot)), and the index corresponding to the chosen ballot within the `ballot_tallies` array. - - Increments the `operators_voted` counter in the [`BallotBox`](#ballotbox). -- **Ballot Processing & Tallying**: - - Searches the `ballot_tallies` array for an existing entry matching the `weather_status`. - - If found: Adds the operator's `stake_weights` to the `stake_weights` field of the existing [`BallotTally`](#ballottally) and increments the raw `tally` counter. - - If not found: Initializes a new `BallotTally` entry with the `weather_status`, the operator's `stake_weights`, and a `tally` of 1. Increments `unique_ballots`. -- **Consensus Calculation & Result Creation**: - - Retrieves the total `stake_weights` from the `EpochSnapshot`. - - Compares the winning ballot's accumulated `stake_weights` against the total. - - If the threshold is met _and_ consensus hasn't already been marked: - - Sets the `winning_ballot` field in the `BallotBox`. - - Records the current `slot` in `slot_consensus_reached`. - - Updates the `EpochState`. - - Invokes an instruction (likely via CPI or separate transaction) to create the `ConsensusResult` account, storing the winning status, epoch, weights, and slot. -- **Cross-Validation**: Implicitly ensures the vote aligns with the correct `ncn` and `epoch` through the PDAs used for the involved accounts (`BallotBox`, `OperatorSnapshot`, `EpochState`). - -This multi-layered architecture ensures votes are processed securely, tallied correctly using the snapshotted weights, and that consensus is determined accurately based on stake-weighted participation. - -##### 7.5 Security Considerations in the Voting Process - -The voting process incorporates several key security features: - -- **Sybil Attack Prevention**: - - Voting power is derived directly from snapshotted stake weight, not operator count. - - Operators with zero snapshotted stake weight cannot vote, preventing attacks based on creating numerous fake operators. -- **Replay Protection**: - - The [`BallotBox`](#ballotbox) tracks which operators have voted (`operator_votes` array). - - Attempts by an operator to vote more than once within the same epoch are rejected. -- **Time-Bound Voting**: - - Votes are only accepted if the [`EpochState`](#epochaccountstatus) indicates the voting phase is active for the specified `epoch`. - - While votes might be accepted slightly after consensus is reached (within `valid_slots_after_consensus`), they won't change the already determined outcome. -- **Authority**: Requires `operator_admin` signature. -- **Tamper-Proof Tallying**: Uses immutable snapshotted data created _before_ voting began. -- **Consistent Threshold**: Calculated based on the total stake weight recorded in the [`EpochSnapshot`](#epochsnapshot), providing a fixed target for the epoch. - -These security measures ensure the voting process remains resilient against various attack vectors and manipulation attempts, maintaining the integrity of the consensus mechanism. - -#### 8. Verification - -The Verification phase validates that the voting process completed successfully and that the expected consensus was achieved. This critical step confirms the integrity of the entire system by examining the on-chain data structures ([`BallotBox`](#ballotbox) and [`ConsensusResult`](#consensusresult)) and verifying they contain the expected results. - -##### 8.1 Ballot Box Verification - -After voting concludes, we first verify the `BallotBox` to ensure it correctly reflects that consensus was reached and identifies the expected winning ballot. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Verify the results recorded in the BallotBox -{ - let epoch = fixture.clock().await.epoch; - let ballot_box = ncn_program_client.get_ballot_box(ncn_pubkey, epoch).await?; - - assert!(ballot_box.has_winning_ballot()); - assert!(ballot_box.is_consensus_reached()); - assert_eq!(ballot_box.get_winning_ballot().unwrap().weather_status(), winning_weather_status); -} -``` - -The first verification step examines the `BallotBox` account for the completed epoch: - -- **Winning Ballot Check**: - - `has_winning_ballot()` confirms that the `winning_ballot` field within the `BallotBox` structure is marked as valid. -- **Consensus Status Check**: - -* **Winning Ballot Check**: - - `has_winning_ballot()` confirms that the `winning_ballot` field within the `BallotBox` structure is marked as valid. - -2. **Consensus Status Check**: - - `is_consensus_reached()` checks if the `slot_consensus_reached` field is greater than zero, indicating the consensus condition was met during the voting process. - -- **Outcome Verification**: - - The test retrieves the `winning_ballot` struct and asserts that its `weather_status` field matches the `winning_weather_status` defined earlier (`WeatherStatus::Sunny`). This confirms the correct outcome was identified based on the stake-weighted tally. - -Verifying the `BallotBox` ensures the core voting and tallying mechanism functioned correctly during the active epoch. - -##### 8.2 Consensus Result Account Verification - -Next, we verify the permanently stored `ConsensusResult` account to confirm it accurately records the winning outcome, epoch details, and vote weights, consistent with the `BallotBox`. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Fetch and verify the consensus_result account -{ - let epoch = fixture.clock().await.epoch; - let consensus_result = ncn_program_client - .get_consensus_result(ncn_pubkey, epoch) - .await?; - - assert!(consensus_result.is_consensus_reached()); - assert_eq!(consensus_result.epoch(), epoch); - assert_eq!(consensus_result.weather_status(), winning_weather_status); - - let ballot_box = ncn_program_client.get_ballot_box(ncn_pubkey, epoch).await?; - let winning_ballot_tally = ballot_box.get_winning_ballot_tally().unwrap(); - - assert_eq!(consensus_result.vote_weight(), winning_ballot_tally.stake_weights().stake_weight() as u64); - - println!( - "✅ Consensus Result Verified - Weather Status: {}, Vote Weight: {}, Total Weight: {}, Recorder: {}", - consensus_result.weather_status(), - consensus_result.vote_weight(), - consensus_result.total_vote_weight(), - consensus_result.consensus_recorder() - ); -} -``` - -The second verification step examines the `ConsensusResult` account, which serves as the permanent, immutable record of the voting outcome: - -- **Consensus Result Existence & Fetching**: - - The test successfully fetches the `ConsensusResult` account using its PDA derived from the NCN pubkey and epoch. Its existence implies consensus was reached and the account was created. -- **Consensus Status Validation**: - -* **Consensus Result Existence & Fetching**: - - The test successfully fetches the `ConsensusResult` account using its PDA derived from the NCN pubkey and epoch. Its existence implies consensus was reached and the account was created. - -2. **Consensus Status Validation**: - - `is_consensus_reached()` checks an internal flag derived from stored values (like `consensus_slot` > 0), confirming the outcome is officially recognized. - -- **Metadata Verification**: - - Asserts that the `epoch` field matches the current epoch. - - Asserts that the `weather_status` matches the expected `winning_weather_status`. -- **Cross-Account Consistency Check**: - - Fetches the `BallotBox` again. - - Retrieves the `BallotTally` corresponding to the winning ballot from the `BallotBox`. - - Asserts that the `vote_weight` stored in the `ConsensusResult` exactly matches the `stake_weight` recorded in the winning `BallotTally` within the `BallotBox`. This ensures consistency between the temporary voting record and the permanent result. -- **Detailed Reporting**: - - Prints key details from the verified `ConsensusResult` account for confirmation. - -Verifying the `ConsensusResult` confirms that the outcome was durably stored with the correct details and consistent with the voting process itself. - -##### 8.3 Architecture of Verification and Result Persistence - -The verification phase highlights several important architectural features: - -1. **Dual Records**: - - The system temporarily uses the `BallotBox` during the epoch for active voting and tallying. - - Upon reaching consensus, it creates a separate, permanent `ConsensusResult` account. - - This redundancy allows for cleanup while preserving the essential outcome. -2. **Separation of Process and Outcome**: - - The `BallotBox` (process) can eventually be closed to reclaim rent. - - The `ConsensusResult` (outcome) persists indefinitely as the historical record. -3. **Automated Result Creation**: - - The `ConsensusResult` account is typically created automatically within the `cast_vote` instruction when the consensus threshold is first met. This ensures timely recording without requiring a separate administrative action. -4. **Result Immutability**: - - The `ConsensusResult` account, once created, is designed to be immutable. It stores the outcome based on the state when consensus was reached. -5. **Time and Slot Tracking**: - - Both `BallotBox` and `ConsensusResult` store key timing information (`slot_consensus_reached`, `epoch`). This metadata is crucial for auditing and understanding the system's behavior over time. - -##### 8.4 Verification Techniques and Best Practices - -The verification approach demonstrates several best practices: - -1. **Multi-Level Verification**: Testing both the ephemeral process account (`BallotBox`) and the persistent outcome account (`ConsensusResult`) provides comprehensive validation. -2. **State Assertions**: Using dedicated helper functions on the deserialized accounts (`has_winning_ballot()`, `is_consensus_reached()`) makes tests more readable and robust against internal representation changes. -3. **Equality Assertions**: Using strict equality (`assert_eq!`) for key outcome data (winning status, epoch, weights) ensures exactness. -4. **Cross-Structure Validation**: Comparing critical values (like `vote_weight`) between the `BallotBox` and `ConsensusResult` confirms data consistency across different parts of the system. -5. **Complete Outcome Validation**: Checking not just the winning choice but also associated metadata (epoch, weights, consensus flags) catches more subtle errors. -6. **Clear Reporting**: Outputting verified data (`println!`) provides immediate feedback during test runs. - -This rigorous verification ensures the NCN system reliably achieves and records stake-weighted consensus according to its design. - -#### 9. Cleanup - -After the core functionality has been tested and verified for a given epoch, the temporary accounts associated with that epoch can be closed to reclaim the SOL locked for rent. The persistent `ConsensusResult` account remains. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Close epoch accounts but keep consensus result -let epoch_before_closing_account = fixture.clock().await.epoch; -fixture.close_epoch_accounts_for_test_ncn(&test_ncn).await?; - -// Verify that consensus_result account is not closed -{ - let consensus_result = ncn_program_client - .get_consensus_result(ncn_pubkey, epoch_before_closing_account) - .await?; - - assert!(consensus_result.is_consensus_reached()); - assert_eq!(consensus_result.epoch(), epoch_before_closing_account); -} -``` - -This cleanup process involves: - -- **Identifying Epoch**: Recording the current epoch (`epoch_before_closing_account`) just before initiating closure. -- **Closing Accounts**: Calling `fixture.close_epoch_accounts_for_test_ncn`, which likely iterates through epoch-specific accounts and invokes a `close_epoch_account` instruction for each. -- **Verifying Persistence**: After the cleanup function returns, the test attempts to fetch the `ConsensusResult` account for the _same_ `epoch_before_closing_account`. -- **Confirming Data**: It asserts that the fetched `ConsensusResult` still exists and retains its key data (`is_consensus_reached`, `epoch`), confirming it was _not_ closed during the cleanup process. - -This demonstrates a crucial design feature: - -- **Resource Management**: Temporary accounts are removed, preventing indefinite accumulation of rent-paying accounts. -- **Outcome Preservation**: The final, critical outcome (`ConsensusResult`) is preserved as a permanent on-chain record, suitable for historical lookups or use by other programs. - -This efficient cleanup mechanism allows the NCN system to operate continuously over many epochs without unbounded growth in account storage requirements. - -Now you can save the file and run the test to see the result. - -## Core struct definitions - -Here are the definitions for the core data structures used by the NCN program, typically found in the `/core/src` directory. Understanding these structures is key to understanding the program's logic. - -#### Config - -file: `config.rs` - -- **Purpose**: Stores global, long-lived configuration parameters for the NCN program instance. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct Config { - /// The Pubkey of the associated Jito Restaking NCN account this config belongs to. - pub ncn: Pubkey, - /// The admin authorized to update the tie breaker mechanism or parameters. - pub tie_breaker_admin: Pubkey, - /// Number of slots after consensus is reached where votes are still accepted - /// (though they won't change the outcome). - pub valid_slots_after_consensus: PodU64, - /// Number of epochs without reaching consensus before the cycle is considered stalled. - pub epochs_before_stall: PodU64, - /// Number of epochs to wait after consensus is reached before epoch accounts can be closed. - pub epochs_after_consensus_before_close: PodU64, - /// The first epoch number for which voting is considered valid. - pub starting_valid_epoch: PodU64, - /// Bump seed for the PDA - pub bump: u8, -} -``` - -- **Explanation**: Holds the associated `ncn`, the `tie_breaker_admin`, and various timing/threshold parameters (`valid_slots_after_consensus`, `epochs_before_stall`, `epochs_after_consensus_before_close`, `starting_valid_epoch`). - -#### Ballot - -file: `ballot_box.rs` - -- **Purpose**: Represents a single potential outcome in the consensus process, specifically a weather status in this example. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct Ballot { - /// The weather status value - weather_status: u8, - /// Whether the ballot is valid - is_valid: PodBool, -} -``` - -- **Explanation**: Holds the numeric `weather_status` being voted on and a boolean `is_valid` flag to ensure it corresponds to a known status. - -#### BallotTally - -file: `ballot_box.rs` - -- **Purpose**: Aggregates votes and stake weight for a specific `Ballot`. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct BallotTally { - /// Index of the tally within the ballot_tallies - index: PodU16, - /// The ballot being tallied - ballot: Ballot, - /// Breakdown of all of the stake weights that contribute to the vote - stake_weights: StakeWeights, - /// The number of votes for this ballot - tally: PodU64, -} -``` - -- **Explanation**: Tracks which `ballot` this tally is for, its `index` in the main array, the total `stake_weights` supporting it, and the raw `tally` (count) of votes. - -#### OperatorVote - -file: `ballot_box.rs` - -- **Purpose**: Records the vote cast by a single operator within a specific epoch's `BallotBox`. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct OperatorVote { - /// The operator that cast the vote - operator: Pubkey, - /// The slot when the operator voted - slot_voted: PodU64, - /// The stake weights of the operator - stake_weights: StakeWeights, - /// The index of the ballot in the ballot_tallies array - ballot_index: PodU16, -} -``` - -- **Explanation**: Stores the `operator` pubkey, the current `slot`, their `stake_weights`, and the `ballot_index` they voted for. - -#### BallotBox - -file: `ballot_box.rs` - -- **Purpose**: The central account for managing the voting process within a specific epoch. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct BallotBox { - /// The Pubkey of the NCN this ballot box is for - ncn: Pubkey, - /// The epoch this ballot box is for - epoch: PodU64, - /// Bump seed for the PDA - bump: u8, - /// Slot when this ballot box was created - slot_created: PodU64, - /// Slot when consensus was reached - slot_consensus_reached: PodU64, - /// Number of operators that have voted - operators_voted: PodU64, - /// Number of unique ballots - unique_ballots: PodU64, - /// The ballot that got at least 66% of votes - winning_ballot: Ballot, - /// Operator votes - operator_votes: [OperatorVote; MAX_OPERATORS], - /// Mapping of ballots votes to stake weight - ballot_tallies: [BallotTally; MAX_OPERATORS], -} -``` - -- **Explanation**: Holds metadata (`ncn`, `epoch`, timestamps), vote counts, and arrays for individual operator votes and aggregated tallies. - -#### ConsensusResult - -file: `consensus_result.rs` - -- **Purpose**: A persistent account storing the final, immutable outcome of a consensus cycle for a specific epoch. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct ConsensusResult { - /// The Pubkey of the NCN this consensus result is for - ncn: Pubkey, - /// The epoch this consensus result is for - epoch: PodU64, - /// The vote weight that supported the winning status - vote_weight: PodU64, - /// The total vote weight in the ballot box - total_vote_weight: PodU64, - /// The slot at which consensus was reached - consensus_slot: PodU64, - /// Bump seed for the PDA - bump: u8, - /// The winning weather status that reached consensus - weather_status: u8, -} -``` - -- **Explanation**: Stores the `ncn`, `epoch`, the winning `weather_status`, and the `consensus_slot`. - -#### AccountPayer - -file: `account_payer.rs` - -- **Purpose**: An empty, uninitialized system account used solely as a Program Derived Address (PDA) to hold SOL temporarily for paying rent during account creation or reallocation within the NCN program. -- **Definition**: - -```rust -pub struct AccountPayer {} -``` - -- **Explanation**: This is a marker struct with no fields. Its associated functions handle deriving the PDA and performing SOL transfers for rent payments using `invoke_signed`. - -#### EpochMarker - -file: `epoch_marker.rs` - -- **Purpose**: An empty account created as a marker to signify that all temporary accounts associated with a specific NCN epoch have been successfully closed. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct EpochMarker { - ncn: Pubkey, - epoch: PodU64, - slot_closed: PodU64, -} -``` - -- **Explanation**: Contains the `ncn`, the `epoch` that was closed, and the `slot_closed`. Its existence confirms cleanup completion for that epoch. - -#### EpochSnapshot - -file: `epoch_snapshot.rs` - -- **Purpose**: Captures the aggregated state of the NCN system at the beginning of a specific epoch snapshot phase. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct EpochSnapshot { - /// The Pubkey of the NCN this snapshot is for - ncn: Pubkey, - /// The epoch this snapshot is for - epoch: PodU64, - /// Bump seed for the PDA - bump: u8, - /// Slot when this EpochSnapshot account was created - slot_created: PodU64, - /// Slot when the snapshotting process (including all operator delegations) was completed - slot_finalized: PodU64, - /// Number of operators in the epoch - operator_count: PodU64, - /// Number of vaults in the epoch - vault_count: PodU64, - /// Keeps track of the number of completed operator registration through `snapshot_vault_operator_delegation` and `initialize_operator_snapshot` - operators_registered: PodU64, - /// Keeps track of the number of valid operator vault delegations - valid_operator_vault_delegations: PodU64, - /// Tallies the total stake weights for all vault operator delegations - stake_weights: StakeWeights, -} -``` - -- **Explanation**: Stores metadata (`ncn`, `epoch`, timestamps), counts (`operator_count`, `vault_count`), progress trackers, and the total aggregated `stake_weights` for the epoch. - -#### OperatorSnapshot - -file: `epoch_snapshot.rs` - -- **Purpose**: Captures the state of a single operator for a specific epoch, including their total delegated stake weight and a breakdown of contributions from each vault. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct OperatorSnapshot { - operator: Pubkey, - ncn: Pubkey, - ncn_epoch: PodU64, - bump: u8, - slot_created: PodU64, - slot_finalized: PodU64, - is_active: PodBool, - ncn_operator_index: PodU64, - operator_index: PodU64, - operator_fee_bps: PodU16, - vault_operator_delegation_count: PodU64, - vault_operator_delegations_registered: PodU64, - valid_operator_vault_delegations: PodU64, - stake_weights: StakeWeights, - vault_operator_stake_weight: [VaultOperatorStakeWeight; MAX_VAULTS], -} -``` - -- **Explanation**: Contains operator/NCN identifiers, timestamps, status, indices, `operator_fee_bps`, delegation counts/progress, the operator's total `stake_weights`, and a detailed breakdown in `vault_operator_stake_weight`. - -#### VaultOperatorStakeWeight - -file: `epoch_snapshot.rs` - -- **Purpose**: A helper struct within `OperatorSnapshot` to store the calculated stake weight originating from one specific vault's delegation to that operator. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)] -pub struct VaultOperatorStakeWeight { - vault: Pubkey, - vault_index: PodU64, - stake_weight: StakeWeights, -} -``` - -- **Explanation**: Links a `vault` pubkey and `vault_index` to the specific `stake_weight` derived from its delegation to the parent `OperatorSnapshot`. - -#### StMintEntry - -file: `vault_registry.rs` - -- **Purpose**: Represents a supported token mint within the `VaultRegistry`, storing its address and associated voting weight. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct StMintEntry { - /// The supported token ( ST ) mint - st_mint: Pubkey, - // Either a switchboard feed or a weight must be set - /// The switchboard feed for the mint - reserve_switchboard_feed: [u8; 32], - /// The weight - weight: PodU128, -} -``` - -- **Explanation**: Stores the `st_mint` address and its assigned voting `weight`. `reserve_switchboard_feed` is unused here. - -#### VaultEntry - -file: `vault_registry.rs` - -- **Purpose**: Represents a registered vault within the `VaultRegistry`. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct VaultEntry { - /// The vault account - vault: Pubkey, - /// The supported token ( ST ) mint of the vault - st_mint: Pubkey, - /// The index of the vault in respect to the NCN account - vault_index: PodU64, - /// The slot the vault was registered - slot_registered: PodU64, -} -``` - -- **Explanation**: Stores the `vault` address, the `st_mint` it holds, its assigned `vault_index`, and the `slot_registered`. - -#### VaultRegistry - -file: `vault_registry.rs` - -- **Purpose**: A global account for the NCN program instance that maintains the list of all supported token mints (`StMintEntry`) and all registered vaults (`VaultEntry`). -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct VaultRegistry { - /// The Pubkey of the associated NCN account this registry belongs to. - pub ncn: Pubkey, - /// Bump seed for the PDA - pub bump: u8, - /// Array storing entries for each supported token mint - pub st_mint_list: [StMintEntry; MAX_ST_MINTS], - /// Array storing entries for each vault - pub vault_list: [VaultEntry; MAX_VAULTS], -} -``` - -- **Explanation**: Holds the `ncn` identifier, `bump`, and arrays for `st_mint_list` and `vault_list`. - -#### WeightTable - -file: `weight_table.rs` - -- **Purpose**: An epoch-specific account that snapshots the weights of all supported tokens at the beginning of the epoch. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct WeightTable { - /// The Pubkey of the associated NCN account this account is for. - ncn: Pubkey, - /// The epoch this account is for. - epoch: PodU64, - /// Slot when this WeightTable account was created. - slot_created: PodU64, - /// Number of vaults in tracked mints at the time of creation - vault_count: PodU64, - /// Bump seed for the PDA - bump: u8, - /// A snapshot copy of the relevant vault entries from the VaultRegistry - vault_registry: [VaultEntry; MAX_VAULTS], - /// The weight table - table: [WeightEntry; MAX_ST_MINTS], -} -``` - -- **Explanation**: Contains metadata (`ncn`, `epoch`, `slot_created`, `vault_count`), a snapshot of the `vault_registry`, and the main `table` holding `WeightEntry` structs with the frozen weights for the epoch. - -#### EpochAccountStatus - -file: `epoch_state.rs` - -- **Purpose**: A helper struct within `EpochState` used to track the lifecycle status of various temporary accounts associated with a specific epoch. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct EpochAccountStatus { - /// Status of the main EpochState account itself. - epoch_state: u8, - /// Status of the WeightTable account for this epoch. - weight_table: u8, - /// Status of the main EpochSnapshot account for this epoch. - epoch_snapshot: u8, - /// Status array for each individual OperatorSnapshot account. - operator_snapshot: [u8; MAX_OPERATORS], - /// Status of the BallotBox account for this epoch. - ballot_box: u8, -} -``` - -- **Explanation**: Uses `u8` fields to represent the status of various temporary accounts associated with a specific epoch. diff --git a/docs/_restaking/00_restaking_accounts.md b/docs/_restaking/00_restaking_accounts.md deleted file mode 100644 index 96ebfd6f..00000000 --- a/docs/_restaking/00_restaking_accounts.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Restaking Accounts -mermaid: true -category: Jekyll -layout: post ---- - -## 1. About the program - -The restaking program acts as a registry for NCNs, operators, and relationships between NCNs, operators, and vaults. - -It allows users to do the following: - -- Registers NCN, operators, and their configurations. -- Stores relationships between NCN, operators, and vaults. - -The restaking program does not store any funds; it is purely used as a registry and relationship manager between -entities in the system. - -## 2. Diagram - -![Restaking Accounts](/assets/images/restaking_accounts.png) - -## 2. Ncn - -NCN (Node Consensus Network) are services that provide infrastructure to the network, such as validators, oracles, keepers, bridges, L2s, and -other services that require a staking mechanism for security. - -NCN can be registered through the restaking program. - -There are several things one can do after registering an NCN: - -- Add and remove support for operators participating in the NCN operator set. -- Add and remove support for vaults -- Add and remove support for slashers -- Withdraw funds sent to the NCN from rewards, airdrops, and other sources. - -## 3. Operator - -Operators are entities responsible for running NCN software. - -Operators can register through the restaking program and configure several variables: - -- Add and remove support for vaults -- Add and remove support for NCN -- Change voter keys -- Withdraw funds sent to the operator from rewards, airdrops, and other sources. - -## 4.1. NcnOperatorState - -This state represents the mutual opt-in relationship between an NCN and an Operator. The NCN initializes this state. Once created, the NCN and operator can both warm-up and cooldown the state to show support for each other. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - NCN[NCN]:::main - Operator[Operator]:::main - NcnOperatorState[NcnOperatorState]:::ticket - NCN -->|Creates| NcnOperatorState - NCN -.->|Opts in| Operator - Operator -.->|Opts in| NCN - Operator -->|Updates| NcnOperatorState -``` - -## 4.2. NcnVaultTicket - -This ticket represents the relationship between an NCN and a Vault. It is created by the NCN to opt in to work with a Vault. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - NCN[NCN]:::main - Vault[Vault]:::main - NcnVaultTicket[NcnVaultTicket]:::ticket - NCN -->|Creates| NcnVaultTicket - NCN -.->|Opts in| Vault -``` - -## 4.3. OperatorVaultTicket - -This ticket represents the relationship between an Operator and a Vault. It is created by the Operator to opt in to work with a Vault. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - Operator[Operator]:::main - Vault[Vault]:::main - OperatorVaultTicket[OperatorVaultTicket]:::ticket - Operator -->|Creates| OperatorVaultTicket - Operator -.->|Opts in| Vault -``` - -## 4.4. NcnVaultSlasherTicket - -This ticket represents the slashing relationship between an NCN and a Vault. The NCN register slashers. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - Ncn[Ncn]:::main - Vault[Vault]:::main - NcnVaultSlasherTicket[NcnVaultSlasherTicket]:::ticket - Ncn -->|Creates| NcnVaultSlasherTicket - Ncn -.->|Opts in| Vault -``` diff --git a/docs/_terminology/00_terminology.md b/docs/_terminology/00_terminology.md deleted file mode 100644 index 93717816..00000000 --- a/docs/_terminology/00_terminology.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Terminology -category: Jekyll -layout: post -weight: 1 ---- - -- **Node**: A piece of software running the required protocols as specified by the node consensus network. -- **Node Consensus Network (NCN)**: A set of nodes running software for the same network, working together to achieve - consensus and provide services. -- **Operator**: An entity that manages one or more nodes in the node consensus network. -- **Vault**: A smart contract that securely holds staked assets and delegates them to operators. -- **Vault Receipt Token (VRT)**: A tokenized representation of staked assets inside a vault, allowing for liquidity and - composability. -- **Staking**: The process of locking up assets as collateral to support network operations and earn rewards. -- **(Re)staking**: The process of staking already-staked assets, such as liquid staking tokens to participate in - additional networks or services. -- **Slashing**: A penalty mechanism where a portion of staked assets is forfeited if an operator misbehaves or fails to - meet performance requirements. -- **Delegation**: The act of assigning staked assets to specific operators within the network. -- **Multi-Asset Staking**: The ability to stake various types of SPL tokens, not limited to native blockchain tokens. -- **Liquid Staking**: A form of staking that provides liquidity to Solana stakers. -- **SPL Token**: Solana Program Library Token, a standard for creating and managing tokens on the Solana blockchain. -- **Epoch**: A fixed period in the blockchain during which staking rewards are calculated and distributed. diff --git a/docs/_tools/00_cli.md b/docs/_tools/00_cli.md deleted file mode 100644 index 02591695..00000000 --- a/docs/_tools/00_cli.md +++ /dev/null @@ -1,1078 +0,0 @@ ---- -title: CLI -category: Jekyll -layout: post -weight: 1 ---- - -# Command-Line Help for `jito-restaking-cli` - -This document contains the help content for the `jito-restaking-cli` command-line program. - -## `jito-restaking-cli` - -A CLI for managing restaking and vault operations - -**Usage:** `jito-restaking-cli [OPTIONS] [COMMAND]` - -###### **Subcommands:** - -* `restaking` — Restaking program commands -* `vault` — Vault program commands - -###### **Options:** - -* `--config-file ` — Path to the configuration file -* `--rpc-url ` — RPC URL to use -* `--commitment ` — Commitment level -* `--restaking-program-id ` — Restaking program ID -* `--vault-program-id ` — Vault program ID -* `--signer ` — Filepath or URL to a keypair -* `--verbose` — Verbose mode -* `--print-tx` — This will print out the raw TX instead of running it - - Default value: `false` -* `--print-json` — This will print out account information in JSON format - - Default value: `false` -* `--print-json-with-reserves` — This will print out account information in JSON format with reserved space - - Default value: `false` - - - -## `jito-restaking-cli restaking` - -Restaking program commands - -**Usage:** `jito-restaking-cli restaking ` - -###### **Subcommands:** - -* `config` — Initialize, get, and set the config struct -* `ncn` — -* `operator` — - - - -## `jito-restaking-cli restaking config` - -Initialize, get, and set the config struct - -**Usage:** `jito-restaking-cli restaking config ` - -###### **Subcommands:** - -* `initialize` — Initialize the config -* `get` — Get the config -* `set-admin` — Set the config admin - - - -## `jito-restaking-cli restaking config initialize` - -Initialize the config - -**Usage:** `jito-restaking-cli restaking config initialize` - - - -## `jito-restaking-cli restaking config get` - -Get the config - -**Usage:** `jito-restaking-cli restaking config get` - - - -## `jito-restaking-cli restaking config set-admin` - -Set the config admin - -**Usage:** `jito-restaking-cli restaking config set-admin ` - -###### **Arguments:** - -* `` — The new admin's pubkey - - - -## `jito-restaking-cli restaking ncn` - -**Usage:** `jito-restaking-cli restaking ncn ` - -###### **Subcommands:** - -* `initialize` — Initialize NCN -* `initialize-ncn-operator-state` — Initialize NCN Operator State -* `ncn-warmup-operator` — Warmup NCN Operator State -* `ncn-cooldown-operator` — NCN Cooldown Operator State -* `initialize-ncn-vault-ticket` — Initialize NCN Vault Ticket -* `warmup-ncn-vault-ticket` — Warmup NCN Vault Ticket -* `cooldown-ncn-vault-ticket` — Cooldown NCN Vault Ticket -* `ncn-delegate-token-account` — NCN Delegate Token Account -* `get` — Get NCN -* `list` — List all NCNs -* `list-ncn-operator-state` — List All Ncn Operator State for a NCN -* `list-ncn-vault-ticket` — List All Ncn Vault Ticket for a NCN -* `ncn-set-admin` — Set NCN Admin -* `ncn-set-secondary-admin` — Set NCN Secondary Admin - - - -## `jito-restaking-cli restaking ncn initialize` - -Initialize NCN - -**Usage:** `jito-restaking-cli restaking ncn initialize [OPTIONS]` - -###### **Options:** - -* `--path-to-base-keypair ` - - - -## `jito-restaking-cli restaking ncn initialize-ncn-operator-state` - -Initialize NCN Operator State - -**Usage:** `jito-restaking-cli restaking ncn initialize-ncn-operator-state ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking ncn ncn-warmup-operator` - -Warmup NCN Operator State - -**Usage:** `jito-restaking-cli restaking ncn ncn-warmup-operator ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking ncn ncn-cooldown-operator` - -NCN Cooldown Operator State - -**Usage:** `jito-restaking-cli restaking ncn ncn-cooldown-operator ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking ncn initialize-ncn-vault-ticket` - -Initialize NCN Vault Ticket - -**Usage:** `jito-restaking-cli restaking ncn initialize-ncn-vault-ticket ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking ncn warmup-ncn-vault-ticket` - -Warmup NCN Vault Ticket - -**Usage:** `jito-restaking-cli restaking ncn warmup-ncn-vault-ticket ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking ncn cooldown-ncn-vault-ticket` - -Cooldown NCN Vault Ticket - -**Usage:** `jito-restaking-cli restaking ncn cooldown-ncn-vault-ticket ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking ncn ncn-delegate-token-account` - -NCN Delegate Token Account - -**Usage:** `jito-restaking-cli restaking ncn ncn-delegate-token-account [OPTIONS] ` - -###### **Arguments:** - -* `` -* `` -* `` - -###### **Options:** - -* `--should-create-token-account` - - - -## `jito-restaking-cli restaking ncn get` - -Get NCN - -**Usage:** `jito-restaking-cli restaking ncn get ` - -###### **Arguments:** - -* `` - - - -## `jito-restaking-cli restaking ncn list` - -List all NCNs - -**Usage:** `jito-restaking-cli restaking ncn list` - - - -## `jito-restaking-cli restaking ncn list-ncn-operator-state` - -List All Ncn Operator State for a NCN - -**Usage:** `jito-restaking-cli restaking ncn list-ncn-operator-state ` - -###### **Arguments:** - -* `` - - - -## `jito-restaking-cli restaking ncn list-ncn-vault-ticket` - -List All Ncn Vault Ticket for a NCN - -**Usage:** `jito-restaking-cli restaking ncn list-ncn-vault-ticket ` - -###### **Arguments:** - -* `` - - - -## `jito-restaking-cli restaking ncn ncn-set-admin` - -Set NCN Admin - -**Usage:** `jito-restaking-cli restaking ncn ncn-set-admin --old-admin-keypair --new-admin-keypair ` - -###### **Arguments:** - -* `` — The NCN pubkey - -###### **Options:** - -* `--old-admin-keypair ` — Filepath or URL to a keypair of old admin -* `--new-admin-keypair ` — Filepath or URL to a keypair of new admin - - - -## `jito-restaking-cli restaking ncn ncn-set-secondary-admin` - -Set NCN Secondary Admin - -**Usage:** `jito-restaking-cli restaking ncn ncn-set-secondary-admin [OPTIONS] ` - -###### **Arguments:** - -* `` — The NCN pubkey -* `` — The new admin pubkey - -###### **Options:** - -* `--set-operator-admin` — Set operator_admin -* `--set-vault-admin` — Set vault_admin -* `--set-slasher-admin` — Set slasher_admin -* `--set-delegate-admin` — Set delegate_admin -* `--set-metadata-admin` — Set metadata_admin -* `--set-weight-table-admin` — Set weight_table_admin -* `--set-ncn-program-admin` — Set ncn_program_admin - - - -## `jito-restaking-cli restaking operator` - -**Usage:** `jito-restaking-cli restaking operator ` - -###### **Subcommands:** - -* `initialize` — Initialize Operator -* `initialize-operator-vault-ticket` — Initialize Operator Vault Ticket -* `warmup-operator-vault-ticket` — Warmup Operator Vault Ticket -* `cooldown-operator-vault-ticket` — Cooldown Operator Vault Ticket -* `operator-warmup-ncn` — Operator Warmup NCN -* `operator-cooldown-ncn` — Operator Cooldown NCN -* `operator-set-admin` — Operator Set Admin -* `operator-set-secondary-admin` — Operator Set Secondary Admin -* `operator-set-fees` — Sets the operator fee -* `operator-delegate-token-account` — Operator Delegate Token Account -* `get` — Get operator -* `list` — List all operators -* `list-operator-vault-ticket` — List Operator Vault Ticket for an Operator -* `list-ncn-operator-state` — List All Ncn Operator State for a Operator - - - -## `jito-restaking-cli restaking operator initialize` - -Initialize Operator - -**Usage:** `jito-restaking-cli restaking operator initialize ` - -###### **Arguments:** - -* `` - - - -## `jito-restaking-cli restaking operator initialize-operator-vault-ticket` - -Initialize Operator Vault Ticket - -**Usage:** `jito-restaking-cli restaking operator initialize-operator-vault-ticket ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking operator warmup-operator-vault-ticket` - -Warmup Operator Vault Ticket - -**Usage:** `jito-restaking-cli restaking operator warmup-operator-vault-ticket ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking operator cooldown-operator-vault-ticket` - -Cooldown Operator Vault Ticket - -**Usage:** `jito-restaking-cli restaking operator cooldown-operator-vault-ticket ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking operator operator-warmup-ncn` - -Operator Warmup NCN - -**Usage:** `jito-restaking-cli restaking operator operator-warmup-ncn ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking operator operator-cooldown-ncn` - -Operator Cooldown NCN - -**Usage:** `jito-restaking-cli restaking operator operator-cooldown-ncn ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking operator operator-set-admin` - -Operator Set Admin - -**Usage:** `jito-restaking-cli restaking operator operator-set-admin --old-admin-keypair --new-admin-keypair ` - -###### **Arguments:** - -* `` — The Operator pubkey - -###### **Options:** - -* `--old-admin-keypair ` — Filepath or URL to a keypair of old admin -* `--new-admin-keypair ` — Filepath or URL to a keypair of new admin - - - -## `jito-restaking-cli restaking operator operator-set-secondary-admin` - -Operator Set Secondary Admin - -**Usage:** `jito-restaking-cli restaking operator operator-set-secondary-admin [OPTIONS] ` - -###### **Arguments:** - -* `` -* `` - -###### **Options:** - -* `--set-ncn-admin` -* `--set-vault-admin` -* `--set-voter-admin` -* `--set-delegate-admin` -* `--set-metadata-admin` - - - -## `jito-restaking-cli restaking operator operator-set-fees` - -Sets the operator fee - -**Usage:** `jito-restaking-cli restaking operator operator-set-fees ` - -###### **Arguments:** - -* `` -* `` - - - -## `jito-restaking-cli restaking operator operator-delegate-token-account` - -Operator Delegate Token Account - -**Usage:** `jito-restaking-cli restaking operator operator-delegate-token-account [OPTIONS] ` - -###### **Arguments:** - -* `` -* `` -* `` - -###### **Options:** - -* `--should-create-token-account` - - - -## `jito-restaking-cli restaking operator get` - -Get operator - -**Usage:** `jito-restaking-cli restaking operator get ` - -###### **Arguments:** - -* `` - - - -## `jito-restaking-cli restaking operator list` - -List all operators - -**Usage:** `jito-restaking-cli restaking operator list` - - - -## `jito-restaking-cli restaking operator list-operator-vault-ticket` - -List Operator Vault Ticket for an Operator - -**Usage:** `jito-restaking-cli restaking operator list-operator-vault-ticket ` - -###### **Arguments:** - -* `` - - - -## `jito-restaking-cli restaking operator list-ncn-operator-state` - -List All Ncn Operator State for a Operator - -**Usage:** `jito-restaking-cli restaking operator list-ncn-operator-state ` - -###### **Arguments:** - -* `` - - - -## `jito-restaking-cli vault` - -Vault program commands - -**Usage:** `jito-restaking-cli vault ` - -###### **Subcommands:** - -* `config` — -* `vault` — Vault commands - - - -## `jito-restaking-cli vault config` - -**Usage:** `jito-restaking-cli vault config ` - -###### **Subcommands:** - -* `initialize` — Creates global config (can only be done once) -* `get` — Fetches global config -* `set-admin` — Set the config admin -* `set-program-fee` — Set the program fee -* `set-program-fee-wallet` — Set the program fee wallet - - - -## `jito-restaking-cli vault config initialize` - -Creates global config (can only be done once) - -**Usage:** `jito-restaking-cli vault config initialize ` - -###### **Arguments:** - -* `` — The program fee in basis points -* `` — The program fee wallet pubkey - - - -## `jito-restaking-cli vault config get` - -Fetches global config - -**Usage:** `jito-restaking-cli vault config get` - - - -## `jito-restaking-cli vault config set-admin` - -Set the config admin - -**Usage:** `jito-restaking-cli vault config set-admin ` - -###### **Arguments:** - -* `` — The new admin's pubkey - - - -## `jito-restaking-cli vault config set-program-fee` - -Set the program fee - -**Usage:** `jito-restaking-cli vault config set-program-fee ` - -###### **Arguments:** - -* `` — The program fee - - - -## `jito-restaking-cli vault config set-program-fee-wallet` - -Set the program fee wallet - -**Usage:** `jito-restaking-cli vault config set-program-fee-wallet ` - -###### **Arguments:** - -* `` — The program fee wallet - - - -## `jito-restaking-cli vault vault` - -Vault commands - -**Usage:** `jito-restaking-cli vault vault ` - -###### **Subcommands:** - -* `initialize` — Creates a new vault -* `create-token-metadata` — Creates token metadata for the vault's LRT token -* `update-token-metadata` — -* `initialize-vault-update-state-tracker` — Starts the vault update cycle -* `crank-vault-update-state-tracker` — Cranks the vault update state tracker, needs to be run per operator -* `close-vault-update-state-tracker` — Ends the vault update cycle -* `mint-vrt` — Mints VRT tokens -* `initialize-operator-delegation` — Sets up the delegations for an operator -* `delegate-to-operator` — Delegates tokens to an operator -* `cooldown-operator-delegation` — Cooldown delegation for an operator -* `initialize-vault-ncn-ticket` — Initialize Vault NCN Ticket -* `warmup-vault-ncn-ticket` — Warmup Vault NCN Ticket -* `cooldown-vault-ncn-ticket` — Cooldown Vault NCN Ticket -* `enqueue-withdrawal` — Starts the withdrawal process -* `change-withdrawal-ticket-owner` — Change withdrawal ticket owner -* `burn-withdrawal-ticket` — Burns the withdrawal ticket, ending the withdrawal process -* `get-vault-update-state-tracker` — Gets the update state tracker for a vault -* `get-operator-delegations` — Gets the operator delegations for a vault -* `get-operator-delegation` — Gets the operator delegation for a vault -* `get-withdrawal-ticket` — -* `get` — Gets a vault -* `list` — List all vaults -* `set-admin` — Set Admin -* `set-capacity` — Sets the deposit capacity in the vault -* `set-fees` — Sets the fees in the vault -* `set-is-paused` — Sets the vault's pause state -* `set-secondary-admin` — Set Secondary Admin -* `update-vault-balance` — Update Vault Balance -* `delegate-token-account` — Delegate a token account -* `delegated-token-transfer` — Transfer a token account - - - -## `jito-restaking-cli vault vault initialize` - -Creates a new vault - -**Usage:** `jito-restaking-cli vault vault initialize [VRT_MINT_ADDRESS_FILE_PATH]` - -###### **Arguments:** - -* `` — The token which is allowed to be deposited into the vault -* `` — The deposit fee in bips -* `` — The withdrawal fee in bips -* `` — The reward fee in bips -* `` — The decimals of the token -* `` — The amount of tokens to initialize the vault with ( in the smallest unit ) -* `` — The file path of VRT mint address - - - -## `jito-restaking-cli vault vault create-token-metadata` - -Creates token metadata for the vault's LRT token - -**Usage:** `jito-restaking-cli vault vault create-token-metadata ` - -###### **Arguments:** - -* `` — The vault pubkey -* `` — The name of the token -* `` — The symbol of the token -* `` — The URI for the token metadata - - - -## `jito-restaking-cli vault vault update-token-metadata` - -**Usage:** `jito-restaking-cli vault vault update-token-metadata ` - -###### **Arguments:** - -* `` — The vault pubkey -* `` — The name of the token -* `` — The symbol of the token -* `` — The URI for the token metadata - - - -## `jito-restaking-cli vault vault initialize-vault-update-state-tracker` - -Starts the vault update cycle - -**Usage:** `jito-restaking-cli vault vault initialize-vault-update-state-tracker ` - -###### **Arguments:** - -* `` — Vault account - - - -## `jito-restaking-cli vault vault crank-vault-update-state-tracker` - -Cranks the vault update state tracker, needs to be run per operator - -**Usage:** `jito-restaking-cli vault vault crank-vault-update-state-tracker ` - -###### **Arguments:** - -* `` — Vault account -* `` — Operator account - - - -## `jito-restaking-cli vault vault close-vault-update-state-tracker` - -Ends the vault update cycle - -**Usage:** `jito-restaking-cli vault vault close-vault-update-state-tracker [NCN_EPOCH]` - -###### **Arguments:** - -* `` — Vault account -* `` — Optional NCN epoch to close - - - -## `jito-restaking-cli vault vault mint-vrt` - -Mints VRT tokens - -**Usage:** `jito-restaking-cli vault vault mint-vrt ` - -###### **Arguments:** - -* `` — Vault account -* `` — Amount to deposit -* `` — Minimum amount of VRT to mint - - - -## `jito-restaking-cli vault vault initialize-operator-delegation` - -Sets up the delegations for an operator - -**Usage:** `jito-restaking-cli vault vault initialize-operator-delegation ` - -###### **Arguments:** - -* `` — Vault account -* `` — Operator account - - - -## `jito-restaking-cli vault vault delegate-to-operator` - -Delegates tokens to an operator - -**Usage:** `jito-restaking-cli vault vault delegate-to-operator ` - -###### **Arguments:** - -* `` — Vault account -* `` — Operator account -* `` — Amount to delegate - - - -## `jito-restaking-cli vault vault cooldown-operator-delegation` - -Cooldown delegation for an operator - -**Usage:** `jito-restaking-cli vault vault cooldown-operator-delegation ` - -###### **Arguments:** - -* `` — Vault account -* `` — Operator account -* `` — Amount to cooldown - - - -## `jito-restaking-cli vault vault initialize-vault-ncn-ticket` - -Initialize Vault NCN Ticket - -**Usage:** `jito-restaking-cli vault vault initialize-vault-ncn-ticket ` - -###### **Arguments:** - -* `` — Vault account -* `` — NCN account - - - -## `jito-restaking-cli vault vault warmup-vault-ncn-ticket` - -Warmup Vault NCN Ticket - -**Usage:** `jito-restaking-cli vault vault warmup-vault-ncn-ticket ` - -###### **Arguments:** - -* `` — Vault account -* `` — NCN account - - - -## `jito-restaking-cli vault vault cooldown-vault-ncn-ticket` - -Cooldown Vault NCN Ticket - -**Usage:** `jito-restaking-cli vault vault cooldown-vault-ncn-ticket ` - -###### **Arguments:** - -* `` — Vault account -* `` — NCN account - - - -## `jito-restaking-cli vault vault enqueue-withdrawal` - -Starts the withdrawal process - -**Usage:** `jito-restaking-cli vault vault enqueue-withdrawal ` - -###### **Arguments:** - -* `` — Vault account -* `` — Amount to withdraw - - - -## `jito-restaking-cli vault vault change-withdrawal-ticket-owner` - -Change withdrawal ticket owner - -**Usage:** `jito-restaking-cli vault vault change-withdrawal-ticket-owner --old-ticket-owner-keypair ` - -###### **Arguments:** - -* `` — The vault pubkey -* `` — The new ticket owner pubkey - -###### **Options:** - -* `--old-ticket-owner-keypair ` — The old ticket owner keypair - - - -## `jito-restaking-cli vault vault burn-withdrawal-ticket` - -Burns the withdrawal ticket, ending the withdrawal process - -**Usage:** `jito-restaking-cli vault vault burn-withdrawal-ticket ` - -###### **Arguments:** - -* `` — Vault account - - - -## `jito-restaking-cli vault vault get-vault-update-state-tracker` - -Gets the update state tracker for a vault - -**Usage:** `jito-restaking-cli vault vault get-vault-update-state-tracker ` - -###### **Arguments:** - -* `` — Vault account - - - -## `jito-restaking-cli vault vault get-operator-delegations` - -Gets the operator delegations for a vault - -**Usage:** `jito-restaking-cli vault vault get-operator-delegations ` - -###### **Arguments:** - -* `` — Vault account - - - -## `jito-restaking-cli vault vault get-operator-delegation` - -Gets the operator delegation for a vault - -**Usage:** `jito-restaking-cli vault vault get-operator-delegation ` - -###### **Arguments:** - -* `` — Vault account -* `` — Operator account - - - -## `jito-restaking-cli vault vault get-withdrawal-ticket` - -**Usage:** `jito-restaking-cli vault vault get-withdrawal-ticket [STAKER]` - -###### **Arguments:** - -* `` — Vault account -* `` — Staker account - - - -## `jito-restaking-cli vault vault get` - -Gets a vault - -**Usage:** `jito-restaking-cli vault vault get ` - -###### **Arguments:** - -* `` — The vault pubkey - - - -## `jito-restaking-cli vault vault list` - -List all vaults - -**Usage:** `jito-restaking-cli vault vault list` - - - -## `jito-restaking-cli vault vault set-admin` - -Set Admin - -**Usage:** `jito-restaking-cli vault vault set-admin --old-admin-keypair --new-admin-keypair ` - -###### **Arguments:** - -* `` — The Vault pubkey - -###### **Options:** - -* `--old-admin-keypair ` — Filepath or URL to a keypair of old admin -* `--new-admin-keypair ` — Filepath or URL to a keypair of new admin - - - -## `jito-restaking-cli vault vault set-capacity` - -Sets the deposit capacity in the vault - -**Usage:** `jito-restaking-cli vault vault set-capacity ` - -###### **Arguments:** - -* `` — The vault pubkey -* `` — The new capacity - - - -## `jito-restaking-cli vault vault set-fees` - -Sets the fees in the vault - -**Usage:** `jito-restaking-cli vault vault set-fees [OPTIONS] ` - -###### **Arguments:** - -* `` — The vault pubkey - -###### **Options:** - -* `--deposit-fee-bps ` — The deposit fee BPS -* `--withdrawal-fee-bps ` — The withdrawal fee BPS -* `--reward-fee-bps ` — The reward fee BPS - - - -## `jito-restaking-cli vault vault set-is-paused` - -Sets the vault's pause state - -**Usage:** `jito-restaking-cli vault vault set-is-paused [OPTIONS] ` - -###### **Arguments:** - -* `` — The vault pubkey - -###### **Options:** - -* `--set-pause` — Set pause - - - -## `jito-restaking-cli vault vault set-secondary-admin` - -Set Secondary Admin - -**Usage:** `jito-restaking-cli vault vault set-secondary-admin [OPTIONS] ` - -###### **Arguments:** - -* `` — The vault pubkey -* `` — The new admin pubkey - -###### **Options:** - -* `--set-delegation-admin` — Set delegation_admin -* `--set-operator-admin` — Set operator_admin -* `--set-ncn-admin` — Set ncn_admin -* `--set-slasher-admin` — Set slasher_admin -* `--set-capacity-admin` — Set capacity_admin -* `--set-fee-wallet` — Set fee_wallet -* `--set-mint-burn-admin` — Set mint_burn_admin -* `--set-delegate-asset-admin` — Set delegate_asset_admin -* `--set-fee-admin` — Set fee_admin -* `--set-metadata-admin` — Set metadata_admin - - - -## `jito-restaking-cli vault vault update-vault-balance` - -Update Vault Balance - -**Usage:** `jito-restaking-cli vault vault update-vault-balance ` - -###### **Arguments:** - -* `` — The vault pubkey - - - -## `jito-restaking-cli vault vault delegate-token-account` - -Delegate a token account - -**Usage:** `jito-restaking-cli vault vault delegate-token-account ` - -###### **Arguments:** - -* `` — The vault pubkey -* `` — The delegate account -* `` — The token mint -* `` — The token account - - - -## `jito-restaking-cli vault vault delegated-token-transfer` - -Transfer a token account - -**Usage:** `jito-restaking-cli vault vault delegated-token-transfer ` - -###### **Arguments:** - -* `` — The token account -* `` — The recipient pubkey -* `` — The amount to transfer - - - -
- - - This document was generated automatically by - clap-markdown. - - diff --git a/docs/_vault/00_vault_accounts.md b/docs/_vault/00_vault_accounts.md deleted file mode 100644 index d4f7c6a6..00000000 --- a/docs/_vault/00_vault_accounts.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -title: Vault Accounts -mermaid: true -category: Jekyll -layout: post -weight: 1 ---- - -## 1. About the program - -The vault program manages the vault receipt tokens (VRTs) and associated deposits. The program stores deposited funds and handles the minting and burning of tokenized stake. It also manages the vault's stake in an NCN, including delegation. - -## 2. Diagram - -![Vault Accounts](/assets/images/vault_accounts.png) - -## 3. Accounts - -All accounts for the vault program are defined in the [jito-vault-core](https://github.com/jito-foundation/restaking/tree/master/vault_core/src) crate. - -### 3.1. Config - -The configuration account is a global account that is used to configure the vault program. It is used to set the restaking program and other program-wide settings. It also keeps track of the number of vaults, fee caps, and other program-wide settings. The number of vaults is used so programs can programmatically iterate through all vaults in the program. - -### 3.2. Vault - -The Vault account is a central component of the vault program, responsible for managing token deposits, VRT minting and burning, and delegation states. It contains several administrative pubkeys and other vault-wide settings. - -### 3.3. VaultNcnTicket - -- VaultNcnTicket is created by the vault to signify support (or lack of) for a given NCN. -- VaultNcnTicket is activated through a warmup process. -- VaultNcnTicket is deactivated through a cooldown process. -- VaultNcnTicket is used to track the state of the vault's support for a given NCN. -- Only the Vault ncn_admin pubkey can modify the VaultNcnTicket account. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - Vault[Vault]:::main - Ncn[Ncn]:::main - VaultNcnTicket[VaultNcnTicket]:::ticket - Vault -->|Creates| VaultNcnTicket - Vault -.->|Opts in| Ncn -``` - -### 3.4. VaultOperatorDelegation - -- VaultOperatorDelegation account is created by the vault to signify that the vault has delegated its stake to a given operator. -- Only the Vault operator_admin pubkey can create the VaultOperatorDelegation account. -- The Vault delegation_admin pubkey can modify the VaultOperatorDelegation account. -- The VaultOperatorDelegation account is used to track the state of the vault's delegation to a given operator. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - Vault[Vault]:::main - Operator[Operator]:::main - VaultOperatorDelegation[VaultOperatorDelegation]:::ticket - Vault -->|Creates| VaultOperatorDelegation - Vault -.->|Opts in| Operator -``` - -### 3.5. VaultNcnSlasherTicket - -- VaultNcnSlasherTicket is created by the vault to signify that the vault has opted-in to a given slasher. -- Only the Vault ncn_admin pubkey can modify the VaultNcnSlasherTicket account. -- The VaultNcnSlasherTicket account is used to track the state of the vault's opt-in to a given slasher. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - Vault[Vault]:::main - NcnVaultSlasherTicket[NcnVaultSlasherTicket]:::ticket - Vault -->|Creates| VaultNcnSlasherTicket - Vault -.->|Recognizes and copies from| NcnVaultSlasherTicket -``` - -### 3.6. VaultNcnSlasherOperatorTicket - -- VaultNcnSlasherOperatorTicket is created by the vault to track slashings for a given vault, ncn, slasher, operator, epoch pair. - -```mermaid -graph TD - classDef main fill: #f9f, stroke: #333, stroke-width: 2px; - classDef ticket fill: #fff, stroke: #333, stroke-width: 1px; - Vault[Vault]:::main - Ncn[Ncn]:::main - Slasher[Slasher]:::main - Operator[Operator]:::main - VaultNcnSlasherOperatorTicket[VaultNcnSlasherOperatorTicket]:::ticket - Vault -->|Creates| VaultNcnSlasherOperatorTicket - Vault -.->|Tracks slashing for| Ncn - Vault -.->|Tracks slashing by| Slasher - Vault -.->|Tracks slashing of| Operator -``` - -### 3.7. VaultStakerWithdrawalTicket - -- VaultStakerWithdrawalTicket is created by the vault to track the withdrawal of a given staker. -- Stakers create the VaultStakerWithdrawalTicket account when they initiate a withdrawal. - -### 3.8. VaultUpdateStateTracker - -- VaultUpdateStateTracker is created by the vault during epoch updates to aggregate stake information of the `VaultOperatorDelegation` accounts. - -## 4. Tracking State - -State in these programs is spread out across many accounts. -To reason about the state of stake at any given time, one can reference the chart below. - -Assets are considered staked iff: - -- The NCN has opted-in to the operator -- The operator has opted-in to the NCN -- The operator has opted-in to the vault -- The vault has opted-in to the operator -- The vault has opted-in to the NCN -- The NCN has opted-in to the vault -- The Vault is delegated to that operator - -![img.png](/assets/images/staked_venn_diagram.png) diff --git a/docs/assets/images/ncn.png b/docs/assets/images/ncn.png deleted file mode 100644 index 580244a7..00000000 Binary files a/docs/assets/images/ncn.png and /dev/null differ diff --git a/docs/assets/images/opt_in.png b/docs/assets/images/opt_in.png deleted file mode 100644 index c22063f2..00000000 Binary files a/docs/assets/images/opt_in.png and /dev/null differ diff --git a/docs/assets/images/restaking_accounts.png b/docs/assets/images/restaking_accounts.png deleted file mode 100644 index f58e2f68..00000000 Binary files a/docs/assets/images/restaking_accounts.png and /dev/null differ diff --git a/docs/assets/images/staked_venn_diagram.png b/docs/assets/images/staked_venn_diagram.png deleted file mode 100644 index c83cfc35..00000000 Binary files a/docs/assets/images/staked_venn_diagram.png and /dev/null differ diff --git a/docs/assets/images/vault_accounts.png b/docs/assets/images/vault_accounts.png deleted file mode 100644 index ade299ec..00000000 Binary files a/docs/assets/images/vault_accounts.png and /dev/null differ diff --git a/docs/assets/images/vault_interactions.png b/docs/assets/images/vault_interactions.png deleted file mode 100644 index fa533cfc..00000000 Binary files a/docs/assets/images/vault_interactions.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md index 8df38605..8bcd51b2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,210 +1,5 @@ ---- -title: Jito (Re)Staking Overview -category: Jekyll -layout: post -weight: 1 ---- +# Restaking Documentation Moved -**Jito (Re)Staking** is a multi-asset staking protocol for node consensus networks (NCNs). It provides a universal framework for staking SPL tokens to decentralized systems on Solana, with flexibility baked in at every level. +The Restaking documentation has been moved to the [Jito Omnidocs](https://github.com/jito-foundation/jito-omnidocs/tree/master/restaking). -The system consists of two programs: the **Restaking Program**, which acts as an onchain registry for NCNs and Operators, and the **Vault Program**, which manages tokenized stake through Vault Receipt Tokens (VRTs). Both of these programs use flexible admin models. NCNs, Operators, and Vaults can define who they interact with and under what conditions. - -At the core of the protocol are 3 roles: NCNs, Vaults, and Operators. Developers register NCNs with custom rules. The Operator(s) performs (perform) arbitrary logic defined by the NCN, whether that’s processing data, running models, or verifying offchain inputs. The vaults hold staked SPL tokens and delegate them to Operators. The Restaking program and the Vault Program act as the coordination layer, managing delegation and execution across the system. - -![img.png](/assets/images/ncn.png) - -### Onchain Coordination, Offchain Execution - -Jito (Re)Staking is an onchain registry of services, stakers, and node operators built on Solana. Its core design separates execution and coordination: services run offchain, while consensus and enforcement happen onchain. - -- All core activity (computation, validation, data collection) happens offchain. -- All coordination (stake delegation, voting, rewards) is tracked, maintained, and enforced onchain. - -This split enables scalability and flexibility for developers while retaining cryptoeconomic guarantees from Solana’s base layer. It makes it easier to bootstrap decentralized networks of economically aligned operators and stakers, without building infrastructure from scratch or relying on high emissions. Effectively, this model creates a more efficient and cost effective security model (For example, one set of staked tokens can secure multiple services) and unlocks more effective capital allocation toward core development. - -## Core Components - -### Node Consensus Network (NCN) - -An NCN is a decentralized service or network that reaches onchain consensus on offchain data or workloads. This may include oracles, DePIN services, bridges, co-processors, or new chains. In most cases, NCNs will also include their own custom onchain programs to define consensus logic, handle submissions, or verify work. Jito (Re)Staking comprises two different on-chain programs. The restaking program (`RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q`) handles relations between Operators and NCNs, and the Vault Program (`Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8`) that facilitates staking and delegating assets to make the NCNs function. - -### Vaults - -Vaults serve as deposit pools that hold staked tokens (e.g. JitoSOL) and issue vault receipt tokens (VRTs) representing those positions. Vaults opt into supporting specific NCNs and delegate stake to approved operators. Users retain ownership of their stake via VRTs. - -Each vault defines key parameters, including how much stake is allocated to each node operator. - -### Operators - -Operators are infrastructure providers that run the offchain services for NCNs and are delegated stake. They opt in to serve specific NCNs and receive stake from approved vaults. Operators have no direct control of the stake and they are usually rewarded in proportion to the stake they have. Operators can serve multiple NCNs simultaneously, enabling efficient resource usage and shared security. - -### The Opt-In Handshake - -Participation in Jito (Re)Staking is governed on-chain by mutual consent: - -1. NCNs register onchain and approve operators and vaults to participate in their network -2. Operators opt in to NCNs and must be accepted. -3. Vaults opt in to NCNs and delegate to approved operators. - -This handshake guarantees that vaults, operators, or NCNs are not forced into any connection. All actively staked links are explicitly approved, creating a modular and flexible system for stake delegation and service coordination. - -### Why Jito (Re)Staking Matters - -We built the Jito (Re)Staking protocol because there are aspects of Jito Network that can benefit from incremental decentralization. And as the Solana ecosystem continues to mature, we expect other developers will eventually transition to prioritizing resiliency over product iteration speed and seek to build custom decentralized solutions that fit the needs of their protocol. The primary benefits of using the restaking protocol to bootstrap decentralized protocol include: - -- **Wide Distribution**: Jito Network is deeply integrated with the Solana ecosystem, including JitoSOL (the largest stakepool) and some of the most established, high performant operators. NCNs can immediately tap into Jito’s network effects without having to kickstart their own operator set or attract native stake from scratch. This means NCNs don’t need to leverage native tokens to emit high inflationary rewards or lock staked tokens to bootstrap stakers or operators. Instead, by registering with the Jito (Re)Staking framework, NCNs can tap into existing stakers, token holders, operators, and vaults. - -- **Capital efficiency**: The same stake can secure multiple services. The same operators can operate multiple services. -- **Aligned incentives**: Stakers, operators, and NCN developers all benefit from performance, transparency, and modular security. - -Jito (Re)Staking greatly reduces the friction to launch, or transition existing services into, decentralized protocols with proof of stake security rooted on Solana. - -## Key Roles and Responsibilities - -This section focuses on the organizational roles behind the system. Each persona (whether they’re launching a network, managing capital, running infrastructure, or providing stake) has clearly defined administrative capabilities and responsibilities. This alignment is central to how Jito (Re)Staking ensures trust and coordination in a modular, multi-party environment. - -### NCN Admin - -The NCN admin is the team or entity launching and managing the NCN. This could be a protocol team, research group, DAO, or company. - -**Key responsibilities:** - -- Register the NCN account, deploy the NCN program(s) including defining consensus, and configure the parameters of the NCN (e.g. accepted tokens and operator quorum) -- Approve or remove Operators that serve the network -- Launch and finalize epochs (start/close voting periods) -- Monitors results, sets fees and manages relationships between operators and vaults -- Define slashing logic including slashable behavior and subsequent penalties - -This role is active and ongoing. Admins aren’t just deployers. They’re stewards of the network’s operation, performance, consensus, and upgrades. - -### Vault Admin - -Vault admins control how users' stake is allocated across NCNs. They manage the vault configuration and oversee delegations. Vaults may be admin-controlled or governed by token holders (e.g. via DAO voting). - -**Key responsibilities:** - -- Create and register Vaults that support specific SPL tokens -- Opt into selected NCNs and operators and define allocation parameters -- Delegate stake to approved Operators -- Process reward distributions -- Manage warmup/cooldown periods for stake activation and withdrawal - -Vault admins allocate the capital. Their decisions influence which NCNs receive economic security and which Operators are trusted with stake. - -### Operators - -Operators, such as existing Solana validators, run nodes that opt in to run NCN-specific offchain workloads. They are rewarded based on performance which can include uptime, correctness, and participation, subject to the NCN. On top of this, they are penalized for underperformance or misconduct Penalties may include losing stake delegations or connections or being slashed. - -**Key responsibilities:** - -- Opt in to serve one or more NCNs -- Accept stake from Vaults and remain compliant to avoid slashing -- Run offchain services as specified by each NCN (e.g. compute, validate, read data) -- Stay online and responsive throughout each consensus cycle -- Participate in voting and contribute to onchain consensus - -Operators form the execution layer of the network. Because they receive stake from Vaults and are rewarded on a stake-weighted basis, they are economically incentivized to perform correctly and continuously. - -### Stakers - -Stakers are users who deposit JitoSOL and other tokens into Vaults to secure NCNs and earn yield. Their capital is the backbone for NCNs because it provides economic security i.e. an economic incentive to follow protocol. - -**Key responsibilities:** - -- Choose which Vault(s) to deposit stake, based on supported NCNs and risk preferences -- Receive Vault receipt tokens (VRTs) representing their positions -- Earn native staking rewards plus additional restaking yield -- Participate in governance (if the Vault allows it) to help steer delegation choices - -Stakers are the foundation of restaking, they choose where to place their economic security and their deposits give Vaults the power to delegate that economic security to NCNs. - -## Lifecycle of a Node Consensus Network (NCN) - -Once an NCN is initialized, it operates in continuous cycles which are coordinated across three distinct phases: setup, operation, and maintenance. This lifecycle involves initializing the network and its participants, running its offchain tasks, enforcing onchain accountability, and evolving over time based on the demands of the NCN. Each phase involves actions by multiple parties: NCN admins, vault controllers, and node operators. - -### Setup Phase: Building the Foundation - -The setup phase establishes the NCN’s identity, rules, and participants. - -- **NCN Registration**: The NCN admin initializes an NCN account onchain by calling the restaking program, requiring both the NCN admin key and a base NCN key. This account serves as an entry in the registry and does not store detailed network configurations. Instead, configurations like accepted tokens, slashing conditions, quorum thresholds, and operator requirements are managed separately, typically via the NCN's own onchain program, CLI, or off-chain enforcement logic. -- **Vault and Operator Onboarding**: Vaults and Operators must each explicitly opt into the NCN, and the NCN must in turn approve them. Before establishing these relationships, vaults and operators must first register through the **Vault Program** and **Restaking Program**, respectively. Once registered, they can initiate connections to a specific NCN. - -Once approved by the NCN, the admins should call a warm-up instruction to activate the connections. The warm-up period lasts for one full epoch, not including the current partial epoch. The stake becomes active once all three components initiate and warm up the connections. This opt-in approval process ensures that all active stake delegations are mutual and intentional. - -- **Stake Delegation**: After the warm-up period, vaults can delegate stake to the operator. Stake becomes active immediately. However, when a user withdraws their stake, it must undergo a cooldown period that lasts for one full NCN-defined epoch. - -By the completion of this setup phase, the NCN has established its security parameters, approved operators, and activated its initial stake. This foundation sets the stage for the operations phase, and different dynamics come into play. - -### Operations Phase: Running the Network - -Each NCN progresses through consensus cycles, which may be time-bound to epoch lengths or follow a custom logic defined by the NCN admin. Within each cycle, operators perform offchain tasks and submit results, which are validated and finalized onchain. This structure allows NCNs to adopt models ranging from fixed epochs to flexible, event-driven consensus. - -Key steps for this include: - -- **NCN Configurations:** The NCN admin registers the NCN through the restaking program and separately configures the associated onchain program and accounts. This may differ from NCN to NCN. For example, here are some configurations from the flagship TipRouter NCN: - - `tie_breaker_admin`: The admin authorized to update the tie breaker mechanism or parameters. - - `valid_slots_after_consensus`: Number of slots after consensus is reached where votes are still accepted - - `epochs_before_stall`: Number of epochs to wait after consensus is reached before epoch accounts can be closed. - - `epochs_after_consensus_before_close`: Number of epochs without reaching consensus before the cycle is considered stalled. -- **Snapshot**: The NCN admin initiates a new consensus cycle by triggering the snapshot phase, which creates a frozen, point-in-time view of the network's state. During this phase, the system captures and records several critical pieces of information: the current set of active operators, their associated vault delegations, and the weighted stake distribution across the network. - -For example, the Tip Router NCN handles snapshotting as follows: This snapshot process involves creating an `EpochSnapshot` account to track the total stake weight and participant counts, individual `OperatorSnapshot` accounts for each operator to record their specific delegated stake weights, and a `WeightTable` that freezes the voting weights for each supported token. By locking this configuration at the start of the cycle, the system ensures that all subsequent votes are based on a consistent, immutable view of the network's state, preventing any manipulation of voting power through strategic stake movements or delegation changes during the active voting period. - -Note: While this applies to the Tip Router and the default Template NCN, other NCNs may define different snapshot mechanisms or parameter names depending on their specific design. - -- **Offchain Execution**: Operators execute the offchain service and protocol designed by the NCN. Examples include generating ZK proofs, querying external APIs, performing computations, or validating event data. In some cases, this step also involves submitting data of the results onchain or offchain from which it can be read for vote submissions. -- **Vote Submission**: Operators cast signed votes for specific results to the NCN’s onchain program. That program is responsible for tallying votes and determining whether consensus has been reached. Once consensus is finalized, it can trigger actions like reward distribution or slashing through the Restaking program. -- **Finalization and Rewarding**: Once consensus is reached, the result is sent to another program that invokes a state transition instruction for the NCN i.e. updating accounts, distributing rewards, etc. If successful, rewards are distributed to operators based on stake-weight and other logic that is codified by the NCN. -- **Slashing Enforcement (In Current Development)**: When operators fail to meet their obligations, whether through missed votes, invalid data submission, or malicious behavior, their stake is slashed by a designated slasher (set by the NCN) can submit a slashing request. The slashing mechanism ensures that economic incentives remain properly aligned throughout the system. (The slashing program is not currently live). - -The operation phase is where the network’s value is produced. While all service execution happens offchain, consensus protocol and incentives are preserved onchain, creating a scalable and trust-minimized decentralized protocol. - -## Critical Concepts - -Jito (Re)Staking introduces architectural patterns that enable scalable, secure coordination for independent decentralized services. These include: - -### Stake-Weighted Voting and Slashing - -Consensus in Jito (Re)Staking is driven by submitting onchain votes on offchain execution. Voting power is backed by delegated **onchain** stake. Please note that the slashing functionality is still currently being developed. - -During each epoch: - -- Operators perform offchain services (e.g. computation, validation, data retrieval) -- They submit signed votes onchain based on their results -- These votes are weighted by how much stake they are delegated from vaults -- Finalized results require reaching a threshold of weighted agreement -- Once slashing is live, NCNs can implement custom slashing logic for missed votes or malicious behavior - -### Modular and Permissioned Participation - -All connections between NCNs, vaults, and operators are explicitly approved by each party—no one is auto-connected. This mutual opt-in model guarantees intentional coordination, while enabling a modular topology: - -- Operators can serve multiple NCNs -- Vaults can allocate and rebalance stake freely -- NCNs evolve independently with custom rules and economic structures - -### Maintenance Phase: Keeping the Network Healthy - -At the end of each epoch, administrative and system-level tasks are performed to keep the NCN aligned and efficient. **A brief list includes**: - -- **Account Cleanup**: To minimize rent costs, temporary accounts like vote records and delegation states are closed at epoch boundaries, reclaiming SOL for reuse. -- **Operator and Vault Updates**: The NCN admin can add or remove operators and vaults, and adjust minimum quorum thresholds. In addition, operators and vaults have the ability to opt out or move between NCNs. -- **Token Weight and Allocation Adjustments**: NCNs can refresh or rebalance token weights through their own onchain program. Vault admins can update stake allocations across NCNs or shift delegations between operators. -- **Dynamic Configuration Changes**: NCN parameters, including reward splits, penalty thresholds, and voting logic, can be revised over time, allowing the network to evolve, upgrade, and adapt to changing usage patterns. - -This ongoing maintenance ensures the NCN remains flexible and cost-efficient. Administrative actions are coordinated through permissioned onchain records, making changes transparent and auditable. Additional improvements around cross-epoch delegation efficiency are being explored in the development roadmap. - -## Addresses - -| Network | Program | Address | Version | -| ------- | --------- | ------------------------------------------- | ------- | -| Mainnet | Restaking | RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q | 0.0.5 | -| Mainnet | Vault | Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 | 0.0.5 | -| Testnet | Restaking | RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q | 0.0.5 | -| Testnet | Vault | Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 | 0.0.5 | -| Devnet | Restaking | RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q | 0.0.5 | -| Devnet | Vault | Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 | 0.0.5 | - -## License - -This project is licensed under the Apache License 2.0 - see the [LICENSE.md](https://github.com/jito-foundation/restaking/blob/master/LICENSE) file for details. +**📖 View the documentation:** [jito.network/docs/restaking/jito-restaking-overview/](https://www.jito.network/docs/restaking/jito-restaking-overview/) diff --git a/idl/jito_vault.json b/idl/jito_vault.json index 38b8e4fe..3f30143c 100644 --- a/idl/jito_vault.json +++ b/idl/jito_vault.json @@ -962,6 +962,46 @@ "value": 20 } }, + { + "name": "RevokeDelegateTokenAccount", + "accounts": [ + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "vault", + "isMut": false, + "isSigner": false + }, + { + "name": "delegateAssetAdmin", + "isMut": false, + "isSigner": true + }, + { + "name": "tokenMint", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 21 + } + }, { "name": "SetAdmin", "accounts": [ @@ -989,7 +1029,7 @@ "args": [], "discriminant": { "type": "u8", - "value": 21 + "value": 22 } }, { @@ -1026,7 +1066,7 @@ ], "discriminant": { "type": "u8", - "value": 22 + "value": 23 } }, { @@ -1066,7 +1106,7 @@ ], "discriminant": { "type": "u8", - "value": 23 + "value": 24 } }, { @@ -1106,7 +1146,7 @@ ], "discriminant": { "type": "u8", - "value": 24 + "value": 25 } }, { @@ -1146,7 +1186,7 @@ "args": [], "discriminant": { "type": "u8", - "value": 25 + "value": 26 } }, { @@ -1188,7 +1228,7 @@ ], "discriminant": { "type": "u8", - "value": 26 + "value": 27 } }, { @@ -1223,7 +1263,7 @@ "args": [], "discriminant": { "type": "u8", - "value": 27 + "value": 28 } }, { @@ -1258,7 +1298,7 @@ ], "discriminant": { "type": "u8", - "value": 28 + "value": 29 } }, { @@ -1316,7 +1356,7 @@ ], "discriminant": { "type": "u8", - "value": 29 + "value": 30 } }, { @@ -1364,7 +1404,7 @@ ], "discriminant": { "type": "u8", - "value": 30 + "value": 31 } }, { @@ -1389,7 +1429,39 @@ "args": [], "discriminant": { "type": "u8", - "value": 31 + "value": 32 + } + }, + { + "name": "SetConfigSecondaryAdmin", + "accounts": [ + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "newAdmin", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "configAdminRole", + "type": { + "defined": "ConfigAdminRole" + } + } + ], + "discriminant": { + "type": "u8", + "value": 33 } } ], @@ -2077,6 +2149,17 @@ ] } }, + { + "name": "ConfigAdminRole", + "type": { + "kind": "enum", + "variants": [ + { + "name": "FeeAdmin" + } + ] + } + }, { "name": "VaultAdminRole", "type": { diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index efcb357e..1bc13c7f 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -9,6 +9,9 @@ license = { workspace = true } edition = { workspace = true } readme = { workspace = true } +[dependencies] +log = "0.4.21" + [dev-dependencies] borsh = { workspace = true } jito-bytemuck = { workspace = true } @@ -30,6 +33,3 @@ spl-token-2022 = { workspace = true } test-case = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } - -[dependencies] -log = "0.4.21" diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index aecf82e9..1c7f78cd 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -17,7 +17,7 @@ use jito_vault_core::{ use jito_vault_sdk::{ error::VaultError, inline_mpl_token_metadata, - instruction::{VaultAdminRole, WithdrawalAllocationMethod}, + instruction::{ConfigAdminRole, VaultAdminRole, WithdrawalAllocationMethod}, sdk::{ add_delegation, cooldown_delegation, cooldown_vault_ncn_ticket, initialize_config, initialize_vault, set_deposit_capacity, warmup_vault_ncn_slasher_ticket, @@ -845,6 +845,33 @@ impl VaultProgramClient { .await } + pub async fn revoke_delegate_token_account( + &mut self, + config: &Pubkey, + vault: &Pubkey, + delegate_asset_admin: &Keypair, + token_mint: &Pubkey, + token_account: &Pubkey, + token_program_id: &Pubkey, + ) -> Result<(), TestError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self._process_transaction(&Transaction::new_signed_with_payer( + &[jito_vault_sdk::sdk::revoke_delegate_token_account( + &jito_vault_program::id(), + config, + vault, + &delegate_asset_admin.pubkey(), + token_mint, + token_account, + token_program_id, + )], + Some(&self.payer.pubkey()), + &[&self.payer, delegate_asset_admin], + blockhash, + )) + .await + } + pub async fn set_admin( &mut self, config: &Pubkey, @@ -1931,6 +1958,29 @@ impl VaultProgramClient { Ok(()) } + + pub async fn set_config_secondary_admin( + &mut self, + config: &Pubkey, + old_admin: &Keypair, + new_admin: &Pubkey, + role: ConfigAdminRole, + ) -> Result<(), TestError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self._process_transaction(&Transaction::new_signed_with_payer( + &[jito_vault_sdk::sdk::set_config_secondary_admin( + &jito_vault_program::id(), + config, + &old_admin.pubkey(), + &new_admin, + role, + )], + Some(&old_admin.pubkey()), + &[old_admin], + blockhash, + )) + .await + } } #[inline(always)] diff --git a/integration_tests/tests/vault/mod.rs b/integration_tests/tests/vault/mod.rs index 86a02b09..0fba3278 100644 --- a/integration_tests/tests/vault/mod.rs +++ b/integration_tests/tests/vault/mod.rs @@ -14,10 +14,12 @@ mod initialize_vault_ncn_slasher_ticket; mod initialize_vault_ncn_ticket; mod initialize_vault_operator_delegation; mod initialize_vault_update_state_tracker; +mod revoke_delegate_token_account; mod reward_fee; mod set_admin; mod set_capacity; mod set_config_admin; +mod set_config_secondary_admin; mod set_fees; mod set_is_paused; mod set_program_fee_wallet; diff --git a/integration_tests/tests/vault/revoke_delegate_token_account.rs b/integration_tests/tests/vault/revoke_delegate_token_account.rs new file mode 100644 index 00000000..ec020a99 --- /dev/null +++ b/integration_tests/tests/vault/revoke_delegate_token_account.rs @@ -0,0 +1,253 @@ +#[cfg(test)] +mod tests { + use jito_vault_core::config::Config; + use jito_vault_sdk::error::VaultError; + use solana_program::{program_option::COption, pubkey::Pubkey}; + use solana_sdk::signature::{Keypair, Signer}; + use spl_associated_token_account::get_associated_token_address; + use test_case::test_case; + + use crate::fixtures::{ + fixture::TestBuilder, + vault_client::{assert_vault_error, VaultRoot}, + }; + + const MINT_AMOUNT: u64 = 100_000; + + async fn setup(token_program_id: &Pubkey) -> (TestBuilder, Pubkey, Keypair, Keypair, Keypair) { + let mut fixture = TestBuilder::new().await; + + let mut vault_program_client = fixture.vault_program_client(); + + let ( + _config_admin, + VaultRoot { + vault_pubkey, + vault_admin, + }, + ) = vault_program_client + .setup_config_and_vault(99, 100, 101) + .await + .unwrap(); + + let random_mint = Keypair::new(); + vault_program_client + .create_token_mint(&random_mint, token_program_id) + .await + .unwrap(); + + let vault_token_account = Keypair::new(); + if token_program_id.eq(&spl_token::id()) { + fixture + .mint_spl_to( + &random_mint.pubkey(), + &vault_pubkey, + MINT_AMOUNT, + token_program_id, + ) + .await + .unwrap(); + } else { + fixture + .create_token_account( + token_program_id, + &vault_token_account, + &random_mint.pubkey(), + &vault_pubkey, + &[], + ) + .await + .unwrap(); + fixture + .mint_spl_to( + &random_mint.pubkey(), + &vault_token_account.pubkey(), + MINT_AMOUNT, + token_program_id, + ) + .await + .unwrap(); + } + + ( + fixture, + vault_pubkey, + vault_admin, + random_mint, + vault_token_account, + ) + } + + #[test_case(spl_token::id(); "token")] + // #[test_case(spl_token_2022::id(); "token-2022")] + #[tokio::test] + async fn test_delegate_token_account_ok(token_program_id: Pubkey) { + let (mut fixture, vault_pubkey, vault_admin, random_mint, vault_token_account) = + setup(&token_program_id).await; + let mut vault_program_client = fixture.vault_program_client(); + let config_pubkey = Config::find_program_address(&jito_vault_program::id()).0; + + let bob = Pubkey::new_unique(); + if token_program_id.eq(&spl_token::id()) { + // Delegate + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &bob, + &token_program_id, + ) + .await + .unwrap(); + let ata = get_associated_token_address(&vault_pubkey, &random_mint.pubkey()); + let token_account_acc = fixture.get_token_account(&ata).await.unwrap(); + + assert_eq!(token_account_acc.delegate, COption::Some(bob)); + assert_eq!(token_account_acc.delegated_amount, u64::MAX); + + // Revoke + vault_program_client + .revoke_delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &token_program_id, + ) + .await + .unwrap(); + let ata = get_associated_token_address(&vault_pubkey, &random_mint.pubkey()); + let token_account_acc = fixture.get_token_account(&ata).await.unwrap(); + + assert_eq!(token_account_acc.delegate, COption::None); + assert_eq!(token_account_acc.delegated_amount, 0); + } else { + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &bob, + &token_program_id, + ) + .await + .unwrap(); + + let vault_token_acc = fixture + .get_token_account(&vault_token_account.pubkey()) + .await + .unwrap(); + + assert_eq!(vault_token_acc.delegate, COption::Some(bob)); + assert_eq!(vault_token_acc.delegated_amount, u64::MAX); + + // Revoke + vault_program_client + .revoke_delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &token_program_id, + ) + .await + .unwrap(); + + let vault_token_acc = fixture + .get_token_account(&vault_token_account.pubkey()) + .await + .unwrap(); + + assert_eq!(vault_token_acc.delegate, COption::None); + assert_eq!(vault_token_acc.delegated_amount, 0); + } + } + + #[test_case(spl_token::id(); "token")] + // #[test_case(spl_token_2022::id(); "token-2022")] + #[tokio::test] + async fn test_delegate_vault_wrong_delegate_asset_admin_fails(token_program_id: Pubkey) { + let (mut fixture, vault_pubkey, vault_admin, random_mint, vault_token_account) = + setup(&token_program_id).await; + let mut vault_program_client = fixture.vault_program_client(); + let config_pubkey = Config::find_program_address(&jito_vault_program::id()).0; + + let wrong_delegate_asset_admin = Keypair::new(); + let bob = Pubkey::new_unique(); + if token_program_id.eq(&spl_token::id()) { + // Delegate + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &bob, + &token_program_id, + ) + .await + .unwrap(); + let ata = get_associated_token_address(&vault_pubkey, &random_mint.pubkey()); + let token_account_acc = fixture.get_token_account(&ata).await.unwrap(); + + assert_eq!(token_account_acc.delegate, COption::Some(bob)); + assert_eq!(token_account_acc.delegated_amount, u64::MAX); + + // Revoke + let response = vault_program_client + .revoke_delegate_token_account( + &config_pubkey, + &vault_pubkey, + &wrong_delegate_asset_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &token_program_id, + ) + .await; + + assert_vault_error(response, VaultError::VaultDelegateAssetAdminInvalid); + } else { + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &bob, + &token_program_id, + ) + .await + .unwrap(); + + let vault_token_acc = fixture + .get_token_account(&vault_token_account.pubkey()) + .await + .unwrap(); + + assert_eq!(vault_token_acc.delegate, COption::Some(bob)); + assert_eq!(vault_token_acc.delegated_amount, u64::MAX); + + let response = vault_program_client + .revoke_delegate_token_account( + &config_pubkey, + &vault_pubkey, + &wrong_delegate_asset_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &token_program_id, + ) + .await; + + assert_vault_error(response, VaultError::VaultDelegateAssetAdminInvalid); + } + } +} diff --git a/integration_tests/tests/vault/set_config_secondary_admin.rs b/integration_tests/tests/vault/set_config_secondary_admin.rs new file mode 100644 index 00000000..629f1871 --- /dev/null +++ b/integration_tests/tests/vault/set_config_secondary_admin.rs @@ -0,0 +1,75 @@ +#[cfg(test)] +mod tests { + use jito_vault_core::config::Config; + use jito_vault_sdk::{error::VaultError, instruction::ConfigAdminRole}; + use solana_sdk::{ + pubkey::Pubkey, + signature::{Keypair, Signer}, + }; + + use crate::fixtures::{fixture::TestBuilder, vault_client::assert_vault_error}; + + #[tokio::test] + async fn test_set_config_secondary_admin_ok() { + let fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let config_address = Config::find_program_address(&jito_vault_program::id()).0; + + // Initialize config + let old_admin = vault_program_client.do_initialize_config().await.unwrap(); + + { + // Fee Admin + let new_admin = Pubkey::new_unique(); + vault_program_client + .set_config_secondary_admin( + &config_address, + &old_admin, + &new_admin, + ConfigAdminRole::FeeAdmin, + ) + .await + .unwrap(); + + let config = vault_program_client + .get_config(&config_address) + .await + .unwrap(); + assert_eq!(config.fee_admin, new_admin); + } + } + + #[tokio::test] + async fn test_set_config_secondary_admin_invalid_old_admin() { + let fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let config_address = Config::find_program_address(&jito_vault_program::id()).0; + + // Initialize config + let _old_admin = vault_program_client.do_initialize_config().await.unwrap(); + + // Create invalid old admin and new admin + let invalid_old_admin = Keypair::new(); + + vault_program_client + .airdrop(&invalid_old_admin.pubkey(), 1.0) + .await + .unwrap(); + + // Attempt to set new admin with invalid old admin + { + // Fee Admin + let new_admin = Pubkey::new_unique(); + let response = vault_program_client + .set_config_secondary_admin( + &config_address, + &invalid_old_admin, + &new_admin, + ConfigAdminRole::FeeAdmin, + ) + .await; + + assert_vault_error(response, VaultError::ConfigAdminInvalid); + } + } +} diff --git a/makefile b/makefile index 193702e3..ef26df1c 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,4 @@ # Makefile for Jito Restaking Project - # Commands CARGO := cargo CARGO_SORT := cargo sort @@ -9,7 +8,10 @@ CARGO_SBF := cargo-build-sbf CARGO_NEXTEST := cargo nextest YARN := yarn SHANK_CLI := ./target/release/jito-shank-cli -RESTAKING_CLI := ./target/release/jito-restaking-cli + +# Environment and paths +ENV_PATH := ./config/program.env +IDL_OUTPUT_PATH := ./idl # Default target .PHONY: all @@ -24,13 +26,38 @@ lint: # Code generation .PHONY: generate-code -generate-code: build-release - $(RESTAKING_CLI) --markdown-help > ./docs/_tools/00_cli.md - $(SHANK_CLI) +generate-code: build-release generate-idl $(YARN) install $(YARN) generate-clients $(YARN) update-dependencies +# Generate IDL files +.PHONY: generate-idl +generate-idl: + $(SHANK_CLI) \ + --program-env-path $(ENV_PATH) \ + --output-idl-path $(IDL_OUTPUT_PATH) \ + generate \ + --program-id-key "RESTAKING_PROGRAM_ID" \ + --idl-name jito_restaking \ + --module-paths "restaking_sdk" \ + --module-paths "restaking_core" \ + --module-paths "restaking_program" \ + --module-paths "bytemuck" \ + --module-paths "core" + + $(SHANK_CLI) \ + --program-env-path $(ENV_PATH) \ + --output-idl-path $(IDL_OUTPUT_PATH) \ + generate \ + --program-id-key "VAULT_PROGRAM_ID" \ + --idl-name jito_vault \ + --module-paths "vault_sdk" \ + --module-paths "vault_core" \ + --module-paths "vault_program" \ + --module-paths "bytemuck" \ + --module-paths "core" + # Build debug .PHONY: build build: diff --git a/restaking_program/src/cooldown_ncn_vault_ticket.rs b/restaking_program/src/cooldown_ncn_vault_ticket.rs index ba373bf1..257db876 100644 --- a/restaking_program/src/cooldown_ncn_vault_ticket.rs +++ b/restaking_program/src/cooldown_ncn_vault_ticket.rs @@ -43,5 +43,11 @@ pub fn process_cooldown_ncn_vault_ticket( return Err(RestakingError::NcnVaultTicketFailedCooldown.into()); } + msg!( + "COOLDOWN NCN_VAULT_TICKET: NCN {} deactivating Vault {}", + ncn_vault_ticket.ncn, + ncn_vault_ticket.vault, + ); + Ok(()) } diff --git a/restaking_program/src/cooldown_operator_vault_ticket.rs b/restaking_program/src/cooldown_operator_vault_ticket.rs index 8e1cd57a..ef41ee5f 100644 --- a/restaking_program/src/cooldown_operator_vault_ticket.rs +++ b/restaking_program/src/cooldown_operator_vault_ticket.rs @@ -47,5 +47,11 @@ pub fn process_cooldown_operator_vault_ticket( return Err(RestakingError::OperatorVaultTicketFailedCooldown.into()); } + msg!( + "COOLDOWN OPERATOR_VAULT_TICKET: Operator {} deactivating Vault {}", + operator_vault_ticket.operator, + operator_vault_ticket.vault, + ); + Ok(()) } diff --git a/restaking_program/src/ncn_cooldown_operator.rs b/restaking_program/src/ncn_cooldown_operator.rs index 2b12cfbd..f350d178 100644 --- a/restaking_program/src/ncn_cooldown_operator.rs +++ b/restaking_program/src/ncn_cooldown_operator.rs @@ -48,5 +48,11 @@ pub fn process_ncn_cooldown_operator( return Err(RestakingError::NcnCooldownOperatorFailed.into()); } + msg!( + "COOLDOWN NCN_OPERATOR_STATE: NCN {} deactivating Operator {}", + ncn_operator_ticket.ncn, + ncn_operator_ticket.operator, + ); + Ok(()) } diff --git a/restaking_program/src/ncn_warmup_operator.rs b/restaking_program/src/ncn_warmup_operator.rs index c274a405..68c91a20 100644 --- a/restaking_program/src/ncn_warmup_operator.rs +++ b/restaking_program/src/ncn_warmup_operator.rs @@ -42,5 +42,11 @@ pub fn process_ncn_warmup_operator(program_id: &Pubkey, accounts: &[AccountInfo] return Err(RestakingError::NcnWarmupOperatorFailed.into()); } + msg!( + "WARMUP NCN_OPERATOR_STATE: NCN {} activating Operator {}", + ncn_operator_state.ncn, + ncn_operator_state.operator, + ); + Ok(()) } diff --git a/restaking_program/src/operator_cooldown_ncn.rs b/restaking_program/src/operator_cooldown_ncn.rs index a0d799bd..3c3a151c 100644 --- a/restaking_program/src/operator_cooldown_ncn.rs +++ b/restaking_program/src/operator_cooldown_ncn.rs @@ -46,5 +46,11 @@ pub fn process_operator_cooldown_ncn( return Err(RestakingError::OperatorCooldownNcnFailed.into()); } + msg!( + "COOLDOWN NCN_OPERATOR_STATE: Operator {} deactivating NCN {}", + ncn_operator_state.operator, + ncn_operator_state.ncn, + ); + Ok(()) } diff --git a/restaking_program/src/operator_warmup_ncn.rs b/restaking_program/src/operator_warmup_ncn.rs index 084d48ea..073f1d5f 100644 --- a/restaking_program/src/operator_warmup_ncn.rs +++ b/restaking_program/src/operator_warmup_ncn.rs @@ -42,5 +42,11 @@ pub fn process_operator_warmup_ncn(program_id: &Pubkey, accounts: &[AccountInfo] return Err(RestakingError::OperatorWarmupNcnFailed.into()); } + msg!( + "WARMUP NCN_OPERATOR_STATE: Operator {} activating NCN {}", + ncn_operator_state.operator, + ncn_operator_state.ncn, + ); + Ok(()) } diff --git a/restaking_program/src/warmup_ncn_vault_ticket.rs b/restaking_program/src/warmup_ncn_vault_ticket.rs index 5a3d909b..39358a85 100644 --- a/restaking_program/src/warmup_ncn_vault_ticket.rs +++ b/restaking_program/src/warmup_ncn_vault_ticket.rs @@ -45,5 +45,11 @@ pub fn process_warmup_ncn_vault_ticket( return Err(RestakingError::NcnVaultTicketFailedWarmup.into()); } + msg!( + "WARMUP NCN_VAULT_TICKET: NCN {} activating Vault {}", + ncn_vault_ticket.ncn, + ncn_vault_ticket.vault, + ); + Ok(()) } diff --git a/restaking_program/src/warmup_operator_vault_ticket.rs b/restaking_program/src/warmup_operator_vault_ticket.rs index 529adcb2..98c75e9e 100644 --- a/restaking_program/src/warmup_operator_vault_ticket.rs +++ b/restaking_program/src/warmup_operator_vault_ticket.rs @@ -46,5 +46,11 @@ pub fn process_warmup_operator_vault_ticket( return Err(RestakingError::OperatorVaultTicketFailedWarmup.into()); } + msg!( + "WARMUP OPERATOR_VAULT_TICKET: Operator {} activating Vault {}", + operator_vault_ticket.operator, + operator_vault_ticket.vault, + ); + Ok(()) } diff --git a/shank-cli/README.md b/shank-cli/README.md new file mode 100644 index 00000000..50db2221 --- /dev/null +++ b/shank-cli/README.md @@ -0,0 +1,93 @@ +# Jito Shank CLI + +A command-line interface tool for managing and generating IDL files for Solana programs using the shank-idl library. + +## Overview + +Jito Shank CLI simplifies the process of generating IDL files. +It allows you to extract IDL definitions from multiple module paths and combine them into a single comprehensive IDL file. + +## Installation + +```bash +cargo install jito-shank-cli +``` + +## Getting Started + +### Generate IDL + +### Command Structure + +The CLI supports the following commands and options: + +#### Global Options + +- `--program-env-path`: Path to the environment file containing program IDs +- `--output-idl-path`: Directory where the generated IDL file will be saved + +#### Commands + +##### Generate + +Generates an IDL file. + +```bash +shank-cli --program-env-path ./.env --output-idl-path ./idl generate --program-env-key MY_PROGRAM_ID --idl-name my_program --module-paths core program sdk +``` + +###### Options + +- `--program-env-key`: Key in the environment file that contains the program ID +- `--idl-name`: Name for the generated IDL file +- `--module-paths`: One or more paths to Rust modules containing shank annotations + +## Environment File Format + +The environment file should contain key-value pairs: + +``` +RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q +VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 +``` + +## Example + +Jito Restaking Program + +```bash +cargo r -p jito-shank-cli -- \ +--program-env-path ./config/program.env \ +--output-idl-path ./idl \ +generate \ +--program-id-key "RESTAKING_PROGRAM_ID" \ +--idl-name jito_restaking \ +--module-paths "restaking_sdk" \ +--module-paths "restaking_core" \ +--module-paths "restaking_program" \ +--module-paths "bytemuck" \ +--module-paths "core" +``` + +This will: +1. Read the program ID from the `PROGRAM_ID` key in `./config/program.env` +2. Extract IDL definitions from the modules in `./restaking_sdk`, `./restaking_core`, `./restaking_program`, `./bytemuck` and `core`. +3. Combine them into a single IDL +4. Save the result as `./idl/jito_restaking.json` + + +Jito Vault Program + +```bash +cargo r -p jito-shank-cli -- \ +--program-env ./config/program.env \ +--output-idl-path ./idl \ +generate \ +--program-id-key "VAULT_PROGRAM_ID" \ +--idl-name jito_vault \ +--module-paths "vault_sdk" \ +--module-paths "vault_core" \ +--module-paths "vault_program" \ +--module-paths "bytemuck" \ +--module-paths "core" +``` diff --git a/shank-cli/src/main.rs b/shank-cli/src/main.rs index cf8fb2e8..03296adb 100644 --- a/shank-cli/src/main.rs +++ b/shank-cli/src/main.rs @@ -1,118 +1,135 @@ -use std::{fs::File, io::Write}; +use std::{fs::File, io::Write, path::PathBuf}; use anyhow::{anyhow, Result}; +use clap::{Args, Parser, Subcommand}; use env_logger::Env; use log::{debug, info}; use shank_idl::{extract_idl, manifest::Manifest, ParseIdlOpts}; +#[derive(Parser)] +#[command(author, version, about = "A CLI for managing shank", long_about = None)] +struct Cli { + /// Path to the program_env file + #[arg(long)] + program_env_path: PathBuf, + + /// Path to the idl + #[arg(long)] + output_idl_path: PathBuf, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Generate IDL + Generate(GenerateArgs), +} + +#[derive(Args)] +struct GenerateArgs { + /// Program id key in program.env + #[arg(long)] + program_id_key: String, + + /// IDL names + #[arg(long)] + idl_name: String, + + /// Module paths (core, program, sdk...) + #[arg(long)] + module_paths: Vec, +} + struct IdlConfiguration { program_id: String, - name: &'static str, - paths: Vec<&'static str>, + name: String, + paths: Vec, } fn main() -> Result<()> { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - let crate_root = std::env::current_dir()?; - - let envs = envfile::EnvFile::new(crate_root.join("config").join("program.env"))?; - let restaking_program_id = envs - .get("RESTAKING_PROGRAM_ID") - .ok_or_else(|| anyhow!("RESTAKING_PROGRAM_ID not found"))? - .to_string(); - let vault_program_id = envs - .get("VAULT_PROGRAM_ID") - .ok_or_else(|| anyhow!("VAULT_PROGRAM_ID not found"))? - .to_string(); - - let idl_configs = vec![ - IdlConfiguration { - program_id: restaking_program_id, - name: "jito_restaking", - paths: vec![ - "restaking_sdk", - "restaking_core", - "restaking_program", - "bytemuck", - "core", - ], - }, - IdlConfiguration { - program_id: vault_program_id, - name: "jito_vault", - paths: vec![ - "vault_sdk", - "vault_core", - "vault_program", - "bytemuck", - "core", - ], - }, - ]; - - let crate_root = std::env::current_dir().unwrap(); - let out_dir = crate_root.join("idl"); - for idl in idl_configs { - let mut idls = Vec::new(); - for path in idl.paths { - let cargo_toml = crate_root.join(path).join("Cargo.toml"); - if !cargo_toml.exists() { - return Err(anyhow!( - "Did not find Cargo.toml at the path: {}", - crate_root.display() - )); - } - let manifest = Manifest::from_path(&cargo_toml)?; - debug!("manifest: {:?}", manifest); - let lib_rel_path = manifest - .lib_rel_path() - .ok_or_else(|| anyhow!("Program needs to be a lib"))?; - debug!("lib_rel_path: {:?}", lib_rel_path); - let lib_full_path_str = crate_root.join(path).join(lib_rel_path); - let lib_full_path = lib_full_path_str - .to_str() - .ok_or_else(|| anyhow!("Invalid Path"))?; - debug!("lib_full_path: {:?}", lib_full_path); - // Extract IDL and convert to JSON - let opts = ParseIdlOpts { - program_address_override: Some(idl.program_id.to_string()), - ..ParseIdlOpts::default() + + let args: Cli = Cli::parse(); + + match args.command { + Commands::Generate(generate_args) => { + let envs = envfile::EnvFile::new(args.program_env_path)?; + let program_id = envs + .get(&generate_args.program_id_key) + .ok_or_else(|| anyhow!("{} not found", generate_args.program_id_key))? + .to_string(); + let idl_config = IdlConfiguration { + program_id, + name: generate_args.idl_name, + paths: generate_args.module_paths, }; - let idl = extract_idl(lib_full_path, opts)? - .ok_or_else(|| anyhow!("No IDL could be extracted"))?; - idls.push(idl); - } - let mut accumulator = idls.pop().unwrap(); - for other_idls in idls { - accumulator.constants.extend(other_idls.constants); - accumulator.instructions.extend(other_idls.instructions); - accumulator.accounts.extend(other_idls.accounts); - accumulator.types.extend(other_idls.types); - if let Some(events) = other_idls.events { - if let Some(accumulator_events) = &mut accumulator.events { - accumulator_events.extend(events); - } else { - accumulator.events = Some(events); + let crate_root = std::env::current_dir().unwrap(); + let mut idls = Vec::new(); + for path in idl_config.paths.iter() { + let cargo_toml = crate_root.join(path).join("Cargo.toml"); + if !cargo_toml.exists() { + return Err(anyhow!( + "Did not find Cargo.toml at the path: {}", + crate_root.display() + )); } + let manifest = Manifest::from_path(&cargo_toml)?; + debug!("manifest: {:?}", manifest); + let lib_rel_path = manifest + .lib_rel_path() + .ok_or_else(|| anyhow!("Program needs to be a lib"))?; + + debug!("lib_rel_path: {:?}", lib_rel_path); + let lib_full_path_str = crate_root.join(path).join(lib_rel_path); + let lib_full_path = lib_full_path_str + .to_str() + .ok_or_else(|| anyhow!("Invalid Path"))?; + debug!("lib_full_path: {:?}", lib_full_path); + + // Extract IDL and convert to JSON + let opts = ParseIdlOpts { + program_address_override: Some(idl_config.program_id.to_string()), + ..ParseIdlOpts::default() + }; + let idl = extract_idl(lib_full_path, opts)? + .ok_or_else(|| anyhow!("No IDL could be extracted"))?; + idls.push(idl); } - if let Some(errors) = other_idls.errors { - if let Some(accumulator_errors) = &mut accumulator.errors { - accumulator_errors.extend(errors); - } else { - accumulator.errors = Some(errors); + + let mut accumulator = idls.pop().unwrap(); + for other_idls in idls { + accumulator.constants.extend(other_idls.constants); + accumulator.instructions.extend(other_idls.instructions); + accumulator.accounts.extend(other_idls.accounts); + accumulator.types.extend(other_idls.types); + if let Some(events) = other_idls.events { + if let Some(accumulator_events) = &mut accumulator.events { + accumulator_events.extend(events); + } else { + accumulator.events = Some(events); + } + } + if let Some(errors) = other_idls.errors { + if let Some(accumulator_errors) = &mut accumulator.errors { + accumulator_errors.extend(errors); + } else { + accumulator.errors = Some(errors); + } } } - } - accumulator.name = idl.name.to_string(); + accumulator.name = idl_config.name.to_string(); - let idl_json = accumulator.try_into_json()?; - let mut idl_path = out_dir.join(idl.name); - idl_path.set_extension("json"); + let idl_json = accumulator.try_into_json()?; + let mut idl_path = args.output_idl_path.join(idl_config.name); + idl_path.set_extension("json"); - info!("Writing IDL to {:?}", idl_path); - let mut idl_json_file = File::create(idl_path)?; - idl_json_file.write_all(idl_json.as_bytes())?; + info!("Writing IDL to {:?}", idl_path); + let mut idl_json_file = File::create(idl_path)?; + idl_json_file.write_all(idl_json.as_bytes())?; + } } Ok(()) diff --git a/vault_core/src/config.rs b/vault_core/src/config.rs index b2c97a36..e4649740 100644 --- a/vault_core/src/config.rs +++ b/vault_core/src/config.rs @@ -177,6 +177,16 @@ impl Config { Ok(fee) } + /// Check admin validity and signature + #[inline(always)] + pub fn check_admin(&self, admin: &Pubkey) -> Result<(), VaultError> { + if self.admin.ne(admin) { + msg!("Config admin does not match the provided admin"); + return Err(VaultError::ConfigAdminInvalid); + } + Ok(()) + } + pub fn seeds() -> Vec> { vec![b"config".to_vec()] } @@ -310,4 +320,15 @@ mod tests { Err(VaultError::VaultFeeCapExceeded) ); } + + #[test] + fn test_check_admin() { + let admin = Pubkey::new_unique(); + let bad_admin = Pubkey::new_unique(); + + let config = Config::new(admin, Pubkey::new_unique(), Pubkey::new_unique(), 0, 0); + + assert!(config.check_admin(&admin).is_ok()); + assert!(config.check_admin(&bad_admin).is_err()); + } } diff --git a/vault_program/src/add_delegation.rs b/vault_program/src/add_delegation.rs index 3ee81605..75e43f5d 100644 --- a/vault_program/src/add_delegation.rs +++ b/vault_program/src/add_delegation.rs @@ -65,5 +65,12 @@ pub fn process_add_delegation( .delegation_state .delegate(amount)?; + // msg!( + // "ADD DELEGATION: Vault {} delegating {} tokens to Operator {}", + // vault_info.key, + // amount, + // operator.key + // ); + Ok(()) } diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index 9eb2de24..b0d6eb66 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -125,6 +125,15 @@ pub fn process_burn_withdrawal_ticket( .collect(); drop(vault_staker_withdrawal_ticket_data); + // msg!( + // "COMPLETE WITHDRAWAL: Staker {} received {} tokens (burned {} VRT, fees: {} vault, {} program)", + // staker.key, + // out_amount, + // burn_amount, + // vault_fee_amount, + // program_fee_amount + // ); + // transfer fee to fee wallet invoke_signed( &transfer( diff --git a/vault_program/src/cooldown_delegation.rs b/vault_program/src/cooldown_delegation.rs index 679f2edb..8037ad19 100644 --- a/vault_program/src/cooldown_delegation.rs +++ b/vault_program/src/cooldown_delegation.rs @@ -57,5 +57,12 @@ pub fn process_cooldown_delegation( .cooldown(amount)?; vault.delegation_state.cooldown(amount)?; + // msg!( + // "COOLDOWN DELEGATION: Vault {} undelegating {} tokens from Operator {}", + // vault_info.key, + // amount, + // operator.key + // ); + Ok(()) } diff --git a/vault_program/src/cooldown_vault_ncn_ticket.rs b/vault_program/src/cooldown_vault_ncn_ticket.rs index 71c8f5b3..907ac34c 100644 --- a/vault_program/src/cooldown_vault_ncn_ticket.rs +++ b/vault_program/src/cooldown_vault_ncn_ticket.rs @@ -49,5 +49,11 @@ pub fn process_cooldown_vault_ncn_ticket( return Err(VaultError::VaultNcnTicketFailedCooldown.into()); } + msg!( + "COOLDOWN VAULT_NCN_TICKET: Vault {} deactivating NCN {}", + vault_info.key, + ncn.key + ); + Ok(()) } diff --git a/vault_program/src/enqueue_withdrawal.rs b/vault_program/src/enqueue_withdrawal.rs index 15dcb829..6748c44d 100644 --- a/vault_program/src/enqueue_withdrawal.rs +++ b/vault_program/src/enqueue_withdrawal.rs @@ -116,6 +116,12 @@ pub fn process_enqueue_withdrawal( vault.increment_vrt_enqueued_for_cooldown_amount(vrt_amount)?; + // msg!( + // "ENQUEUE WITHDRAWAL: {} VRT tokens from staker {} added to cooldown queue", + // vrt_amount, + // staker.key + // ); + // Withdraw funds from the staker's VRT account, transferring them to an ATA owned // by the VaultStakerWithdrawalTicket invoke( diff --git a/vault_program/src/lib.rs b/vault_program/src/lib.rs index ad620244..755da580 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -18,9 +18,11 @@ mod initialize_vault_operator_delegation; mod initialize_vault_update_state_tracker; mod initialize_vault_with_mint; mod mint_to; +mod revoke_delegate_token_account; mod set_admin; mod set_capacity; mod set_config_admin; +mod set_config_secondary_admin; mod set_fees; mod set_is_paused; mod set_program_fee; @@ -33,6 +35,7 @@ mod warmup_vault_ncn_ticket; use borsh::BorshDeserialize; use jito_vault_sdk::instruction::VaultInstruction; +use set_config_secondary_admin::process_set_config_secondary_admin; use set_program_fee::process_set_program_fee; use solana_program::{ account_info::AccountInfo, declare_id, entrypoint::ProgramResult, msg, @@ -59,6 +62,7 @@ use crate::{ initialize_vault_operator_delegation::process_initialize_vault_operator_delegation, initialize_vault_update_state_tracker::process_initialize_vault_update_state_tracker, initialize_vault_with_mint::process_initialize_vault_with_mint, mint_to::process_mint, + revoke_delegate_token_account::process_revoke_delegate_token_account, set_admin::process_set_admin, set_capacity::process_set_deposit_capacity, set_config_admin::process_set_config_admin, set_fees::process_set_fees, set_is_paused::process_set_is_paused, set_program_fee_wallet::process_set_program_fee_wallet, @@ -162,6 +166,10 @@ pub fn process_instruction( msg!("Instruction: DelegateTokenAccount"); process_delegate_token_account(program_id, accounts) } + VaultInstruction::RevokeDelegateTokenAccount => { + msg!("Instruction: RevokeDelegateTokenAccount"); + process_revoke_delegate_token_account(program_id, accounts) + } VaultInstruction::SetFees { deposit_fee_bps, withdrawal_fee_bps, @@ -280,5 +288,9 @@ pub fn process_instruction( msg!("Instruction: SetConfigAdmin"); process_set_config_admin(program_id, accounts) } + VaultInstruction::SetConfigSecondaryAdmin(role) => { + msg!("Instruction: SetConfigSecondaryAdmin"); + process_set_config_secondary_admin(program_id, accounts, role) + } } } diff --git a/vault_program/src/mint_to.rs b/vault_program/src/mint_to.rs index 9afe8860..aac7a4d9 100644 --- a/vault_program/src/mint_to.rs +++ b/vault_program/src/mint_to.rs @@ -99,6 +99,14 @@ pub fn process_mint( return Err(VaultError::VrtOutCannotBeZero.into()); } + // msg!( + // "MINT: Depositor {} added {} tokens, received {} VRT (fee: {} VRT)", + // depositor.key, + // amount_in, + // vrt_to_depositor, + // vrt_to_fee_wallet + // ); + // transfer tokens from depositor to vault { invoke( diff --git a/vault_program/src/revoke_delegate_token_account.rs b/vault_program/src/revoke_delegate_token_account.rs new file mode 100644 index 00000000..fed88e55 --- /dev/null +++ b/vault_program/src/revoke_delegate_token_account.rs @@ -0,0 +1,84 @@ +use jito_bytemuck::AccountDeserialize; +use jito_jsm_core::loader::{load_signer, load_token_account, load_token_mint, load_token_program}; +use jito_vault_core::{config::Config, vault::Vault}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke_signed, + program_error::ProgramError, pubkey::Pubkey, +}; + +/// Processes the revoke delegate token account instruction: [`crate::VaultInstruction::RevokeDelegateTokenAccount`] +/// +/// This instruction revokes delegation previously set by the [`crate::VaultInstruction::DelegateTokenAccount`] instruction. +/// +/// # Arguments +/// * `program_id` - The public key of the program to ensure the correct program is being executed. +/// * `accounts` - A slice of `AccountInfo` representing the accounts required for this instruction. +/// +/// # Returns +/// * `ProgramResult` - Returns `Ok(())` if the delegation is successful, otherwise returns an appropriate `ProgramError`. +pub fn process_revoke_delegate_token_account( + program_id: &Pubkey, + accounts: &[AccountInfo], +) -> ProgramResult { + let [config, vault_info, delegate_asset_admin, token_mint, token_account, token_program_info] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + Config::load(program_id, config, false)?; + Vault::load(program_id, vault_info, false)?; + load_signer(delegate_asset_admin, false)?; + load_token_mint(token_mint)?; + load_token_account( + token_account, + vault_info.key, + token_mint.key, + token_program_info, + )?; + // Only the original spl token program is allowed + load_token_program(token_program_info)?; + + // The owner of token mint and token account must match + if token_mint.owner.ne(token_account.owner) { + return Err(ProgramError::InvalidAccountData); + } + + let vault_data = vault_info.data.borrow(); + let vault = Vault::try_from_slice_unchecked(&vault_data)?; + if vault.supported_mint.eq(token_mint.key) { + msg!("Cannot revoke delegation for the supported mint of a vault!"); + return Err(ProgramError::InvalidAccountData); + } + + // The Vault delegate_asset_admin shall be the signer of the transaction + vault.check_delegate_asset_admin(delegate_asset_admin.key)?; + + let vault_seeds = vault.signing_seeds(); + + drop(vault_data); + + // This is compatible with the spl-token and spl-token-2022 programs + let ix = spl_token_2022::instruction::revoke( + token_program_info.key, + token_account.key, + vault_info.key, + &[], + )?; + + invoke_signed( + &ix, + &[ + token_program_info.clone(), + token_account.clone(), + vault_info.clone(), + ], + &[vault_seeds + .iter() + .map(|seed| seed.as_slice()) + .collect::>() + .as_slice()], + )?; + + Ok(()) +} diff --git a/vault_program/src/set_config_secondary_admin.rs b/vault_program/src/set_config_secondary_admin.rs new file mode 100644 index 00000000..223dd244 --- /dev/null +++ b/vault_program/src/set_config_secondary_admin.rs @@ -0,0 +1,40 @@ +use jito_bytemuck::AccountDeserialize; +use jito_jsm_core::loader::load_signer; +use jito_vault_core::config::Config; +use jito_vault_sdk::instruction::ConfigAdminRole; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, +}; + +/// Sets a secondary admin for a specific role in the configuration. +/// +/// This function updates the configuration to set a new account as a secondary administrator +/// with a specific role. +/// +/// - Fee Admin +pub fn process_set_config_secondary_admin( + program_id: &Pubkey, + accounts: &[AccountInfo], + role: ConfigAdminRole, +) -> ProgramResult { + let [config, admin, new_admin] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + Config::load(program_id, config, true)?; + let mut config_data = config.data.borrow_mut(); + let config = Config::try_from_slice_unchecked_mut(&mut config_data)?; + load_signer(admin, false)?; + + config.check_admin(admin.key)?; + + match role { + ConfigAdminRole::FeeAdmin => { + config.fee_admin = *new_admin.key; + msg!("Fee admin set to {:?}", new_admin.key); + } + } + + Ok(()) +} diff --git a/vault_program/src/warmup_vault_ncn_ticket.rs b/vault_program/src/warmup_vault_ncn_ticket.rs index 291cb695..b0a96e5a 100644 --- a/vault_program/src/warmup_vault_ncn_ticket.rs +++ b/vault_program/src/warmup_vault_ncn_ticket.rs @@ -48,5 +48,11 @@ pub fn process_warmup_vault_ncn_ticket( return Err(VaultError::VaultNcnTicketFailedWarmup.into()); } + msg!( + "WARMUP VAULT_NCN_TICKET: Vault {} activating NCN {}", + vault_ncn_ticket.vault, + vault_ncn_ticket.ncn, + ); + Ok(()) } diff --git a/vault_sdk/src/instruction.rs b/vault_sdk/src/instruction.rs index e29892d6..84b332bb 100644 --- a/vault_sdk/src/instruction.rs +++ b/vault_sdk/src/instruction.rs @@ -223,6 +223,15 @@ pub enum VaultInstruction { #[account(6, name = "token_program")] DelegateTokenAccount, + /// Revoke Delegate of the token account + #[account(0, name = "config")] + #[account(1, name = "vault")] + #[account(2, signer, name = "delegate_asset_admin")] + #[account(3, name = "token_mint")] + #[account(4, writable, name = "token_account")] + #[account(5, name = "token_program")] + RevokeDelegateTokenAccount, + /// Changes the signer for vault admin #[account(0, name = "config")] #[account(1, writable, name = "vault")] @@ -320,6 +329,18 @@ pub enum VaultInstruction { #[account(2, name = "new_admin")] SetConfigAdmin, + /// Changes the secondary admin for the config + #[account(0, name = "config")] + #[account(1, signer, name = "admin")] + #[account(2, name = "new_admin")] + SetConfigSecondaryAdmin (ConfigAdminRole), + +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +#[repr(u8)] +pub enum ConfigAdminRole { + FeeAdmin, } #[derive(Debug, BorshSerialize, BorshDeserialize)] diff --git a/vault_sdk/src/sdk.rs b/vault_sdk/src/sdk.rs index d0b0ab22..aa31a3e1 100644 --- a/vault_sdk/src/sdk.rs +++ b/vault_sdk/src/sdk.rs @@ -7,7 +7,7 @@ use solana_program::{ use crate::{ inline_mpl_token_metadata::{self}, - instruction::{VaultAdminRole, VaultInstruction, WithdrawalAllocationMethod}, + instruction::{ConfigAdminRole, VaultAdminRole, VaultInstruction, WithdrawalAllocationMethod}, }; pub fn initialize_config( @@ -302,6 +302,33 @@ pub fn delegate_token_account( } } +#[allow(clippy::too_many_arguments)] +pub fn revoke_delegate_token_account( + program_id: &Pubkey, + config: &Pubkey, + vault: &Pubkey, + delegate_asset_admin: &Pubkey, + token_mint: &Pubkey, + token_account: &Pubkey, + token_program_id: &Pubkey, +) -> Instruction { + let accounts = vec![ + AccountMeta::new_readonly(*config, false), + AccountMeta::new_readonly(*vault, false), + AccountMeta::new_readonly(*delegate_asset_admin, true), + AccountMeta::new_readonly(*token_mint, false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(*token_program_id, false), + ]; + Instruction { + program_id: *program_id, + accounts, + data: VaultInstruction::RevokeDelegateTokenAccount + .try_to_vec() + .unwrap(), + } +} + pub fn set_admin( program_id: &Pubkey, config: &Pubkey, @@ -819,3 +846,24 @@ pub fn set_config_admin( data: VaultInstruction::SetConfigAdmin.try_to_vec().unwrap(), } } + +pub fn set_config_secondary_admin( + program_id: &Pubkey, + config: &Pubkey, + old_admin: &Pubkey, + new_admin: &Pubkey, + role: ConfigAdminRole, +) -> Instruction { + let accounts = vec![ + AccountMeta::new(*config, false), + AccountMeta::new_readonly(*old_admin, true), + AccountMeta::new_readonly(*new_admin, false), + ]; + Instruction { + program_id: *program_id, + accounts, + data: VaultInstruction::SetConfigSecondaryAdmin(role) + .try_to_vec() + .unwrap(), + } +}