From 7a039fd043bcca6ee85f9d02d89f6baf34b029e0 Mon Sep 17 00:00:00 2001 From: magdhz Date: Thu, 26 Jun 2025 13:35:21 -0400 Subject: [PATCH 01/12] Magdhz doc edits (#252) made some edits --------- Co-authored-by: MohammedAlabd Co-authored-by: pbillingsby <45179070+PBillingsby@users.noreply.github.com> Co-authored-by: PBillingsby --- docs/_ncn/00_implementation.md | 1089 ++++++++++++++++++++++++++++++-- 1 file changed, 1027 insertions(+), 62 deletions(-) diff --git a/docs/_ncn/00_implementation.md b/docs/_ncn/00_implementation.md index 79fc81e8..08baaa1c 100644 --- a/docs/_ncn/00_implementation.md +++ b/docs/_ncn/00_implementation.md @@ -7,9 +7,9 @@ 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. +Node Consensus Networks (NCN) are robust consensus networks built on Solana. They enables network participants to leverage staked assets to agree on critical network decisions. NCNs utilize 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. +This tutorial focuses on a [pre-built NCN program](https://github.com/jito-foundation/ncn-template) that serves as 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. @@ -42,7 +42,13 @@ Operators are accounts that receive delegated stake from vaults and actively par 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 +#### 3. Keepers +Keepers are offchain agents that monitor the network and submit onchain instructions to advance the NCN through its lifecycle. They operate autonomously and are fully permissionless. Their responsibilities include: +1. Monitoring the current onchain state. +2. Executing program instructions to progress through state like voting, post-vote logging and epoch finalization. +3. Emitting metrics or logs to external systems for observability. + +#### 4. 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: @@ -83,11 +89,11 @@ Our example NCN Program facilitates consensus on a simple "weather status" using The program uses several types of accounts: -1. **Global Accounts**: Initialized once at the start and updated infrequently. +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. +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). @@ -95,7 +101,7 @@ The program uses several types of accounts: - **[`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. +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. @@ -129,7 +135,7 @@ The onchain program is written in Rust (without using the Anchor framework) and The instructions are broadly categorized: -1. **Admin Instructions**: These require administrator privileges and are used for initial setup and configuration. +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. @@ -137,7 +143,7 @@ The instructions are broadly categorized: - `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. +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. @@ -151,7 +157,7 @@ The instructions are broadly categorized: - `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. +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). @@ -168,8 +174,8 @@ The simulation test we'll be creating below can also be found in the [example NC 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: +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 @@ -204,9 +210,11 @@ mod tests { // 7. VOTING - // 8. VERIFICATION + // 8. REWARDS DISTRIBUTION + + // 9. VERIFICATION - // 9. CLEANUP + // 10. CLEANUP Ok(()) } @@ -292,10 +300,10 @@ let delegations = [ 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. +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 @@ -339,10 +347,10 @@ This `add_operators_to_test_ncn` function performs several actions by calling in 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`). +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](/) @@ -437,7 +445,7 @@ 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 +- 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 @@ -448,8 +456,8 @@ Each operator accumulates voting power from all the different delegations they r **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. +- 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: @@ -472,8 +480,8 @@ Each operator accumulates voting power from all the different delegations they r **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. +- 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: @@ -617,10 +625,10 @@ for vault in test_ncn.vaults.iter() { 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 +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. @@ -630,10 +638,10 @@ This registration process establishes the complete set of vaults that can contri 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 +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. @@ -690,14 +698,14 @@ ncn_program_client The weight table mechanism handles the token weights for the current epoch in two stages: -1. **Weight Table Initialization**: +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**: +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. @@ -848,17 +856,17 @@ The [`BallotBox`](#ballotbox) becomes the central repository where all votes are 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: +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: +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**: +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. @@ -1036,11 +1044,305 @@ The voting process incorporates several key security features: 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 +#### 8. Rewards Distribution + +After consensus is reached, the NCN system can distribute rewards to participants based on their contributions to the consensus process. The rewards system operates through a multi-layered distribution mechanism that allocates rewards to different stakeholders: the Protocol, the NCN itself, operators, and vaults. + +The reward distribution process consists of three main phases: + +1. **Router Initialization**: Setting up the infrastructure for reward routing +2. **NCN Reward Routing and Distributing**: Routing and distributing rewards according to the fee structure to the protocol and the NCN, and to the Operator_Vault couples +3. **Operator Vault Reward Routing and Distributing**: Routing and distributing rewards to operators and their delegated vaults + +##### 8.1 Reward Router Initialization + +Before rewards can be distributed, the system must initialize reward routers that manage the flow of rewards to different participants. + +Copy and paste the following code at the bottom of your test function: + +```rust +// Setup reward routers for NCN and operators +{ + let ncn = test_ncn.ncn_root.ncn_pubkey; + let clock = fixture.clock().await; + let epoch = clock.epoch; + + ncn_program_client + .do_full_initialize_ncn_reward_router(ncn, epoch) + .await?; + + for operator_root in test_ncn.operators.iter() { + let operator = operator_root.operator_pubkey; + + ncn_program_client + .do_initialize_operator_vault_reward_router(ncn, operator, epoch) + .await?; + } +} +``` + +This step creates the infrastructure for reward distribution: + +- **NCN Reward Router**: A primary router that receives all rewards and distributes them according to the configured fee structure. It manages the overall reward pool and calculates allocations for Protocol, NCN, and operator rewards. +- **Operator Vault Reward Routers**: Individual routers for each operator that manage the distribution of rewards to operators and their associated vaults. These handle the final distribution to operators and their delegated vaults. + +The reward routers implement a hierarchical distribution system: + +1. All rewards initially flow into the NCN Reward Router +2. The NCN Reward Router distributes rewards based on fee configurations +3. Operator-specific rewards flow through Operator Vault Reward Routers +4. Finally, rewards reach the ultimate recipients (operators and vault holders) + +##### 8.2 NCN Reward Routing and Distribution + +The first phase of reward distribution involves routing rewards into the NCN system and distributing them according to the configured fee structure. + +Copy and paste the following code at the bottom of your test function: + +```rust +// Route rewards into the NCN reward system +{ + let ncn = test_ncn.ncn_root.ncn_pubkey; + let epoch = fixture.clock().await.epoch; + + const REWARD_AMOUNT: u64 = 1_000_000; + + // Advance the clock to ensure we are in a valid time window for reward distribution. + let valid_slots_after_consensus = { + let config = ncn_program_client.get_ncn_config(ncn).await?; + config.valid_slots_after_consensus() + }; + fixture + .warp_slot_incremental(valid_slots_after_consensus + 1) + .await?; + + // Send rewards to the NCN reward receiver + let ncn_reward_receiver = + NCNRewardReceiver::find_program_address(&ncn_program::id(), &ncn, epoch).0; + + fn lamports_to_sol(lamports: u64) -> f64 { + lamports as f64 / 1_000_000_000.0 + } + + let sol_rewards = lamports_to_sol(REWARD_AMOUNT); + ncn_program_client + .airdrop(&ncn_reward_receiver, sol_rewards) + .await?; + + // Route rewards through the NCN reward system + ncn_program_client.do_route_ncn_rewards(ncn, epoch).await?; + // Should be able to route twice (idempotent operation) + ncn_program_client.do_route_ncn_rewards(ncn, epoch).await?; + + let ncn_reward_router = ncn_program_client.get_ncn_reward_router(ncn, epoch).await?; + + // Distribute Protocol Rewards (4% of total) + { + let rewards = ncn_reward_router.protocol_rewards(); + + if rewards > 0 { + let config = ncn_program_client.get_ncn_config(ncn).await?; + let protocol_fee_wallet = config.fee_config.protocol_fee_wallet(); + + let balance_before = { + let account = fixture.get_account(protocol_fee_wallet).await?; + account.unwrap().lamports + }; + + println!("Distributing {} of Protocol Rewards", rewards); + ncn_program_client + .do_distribute_protocol_rewards(ncn, epoch) + .await?; + + let balance_after = { + let account = fixture.get_account(protocol_fee_wallet).await?; + account.unwrap().lamports + }; + + assert_eq!( + balance_after, + balance_before + rewards, + "Protocol fee wallet balance should increase by the rewards amount" + ); + } + } + + // Distribute NCN Rewards (4% of total) + { + let rewards = ncn_reward_router.ncn_rewards(); + + if rewards > 0 { + let config = ncn_program_client.get_ncn_config(ncn).await?; + let ncn_fee_wallet = config.fee_config.ncn_fee_wallet(); + + let balance_before = { + let account = fixture.get_account(ncn_fee_wallet).await?; + account.unwrap().lamports + }; + + println!("Distributing {} of NCN Rewards", rewards); + ncn_program_client + .do_distribute_ncn_rewards(ncn, epoch) + .await?; + + let balance_after = { + let account = fixture.get_account(ncn_fee_wallet).await?; + account.unwrap().lamports + }; + + assert_eq!( + balance_after, + balance_before + rewards, + "NCN fee wallet balance should increase by the rewards amount" + ); + } + } + + // Distribute Operator Vault Rewards (92% of total) + { + for operator_root in test_ncn.operators.iter() { + let operator = operator_root.operator_pubkey; + + let operator_route = ncn_reward_router.operator_vault_reward_route(&operator); + let rewards = operator_route.rewards().unwrap_or(0); + + if rewards == 0 { + continue; + } + + println!("Distribute NCN Reward {}", rewards); + ncn_program_client + .do_distribute_operator_vault_reward_route(operator, ncn, epoch) + .await?; + } + } +} +``` + +The NCN reward routing process follows these steps: + +1. **Timing Validation**: The system waits for the configured `valid_slots_after_consensus` period to ensure proper timing for reward distribution. +2. **Reward Reception**: Rewards are deposited into the NCN Reward Receiver account, which serves as the entry point for all rewards. +3. **Fee Calculation**: The system automatically calculates different fee categories based on the NCN configuration: + - **Protocol Fees**: 4% allocated to the Protocol for maintaining the underlying restaking infrastructure + - **NCN Fees**: 4% retained by the NCN for operational costs + - **Operator Vault Rewards**: 92% allocated to operators and their delegated vaults + +4. **Distribution Execution**: Each category of rewards is distributed to its respective recipients: + - **Protocol Rewards**: Transferred directly to the configured Protocol fee wallet + - **NCN Rewards**: Transferred to the NCN's fee wallet + - **Operator Vault Rewards**: Routed to individual Operator Vault Reward Routers for further distribution + +The distribution is weighted based on the operators' voting participation and stake weights from the consensus process, ensuring that rewards flow proportionally to participants who contributed to achieving consensus. + +##### 8.3 Operator Vault Reward Routing + +The second phase distributes rewards that were allocated to operators and vaults, managing the final distribution to individual participants. + +Copy and paste the following code at the bottom of your test function: + +```rust +// Route rewards to operators and their delegated vaults +{ + let ncn = test_ncn.ncn_root.ncn_pubkey; + let epoch = fixture.clock().await.epoch; + + for operator_root in test_ncn.operators.iter() { + let operator = operator_root.operator_pubkey; + + // Route rewards to operator and vaults + ncn_program_client + .do_route_operator_vault_rewards(ncn, operator, epoch) + .await?; + // Should be able to route twice (idempotent operation) + ncn_program_client + .do_route_operator_vault_rewards(ncn, operator, epoch) + .await?; + + let operator_vault_reward_router = ncn_program_client + .get_operator_vault_reward_router(operator, ncn, epoch) + .await?; + + // Distribute operator's fee portion + let operator_rewards = operator_vault_reward_router.operator_rewards(); + if operator_rewards > 0 { + ncn_program_client + .do_distribute_operator_rewards(operator, ncn, epoch) + .await?; + } + + // Distribute rewards to vaults that delegated to this operator + for vault_root in test_ncn.vaults.iter() { + let vault = vault_root.vault_pubkey; + + let vault_reward_route = operator_vault_reward_router.vault_reward_route(&vault); + + if let Ok(vault_reward_route) = vault_reward_route { + let vault_rewards = vault_reward_route.rewards(); + + if vault_rewards > 0 { + ncn_program_client + .do_distribute_vault_rewards(vault, operator, ncn, epoch) + .await?; + } + } + } + } +} +``` + +The operator vault reward routing process manages distribution at the most granular level: + +1. **Operator Fee Calculation**: Each operator's configured fee (basis points) is calculated and retained by the operator. This fee is deducted from the total rewards allocated to that operator before vault distribution. +2. **Vault Reward Distribution**: The remaining rewards are distributed to vaults that delegated stake to the operator, proportional to their delegation amounts and token weights. +3. **Proportional Allocation**: Rewards are allocated based on: + - **Delegation Weight**: Larger delegations receive proportionally more rewards + - **Token Weight**: Different token types contribute different weighted values based on the weight table + - **Participation**: Only delegations that contributed to the voting process receive rewards +4. **Idempotent Operations**: The routing operations are designed to be idempotent, meaning they can be called multiple times without adverse effects, ensuring reliability in distributed systems. + +This ensures that the economic incentives align with the security and participation goals of the NCN system. + +##### 8.4 Reward Architecture and Considerations + +The rewards system implements several key architectural principles: + +1. **Multi-Tier Distribution**: + - **Infrastructure Level**: Protocol receives 4% fees for maintaining the underlying restaking infrastructure + - **Network Level**: NCN receives 4% fees for operating the consensus network + - **Operator Level**: Operators receive their configured fee percentage for participation and validation services + - **Delegator Level**: Vault holders receive proportional rewards for providing stake +2. **Proportional Incentives**: + - Rewards are distributed proportionally to stake weight contributions from the epoch snapshot + - Higher token weights result in higher reward allocations + - Active participation in voting is required to receive rewards + - Only operators with valid stake delegations can receive rewards +3. **Configurable Fee Structure**: + - Protocol and NCN fees are set at 4% each in the current implementation + - Operator fees are individually configurable (e.g., 100 basis points = 1%) + - The system supports flexible reward allocation policies through configuration +4. **Economic Security**: + - Reward distribution aligns economic incentives with network security + - Participants are rewarded for honest behavior and penalized for non-participation + - The system creates sustainable incentives for long-term network health + - Rewards are only distributed after consensus is reached +5. **Transparency and Auditability**: + - All reward distributions are recorded on-chain with detailed routing accounts + - The calculation methodology is transparent and verifiable through the reward router accounts + - Historical reward data enables analysis of network economics + - Balance checks ensure accurate reward distribution +6. **Reliability and Safety**: + - Timing constraints ensure rewards are only distributed after consensus finalization + - Idempotent operations prevent double-spending or incorrect distributions + - Balance verification ensures rewards are correctly transferred to recipients + +This comprehensive reward system ensures that all participants in the NCN ecosystem are appropriately compensated for their contributions while maintaining the security and integrity of the consensus mechanism. + +#### 9. 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 +##### 9.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. @@ -1064,10 +1366,10 @@ The first verification step examines the `BallotBox` account for the completed e - `has_winning_ballot()` confirms that the `winning_ballot` field within the `BallotBox` structure is marked as valid. - **Consensus Status Check**: -* **Winning Ballot 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**: +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**: @@ -1075,7 +1377,7 @@ The first verification step examines the `BallotBox` account for the completed e Verifying the `BallotBox` ensures the core voting and tallying mechanism functioned correctly during the active epoch. -##### 8.2 Consensus Result Account Verification +##### 9.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`. @@ -1114,10 +1416,10 @@ The second verification step examines the `ConsensusResult` account, which serve - 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**: +- **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**: +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**: @@ -1132,38 +1434,38 @@ The second verification step examines the `ConsensusResult` account, which serve 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 +##### 9.3 Architecture of Verification and Result Persistence The verification phase highlights several important architectural features: -1. **Dual Records**: +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**: +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**: +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**: +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**: +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 +##### 9.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. +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 +#### 10. 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. @@ -1201,6 +1503,542 @@ This efficient cleanup mechanism allows the NCN system to operate continuously o Now you can save the file and run the test to see the result. +## NCN Keeper +Each NCN relies on off-chain agents called keepers. Keepers are essentially permissionless automation agents that execute all necessary on-chain instructions to advance (“crank”) the NCN through its epoch phases. Anyone can run a keeper. There are no special authorities required to keep the NCN operational. By monitoring network state and calling the NCN program’s instructions at certain times, keepers make sure the NCN progresses correctly and remains in sync with Solana’s epoch. + +This guide provides an overview of how to use the `ncn-program-cli`, a command-line interface for interacting with an NCN program using the [NCN template](https://github.com/jito-foundation/ncn-template). Below, we cover installation, configuration, and step-by-step usage of the CLI, from initial setup through running the NCN keeper to automate state management. + +### Installation and Setup + +Before using the Template NCN Program CLI, ensure you have it installed and configured properly, along with the related Jito (Re)Staking tools: + +1. Build and install the NCN Program CLI: If you have the [NCN program template repo](https://github.com/jito-foundation/ncn-template), compile and install the CLI binary. For example, using Cargo: + + ```bash + # Clone the template repo + git clone git@github.com:jito-foundation/ncn-template.git + cd ncn-template + # Build the CLI from the repository (assuming you're in the repo directory) + cargo build --release + # Install the CLI binary + cargo install --path ./cli --bin ncn-program-cli --locked + ``` + + After installation, verify it works by running: + + ```bash + ncn-program-cli --help + ``` + + This should display the general help and list available subcommands. + +2. Install Jito (Re)Staking CLI (if not already): The NCN program operates alongside Jito’s restaking program. You may need the Jito (Re)Staking CLI (`jito-restaking-cli`) to manage restaking registry tasks (like registering NCNs, operators, and vaults). Install it using Cargo: + + ```bash + cargo install jito-restaking-cli + ``` + + Confirm it is installed: + + ```bash + jito-restaking-cli --help + ``` + +3. Configure Environment Variables: The `ncn-program-cli` accepts configuration through command-line options or environment variables. Optionally, to avoid passing flags every time, you can use a `.env` file for convenience: + + ```bash + # NCN Operator & Program CLI Environment Configuration + # Copy this file to `.env` and update the values below + + # --------------- REQUIRED -------------------- + + # Solana cluster (mainnet, devnet, testnet, or localnet) + CLUSTER=devnet + + # RPC endpoint for your Solana cluster (must support getBlock and transaction history) + RPC_URL=https://api.devnet.solana.com + + # Commitment level for RPC operations (e.g. confirmed or finalized) + COMMITMENT=confirmed + + # On-chain NCN instance address (created by the NCN admin) + NCN= + + # Path to your Solana keypair file (must have admin/operator authority) + KEYPAIR_PATH=~/.config/solana/id.json + + # Operator public key (the account responsible for voting) + OPERATOR=BSia35bXHZx69XzCQeMUnWqZJsUwJURVvuUg8Jup2BcP + + # OpenWeather API key (used by the example weather oracle operator) + OPENWEATHER_API_KEY=your_api_key_here + + # --------------- PROGRAM IDS -------------------- + + # Use these only if you are deploying custom programs + # Otherwise, leave them blank to use defaults + + # NCN Program ID (default: 7rNw1g2ZUCdTrCyVGZwCJLnbp3ssTRK5mdkH8gm9AKE8) + NCN_PROGRAM_ID= + + # Jito Restaking program (default value) + RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q + + # Jito Vault program (default value) + VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 + + # --------------- LOGGING -------------------- + + # Set the Rust log level (e.g., info, debug) + RUST_LOG=info + + ``` + + These variables will be picked up by the CLI, or you can supply equivalent `--rpc-url`, `--ncn-program-id`, `--ncn`, `--keypair-path`, etc., flags to each command. + + +#### Initializing a New NCN Program + +Before running the keeper, some setup and initialization steps are required to configure the NCN program and connect it. Below is a typical workflow for initializing a new NCN: + +1. Fund the Account Payer: The NCN program will create and maintain several temporary accounts (for snapshots, vote tracking, etc.). The program uses a payer account to pay rent for these accounts. You should fund this payer with some SOL to cover rent and fees. The CLI provides a command to transfer SOL from your keypair to the payer account: + +```bash +ncn-program-cli admin-fund-account-payer --amount-in-sol 10 +``` + +This example funds the account payer with 10 SOL. + +2. Create the NCN Config: Initialize the NCN program’s global configuration on-chain. This must be done by the NCN’s `ncn_admin`: + +```bash +ncn-program-cli admin-create-config --tie-breaker-admin +``` + +This creates the NCN’s config account and sets an admin to resolve tied votes or set consensus manually, if needed. You can also override default consensus parameters with options like `--epochs-before-stall`, `--valid-slots-after-consensus`, etc., but in most cases defaults are fine. Run with `--help` to see all available options. + +3. Create the Vault Registry: The Vault Registry is an on-chain account in the NCN program that will list all vaults (stake pools or restaked assets) participating in this particular NCN. Initialize it with: + +```bash +ncn-program-cli create-vault-registry +``` + +This sets up an empty VaultRegistry account. + +4. Register Supported Tokens: Each vault that will contribute stake must be registered under a supported stake token with a weight. The VaultRegistry tracks supported mints and vaults, allowing the snapshot phase to identify which operators hold stake and calculate their voting power: + +--- + +```bash +ncn-program-cli admin-register-st-mint --vault --weight --keypair-path +``` + +For example, if you want to include a vault with mint `ABC...` at weight 100, you’d put that address and weight. This call authorizes that vault for the NCN. Please note that the vault must have already been approved on the restaking program side via a handshake with this NCN. + +### Running the Keeper + +The `keeper` command automates key tasks for each epoch, including creating epoch state accounts, performing stake snapshots, and handling the voting process. It runs continuously while monitoring the blockchain and executing actions based on the current epoch phase. + +To start the keeper, run: + +```bash +ncn-program-cli keeper +``` + +By default, the keeper checks for actions every 10 minutes, retries on errors after 10 seconds, targets the `testnet` cluster and reports metrics using the `solana_metrics` crate with the `local` region label. + +Let’s break down the keeper’s workflow step by step. + +#### 1. Vault Registration + +After registering the stake mints, you need to create entries in the Vault Registry for any vaults that have opted into the NCN. This is a permissionless crank operation: `ncn-program-cli crank-register-vaults`. + +`crank_register_vaults` is a function that registers any unregistered vaults that have been approved by the NCN but not added to the registry yet. It will: + +- Fetch all approved accounts +- Retrieve the current vault registry +- Identify the missing vaults by comparing approved vaults against already registered ones +- Register each missing vault individually + +Once all eligible vaults are registered, the keeper continues its loop by checking and updating the current epoch state. + +#### 2. Fetch Epoch State + +Next, the keeper then reads the current epoch from the Solana cluster using `state.fetch(handler, current_keeper_epoch).await` and fetches the corresponding `EpochState` account from the NCN program. If the account already exists, it loads it into local memory. + +If the epoch has already been marked as complete, the keeper exits the loop early and waits for the next epoch. + +#### 3. Update Epoch state - Syncing local state with on-chain epoch data + +The `update_epoch_state` method ensures the keeper’s in-memory state reflects the latest on-chain data for the current epoch. It performs the following actions: + +- Checks if the epoch is already completed using `get_is_epoch_completed`. If so, it flags the local state and exits early +- Fetches the `EpochState` account +- Validates the account data to make sure it is present and of the correct size. +- Deserializes the account data into an `EpochState` struct. +- Updates the keeper's memory with the deserialized state. +- Determines the current phase of the epoch by calling `update_current_state`. + +This function acts as the gatekeeper. If the epoch is already finished, the keeper skips further processing for that loop iteration. + +#### 4. Core State Machine Operations + +At this point in the loop, the keeper enters its core state machine execution phase, where it actively drives the NCN epoch forward based on its current on-chain state. + +The NCN program defines a set of epoch phases. Each phase requires actions to be executed before the epoch can progress. The keeper reads the current `EpochState`, determines the phase and runs the appropriate handler. + +The epoch lifecycle states are: + +1. `SetWeight` → Establishes voting weight structure for the epoch +2. `Snapshot` → Captures stake distribution across operators +3. `Vote` → This is skipped by the NCN keeper +4. `PostVoteCooldown` → Manages post-consensus waiting period +5. `Distribute` → Distributes rewards to participants based on their contributions +6. `Close` → Cleans up completed epoch accounts + +Each state represents a distinct phase in the epoch lifecycle and the keeper automatically transitions between states as on-chain conditions are met. These operations are permissionless meaning any keeper can execute them when the appropriate conditions are satisfied. It is important to note that this is an example of an NCN’s lifecycle. NCNs may have different states to crank through. + +Let's examine each state handler, starting with the weight setup phase: + +#### `SetWeight` + +The SetWeight state is the first operational phase of each epoch, responsible for establishing the voting power structure that will be used during consensus. This phase uses the function `crank_set_weight` to set up the foundation for stake-weighted voting by creating and populating the weight table. + +This function performs two steps: + +1. **`create_weight_table`** – Initializes and sizes the `WeightTable` account + - Depends on the total number of vaults in the registry + - Prepares a data structure to store weights efficiently on-chain +2. **`set_epoch_weights`** – Calculates and stores each vault’s voting weight + - Fetches the registered stake mints and their weights + - Calculates each vault’s total effective stake based on these weights + - Writes the results into the `WeightTable` account + +Once voting weights are set, the epoch transitions to the Snapshot state, where the current stake distribution across all registered operators is captured. + +#### `Snapshot` + +The Snapshot phase records the current stake distribution across all vault-operator pairs for the epoch. This step guarantees a fixed, on-chain snapshot of delegated stake that will be used in the upcoming consensus vote. + +The `crank_snapshot` function performs several steps: + +1. **Retrieve vaults and operators** + - Fetches all valid vaults from the `VaultRegistry` + - Fetches all registered operators in the NCN +2. **Skips if already finalized** + - If the `EpochSnapshot` has already been finalized, the function exits early and moves on the next state +3. **Loop through each operator** + - Makes sure an `OperatorSnapshot` exists for the current epoch + - Filters vaults that have not yet been recorded in this snapshot +4. **Process vaults** + - Calls `full_vault_update()` to update the vault’s state and stake balances + - Calls `snapshot_vault_operator_delegation()` to record how much stake the vault has delegated to this operator + +This snapshot process creates a record of how much stake is delegated from each vault to each operator. It ensures that consensus voting in the next phase is based on accurate stake amounts. + +#### `Vote` + +This is skipped by the NCN while waiting for the operator to vote. + +#### `PostVoteCooldown` + +The PostVoteCooldown state serves as a buffer between finalizing consensus and performing cleanup. It gives the network time to settle and provides visibility into the outcome of the voting phase. + +The `crank_post_vote_cooldown` function performs two simple but important steps: + +1. **Fetch Result**: Loads the finalized `ConsensusResult` account for the epoch from the chain. +2. **Log Outcome**: Prints the result to the logs for debugging and audit purposes. + +This phase does **not** submit any transactions or mutate state. It simply confirms that consensus has been reached and prepares the system for the final cleanup phase. + +Once completed, the epoch transitions to the **Close** state, where all temporary accounts are cleaned up. + +#### `Distribute` + +The Distribute state allocates rewards to operators and vaults based on their contributions during the epoch. + +The `crank_distribute` function performs the following steps: + +1. **Distribute NCN Rewards:** Calls `distribute_ncn_rewards` to allocate base rewards tied to consensus participation. +2. **Distribute Protocol Rewards:** Invokes `distribute_jito_rewards` to distribute incentives. +3. **Route NCN Receiver Rewards:** If rewards exist for the reward receiver at the NCN-level, routes them using `route_ncn_rewards`. +4. **Operator Vault Reward Routing:** For each operator, it will set up their reward routing and distributes rewards to associated vaults. +5. **Distribute Operator Rewards:** If an operator has accumulated rewards, it distributes them via `distribute_ncn_operator_rewards`. +6. **Distribute Vault Rewards:** Loops through each vault under the operator and distributes rewards via `distribute_ncn_vault_rewards`. + +All reward distribution and routing steps are logged. Errors are non-blocking and distribution will be retried in future keeper loops if any step fails. + +Once completed, the epoch moves to the `Close` state, where the temporary accounts are cleaned up. + +#### `Close` + +The **Close** state marks the end of an NCN’s epoch lifecycle. During this phase, the keeper performs a full cleanup by closing all temporary accounts created during the epoch. This will reclaim rent, free up state, and prepare the NCN for the next epoch. + +The `crank_close_epoch_accounts` function performs the following operations: + +1. **Close Ballot Box** – Closes the `BallotBox` account that tracked consensus voting +2. **Close Operator Snapshots** – Iterates through each operator and closes their `OperatorSnapshot` account +3. **Close Epoch Snapshot** – Closes the global `EpochSnapshot` that captured the operator-vault stake mapping +4. **Close Weight Table** – Closes the `WeightTable` account that stored epoch voting weights +5. **Close Epoch State** – Closes the `EpochState` account that tracked progress through the state machine + +Each closure is attempted independently and any errors are logged. Failures do not block anything. ****The keeper will simply attempt to retry them in subsequent loops. + +#### 5. Timeout and Heartbeat +At the end of each loop, the keeper: + +- Checks whether the epoch has stalled +- If a stall is detected and no actions remain, it waits for the `loop_timeout_ms` duration +- Emits a heartbeat metric with the current tick count +- Starts the next iteration + +This ensures the keeper remains responsive during stalled epochs while continuously reporting liveness for monitoring and reward tracking. + +## Operator + +With NCNs, operators are responsible for driving consensus. While each operator can have its own unique logic, it's up to the NCN designer to define that behavior. Operators perform all computation off-chain and submit votes on-chain during specific windows, using stake delegated by vaults. To simplify their responsibilities, the operator process automates the on-chain tasks for registered operators, primarily casting votes, handling post-vote logic, and reporting metrics. It runs continuously and monitors the state of the network and acts when it's the operator’s turn to participate. In this guide, we'll be looking at a template operator that fetches weather data and votes on the result. + +This process is typically run by the same entity that registered the operator, such as a validator, DAO or data provider participating in the NCN. + +This guide explains how to configure and run the operator using the `ncn-operator-cli` from the [NCN template](https://github.com/jito-foundation/ncn-template). It breaks down the operator loop, details how votes are cast using real-world weather data and walks through the behavior during different epoch states like `Vote`, `PostVoteCooldown`, and `Close`. + +### Installation and Setup + +Before using the Template Operator CLI, install the necessary binaries: + +1. Clone the repo + + ```bash + # Clone the template repo + git clone git@github.com:jito-foundation/ncn-template.git + cd ncn-template + # Build the CLI from the repository (assuming you're in the repo directory) + cargo build --release + # Install the CLI binary + cargo install --path ./cli --bin ncn-operator-cli --locked + ``` + + After installation, verify it works by running: + + ```bash + ncn-operator-cli --help + ``` + +2. Install Jito (Re)Staking CLI (if not already): The NCN program operates alongside Jito’s restaking program. You may need the Jito (Re)Staking CLI (`jito-restaking-cli`) to manage restaking registry tasks (like registering NCNs, operators, and vaults). Install it using Cargo: + + ```bash + cargo install jito-restaking-cli + ``` + + Confirm it works: + + ```bash + jito-restaking-cli --help + ``` + +1. Configure Environment Variables: The `ncn-program-cli` accepts configuration through command-line flags or environment variables. Optionally, to avoid passing flags every time, you can use a `.env` file for convenience: + + ```bash + # Operator Environment Configuration + # Copy this file to `.env` and update the values below + + # --------------- REQUIRED -------------------- + + # Solana cluster (mainnet, devnet, testnet, or localnet) + CLUSTER=devnet + + # Solana RPC endpoint (must support getBlock and transaction history) + RPC_URL=https://api.devnet.solana.com + + # Commitment level for operations (e.g. confirmed or finalized) + COMMITMENT=confirmed + + # Your deployed NCN instance address + NCN= + + # Path to your keypair file (admin/operator authority) + KEYPAIR_PATH=~/.config/solana/id.json + + # Operator public key (the account that votes on-chain) + OPERATOR=BSia35bXHZx69XzCQeMUnWqZJsUwJURVvuUg8Jup2BcP + + # OpenWeather API key for the example oracle operator + OPENWEATHER_API_KEY=your_api_key_here + + # --------------- PROGRAM IDS -------------------- + + # Leave blank to use defaults unless you have custom deployments + NCN_PROGRAM_ID== + RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q + VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 + + # --------------- LOGGING -------------------- + + # Set log level (info, debug, etc.) + RUST_LOG=info + ``` + + These variables will be picked up by the CLI, or you can supply equivalent `--rpc-url`, `--ncn-program-id`, `--ncn`, etc., flags to each command. + +### Running the Operator + +The `run-operator` command automates vote casting and post-vote actions for a registered operator. It runs continuously, monitoring the NCN’s epoch state and executing vote-related instructions when appropriate. It also emits metrics for visibility and debugging. + +To start the operator, run: + +```bash +ncn-program-cli run-operator +``` + +By default, the operator loop checks for actions every 10 minutes, retries on errors after 10 seconds, targets the `testnet` cluster and reports metrics with the `local` region label. + +Let’s break down the operator’s workflow step by step. + +#### 1. Epoch Progression + +Before doing any work, the operator checks whether a new epoch has started by querying the cluster by calling `progress_epoch` if the epoch state is completed. This checks that the operator is aligned with the live on-chain epoch and doesn’t act on stale data. + +The loop progresses through: + +- Advancing to a new epoch if the chain has moved forward +- Looping back to the start of the same epoch if it's marked complete +- Staying on the same epoch if work is still pending + +--- + +#### 2. Fetch or Update Operator State + +The operator maintains an internal `KeeperState` that tracks the current epoch, cached on-chain accounts and the latest `EpochState`. This block loads the latest on-chain data to keep the operator aligned with the current epoch. + +There are two possible paths here: + +- **New Epoch Detected**: + + If the loop has progressed to a new epoch, it calls `state.fetch(...)` which does the following: + + - Sets the operator’s internal epoch value to the current one + - Loads all relevant on-chain accounts + - Calls `update_epoch_state(...)` internally to populate the latest `EpochState` +- **Same Epoch**: + + If the epoch hasn’t changed, it will skip the full fetch and just refresh the `EpochState` using `update_epoch_state(...)` + + This avoids unnecessary on-chain requests and helps keep everything responsive. + + +If either call fails, the operator logs the error and skips the current loop without submitting any vote or metrics. + +--- + +#### 3. Check for Valid EpochState + +After updating its state, the operator then checks if a valid `EpochState` exists. + +If the `EpochState` is missing or not yet initialized on-chain, the operator will: + +- Log that the epoch has no associated state +- Mark the epoch as completed locally +- Skip to the next loop cycle + +This prevents the operator from crashing or spinning unnecessarily while waiting for the epoch to be initialized. + +--- + +#### 4. Core State Machine Operations + +Once the `EpochState` is loaded, the operator identifies the current phase and reacts based on its role as an operator. Only a subset of phases require action. + +It will evaluate internal conditions to determine eligibility. If the operator is permitted to vote in the current phase, it proceeds with the voting logic. + +The epoch lifecycle states are: + +--- + +1. `SetWeight` → Establishes voting weight structure for the epoch. No operator action is needed for this step. +2. `Snapshot` → Captures stake distribution across operators. No operator action is needed for this step. +3. `Vote` → Casts vote +4. `PostVoteCooldown` → Triggers post-vote logic and emits operator metrics. Marks the epoch as completed. +5. `Close` → Cleans up completed epoch accounts + +#### `SetWeight` + +This step is skipped by the operator as no action is needed. + +#### `Snapshot` + +Again, this step is skipped by the operator. + +#### `Vote` + +The `Vote` phase is where the operator performs its most important role: submitting a vote that contributes to the NCN’s consensus process. This phase is only active if the operator has received delegation from at least one vault and has not yet cast a vote for the current epoch. + +During this phase, the operator: + +1. **Loads Required Data** + + It fetches both the `BallotBox` and the `OperatorSnapshot` (which contains data about the operator’s delegation and voting history). These accounts determine whether the operator is eligible to vote and if they’ve already participated in this round. + +2. **Checks Eligibility** + + Using `can_operator_vote(...)`, it will verify that the operator: + + - Has been delegated stake for this epoch + - Has not already voted + - Is listed in the ballot box with an appropriate weight +3. **Casts the Vote** + + If eligible, the operator calls `operator_crank_vote(...)` to submit the vote on-chain. The actual vote content will be determined by the NCN’s logic. In the default template, it maps mock weather data to a vote value. In real NCNs, this would be replaced with your logic and inputs (e.g. price feeds, validator scores, etc.). + +4. **Handles Errors** + + If voting fails, the operator logs the error, delays for the `--error-timeout-ms` and retries the loop. This prevents spammy retries and gives the network time to recover from short lived failures. + +5. **Emits Metrics** + + Once successful, the operator emits the operator vote metrics using `emit_ncn_metrics_operator_vote(...)`. This helps monitor and track vote activity and operator performance in real time. + +6. **Post-Vote Flow** + + If the operator has already voted or is ineligible: + + - The operator instead performs a `post_vote` action which typically submits metadata or confirms the final state + - It emits corresponding post-vote metrics + - Finally, it marks the epoch as complete for this operator and allows the operator to skip this epoch in future iterations + +--- + +#### `PostVoteCooldown` + +This phase is used to report the result of the voting process. + +The operator: + +- Loads the `BallotBox` +- Checks whether consensus was reached +- Logs the outcome of the vote (including weights, operator decisions and winning ballot) +- Emits post-vote metrics + +While no vote is cast, the operator may still submit an on-chain transaction (e.g. metrics or metadata), depending on the implementation. + +#### `Close` + +This phase is similar to `PostVoteCooldown`, but is used at the very end of the epoch. + +The operator once again: + +- Loads the ballot box and logs the final consensus result +- Emits final metrics +- Marks the epoch as completed so the operator loop can progress to the next one + +#### 5. Timeout and Heartbeat + +At the end of each loop, the operator: + +- Waits for `-loop-timeout-ms` duration +- Emits a heartbeat metric with the current tick count +- Starts the loop again + +This helps avoid overloading the RPC and keeps the operator reporting liveness for monitoring dashboards, alerting systems, and reward eligibility checks. ## 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. @@ -1613,3 +2451,130 @@ pub struct EpochAccountStatus { ``` - **Explanation**: Uses `u8` fields to represent the status of various temporary accounts associated with a specific epoch. + +#### NCNRewardRouter + +file: `ncn_reward_router.rs` + +- **Purpose**: The main entry point for routing rewards from NCNs. This router receives rewards and distributes them according to the fee structure. +- **Definition**: + +```rust +#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] +#[repr(C)] +pub struct NCNRewardRouter { + /// NCN the account is associated with + ncn: Pubkey, + /// The epoch the account is associated with + epoch: PodU64, + /// Bump seed for the PDA + bump: u8, + /// Slot the account was created + slot_created: PodU64, + /// Total rewards routed (in lamports) - cumulative amount ever processed + total_rewards: PodU64, + /// Amount of rewards in the reward pool (in lamports) - awaiting distribution + reward_pool: PodU64, + /// Amount of rewards processed (in lamports) - moved out of reward pool for distribution + rewards_processed: PodU64, + /// Reserved space for future fields + reserved: [u8; 128], + /// Last vote index processed during routing (for resuming partial operations) + last_vote_index: PodU16, + /// Last rewards amount being processed during routing (for resuming partial operations) + last_rewards_to_process: PodU64, + /// Rewards allocated to the Protocol (ready for distribution) + protocol_rewards: PodU64, + /// Rewards allocated to the NCN (ready for distribution) + ncn_rewards: PodU64, + /// Total rewards allocated to operator-vault reward receivers (before individual routing) + operator_vault_rewards: PodU64, + /// Individual operator reward routes - tracks rewards per operator + /// Array size 256 limits the number of operators that can participate in an epoch + operator_vault_reward_routes: [OperatorVaultRewardRoute; 256], +} +``` + +- **Explanation**: The router distributes rewards in three tiers: 4% to Protocol, 4% to NCN, and 92% to operator-vault rewards. It supports partial routing through iterations to handle large numbers of operators without hitting transaction limits. + +#### OperatorVaultRewardRouter + +file: `operator_vault_reward_router.rs` + +- **Purpose**: Routes rewards from operators to their associated vaults. This router handles the final stage of reward distribution where operator rewards are further distributed to the vaults they operate. +- **Definition**: + +```rust +#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] +#[repr(C)] +pub struct OperatorVaultRewardRouter { + /// The operator the router is associated with + operator: Pubkey, + /// The NCN the router is associated with + ncn: Pubkey, + /// The epoch the router is associated with + epoch: PodU64, + /// The bump seed for the PDA + bump: u8, + /// The slot the router was created + slot_created: PodU64, + /// The operator's index within the NCN + ncn_operator_index: PodU64, + /// The total rewards that have been routed (in lamports) - cumulative amount ever processed + total_rewards: PodU64, + /// The rewards in the reward pool (in lamports) - awaiting distribution + reward_pool: PodU64, + /// The rewards that have been processed (in lamports) - moved out of reward pool + rewards_processed: PodU64, + /// Rewards allocated to the operator (in lamports) - operator's fee portion + operator_rewards: PodU64, + /// The last rewards amount being processed during routing (for resuming partial operations) + last_rewards_to_process: PodU64, + /// The last vault operator delegation index processed during routing + last_vault_operator_delegation_index: PodU16, + /// Individual vault reward routes - tracks rewards per vault (limited to 64 vaults) + vault_reward_routes: [VaultRewardRoute; 64], +} +``` + +- **Explanation**: The distribution is based on the operator taking their fee percentage first, then remaining rewards are distributed to vaults proportionally by stake weight. It supports partial routing through iterations to handle large numbers of vaults. + +#### OperatorVaultRewardRoute + +file: `ncn_reward_router.rs` + +- **Purpose**: A component structure within `NCNRewardRouter` that tracks rewards allocated to a specific operator within the reward routing system. +- **Definition**: + +```rust +#[derive(Debug, Clone, Copy, Zeroable, Pod, ShankType)] +#[repr(C)] +pub struct OperatorVaultRewardRoute { + /// The operator pubkey + operator: Pubkey, + /// Reward amount allocated to this operator + rewards: NCNRewardRouterRewards, +} +``` + +- **Explanation**: Stores the mapping between an operator and their allocated reward amount within the NCN reward routing system. + +#### VaultRewardRoute + +file: `operator_vault_reward_router.rs` + +- **Purpose**: A component structure within `OperatorVaultRewardRouter` that tracks rewards allocated to a specific vault within the operator's reward distribution. +- **Definition**: + +```rust +#[derive(Debug, Clone, Copy, Zeroable, Pod, ShankType)] +#[repr(C)] +pub struct VaultRewardRoute { + /// The vault pubkey that will receive rewards + vault: Pubkey, + /// The amount of rewards allocated to this vault (in lamports) + rewards: PodU64, +} +``` + +- **Explanation**: Stores the mapping between a vault and its allocated reward amount within an operator's reward distribution system. From 681eaaf2589652fe0062077825137ba8cb84ae17 Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Fri, 27 Jun 2025 05:15:26 +0900 Subject: [PATCH 02/12] CI/CD: Fix jekyll build error (#253) --- .github/workflows/jekyll-gh-pages.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jekyll-gh-pages.yaml b/.github/workflows/jekyll-gh-pages.yaml index 8546b224..5a9755c2 100644 --- a/.github/workflows/jekyll-gh-pages.yaml +++ b/.github/workflows/jekyll-gh-pages.yaml @@ -17,9 +17,10 @@ jobs: - 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 + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly-2024-07-25 + toolchain: stable - name: Generate Cargo Docs run: cargo doc --no-deps - name: Copy Cargo Docs to Jekyll source From 57513292cbc4c2eab01316f6843b0842cd5987f9 Mon Sep 17 00:00:00 2001 From: magdhz Date: Tue, 1 Jul 2025 12:31:54 -0400 Subject: [PATCH 03/12] edits to index, implementation, and terminology (#255) changed some definitions in terminology, updated some language in index. --- docs/_ncn/00_implementation.md | 2 +- docs/_terminology/00_terminology.md | 14 ++++------- docs/index.md | 39 +++++++++++++++++------------ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/docs/_ncn/00_implementation.md b/docs/_ncn/00_implementation.md index 08baaa1c..8c9cb212 100644 --- a/docs/_ncn/00_implementation.md +++ b/docs/_ncn/00_implementation.md @@ -50,7 +50,7 @@ Keepers are offchain agents that monitor the network and submit onchain instruct #### 4. 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: +The NCN Program is the core onchain 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. diff --git a/docs/_terminology/00_terminology.md b/docs/_terminology/00_terminology.md index 93717816..00c86f39 100644 --- a/docs/_terminology/00_terminology.md +++ b/docs/_terminology/00_terminology.md @@ -5,18 +5,14 @@ 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. +- **Node Operator (or Operator)**: A specialized party who runs core functionalities for NCNs, such as performing specific offchain computations and participating in consensus +- **Node Consensus Network (NCN)**: A set of node operators running software for the same network, working together to achieve consensus and provide services. - **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. +- **Staking**: The process of collateraliziung assets to support network operations and earn rewards. +- **(Re)staking**: The process of staking already-staked assets, such as liquid staking tokens. E.g. Whereas users can “stake” SOL to NCNs, users can “(re)stake” JitoSOL to NCNs. +- **Slashing**: A penalty mechanism where a portion of staked assets and/or expected rewards are 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. diff --git a/docs/index.md b/docs/index.md index 8df38605..bc20f00a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,32 +1,32 @@ --- -title: Jito (Re)Staking Overview +title: Jito (Re)staking Overview category: Jekyll layout: post weight: 1 --- -**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. +**Jito (Re)staking** is a universal staking protocol for decentralized networks called node consensus networks (NCNs) built on Solana. It provides a framework for deploying NCNs and staking SPL tokens to them, with flexibility baked in at every level. Altogether, Jito (Re)staking is a coordination protocol for developers to build external networks that use the Solana runtime to enforce custom proof of stake protocol. -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. +The protocol coordinates stake and rewards between three main participants: NCNs, Vaults, and Operators. Developers register NCNs with custom rules. Operators perform arbitrary logic defined by the NCN, whether that’s processing data, orchestrating or coordination tasks, running models, or verifying offchain inputs. Vaults hold staked SPL tokens and delegate them to Operators. 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. +The system consists of two programs: the **Restaking Program** (`RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q`), which acts as an onchain registry for NCNs and Operators, and the **Vault Program** (`Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8`), which manages tokenized stake through Vault Receipt Tokens (VRTs) between participants. Both of these programs gives developers the flexibility to customize network operations and various administrative authorities, including appointing stake delegators, setting fees, making protocol upgrades, and updating supported tokens. Together, the Restaking program and the Vault Program manage stake delegations 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. +Jito (Re)staking is an onchain registry of services, stakers, and node operators on Solana. Its design separates core network services and coordination: - All core activity (computation, validation, data collection) happens offchain. -- All coordination (stake delegation, voting, rewards) is tracked, maintained, and enforced onchain. +- All coordination (stake delegation, voting, rewards distributions) 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. +This split enables scalability and flexibility for developers while retaining cryptoeconomic guarantees from Solana’s base layer. It makes it easier to bootstrap distributed 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 (e.g. one set of staked tokens can secure multiple services) and allows teams to allocate moreresources 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. +An NCN is a decentralized network that reaches consensus on offchain data or workloads and updates onchain state. These networks may include oracle networks, DePIN services, bridges, co-processors, or new chains. In most cases, NCNs will include their own custom onchain programs to handle proof submissions, verify work, and distribute rewards. Consensus can take place onchain or offchain. ### Vaults @@ -40,7 +40,7 @@ Operators are infrastructure providers that run the offchain services for NCNs a ### The Opt-In Handshake -Participation in Jito (Re)Staking is governed on-chain by mutual consent: +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. @@ -48,20 +48,27 @@ Participation in Jito (Re)Staking is governed on-chain by mutual consent: 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 +### 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: +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. +- **Token Utility**: The restaking protocol is completely non-custodial and requires multiple parties to opt-in and coordinate network connections, operations, and rewards distributions, unlocking a path for NCNs to build decentralized networks and install token utility. + +- **Access to professional node operators**: NCNs require different hardware requirements and software competencies. Jito Network is deeply integrated with Solana’s validator ecosystem, which includes a wide range of sophisticated independent operators and institutional operators. This makes it very trivial for NCNs to connect with the industry’s best node operators to participate in their networks, regardless of the underlying network’s hardware and software requirements. + +- **Wide Distribution**: JitoSOL is the largest stakepool and is deeply integrated with the Solana DeFi ecosystem. NCNs can immediately tap into Jito’s network effects without having to attract native stake from scratch. This means, by registering with the Jito (Re)staking framework, bootstrapping and building staked networks is very cost-effective and extremely trivial. - **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. +- **Instant access to Internet Capital Markets**: NCNs have instant access to [Internet Capital Markets](https://multicoin.capital/2025/01/22/the-solana-thesis-internet-capital-markets/). Vaults have the incentive to integrate VRTs across DeFi, creating market structures for native tokens. + +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. +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 @@ -161,11 +168,11 @@ The operation phase is where the network’s value is produced. While all servic ## Critical Concepts -Jito (Re)Staking introduces architectural patterns that enable scalable, secure coordination for independent decentralized services. These include: +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. +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: From ab87da884b976de9d3319a6e5c03ac2fed4c337e Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Wed, 2 Jul 2025 06:25:33 +0900 Subject: [PATCH 04/12] Docs: Remove docs (#254) --- .github/workflows/ci.yaml | 9 +- .github/workflows/jekyll-gh-pages.yaml | 47 - README.md | 8 + docs/_about/01_high_level.md | 42 - docs/_config.yaml | 46 - docs/_ncn/00_implementation.md | 2580 -------------------- docs/_restaking/00_restaking_accounts.md | 109 - docs/_terminology/00_terminology.md | 20 - docs/_tools/00_cli.md | 1078 -------- docs/_vault/00_vault_accounts.md | 125 - docs/assets/images/ncn.png | Bin 167151 -> 0 bytes docs/assets/images/opt_in.png | Bin 80018 -> 0 bytes docs/assets/images/restaking_accounts.png | Bin 45427 -> 0 bytes docs/assets/images/staked_venn_diagram.png | Bin 59450 -> 0 bytes docs/assets/images/vault_accounts.png | Bin 51304 -> 0 bytes docs/assets/images/vault_interactions.png | Bin 180683 -> 0 bytes docs/index.md | 218 +- makefile | 2 - 18 files changed, 12 insertions(+), 4272 deletions(-) delete mode 100644 .github/workflows/jekyll-gh-pages.yaml delete mode 100644 docs/_about/01_high_level.md delete mode 100644 docs/_config.yaml delete mode 100644 docs/_ncn/00_implementation.md delete mode 100644 docs/_restaking/00_restaking_accounts.md delete mode 100644 docs/_terminology/00_terminology.md delete mode 100644 docs/_tools/00_cli.md delete mode 100644 docs/_vault/00_vault_accounts.md delete mode 100644 docs/assets/images/ncn.png delete mode 100644 docs/assets/images/opt_in.png delete mode 100644 docs/assets/images/restaking_accounts.png delete mode 100644 docs/assets/images/staked_venn_diagram.png delete mode 100644 docs/assets/images/vault_accounts.png delete mode 100644 docs/assets/images/vault_interactions.png diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1b7ba3ed..53712518 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,7 +28,7 @@ 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: @@ -43,13 +43,6 @@ jobs: 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 diff --git a/.github/workflows/jekyll-gh-pages.yaml b/.github/workflows/jekyll-gh-pages.yaml deleted file mode 100644 index 5a9755c2..00000000 --- a/.github/workflows/jekyll-gh-pages.yaml +++ /dev/null @@ -1,47 +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 - - name: Setup Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - - 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/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/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 8c9cb212..00000000 --- a/docs/_ncn/00_implementation.md +++ /dev/null @@ -1,2580 +0,0 @@ ---- -title: NCN Implementation Guide -category: Jekyll -layout: post -weight: 1 ---- - -## Introduction - -Node Consensus Networks (NCN) are robust consensus networks built on Solana. They enables network participants to leverage staked assets to agree on critical network decisions. NCNs utilize Jito's restaking infrastructure, allowing operators with delegated tokens to vote on network parameters and states. - -This tutorial focuses on a [pre-built NCN program](https://github.com/jito-foundation/ncn-template) that serves as 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. Keepers -Keepers are offchain agents that monitor the network and submit onchain instructions to advance the NCN through its lifecycle. They operate autonomously and are fully permissionless. Their responsibilities include: -1. Monitoring the current onchain state. -2. Executing program instructions to progress through state like voting, post-vote logging and epoch finalization. -3. Emitting metrics or logs to external systems for observability. - -#### 4. NCN program - -The NCN Program is the core onchain 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. REWARDS DISTRIBUTION - - // 9. VERIFICATION - - // 10. 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. Rewards Distribution - -After consensus is reached, the NCN system can distribute rewards to participants based on their contributions to the consensus process. The rewards system operates through a multi-layered distribution mechanism that allocates rewards to different stakeholders: the Protocol, the NCN itself, operators, and vaults. - -The reward distribution process consists of three main phases: - -1. **Router Initialization**: Setting up the infrastructure for reward routing -2. **NCN Reward Routing and Distributing**: Routing and distributing rewards according to the fee structure to the protocol and the NCN, and to the Operator_Vault couples -3. **Operator Vault Reward Routing and Distributing**: Routing and distributing rewards to operators and their delegated vaults - -##### 8.1 Reward Router Initialization - -Before rewards can be distributed, the system must initialize reward routers that manage the flow of rewards to different participants. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Setup reward routers for NCN and operators -{ - let ncn = test_ncn.ncn_root.ncn_pubkey; - let clock = fixture.clock().await; - let epoch = clock.epoch; - - ncn_program_client - .do_full_initialize_ncn_reward_router(ncn, epoch) - .await?; - - for operator_root in test_ncn.operators.iter() { - let operator = operator_root.operator_pubkey; - - ncn_program_client - .do_initialize_operator_vault_reward_router(ncn, operator, epoch) - .await?; - } -} -``` - -This step creates the infrastructure for reward distribution: - -- **NCN Reward Router**: A primary router that receives all rewards and distributes them according to the configured fee structure. It manages the overall reward pool and calculates allocations for Protocol, NCN, and operator rewards. -- **Operator Vault Reward Routers**: Individual routers for each operator that manage the distribution of rewards to operators and their associated vaults. These handle the final distribution to operators and their delegated vaults. - -The reward routers implement a hierarchical distribution system: - -1. All rewards initially flow into the NCN Reward Router -2. The NCN Reward Router distributes rewards based on fee configurations -3. Operator-specific rewards flow through Operator Vault Reward Routers -4. Finally, rewards reach the ultimate recipients (operators and vault holders) - -##### 8.2 NCN Reward Routing and Distribution - -The first phase of reward distribution involves routing rewards into the NCN system and distributing them according to the configured fee structure. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Route rewards into the NCN reward system -{ - let ncn = test_ncn.ncn_root.ncn_pubkey; - let epoch = fixture.clock().await.epoch; - - const REWARD_AMOUNT: u64 = 1_000_000; - - // Advance the clock to ensure we are in a valid time window for reward distribution. - let valid_slots_after_consensus = { - let config = ncn_program_client.get_ncn_config(ncn).await?; - config.valid_slots_after_consensus() - }; - fixture - .warp_slot_incremental(valid_slots_after_consensus + 1) - .await?; - - // Send rewards to the NCN reward receiver - let ncn_reward_receiver = - NCNRewardReceiver::find_program_address(&ncn_program::id(), &ncn, epoch).0; - - fn lamports_to_sol(lamports: u64) -> f64 { - lamports as f64 / 1_000_000_000.0 - } - - let sol_rewards = lamports_to_sol(REWARD_AMOUNT); - ncn_program_client - .airdrop(&ncn_reward_receiver, sol_rewards) - .await?; - - // Route rewards through the NCN reward system - ncn_program_client.do_route_ncn_rewards(ncn, epoch).await?; - // Should be able to route twice (idempotent operation) - ncn_program_client.do_route_ncn_rewards(ncn, epoch).await?; - - let ncn_reward_router = ncn_program_client.get_ncn_reward_router(ncn, epoch).await?; - - // Distribute Protocol Rewards (4% of total) - { - let rewards = ncn_reward_router.protocol_rewards(); - - if rewards > 0 { - let config = ncn_program_client.get_ncn_config(ncn).await?; - let protocol_fee_wallet = config.fee_config.protocol_fee_wallet(); - - let balance_before = { - let account = fixture.get_account(protocol_fee_wallet).await?; - account.unwrap().lamports - }; - - println!("Distributing {} of Protocol Rewards", rewards); - ncn_program_client - .do_distribute_protocol_rewards(ncn, epoch) - .await?; - - let balance_after = { - let account = fixture.get_account(protocol_fee_wallet).await?; - account.unwrap().lamports - }; - - assert_eq!( - balance_after, - balance_before + rewards, - "Protocol fee wallet balance should increase by the rewards amount" - ); - } - } - - // Distribute NCN Rewards (4% of total) - { - let rewards = ncn_reward_router.ncn_rewards(); - - if rewards > 0 { - let config = ncn_program_client.get_ncn_config(ncn).await?; - let ncn_fee_wallet = config.fee_config.ncn_fee_wallet(); - - let balance_before = { - let account = fixture.get_account(ncn_fee_wallet).await?; - account.unwrap().lamports - }; - - println!("Distributing {} of NCN Rewards", rewards); - ncn_program_client - .do_distribute_ncn_rewards(ncn, epoch) - .await?; - - let balance_after = { - let account = fixture.get_account(ncn_fee_wallet).await?; - account.unwrap().lamports - }; - - assert_eq!( - balance_after, - balance_before + rewards, - "NCN fee wallet balance should increase by the rewards amount" - ); - } - } - - // Distribute Operator Vault Rewards (92% of total) - { - for operator_root in test_ncn.operators.iter() { - let operator = operator_root.operator_pubkey; - - let operator_route = ncn_reward_router.operator_vault_reward_route(&operator); - let rewards = operator_route.rewards().unwrap_or(0); - - if rewards == 0 { - continue; - } - - println!("Distribute NCN Reward {}", rewards); - ncn_program_client - .do_distribute_operator_vault_reward_route(operator, ncn, epoch) - .await?; - } - } -} -``` - -The NCN reward routing process follows these steps: - -1. **Timing Validation**: The system waits for the configured `valid_slots_after_consensus` period to ensure proper timing for reward distribution. -2. **Reward Reception**: Rewards are deposited into the NCN Reward Receiver account, which serves as the entry point for all rewards. -3. **Fee Calculation**: The system automatically calculates different fee categories based on the NCN configuration: - - **Protocol Fees**: 4% allocated to the Protocol for maintaining the underlying restaking infrastructure - - **NCN Fees**: 4% retained by the NCN for operational costs - - **Operator Vault Rewards**: 92% allocated to operators and their delegated vaults - -4. **Distribution Execution**: Each category of rewards is distributed to its respective recipients: - - **Protocol Rewards**: Transferred directly to the configured Protocol fee wallet - - **NCN Rewards**: Transferred to the NCN's fee wallet - - **Operator Vault Rewards**: Routed to individual Operator Vault Reward Routers for further distribution - -The distribution is weighted based on the operators' voting participation and stake weights from the consensus process, ensuring that rewards flow proportionally to participants who contributed to achieving consensus. - -##### 8.3 Operator Vault Reward Routing - -The second phase distributes rewards that were allocated to operators and vaults, managing the final distribution to individual participants. - -Copy and paste the following code at the bottom of your test function: - -```rust -// Route rewards to operators and their delegated vaults -{ - let ncn = test_ncn.ncn_root.ncn_pubkey; - let epoch = fixture.clock().await.epoch; - - for operator_root in test_ncn.operators.iter() { - let operator = operator_root.operator_pubkey; - - // Route rewards to operator and vaults - ncn_program_client - .do_route_operator_vault_rewards(ncn, operator, epoch) - .await?; - // Should be able to route twice (idempotent operation) - ncn_program_client - .do_route_operator_vault_rewards(ncn, operator, epoch) - .await?; - - let operator_vault_reward_router = ncn_program_client - .get_operator_vault_reward_router(operator, ncn, epoch) - .await?; - - // Distribute operator's fee portion - let operator_rewards = operator_vault_reward_router.operator_rewards(); - if operator_rewards > 0 { - ncn_program_client - .do_distribute_operator_rewards(operator, ncn, epoch) - .await?; - } - - // Distribute rewards to vaults that delegated to this operator - for vault_root in test_ncn.vaults.iter() { - let vault = vault_root.vault_pubkey; - - let vault_reward_route = operator_vault_reward_router.vault_reward_route(&vault); - - if let Ok(vault_reward_route) = vault_reward_route { - let vault_rewards = vault_reward_route.rewards(); - - if vault_rewards > 0 { - ncn_program_client - .do_distribute_vault_rewards(vault, operator, ncn, epoch) - .await?; - } - } - } - } -} -``` - -The operator vault reward routing process manages distribution at the most granular level: - -1. **Operator Fee Calculation**: Each operator's configured fee (basis points) is calculated and retained by the operator. This fee is deducted from the total rewards allocated to that operator before vault distribution. -2. **Vault Reward Distribution**: The remaining rewards are distributed to vaults that delegated stake to the operator, proportional to their delegation amounts and token weights. -3. **Proportional Allocation**: Rewards are allocated based on: - - **Delegation Weight**: Larger delegations receive proportionally more rewards - - **Token Weight**: Different token types contribute different weighted values based on the weight table - - **Participation**: Only delegations that contributed to the voting process receive rewards -4. **Idempotent Operations**: The routing operations are designed to be idempotent, meaning they can be called multiple times without adverse effects, ensuring reliability in distributed systems. - -This ensures that the economic incentives align with the security and participation goals of the NCN system. - -##### 8.4 Reward Architecture and Considerations - -The rewards system implements several key architectural principles: - -1. **Multi-Tier Distribution**: - - **Infrastructure Level**: Protocol receives 4% fees for maintaining the underlying restaking infrastructure - - **Network Level**: NCN receives 4% fees for operating the consensus network - - **Operator Level**: Operators receive their configured fee percentage for participation and validation services - - **Delegator Level**: Vault holders receive proportional rewards for providing stake -2. **Proportional Incentives**: - - Rewards are distributed proportionally to stake weight contributions from the epoch snapshot - - Higher token weights result in higher reward allocations - - Active participation in voting is required to receive rewards - - Only operators with valid stake delegations can receive rewards -3. **Configurable Fee Structure**: - - Protocol and NCN fees are set at 4% each in the current implementation - - Operator fees are individually configurable (e.g., 100 basis points = 1%) - - The system supports flexible reward allocation policies through configuration -4. **Economic Security**: - - Reward distribution aligns economic incentives with network security - - Participants are rewarded for honest behavior and penalized for non-participation - - The system creates sustainable incentives for long-term network health - - Rewards are only distributed after consensus is reached -5. **Transparency and Auditability**: - - All reward distributions are recorded on-chain with detailed routing accounts - - The calculation methodology is transparent and verifiable through the reward router accounts - - Historical reward data enables analysis of network economics - - Balance checks ensure accurate reward distribution -6. **Reliability and Safety**: - - Timing constraints ensure rewards are only distributed after consensus finalization - - Idempotent operations prevent double-spending or incorrect distributions - - Balance verification ensures rewards are correctly transferred to recipients - -This comprehensive reward system ensures that all participants in the NCN ecosystem are appropriately compensated for their contributions while maintaining the security and integrity of the consensus mechanism. - -#### 9. 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. - -##### 9.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. - -##### 9.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. - -##### 9.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. - -##### 9.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. - -#### 10. 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. - -## NCN Keeper -Each NCN relies on off-chain agents called keepers. Keepers are essentially permissionless automation agents that execute all necessary on-chain instructions to advance (“crank”) the NCN through its epoch phases. Anyone can run a keeper. There are no special authorities required to keep the NCN operational. By monitoring network state and calling the NCN program’s instructions at certain times, keepers make sure the NCN progresses correctly and remains in sync with Solana’s epoch. - -This guide provides an overview of how to use the `ncn-program-cli`, a command-line interface for interacting with an NCN program using the [NCN template](https://github.com/jito-foundation/ncn-template). Below, we cover installation, configuration, and step-by-step usage of the CLI, from initial setup through running the NCN keeper to automate state management. - -### Installation and Setup - -Before using the Template NCN Program CLI, ensure you have it installed and configured properly, along with the related Jito (Re)Staking tools: - -1. Build and install the NCN Program CLI: If you have the [NCN program template repo](https://github.com/jito-foundation/ncn-template), compile and install the CLI binary. For example, using Cargo: - - ```bash - # Clone the template repo - git clone git@github.com:jito-foundation/ncn-template.git - cd ncn-template - # Build the CLI from the repository (assuming you're in the repo directory) - cargo build --release - # Install the CLI binary - cargo install --path ./cli --bin ncn-program-cli --locked - ``` - - After installation, verify it works by running: - - ```bash - ncn-program-cli --help - ``` - - This should display the general help and list available subcommands. - -2. Install Jito (Re)Staking CLI (if not already): The NCN program operates alongside Jito’s restaking program. You may need the Jito (Re)Staking CLI (`jito-restaking-cli`) to manage restaking registry tasks (like registering NCNs, operators, and vaults). Install it using Cargo: - - ```bash - cargo install jito-restaking-cli - ``` - - Confirm it is installed: - - ```bash - jito-restaking-cli --help - ``` - -3. Configure Environment Variables: The `ncn-program-cli` accepts configuration through command-line options or environment variables. Optionally, to avoid passing flags every time, you can use a `.env` file for convenience: - - ```bash - # NCN Operator & Program CLI Environment Configuration - # Copy this file to `.env` and update the values below - - # --------------- REQUIRED -------------------- - - # Solana cluster (mainnet, devnet, testnet, or localnet) - CLUSTER=devnet - - # RPC endpoint for your Solana cluster (must support getBlock and transaction history) - RPC_URL=https://api.devnet.solana.com - - # Commitment level for RPC operations (e.g. confirmed or finalized) - COMMITMENT=confirmed - - # On-chain NCN instance address (created by the NCN admin) - NCN= - - # Path to your Solana keypair file (must have admin/operator authority) - KEYPAIR_PATH=~/.config/solana/id.json - - # Operator public key (the account responsible for voting) - OPERATOR=BSia35bXHZx69XzCQeMUnWqZJsUwJURVvuUg8Jup2BcP - - # OpenWeather API key (used by the example weather oracle operator) - OPENWEATHER_API_KEY=your_api_key_here - - # --------------- PROGRAM IDS -------------------- - - # Use these only if you are deploying custom programs - # Otherwise, leave them blank to use defaults - - # NCN Program ID (default: 7rNw1g2ZUCdTrCyVGZwCJLnbp3ssTRK5mdkH8gm9AKE8) - NCN_PROGRAM_ID= - - # Jito Restaking program (default value) - RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q - - # Jito Vault program (default value) - VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 - - # --------------- LOGGING -------------------- - - # Set the Rust log level (e.g., info, debug) - RUST_LOG=info - - ``` - - These variables will be picked up by the CLI, or you can supply equivalent `--rpc-url`, `--ncn-program-id`, `--ncn`, `--keypair-path`, etc., flags to each command. - - -#### Initializing a New NCN Program - -Before running the keeper, some setup and initialization steps are required to configure the NCN program and connect it. Below is a typical workflow for initializing a new NCN: - -1. Fund the Account Payer: The NCN program will create and maintain several temporary accounts (for snapshots, vote tracking, etc.). The program uses a payer account to pay rent for these accounts. You should fund this payer with some SOL to cover rent and fees. The CLI provides a command to transfer SOL from your keypair to the payer account: - -```bash -ncn-program-cli admin-fund-account-payer --amount-in-sol 10 -``` - -This example funds the account payer with 10 SOL. - -2. Create the NCN Config: Initialize the NCN program’s global configuration on-chain. This must be done by the NCN’s `ncn_admin`: - -```bash -ncn-program-cli admin-create-config --tie-breaker-admin -``` - -This creates the NCN’s config account and sets an admin to resolve tied votes or set consensus manually, if needed. You can also override default consensus parameters with options like `--epochs-before-stall`, `--valid-slots-after-consensus`, etc., but in most cases defaults are fine. Run with `--help` to see all available options. - -3. Create the Vault Registry: The Vault Registry is an on-chain account in the NCN program that will list all vaults (stake pools or restaked assets) participating in this particular NCN. Initialize it with: - -```bash -ncn-program-cli create-vault-registry -``` - -This sets up an empty VaultRegistry account. - -4. Register Supported Tokens: Each vault that will contribute stake must be registered under a supported stake token with a weight. The VaultRegistry tracks supported mints and vaults, allowing the snapshot phase to identify which operators hold stake and calculate their voting power: - ---- - -```bash -ncn-program-cli admin-register-st-mint --vault --weight --keypair-path -``` - -For example, if you want to include a vault with mint `ABC...` at weight 100, you’d put that address and weight. This call authorizes that vault for the NCN. Please note that the vault must have already been approved on the restaking program side via a handshake with this NCN. - -### Running the Keeper - -The `keeper` command automates key tasks for each epoch, including creating epoch state accounts, performing stake snapshots, and handling the voting process. It runs continuously while monitoring the blockchain and executing actions based on the current epoch phase. - -To start the keeper, run: - -```bash -ncn-program-cli keeper -``` - -By default, the keeper checks for actions every 10 minutes, retries on errors after 10 seconds, targets the `testnet` cluster and reports metrics using the `solana_metrics` crate with the `local` region label. - -Let’s break down the keeper’s workflow step by step. - -#### 1. Vault Registration - -After registering the stake mints, you need to create entries in the Vault Registry for any vaults that have opted into the NCN. This is a permissionless crank operation: `ncn-program-cli crank-register-vaults`. - -`crank_register_vaults` is a function that registers any unregistered vaults that have been approved by the NCN but not added to the registry yet. It will: - -- Fetch all approved accounts -- Retrieve the current vault registry -- Identify the missing vaults by comparing approved vaults against already registered ones -- Register each missing vault individually - -Once all eligible vaults are registered, the keeper continues its loop by checking and updating the current epoch state. - -#### 2. Fetch Epoch State - -Next, the keeper then reads the current epoch from the Solana cluster using `state.fetch(handler, current_keeper_epoch).await` and fetches the corresponding `EpochState` account from the NCN program. If the account already exists, it loads it into local memory. - -If the epoch has already been marked as complete, the keeper exits the loop early and waits for the next epoch. - -#### 3. Update Epoch state - Syncing local state with on-chain epoch data - -The `update_epoch_state` method ensures the keeper’s in-memory state reflects the latest on-chain data for the current epoch. It performs the following actions: - -- Checks if the epoch is already completed using `get_is_epoch_completed`. If so, it flags the local state and exits early -- Fetches the `EpochState` account -- Validates the account data to make sure it is present and of the correct size. -- Deserializes the account data into an `EpochState` struct. -- Updates the keeper's memory with the deserialized state. -- Determines the current phase of the epoch by calling `update_current_state`. - -This function acts as the gatekeeper. If the epoch is already finished, the keeper skips further processing for that loop iteration. - -#### 4. Core State Machine Operations - -At this point in the loop, the keeper enters its core state machine execution phase, where it actively drives the NCN epoch forward based on its current on-chain state. - -The NCN program defines a set of epoch phases. Each phase requires actions to be executed before the epoch can progress. The keeper reads the current `EpochState`, determines the phase and runs the appropriate handler. - -The epoch lifecycle states are: - -1. `SetWeight` → Establishes voting weight structure for the epoch -2. `Snapshot` → Captures stake distribution across operators -3. `Vote` → This is skipped by the NCN keeper -4. `PostVoteCooldown` → Manages post-consensus waiting period -5. `Distribute` → Distributes rewards to participants based on their contributions -6. `Close` → Cleans up completed epoch accounts - -Each state represents a distinct phase in the epoch lifecycle and the keeper automatically transitions between states as on-chain conditions are met. These operations are permissionless meaning any keeper can execute them when the appropriate conditions are satisfied. It is important to note that this is an example of an NCN’s lifecycle. NCNs may have different states to crank through. - -Let's examine each state handler, starting with the weight setup phase: - -#### `SetWeight` - -The SetWeight state is the first operational phase of each epoch, responsible for establishing the voting power structure that will be used during consensus. This phase uses the function `crank_set_weight` to set up the foundation for stake-weighted voting by creating and populating the weight table. - -This function performs two steps: - -1. **`create_weight_table`** – Initializes and sizes the `WeightTable` account - - Depends on the total number of vaults in the registry - - Prepares a data structure to store weights efficiently on-chain -2. **`set_epoch_weights`** – Calculates and stores each vault’s voting weight - - Fetches the registered stake mints and their weights - - Calculates each vault’s total effective stake based on these weights - - Writes the results into the `WeightTable` account - -Once voting weights are set, the epoch transitions to the Snapshot state, where the current stake distribution across all registered operators is captured. - -#### `Snapshot` - -The Snapshot phase records the current stake distribution across all vault-operator pairs for the epoch. This step guarantees a fixed, on-chain snapshot of delegated stake that will be used in the upcoming consensus vote. - -The `crank_snapshot` function performs several steps: - -1. **Retrieve vaults and operators** - - Fetches all valid vaults from the `VaultRegistry` - - Fetches all registered operators in the NCN -2. **Skips if already finalized** - - If the `EpochSnapshot` has already been finalized, the function exits early and moves on the next state -3. **Loop through each operator** - - Makes sure an `OperatorSnapshot` exists for the current epoch - - Filters vaults that have not yet been recorded in this snapshot -4. **Process vaults** - - Calls `full_vault_update()` to update the vault’s state and stake balances - - Calls `snapshot_vault_operator_delegation()` to record how much stake the vault has delegated to this operator - -This snapshot process creates a record of how much stake is delegated from each vault to each operator. It ensures that consensus voting in the next phase is based on accurate stake amounts. - -#### `Vote` - -This is skipped by the NCN while waiting for the operator to vote. - -#### `PostVoteCooldown` - -The PostVoteCooldown state serves as a buffer between finalizing consensus and performing cleanup. It gives the network time to settle and provides visibility into the outcome of the voting phase. - -The `crank_post_vote_cooldown` function performs two simple but important steps: - -1. **Fetch Result**: Loads the finalized `ConsensusResult` account for the epoch from the chain. -2. **Log Outcome**: Prints the result to the logs for debugging and audit purposes. - -This phase does **not** submit any transactions or mutate state. It simply confirms that consensus has been reached and prepares the system for the final cleanup phase. - -Once completed, the epoch transitions to the **Close** state, where all temporary accounts are cleaned up. - -#### `Distribute` - -The Distribute state allocates rewards to operators and vaults based on their contributions during the epoch. - -The `crank_distribute` function performs the following steps: - -1. **Distribute NCN Rewards:** Calls `distribute_ncn_rewards` to allocate base rewards tied to consensus participation. -2. **Distribute Protocol Rewards:** Invokes `distribute_jito_rewards` to distribute incentives. -3. **Route NCN Receiver Rewards:** If rewards exist for the reward receiver at the NCN-level, routes them using `route_ncn_rewards`. -4. **Operator Vault Reward Routing:** For each operator, it will set up their reward routing and distributes rewards to associated vaults. -5. **Distribute Operator Rewards:** If an operator has accumulated rewards, it distributes them via `distribute_ncn_operator_rewards`. -6. **Distribute Vault Rewards:** Loops through each vault under the operator and distributes rewards via `distribute_ncn_vault_rewards`. - -All reward distribution and routing steps are logged. Errors are non-blocking and distribution will be retried in future keeper loops if any step fails. - -Once completed, the epoch moves to the `Close` state, where the temporary accounts are cleaned up. - -#### `Close` - -The **Close** state marks the end of an NCN’s epoch lifecycle. During this phase, the keeper performs a full cleanup by closing all temporary accounts created during the epoch. This will reclaim rent, free up state, and prepare the NCN for the next epoch. - -The `crank_close_epoch_accounts` function performs the following operations: - -1. **Close Ballot Box** – Closes the `BallotBox` account that tracked consensus voting -2. **Close Operator Snapshots** – Iterates through each operator and closes their `OperatorSnapshot` account -3. **Close Epoch Snapshot** – Closes the global `EpochSnapshot` that captured the operator-vault stake mapping -4. **Close Weight Table** – Closes the `WeightTable` account that stored epoch voting weights -5. **Close Epoch State** – Closes the `EpochState` account that tracked progress through the state machine - -Each closure is attempted independently and any errors are logged. Failures do not block anything. ****The keeper will simply attempt to retry them in subsequent loops. - -#### 5. Timeout and Heartbeat -At the end of each loop, the keeper: - -- Checks whether the epoch has stalled -- If a stall is detected and no actions remain, it waits for the `loop_timeout_ms` duration -- Emits a heartbeat metric with the current tick count -- Starts the next iteration - -This ensures the keeper remains responsive during stalled epochs while continuously reporting liveness for monitoring and reward tracking. - -## Operator - -With NCNs, operators are responsible for driving consensus. While each operator can have its own unique logic, it's up to the NCN designer to define that behavior. Operators perform all computation off-chain and submit votes on-chain during specific windows, using stake delegated by vaults. To simplify their responsibilities, the operator process automates the on-chain tasks for registered operators, primarily casting votes, handling post-vote logic, and reporting metrics. It runs continuously and monitors the state of the network and acts when it's the operator’s turn to participate. In this guide, we'll be looking at a template operator that fetches weather data and votes on the result. - -This process is typically run by the same entity that registered the operator, such as a validator, DAO or data provider participating in the NCN. - -This guide explains how to configure and run the operator using the `ncn-operator-cli` from the [NCN template](https://github.com/jito-foundation/ncn-template). It breaks down the operator loop, details how votes are cast using real-world weather data and walks through the behavior during different epoch states like `Vote`, `PostVoteCooldown`, and `Close`. - -### Installation and Setup - -Before using the Template Operator CLI, install the necessary binaries: - -1. Clone the repo - - ```bash - # Clone the template repo - git clone git@github.com:jito-foundation/ncn-template.git - cd ncn-template - # Build the CLI from the repository (assuming you're in the repo directory) - cargo build --release - # Install the CLI binary - cargo install --path ./cli --bin ncn-operator-cli --locked - ``` - - After installation, verify it works by running: - - ```bash - ncn-operator-cli --help - ``` - -2. Install Jito (Re)Staking CLI (if not already): The NCN program operates alongside Jito’s restaking program. You may need the Jito (Re)Staking CLI (`jito-restaking-cli`) to manage restaking registry tasks (like registering NCNs, operators, and vaults). Install it using Cargo: - - ```bash - cargo install jito-restaking-cli - ``` - - Confirm it works: - - ```bash - jito-restaking-cli --help - ``` - -1. Configure Environment Variables: The `ncn-program-cli` accepts configuration through command-line flags or environment variables. Optionally, to avoid passing flags every time, you can use a `.env` file for convenience: - - ```bash - # Operator Environment Configuration - # Copy this file to `.env` and update the values below - - # --------------- REQUIRED -------------------- - - # Solana cluster (mainnet, devnet, testnet, or localnet) - CLUSTER=devnet - - # Solana RPC endpoint (must support getBlock and transaction history) - RPC_URL=https://api.devnet.solana.com - - # Commitment level for operations (e.g. confirmed or finalized) - COMMITMENT=confirmed - - # Your deployed NCN instance address - NCN= - - # Path to your keypair file (admin/operator authority) - KEYPAIR_PATH=~/.config/solana/id.json - - # Operator public key (the account that votes on-chain) - OPERATOR=BSia35bXHZx69XzCQeMUnWqZJsUwJURVvuUg8Jup2BcP - - # OpenWeather API key for the example oracle operator - OPENWEATHER_API_KEY=your_api_key_here - - # --------------- PROGRAM IDS -------------------- - - # Leave blank to use defaults unless you have custom deployments - NCN_PROGRAM_ID== - RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q - VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 - - # --------------- LOGGING -------------------- - - # Set log level (info, debug, etc.) - RUST_LOG=info - ``` - - These variables will be picked up by the CLI, or you can supply equivalent `--rpc-url`, `--ncn-program-id`, `--ncn`, etc., flags to each command. - -### Running the Operator - -The `run-operator` command automates vote casting and post-vote actions for a registered operator. It runs continuously, monitoring the NCN’s epoch state and executing vote-related instructions when appropriate. It also emits metrics for visibility and debugging. - -To start the operator, run: - -```bash -ncn-program-cli run-operator -``` - -By default, the operator loop checks for actions every 10 minutes, retries on errors after 10 seconds, targets the `testnet` cluster and reports metrics with the `local` region label. - -Let’s break down the operator’s workflow step by step. - -#### 1. Epoch Progression - -Before doing any work, the operator checks whether a new epoch has started by querying the cluster by calling `progress_epoch` if the epoch state is completed. This checks that the operator is aligned with the live on-chain epoch and doesn’t act on stale data. - -The loop progresses through: - -- Advancing to a new epoch if the chain has moved forward -- Looping back to the start of the same epoch if it's marked complete -- Staying on the same epoch if work is still pending - ---- - -#### 2. Fetch or Update Operator State - -The operator maintains an internal `KeeperState` that tracks the current epoch, cached on-chain accounts and the latest `EpochState`. This block loads the latest on-chain data to keep the operator aligned with the current epoch. - -There are two possible paths here: - -- **New Epoch Detected**: - - If the loop has progressed to a new epoch, it calls `state.fetch(...)` which does the following: - - - Sets the operator’s internal epoch value to the current one - - Loads all relevant on-chain accounts - - Calls `update_epoch_state(...)` internally to populate the latest `EpochState` -- **Same Epoch**: - - If the epoch hasn’t changed, it will skip the full fetch and just refresh the `EpochState` using `update_epoch_state(...)` - - This avoids unnecessary on-chain requests and helps keep everything responsive. - - -If either call fails, the operator logs the error and skips the current loop without submitting any vote or metrics. - ---- - -#### 3. Check for Valid EpochState - -After updating its state, the operator then checks if a valid `EpochState` exists. - -If the `EpochState` is missing or not yet initialized on-chain, the operator will: - -- Log that the epoch has no associated state -- Mark the epoch as completed locally -- Skip to the next loop cycle - -This prevents the operator from crashing or spinning unnecessarily while waiting for the epoch to be initialized. - ---- - -#### 4. Core State Machine Operations - -Once the `EpochState` is loaded, the operator identifies the current phase and reacts based on its role as an operator. Only a subset of phases require action. - -It will evaluate internal conditions to determine eligibility. If the operator is permitted to vote in the current phase, it proceeds with the voting logic. - -The epoch lifecycle states are: - ---- - -1. `SetWeight` → Establishes voting weight structure for the epoch. No operator action is needed for this step. -2. `Snapshot` → Captures stake distribution across operators. No operator action is needed for this step. -3. `Vote` → Casts vote -4. `PostVoteCooldown` → Triggers post-vote logic and emits operator metrics. Marks the epoch as completed. -5. `Close` → Cleans up completed epoch accounts - -#### `SetWeight` - -This step is skipped by the operator as no action is needed. - -#### `Snapshot` - -Again, this step is skipped by the operator. - -#### `Vote` - -The `Vote` phase is where the operator performs its most important role: submitting a vote that contributes to the NCN’s consensus process. This phase is only active if the operator has received delegation from at least one vault and has not yet cast a vote for the current epoch. - -During this phase, the operator: - -1. **Loads Required Data** - - It fetches both the `BallotBox` and the `OperatorSnapshot` (which contains data about the operator’s delegation and voting history). These accounts determine whether the operator is eligible to vote and if they’ve already participated in this round. - -2. **Checks Eligibility** - - Using `can_operator_vote(...)`, it will verify that the operator: - - - Has been delegated stake for this epoch - - Has not already voted - - Is listed in the ballot box with an appropriate weight -3. **Casts the Vote** - - If eligible, the operator calls `operator_crank_vote(...)` to submit the vote on-chain. The actual vote content will be determined by the NCN’s logic. In the default template, it maps mock weather data to a vote value. In real NCNs, this would be replaced with your logic and inputs (e.g. price feeds, validator scores, etc.). - -4. **Handles Errors** - - If voting fails, the operator logs the error, delays for the `--error-timeout-ms` and retries the loop. This prevents spammy retries and gives the network time to recover from short lived failures. - -5. **Emits Metrics** - - Once successful, the operator emits the operator vote metrics using `emit_ncn_metrics_operator_vote(...)`. This helps monitor and track vote activity and operator performance in real time. - -6. **Post-Vote Flow** - - If the operator has already voted or is ineligible: - - - The operator instead performs a `post_vote` action which typically submits metadata or confirms the final state - - It emits corresponding post-vote metrics - - Finally, it marks the epoch as complete for this operator and allows the operator to skip this epoch in future iterations - ---- - -#### `PostVoteCooldown` - -This phase is used to report the result of the voting process. - -The operator: - -- Loads the `BallotBox` -- Checks whether consensus was reached -- Logs the outcome of the vote (including weights, operator decisions and winning ballot) -- Emits post-vote metrics - -While no vote is cast, the operator may still submit an on-chain transaction (e.g. metrics or metadata), depending on the implementation. - -#### `Close` - -This phase is similar to `PostVoteCooldown`, but is used at the very end of the epoch. - -The operator once again: - -- Loads the ballot box and logs the final consensus result -- Emits final metrics -- Marks the epoch as completed so the operator loop can progress to the next one - -#### 5. Timeout and Heartbeat - -At the end of each loop, the operator: - -- Waits for `-loop-timeout-ms` duration -- Emits a heartbeat metric with the current tick count -- Starts the loop again - -This helps avoid overloading the RPC and keeps the operator reporting liveness for monitoring dashboards, alerting systems, and reward eligibility checks. -## 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. - -#### NCNRewardRouter - -file: `ncn_reward_router.rs` - -- **Purpose**: The main entry point for routing rewards from NCNs. This router receives rewards and distributes them according to the fee structure. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct NCNRewardRouter { - /// NCN the account is associated with - ncn: Pubkey, - /// The epoch the account is associated with - epoch: PodU64, - /// Bump seed for the PDA - bump: u8, - /// Slot the account was created - slot_created: PodU64, - /// Total rewards routed (in lamports) - cumulative amount ever processed - total_rewards: PodU64, - /// Amount of rewards in the reward pool (in lamports) - awaiting distribution - reward_pool: PodU64, - /// Amount of rewards processed (in lamports) - moved out of reward pool for distribution - rewards_processed: PodU64, - /// Reserved space for future fields - reserved: [u8; 128], - /// Last vote index processed during routing (for resuming partial operations) - last_vote_index: PodU16, - /// Last rewards amount being processed during routing (for resuming partial operations) - last_rewards_to_process: PodU64, - /// Rewards allocated to the Protocol (ready for distribution) - protocol_rewards: PodU64, - /// Rewards allocated to the NCN (ready for distribution) - ncn_rewards: PodU64, - /// Total rewards allocated to operator-vault reward receivers (before individual routing) - operator_vault_rewards: PodU64, - /// Individual operator reward routes - tracks rewards per operator - /// Array size 256 limits the number of operators that can participate in an epoch - operator_vault_reward_routes: [OperatorVaultRewardRoute; 256], -} -``` - -- **Explanation**: The router distributes rewards in three tiers: 4% to Protocol, 4% to NCN, and 92% to operator-vault rewards. It supports partial routing through iterations to handle large numbers of operators without hitting transaction limits. - -#### OperatorVaultRewardRouter - -file: `operator_vault_reward_router.rs` - -- **Purpose**: Routes rewards from operators to their associated vaults. This router handles the final stage of reward distribution where operator rewards are further distributed to the vaults they operate. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, AccountDeserialize, ShankAccount)] -#[repr(C)] -pub struct OperatorVaultRewardRouter { - /// The operator the router is associated with - operator: Pubkey, - /// The NCN the router is associated with - ncn: Pubkey, - /// The epoch the router is associated with - epoch: PodU64, - /// The bump seed for the PDA - bump: u8, - /// The slot the router was created - slot_created: PodU64, - /// The operator's index within the NCN - ncn_operator_index: PodU64, - /// The total rewards that have been routed (in lamports) - cumulative amount ever processed - total_rewards: PodU64, - /// The rewards in the reward pool (in lamports) - awaiting distribution - reward_pool: PodU64, - /// The rewards that have been processed (in lamports) - moved out of reward pool - rewards_processed: PodU64, - /// Rewards allocated to the operator (in lamports) - operator's fee portion - operator_rewards: PodU64, - /// The last rewards amount being processed during routing (for resuming partial operations) - last_rewards_to_process: PodU64, - /// The last vault operator delegation index processed during routing - last_vault_operator_delegation_index: PodU16, - /// Individual vault reward routes - tracks rewards per vault (limited to 64 vaults) - vault_reward_routes: [VaultRewardRoute; 64], -} -``` - -- **Explanation**: The distribution is based on the operator taking their fee percentage first, then remaining rewards are distributed to vaults proportionally by stake weight. It supports partial routing through iterations to handle large numbers of vaults. - -#### OperatorVaultRewardRoute - -file: `ncn_reward_router.rs` - -- **Purpose**: A component structure within `NCNRewardRouter` that tracks rewards allocated to a specific operator within the reward routing system. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, ShankType)] -#[repr(C)] -pub struct OperatorVaultRewardRoute { - /// The operator pubkey - operator: Pubkey, - /// Reward amount allocated to this operator - rewards: NCNRewardRouterRewards, -} -``` - -- **Explanation**: Stores the mapping between an operator and their allocated reward amount within the NCN reward routing system. - -#### VaultRewardRoute - -file: `operator_vault_reward_router.rs` - -- **Purpose**: A component structure within `OperatorVaultRewardRouter` that tracks rewards allocated to a specific vault within the operator's reward distribution. -- **Definition**: - -```rust -#[derive(Debug, Clone, Copy, Zeroable, Pod, ShankType)] -#[repr(C)] -pub struct VaultRewardRoute { - /// The vault pubkey that will receive rewards - vault: Pubkey, - /// The amount of rewards allocated to this vault (in lamports) - rewards: PodU64, -} -``` - -- **Explanation**: Stores the mapping between a vault and its allocated reward amount within an operator's reward distribution system. 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 00c86f39..00000000 --- a/docs/_terminology/00_terminology.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Terminology -category: Jekyll -layout: post -weight: 1 ---- - -- **Node Operator (or Operator)**: A specialized party who runs core functionalities for NCNs, such as performing specific offchain computations and participating in consensus -- **Node Consensus Network (NCN)**: A set of node operators running software for the same network, working together to achieve consensus and provide services. -- **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 collateraliziung assets to support network operations and earn rewards. -- **(Re)staking**: The process of staking already-staked assets, such as liquid staking tokens. E.g. Whereas users can “stake” SOL to NCNs, users can “(re)stake” JitoSOL to NCNs. -- **Slashing**: A penalty mechanism where a portion of staked assets and/or expected rewards are 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 580244a7dae323cf4838e93463e2278faa0ec8a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167151 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 diff --git a/docs/assets/images/opt_in.png b/docs/assets/images/opt_in.png deleted file mode 100644 index c22063f25194f208c2d1259ed4fc92628512813c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80018 zcmeFaWmuG3+dmA5$QF?n6a)kb2?0rIl@O!_DQPJwk*CRH<9I$i?|bYI9*o1f*0t7o{?0zv!dFQ_8vpp2;}{qi_%hceR4_39 z0w0gu#>D|YjvGFG1b$%JsYqYN$Y`RP0{@XP){rrgm&afMpK&oTADd%fqlbWhXTU%3 zXgnsy5%3ce{arlP{%`-nh(EIb`N(bbz{;om4>2&rF=QmJs5)cL40@);IC;=4)R`v+ z66)n9vdF*B&bigdqHVsuD4ijF+Wf|h%fk;etG93Yl4;{Gs=W);!B5elN+>W?30SUs zbLvu5kS~{!`U4_yF5D}HGrDiE@FVaaqF$FX9(k7X{^S*8Qtt-UQkhE=%r15r7HikM z3`2xi$1ZQ7AbP_S(ROxmg%!=AX>SG~$XO0#Nd>ZGcp%64i%7HB&em*{z&r;pcq^_zvGJ1WWs*wa8+DSE?<)Xqvn$s z$3xFH!JEG&&i6S|neHhvVRwAS*fu*K&#qjdunx##R8ijNvXL|0sUG+n0`IMZ8FywPNlirJ*F#mfdS^BGd)u2!_MBvrjK2WoVc~>?-0_a(^hf)Xv5`zgw#LfA(Su z+LqHtCuwSN#cfV!4H^E~o>|yhT$Z|@Q3_teEPMaaL6!H<9>c-4?9CZNaACEu9!o0> z{{#4WVL*@L+2icI7m>nn)%1ZU-~BV3oj6qlo>2KS5+tX1VJOK9DdhhVWFC59JcaKQ z{!r$K2Oh=tx;x;Ks_-Yn#W6nd9y;AVx#DZFu*7-igw>e-WH>Fxw=;)I`2O0Ij4(0d z&p5@VlKjbVLX7+Pet83~ zUngNVPWT`BwopnUbkrb@z_#uyemaQcATSo&NqsxR1i8zF^wlPRr!K=wx9jZQWnCD^ z&!4eDZQXYq(+fX~>(R&KV-2rQ%Q}v)1sM-}-p$8v?3CL5%|Co&9t99!*y^NS0oG5AR%*-T%LjVR z093+$&@+B+VKJA$I8MOMl{(2$;lMcvd%P-xt@;aQq4+a4>gXZi4=SSws2f*zqCCU4 z%PMx(lJ4<5-jBEB7zu-E%FN*fGoGGP{x8l}>K?pJPI3^bz0VT50CbTq>62%kbXz}` zl}fviE2P)yqOlS>6BSeU;KIREC=oy@rs{Jnt6k$lG0U^ z@-M>VHL7domR@Y$cj)Ck!FNd;#s7dX;Q%4{MR0^+j+QIG2plBfd zF6gE?7N$K}vTpC_c3>UE(mq;RjE+aTT>vvI7qYg4&W5Cg$r9}`@Oh;?KgXTLAH4X_ zg^TBdY(VOu*j;Haaqpu8au)X{?wD^3;dcF8jMt|tv2{lvh20&@Qw{VHI~C>iZBb}df)6xKV>#XF9hBJ;Ox z#&75r^CAbuHs^cgW2TD6J{}v2WA~hU0WX;?UX$Js`({_cB^kf@J7Zevqrl+T?WmvQ9xgL9Z8-dQ)I_;#0>@F(nFD$36j~2xSaTH}t z^#nrgne3ii+OAa|MseGpnaT%pn{{qv&PUjMY*?D0*0ia;q3de5TCcN~*6jAv1BwsC z^7r@eeQ!6B6E3wru`p8ATVF3rc_FcF{O#Mf2-xj%cg^^_I#qkF>UaCy2NKd9<3rt! zyDQhf1J!f=qMfVg@y#3FU&ky8!|{sU>I-v|YVnme8|GWK;^WIBUp+a-+ z@~bHWwd#VwNBxPQwJs-mWUcQ+3#bOLz4>YBXgNKh6v`$$sBJjeWtXLIs?s68b&#q7qR+SQo8F06zOlHdccbDGioRM(By9F?YL!BVRw~4I;R*|Z z$V!Xq5pi-3-3Em?(U{rZ+$6UF=XAnJCPITed-hs~-b4%~@bY=Btbl#Yc~ur+@duQ6 z&+eBitjLNireb3g#mbj_VmERkdRC|AA*LQLoOwctyt*!z{&dw^oT0d6vx6o4{?fO| zq_OY1!+4+mm=^uPy2uym`-a?y0|6WtRYHepi4)(8P4Pj6vA2o7-MVSD4A!$*N`9dB z+1(K(IUz=0(JT5r?y?g(f4)(;DvZMxDKc$TNv(&v8r;TF@qQ&a&tXUdKhFFMR8?v5{b$ZVz(w=4(9O8Hm!bTfEk}ufmAKn>Q&63nGO=t%SE}q zB(GV1>oKZqz5$aeJL>g32p!d+xfwr*CFD5s$#J$Xq=qMiRXyX*Vo}*yak&JV?aC@a0>V$kwrLhwl{Tc)$76HPH96E4X9;c^simVi{W5k157 znF(12y$Ov|h@d!T3kwT)u!ov7?rxLU`6LBXFZug6OOam#J=bEqBL0QL-uS~VWU&Y#&Z`roxgx4;q z(v@Bc#8Ap(4N10nXrkgvD5*sQr@KzPys;qN$2Wbd-*QFSkf>I`cC@a7&D9%^<QEbS zMl1d)o{YCewSGgkrS>tBN)uZf%N~l)Dy)sXNvEd@ZLz(UtmOKe-py`Lo1NYR8c6LH zqYG(e$2#M7N#COib++%8k9p>$5^wsl&Wns@C%@w_BC;>^G0L8PBXPLLYYGatzNHT~ z#-WC84z@z+wn!Kf7oF1+=8@t~kl3BxoggPCImpVVY`_sHBDFeM2084)#_zejX~6*^ zf(-G2G(Y8&ql~&0l(Mh_VQc%E#=J_ zK+thuoxMa;%&a0$I3uJZ(fl_5RM86sBkadTBVv=aYs__lj@5yk=4IqkmrZ;5Y+(ZV z7y9^ReBtcYp>=@+EGswjE{M{MKH7ZqHYJXLDEtYe&n%+W?VR4@qDM_JLi{Vatea!p zaT{{{-!931J$IGfIBs##0HAqdqlGJ5w1?nb)m)WOU=Y_ zV=0O_o%GYluigZX!7;hf`WJ) z-11QwYxZB!4K`n|OZHID)NQ<|Q`}KjJG~%ulWbGtEKoB_3?P@nf_PNCr{_Y*pNN0a zDVdx43;t3BCk=_4_Ay!1X<%bI)=}!I>6l&c3Y%Mh3C$X2|>m&@U zU+l6tHSaTexi%(P@GWI;YohIq85)n$nL!rrc`095&4jUB zLNy;(T*WOF3Wh=KS?it+MbVaU>!7y|xv^3uu(HTL623)2%hj3QToiSHV~wNv`4D9| z*~yK2%6S|Q>{|(x+cs{B1>BEy=BR;8Y1qOz$BkSB6{v*z2%$qA(%EdX(e!#r8lf41 zW^Poho0m7u&&>AVymhqFj&ZP+uD z&*z%rg3ivKC}eJ3+ko?qCn>+SQchA;si@&4?3yYgKB@e=d{HB-NFh;&FJZQIc(Iqe zkyvUGIrXOEi;q=j`OPY(jIss4=0W@IAlRwyTZ?7FO{m-+K96#Vk~XAe^m4Tu-6l&J zO>M7JXn$h!TUcGN;tB!7>&&D@7r{opM-J|MZ!I`QX=>StasuXSSwdD^h`L+O8`Sq? zt|Qy?`oc2cJnYo`$t_C1SPQVkYtyHPHE?|Rr5 zy&28U`9-}&fvm!_BCQ@THPV8m{SbIN)lkF!w{C9USB5wH)Lokl(C+dw=@YSnlbf&+ zDL5HF$!PFLSZL}trhwi~k<2CQsE>tr`ERL2y=GsOUbDTid2Oc%X;r)W!nJv+EUVSA zPX39^5P54MOgED<9iEJob{jQmEi!%%cNnmfEB5FVrD62o_|{MJ;n9|p1U4m|8ObaE zqE;&Yuu&sy_S8K$X_lG`e{ ztkY-fJ|EK*h)h$$RU*D| zUzpYAYb>O&|sP3(l-|s+s+;1_-0xXyfUr1 z@KKJ+ZbvA{fo6iNJ?DOZ^A;?&vsuI~-E`;#uv+66vg{_BB4W#j<*RZ?YH2hk5Or-i zu5TS|y^RMp11+95bNv$PHG$;}?`0 z9o+m_r^GSP!Os@IR?yiLCNqS$VfRg%%Gc{de5-_^4k|m?b`~GcLqg;HN4y&1FH6wZ zAh43QMW!}LKQ&i!TjI0DC&;}}aN#4f(~yhlso=IV-%cSVzMc;esIw{@3^hS2z?`?S zSX!GmjL0(KX4g<9^qXRk&5*V7wK5N#O)DWY%|$2gD_z;1Xf?X@9f#I$?e!@bx#;Lp*%XE1TZC%=CSK0SIS}gB`j1MRr)bd?;xBn zVvq(CQ1av`+79Pz;8Km+ zbkR@;;=$#mGEiZ7LOY}8`^oYH+`IkC=mEbMLfhO(w%Z-G^RTk5l<)-dxsudWCo z@M!UYC#G89Axz8`GU}jJY7j;6`Hja?JB5KMJ%cB^&$Zrs3i$GbfAJ}JT;>hUiW7d=ibh%i0fQm&{9R6@D&HechK!cdBNW~L71&dz84C70sU5aaHR_+`bM=6EAD zbC*seOImF{jT4duAq|A96XiD_#&bz2;LNS9a&sd+b8Z0FRN8w+WGt^v?nN8W;=cy+ z?G;xyf<5ia`an$QY_DEUO0$Eg_q?wNahQTn2)>H#tZ=^Mw&|v5yt7s^Jf#pfCugLY zb`x!|)u2Vs@}5O$R`rEcK!q&(HE8(rM}t?@wiC=dYzFuOOSYL6+6PRYZ`K5&Sa#sk zqm8S|r6$|D6mL%&_sg{@jwqEX>p2EB>XI8gvXw2}8BMC^XRR$;{iq)r=K||u8m6h4 ze`L_DpVnOXw#XCJ^X1Ezjo$K1davgh!#PHX!OMl5QUquCj>}n6BZBM1<+bXq1?n7x zW{HPiERyh<3pP#M*YrrpForL06ZPX;3};!GhRZ|F`zU9`jBbo_8@0F@^h_w-)!Q8S z+-b0wQHKL^KZ-{9-VrRUGnTg9h#2~NFkCp zo$jt6i;Q~^Vsnco*&f0}wgbf5Q3NN0N_Nr;gPR+ahrVpMR*Tx=kBbp3!QU;owoF)R z4%Rv_3w5P+$uEieuEDaTZJScd6R7ziVt#S!JH_imr&o1Er){mI(Fq3_9tEbEJGa|@ zP@m&`r<p=;=W$9yDj4b5p?XA3o*WS7H6POknaB29&jy)`V zOkVJ}INC=;vh_kL)ajkMWU^A~E7~PJ z<(76xz;{TNp_?yCZD2c0j2mb9h4~WXHeHkv7MwQZ=i;4tdYBESwjjFSor4wZ01O_H z8`68WL5neKZabN1|DFGV`jPI3k=o}`(XlJ7GQx^-?#`#6Cnc8-et zi5~AD^UG+{aE{P(J(!3fi0bllknk{N;U|C*RNC5!p%yO7_-?<}wdKy8NgO<17i%B4 z4zu6FbZ%?8vGq#qc4}MVLLgDE*P}j3^4**UsMeRVD&w$gTWP5qa{t&K#C+ zM6zu<%<1RfPWG3%t(h<>L?<^?JRF{!xHfPiz=FeCrQyE)6Wh&;86}fRGAJMCXw$wp z7fKI~Hw6>dsLgw`%%e;0GKj*Lf|)2)D?m(tLW_58td*Q-~8i zZ^}w93g{o4F#b?vKj8kAnf*q)Ee&`0>brg(QFT3JU$JB{(hLAk;i@W8DdiJ3S^W>go_ zFnV*XpDzuz7ANC<$+iJ_dQO?csas-_9p(uYnYAdjQk(=UZ`myo+G(9J@vit{ZP3d5 z&uB3`sxsp$#w17sMwc!6^tr=*Ggms^v2BqUyfR#$NDQ-XJmt9>Gy~EdFSebTR_yx4 zis_MyamBp;W2|v*5*W$S-N2};kvtJwsiDz@C9yDAcLm+ADqumJeM>EGc@sN|aa+A1 zA>3+}PtBdfvV*qIetlsikUCKXS%;rhl+yM_rKb6DQ3}6x#CaZ`Nvd|iTLaCDu0bVf<~xc+vvHlg6vIar%Sgo*1yLX5gkvuyQf`;~wR&ND)t0$hffj#gAc(fD z23m;FJ`wnD=wSX;>4{_+Yntz7-Uebk3 z=eb^so1(9)_7lyxgwD#e*0q#u0Pz`;sw_q;_GMPXYdbe3Lz8SbCy-+!Xy6l~oXSV-Ms`xMLe zluZ9MgPz2w;*H`Hf|`)1p29R6oU`evfit*CzvqXw||b`?VqAIj7xu;dMAIK+iArzEKYO#oz1vu zuG4mt_uG*;nU8{1)a(VDVT!9AAJ9JE9Xc1wNMJEvgT0pXMtPv!_z|YJ7hE2G2LNh%BmN?U@>4Q~1&r^Q!;}816_84Iy`1pHHPW2kCD9lJA zlVY2NI)NfVvgV0sO*86B{#l2TD@e3pU}C)hJpuCLCLDXq6<62qE?YxfXLF)%R&(D{ z@0}FOUbbr%ErA43s4+w8>zNH=48Jlzknxt~&+2e-kN8>t*1v^CMD^+Mv6E*7RnL^7 zb4jjhqc|#}=X1(OeaMLf1)LZ3Z5KD)qgiR(H)c%5O3E{=IQ%dJb6RgNeF)POlpG$1 z+pbKuLX-RXi%j0=+(gFFYSQLVaEI^ zL}1(%h6k=2tJQ&D-9N8svvCw~SW%9|*&^9JhwQ1oXUW6k#NG9hI9sxf>K@@h{f193 zzkAOOlz6-j@b71*ZPNG7Y$UQRJO|Q0x9-!OM#KA&XF}}WU0^;3^5F=P<(h~n>v9(l z$SxVV6NzO_x(sS<%>7;rMvY%yQhinbV!uu|6>o0^KyIxy5q0Uw@FG=gqqF zAqK#IjUe97JoD($QuBw%4h#f8Rpk0gxSTEQxm;oSGSa>jmwE*cQ*!bv3OTPBQ|dsioFf^b>#{ zcJiX^vDt3-J@iyQIy$tTIrL0qBi|{5hKc5@@4c2RA`i%1Q}-Qp!4I*jqFbmJy(p(r zWbyWo9CW+M+jeIjP0Ki0Y+Hqj37y`|(v9?9R>5F;e?i?@;Z@cG-)?^~H|bL&ugzZy zbiL4Xvz2&drWo*m^wk?0)YslL0l`+DrYVX&dBeo&B!3bmX?soV=nX^rXW_w=(&`zv zryiX*)Da~mH3&4|e@fRtl+Y=mac(tOSq-@1UHAs1N3gv=N)S~)S-2wTgnfE6JbMOY zO0L0`Jho{%IfWC_FT^jQQ(s8hP_g9(Zk<9ir=l?tlfiP%pz<3C*>0&0){H=BRW)pb z*Pe82q_3U_zI?%?=olQ<zyR{xh67xZsbwNCB*gaQKMjy zuCff6t&uJJBXg97ZS)m`*8)=I9&sBDro{=SHK>ZscNKkzPjPkw-L2&m@6Y+%A1$&C za;8Zn@*REVQnS`HQuR1fNR5hhC~jl(k@1!UC9Ef9FkwYIdsexw2xpYS76l3@0D0mL z*rO%%)cB`{PcPcd_T|qx`8LC+SDr-@(zb|#l-erd{VB9x1K4`=R8cyKv#+wv(uX*E zzqkCQ7s{vv0`JLzOw{bo`Vx80y3EL{iP*AGk9Q==GH@doQT_J$%4~Vt5?oA=8Sk=Z#M5VCVz@|o=$qmK-4U)!jZ(c*97>Fb_%_YR`$u8m*NK$Q~R|Et@cK9AzH zuT?^N>oMiW5QYpSZs(h|>y|I1FpH6MCHzzvez2O$uRUR|irrUI?b4dVl5H%7CQYo1 z03s%e%j?&4*75J!pl%ym1#jY~UPDprdV}M5)e}xz%xDyHdSV7+M0wIA@0E>|?4ZCS zYy*1Ls^+XydGY+x)<@j0Gw^`*n`}g{HBmE6B?j@NsNT-Mx~rdDOL*zDjXah~DVbcP zhaEPA5UMIp+J#M+cA5TQSeDUUYoN9?*_nt7b#I7hJzT>@H&F#~%b zFNx_tNnC$5%msU-N@bzpWz@oDxDvq_f~!0Oa=~8%i@$X?-w!=$TRxd|gmE7naQfTX`LaCu zfCld5YR4&onl+0&ZsFQR%8u2C(;rzifWcotDak#)JGg+LXTpnvMy*NAzVn*P4!gmE4=9L zGN=~b8)aR?Gb2#k69L4+zS3-OiKP4za;vYOgHdNb37;l&@Y^Ky#;=)$3)0NW1$X)J zNr)Sh?r0YBOTG1VeG3V2Z7e2e+ZY|N%}{PS-L2*N@>aNn_=Q#f=NYd83(g@FSH7fe zGP>09K-!S^h?mW~oFeO{9sPFpLpe})C<6UnRYDrepgS@Hd~`rcHR3jC(_(SIi{Wgs z6LT)m4$+1zf#RZ1U4Yda>%}LT6f(>oCB#rS3muavFApUno8YAhUCoZzxG#Yd7lPEd zftK)PB4#{{#w4kxJ}GDp=*ttyivZv5yDS^W2Ly)Og@pFM5m%;_`sN#F9)?TQn9u+( zT{e4X_rsX|TRpJfeRQ*lRljCzW3f z_>X7XiHd9mAKQ{1IM_0%GJ@MQ$H&KK?(?l%xA-RBG+m9S%>vy8F`^!>9_9_Ke_um* zSY_yJf1#LI<_jSXu)IFDg;#augX7~Cdqh$qfe!br`S-5H6|21HO9fq$e}8KyTOK)RVf>ix)3C(yW~c?p}G)D7cnb zOhTL03rH2kKIeYmu zf7i!o!kPsNk~1HU(aij{Q++xZgtBLao$nPeIoSg`@JN8H+_|$Urw?CBazdpq2@}pO zm8gl>^k5b)nFuatR7GveL zkNTkx64GXU#sa0~x2D4OlTVesWPQ@mEw-V5VZ1;7bNU0{tZ$7KEV^kn_1=gy-UGQB zVr|5LhQ>v(;8j*}nO8n9phZ27}a(~5^+KN7(~ zcdn?VW}u6O@t44JAydc64mw9)LKcU;e;2^dEr^#K1)I*v0pMn%KAMO|fp{ihQRd^z zhr^LD{DnBSS65e;Y_m0Kyb3kzUxa4pGs`!GbaM~qt&${En#Ln9Ps?f8!)eMDJV@Ij z1>7^f)ndLq_v{xOj(?36#-VpHy?1of@PGPX6_&V=(_ETaqnbZZDa#~;7?E*h=m_a2 zE&f{ziC@vzz+jfb(9+Ogo-qyu17^`ob=tAm>%scf(<}^G1qGqsnfiI3sXl0?&e+TU z28AIP!IoSbh&1D2n9z>si zP7b|{3tGr92QM5h9eaq9y8)aAt>;140F}6omhW^-hr9KB&)lFPv4PdB<7Tp~I34iM zx9qNjGZYX=<2E0FTIC9Q`=(RZcSlxQV#m5UvisBc989}Z&}OGPi&7TC!al>c(3p9V zzsMm{Y8PWXSH7}(-p~H4u?6Qq-N2&@^z))l8WbB>Rqg9&UDK`nyVH_;_?$fMj_?FW z=}8YXIZDR>B?VWW3lh_|T!%VG*YcO=w0$ZtC*9qTk|%V2C$4jb)pN;goAUM119xo( z1?mS+P?H3lzsRVJ7#0haB)q<_30394Hq2AM(+cT$eDKC`0C(`cve6enM4emMPP&z= z7=5|ybkhBkdwZ??gcT>?CWx$64^$Pn6n?M)<1_{Pxa{QB4ipQT0>?4mpV-|nv>sP@ zb!xe{u31<$Vlx@{V91;xFS1={EI?}|s3xBK*37j*X=vF-OY2Nx6uF6qVBNk&SRn&v zY#w@UwrO17R{(p+`EJOevY=*?+%}kS9~(DMhE@toa1rj7yRN==eDA&b+(*k7LqcZ6 zwrVj);ub33zJ(X+|jguzQ;f{ zKJa7TUZ>I|5y*#vLsGG&(K;K;H+t1k-wam+Gc#7w_yL*^+Bbq2G`6aHrm%y23(*tW zmYU$0DOBE@&yhfh9W)-WUxQpjM@PqVaK5JA&$FeYx#kk^OIXp)=d2t1K9RnY;=d+9 zhh$&vHkQW99sKXLEG^w1b~pv}R(v|pvsSY%v%k^LK1WZ|h(?o1WBF}BzOW9QP@@Mz zmI!VUDfd7wN8M*++K1f1ZwDJPI?l$b^`rJdkh6A>vye+%zcP`nv1lAW8d$j_P{{cBMZMslQL=Q^`CM*FktL{``UG;KXCF z>DF_B<6f>7M5uJ;XxTi@cgg&R=QPdAsj$wJuY$`g6Mm4xp+pYF-FO)iBjgmCUVsLD zI`H=H>V#Qv?!rS!9V+utEG7M_xXj0&jjjJ=V2d!2$8ki;sl7xLK55VObtYR}OoqSy z-k1KL=1e>nNKa&EoYtPE<5AK-GZI%#hyC)r+^_9^Bk-Q?D(Kz)Yj=Gu;!yKPp2Wd~ zO3yieE#ExXfasmxPA1!BiTIJDCPXM1xx+W5QDqUf_Nm>{-9AU{tlHG#QnSv zNCRBQH9LQS@<*QWzx4G#cW@3X_xR3v>R+LE7eN0wCj*VdZ4lggLGjS*`NRD#^8Yq! zpc4~Iyb*qregB*>T~_NtGmuR48yuiJG*=fl`Ek|HirYW6Ts#+eJa9@Df6w$xp9haC z<~bM}Gf z^w3iw7F2|m@_viUueByT*I4%j13?&Wvd*MMMUNqgYi4eKLHqXYh76sO&=@gKQGY@u zOfnmZ|({HENQsoI@1Ly+)1ED%^km` z5Vm|$ zfVAEGjkGZU-esaxwD+Wq5evCG`vr7dTvD-WjuVSJ3qb$I%CxMt=v=&VzX04-Nc5j5 z`T@)MMzCzFIS;EnKgxs=oj`jQr1N$`F?B)UT>Q1iC*KYpqR+6$&x0t6$vJ6HdquJX z3nq{URXefsMmG2i76_-WmmH|N_e@`d%-7n>G2Q0-Dl_mD@b(((uZp*y*pa|BU7H&m zL^r|kYU}DYjc!r06Jhz?M;BHfN7lOFP!FI}WB(=1dyIPq6tt9K4$z0wzq5#uC%k*4s3lA14mWXO^3@pdO?HDvOg0frY{GaWSZkj z91QhkHIY7qB61j?B=_h}82(CJIEeMCcrG*WC8lnJjENja(xt$H>SVF~$l(V}etrv))7pN;_e;9}9c;D0TRY?bWlt&p)9&ssWzQ*1WdOiVM=4SMdI#va^K|tc9P&H9 zw!OWvm$>T(jPC1#^&RfVLjCZ-5x2{%c$hjMe>cjmBmyS0%mBhZ?kYt2_f;Q&s1rmQ zK(~T$z+DHIIg|z*4JsbHcG8p=Ja&fTo)Jtt8HB>W0OIb{N&`+)7>rqRZ{(`v-GnpXouHH=?WI;|)A92V*&%eUR06j4 zl?314Q9F2Cj8&CEkBD;7`*3N?cIqZ|YoI zFH$Km<3-$7-)Me!n7mk)= zdy%u~4CQYDSeXctkyb2)Fa8ME|7WL8WC9gFE!Qk?lrDx~frnYM1Uf@>3ro*6cAfY&*`W_A z1ASa_f3Ss@_=&RDNh*P-?0dP9e2eZJP>eAp!aD5A)V^~dwGjmAlHGk%{0A)1yp2bG zk*COKKW%xCSSm4sFxObH>yLPyj4n~v2wg|~Ms9Xh%S+(Cg-mJe`IiU1d|z{boYN@a z0ogolC;iXweyyOC7=e0B-0kr@d|r@E@r4h{~VbZ;F}VBwJj!Py0ol(V}NiL)N1Td|uP zcyGPVgekE;0gl3>kD_bcIq=4uU4d@V2i4o3m)6j>xI+!mvBs--bbiDC8T8=F_^z%T z2NdG12+rR-5N(!R32bmP@T5$3J*f@J?|m=7pdXJaSpBQB?=<&TzkJm2O{^anqLQf;6=e?_~A2QVplJlQIO)dCWf*e~>#xc9g*Ih~#x#c@y0eYP>Z( zW^Z%M9#~$Jl-0}cx{hh`qJt%5P(SerD_PI^D76#}8Aj+ioRZ{YgmP_uU{8l*{biHXKd~q=`5QV`huL_+Mx0(51VMVi=9}e(mlJpv2=Nlwct{LfeO!x z3-X=lo4Zb+Lny)*F~4EEz7|$VNlEX>h(UAv;Cs8I2|h~jM2iGqFXQj)snu9SsQ30$ zD&1$SZEQ@B9XqB|sEIBdCLR|a?096s{~Or7RHrq;bRF>_|E{RPS@qt2n~Ip4;2TL&$rMcKM7+s{0K@8@=<CfBoiZ7B*mTM*bIXo@Rbq>4Qg}12VkE^W{iu zIAXNM|7}_l`nEFtE-c07VyvcLr(pJAAb6B+CO1F7Q@1QDt7TJE7o8x}oySK5>`ugK zuiFO~2Y+Hni3Li!I&~{0Ffb6J;P`zQXkRc{l$!w;Tig*664HF=#de4c zH-fy}T`4gObb}CBuaXhEBNU|I?+#p*KjiJq#0$7tbZ7ey+2YS%y}S)z{_X6WpRUIt z3&QpT_R!DX@{pJR4HWkQ|DQWOo&F<-kQpQ}SlY7xD6A0w3?fh2p)09><`7;7JG@@U zw~MA9O77qf4CKzm-w-#^8H-(yMik4=&JJBvEXc}wD&o9A>jn$t&++#57M1RmJ9v)o zyh7jISiJJaanIrhl0Io^2EBmVBXQV-pg7#vXzA%GmcDd|BL?o^4aRt9yMKEiEhyMG zfef^O&jUjfTD)H?fAMWVo$28^tvyNUPz7lm#HWT`C(Ou*zM|f3e|!Hu3p5ghY@K;V zyy%u5u#@Bp@dj|G1jxOSS`{3^op?rIGkB-2H~s{`e~HD9U+pFIb>8hKBM-yeT{@sK zUj%g=68QYs#@@Yi!}-7Ie);~sf0z6>82q24G86|_*nK?xsm^uwj%X7+9ym^Q?~(=%7HdhGrF|n`&l44-LDG^oc%*XbFQy}&ou)s zLD_ZSeqij!2WSpnHH~=fyto_tqL9*ei4;Dy{%LD#D~#F`{{7e(dV!#&ux;lX=(RNg znk0Sa9I9Tu{5d#`&yKzqCAEXpVgJ>8s$y3b#IK2)XwC1f&q%xkUdxBOEPHC7;tGHcw(#SF>g7L| zyEiyIAFLzIt#ac}R32^sx@8V4_Szo}ZqNej=o0z5^*cCxUy2lPD$ZawzxvH!&dbow z&d!-mb-hL)0%3#Y9zsHHt25se67K-1QPuc5b-Y{WcYo|9J`oFjD_cQ9L0`JIhQ=p4 zLek9s{{C6S5E@(Jp)nd-TAPH}KP2iGADI2cAG#ZM#p!-Q->C+sd>N{u|aokA+4iomt4*M7!qQ1c_>4#A{JD>fd>VuX}2AnodsUy;;UPB zm^(P|qHE#kKNfat`*jeA8>-0epw;{@XaubVsdL`Mcf5N&E7#F@we;~EMS^r28`c%4 zxdE9d(^Xe=iO|C0olc>M}Sx5uCej6hdYdw4j(d1}-p5&r)f)r8+S-7N*xUJ~5#!KhWR*#nI7mdSHOw z>Wki*yakd7clZ}O!Nj@-!WMk_@x4w1{Wh#c+_ZH+h6m!XlNdVSm@@)5iy1lI7}X21 zicEuVJ}xDsZ!9PZ&$0Gxfh#IQt3Z)cyRHthSl;w19DzVMuCt!enJ947CEb9Rn_JE2 zE&Q@Sn==~dwtKp(`z3i=Ld>X3jc;As#pf3EZ_0 z!AD0Iuc7lOKnGYD3q$VO*kmFcoSoO5>w=sXDbfY_*v)Gr-@kv~1xpqD^5wB+8`oka z2UC#zjkMhV%oXDsSnN6YZPz974ow)NGs+SuBAkIH~ zN~X>swB5o(xcD8j*|Q*4*TBj;;4bu+*yDI*+ghEDf#@7!$6`^A%PR!{pPK!YNtEW7v-~Q`{T0z&-vWf|H;rP) z(eNIAEb+zU9L}j*CNm`4GAs220oA_JgOlIHT@OYv2lKWjj5})%g&H3~>P=!GJD8bYbKf$O5aaX@Vn6-}t zrzyA%^7^Nweu%lw%rN^_`+JcGgyNT1bjmtq#H1uY=!O27R1(TN#$!=4n+qT9XgHUuCsMvnBX#hAbN$iRbCJ2+uup z4rDOl^hI#FDYicUD8@xE+dxI~Dg;-vdSmH|U$_h7s;{(s4RIz{zXIFFa_wfay4LGb z^NU;-$`leJbjc*Si0Xu#*CHEAN*$LA9=B%Q5U5sX;V^0OwR3DzAX$Z(dFEXhUQ;$% zQR_Gt3h?Oxdv;H9Bw-SU%UVM`MvCybP0vxfaJ8RhRna3Z?%8A@|B%3vb3t;IK%mM7Zw-A8$JB5NbTWpyi1#p9)1h2NhFiH|*SgnS1Jx2If)JBZUE+F}zKS z^V~>D1SbXt{=(qm8NKW`-O7b5rBUD{n~ zW_^q665^L`JbVsrzZ4V8uFj|U@a*+dIn;X78j+W8ugUA^_4;G>A8DItAPEMawPW_rN`C*Zp9 zYSqoSKzOb*)s84B@1<$VxN}pi)btcsR|I4eViBy=+@C4mg$x6sA z#a;h)gkn*&J`g&Yb5XfaH_(CJyfl-{6QW_EH0}JzAQ}d9_01Y!{*tyMf9A~`Y>SP$yMjOx z(X>-q2eED1A~uL$_q%6W8U)W}^gKC#umi5Y1oZ9;en)U-@ldCR^~`Mm9o7p^+=$S?lkE zX$NgHVX8?2Hm$^vsa(#c2(gP;1m@a>mqv>NAigj|Ux=u+M@fiSx`VBb0?XD5n``b} zfy5Zchc9hKy|lU6inMh<6^tBy{Cab>@08f%MY366hK$9@y7dj_P4`K)*k3dla#8}9 zxwlA&75p&};wR7%3Z-D^m#xYrwkQe*zX(3Vg$y&9S3a*j67$({XpU)0MHm=AU3%Qh zv{}L|Kw?}|iDZ8DR2OlY_sLVIfVvMW+J@o+N4-Wj{iJ(U5w~@xa8L_QE$rN4r``87PR$Sk5ly%LX5k+6hEoxv+6%ed zEarKKF(jPf{LX1$bn9x=AyRP=uNX104alT}<%Fo@b|)*v%le?%jU_?&i`A$Oi-`@K z_qUd>6gI(_)S7L>v^!mc`>(iEftv~xgtd6)9O#n6cM6Tx+o-Pm!|~d4QC^U89%(u6 z5F+m-&VJNzftC90rG|JU8|{+!Oj*FLS*VV26h5Qr!lv zl|GBi{9^-pz^9pMh~Q4QVDS{eH*$%F(By5IpOKEinMJy9Gxef69vi#Q^0&7ZO;dG? zi*oW>zE^JMY46FZYr4CmEb3pyTAoUn>MJOv#8p0O%0WHB-x#-{iO5_&Fwl#a1>&zZ z%?>g*`r}w!oTo?PU?=DlbCj#Pz65Eopx;7g+A%r$4(>^M zJh7>37t@J=>=iytHFWbJ!a$kON3VFgQ`Pj;X{tS|U^ zk{i}0%UoR4n_z2RKcp`y<3Q&!IThXQ zmOs%Eg#k^$tN!XJJpHc0`vaHrnIrds4@l9&^njo1si=R&x8N&N$##*Qcb>}DaS{0e z)$c4e%YgqTNmC2wvS%LGYH+u5=wt##H4P7&ntg`5=Y;8tDF-=#yww<_BP%z!1e*Ji zJ!^?9{1iEPe`g?^ewZWsT>0?8fTbRM@iOVT`276*RokkDIpXcm`4V@rJiHm__Vo}ljR9YfCk^A^xb;|F!iS|`#+icPUvzG8iePeM z$T#;zfZTYI_=McqI&*~+rFCB|8hRGLe(>%}P%)(C!mpn=HXeKvWgF~U=W=3GAO;p| z6kX?|d4jl3T`iPHTsL&He6f~tw(fb2cj<^8DV>1BwCczS$&CeB=hEdS zA!CCGR}pGa_a0Hx%zFkJb!_LlGxcm{qX^O^^iEK+`7fz>j++wSRsVnNy?0bo+w%ts z5fre{EOby*ssaMiAu7c}6%Y^+73ocSFR@Uh>6I!aC{hFg0RgE&Y0^P@4IQKfLJ5J8 zyd4Fvdi8$qTkHMx*6$x>B`4?XGke;e`OM6#v8Ig;cMlbWgV)GjL+*VXDa$F4psb{K zE#ih_o_6tEv)%7a=Me9`{r8ixP7RqI-rfA$z$WY@g)5mZ{nrUp3_;MgMrtg5RfUWDtJ=K+ zrLFFsElsgvB?o4v!j=;%M9A;Zh|AHid88BP_E<3V^~PR;S{Ouh(A{_aY1=-CZaxIv8Ai)+D3us zmFwj;IWO`EcOp}8!Iw0=aaPr1oO|@pa(kbdZE`rhRm&qg_V9=t{ZiV8DY#GhY9DG! zT0msFOassGCy(b0#DwH|aK=?o2>Jr*T@!B8bmhTVnF(UrIoOHJgy$XrPR|j~KP~Gt zx5Fp|UNO1>an7SSDj#t46fOSCzH_gvzleHY8+{KF)%hRq2|-zm6S7GUm+U3~yrDeJ`xM?7ztV(c-FXp>igZ?B~4H@=hqI}eA-NwpF z-cBZJQEdTRP)lkzs86AMog=G#{ju-O8{M?G_^3vlnjedP+2Dbb1VYc4@ALxrB?k8` z82m$8S&8h<6S3V*Mt=S{x(9W%cLGR-_k!ta@@YrUlVch6?^K9AVwCKs>vqf5(?@UC z$eMR0Pzb+*Uhsxf$X5l^Yxvt-X}m~Fn&@fpd6UAl%IM8Z?u`=X%+BzGkjJ*}Mb!lq z`xMmqc295~ryGfwt_8fWJoT4s%P}%~}5?Y` zPa{U-CT?J5udsadEB!Lncu7%FxOqJHdFWEnXG=jDWlg!N@)73QJAt<@J$&zHFuKgY z@gmlamoHv;jrDHehSK2Z`{olf%p=L-rrFLlLJHk2;+wKrQ-UZbRz;anhjK%&vGtx* z{aF81J_`@3b56w@2^Fu%KbNU_o~&(=_uO4w)|v?4`z#BN{^f80+n`!u_HYBKr`dkj zQ=H({gveAuS*n+7@EFVaZ_ZK&IOg+T62{249}A`-^>aOS{a7@OaN?_pJouKCM`R>L zs(g&bTH-{q{bx(i`yYq+aZ?44 zg`%mx&Wqij-*3q7dwl=GJU#oE3SKg#a!h(e_KlmpvT^WLq+O)bK05#3M3J~3RRE>o zaT4YEXf$kp``iq6bqJ(C66=7Eqw>9A#fXNRiX`NPt3tSLA~%WFs71!NnRefz*WW#e zLv#Vi3pd~2d$C%ZvZUl9lz~kY?$Sw%Ne7u7*i-TZwjwg}->l)MyTB+Zs**m*gnB~W zJ_da|oFQtRSqe<@zjrpjKl$q)iRZwg_s>|V{4qTAg9S^21BA%dhE~uIpZ&CuAElV- z_@d*3zVz4Mo>xIecHqE)P-!3RNTol$c+jy1?$%z6@tY*c-=DXM?EjqOYtpXrq=*(V zmGF-Q(LX-v&;VssGd&r7|K-(N8GsG0am@Got;6~Y?K(6-=q0o)UFB!${c_dz8bfGZ zi-k^gg0VeJeA_|$yG!o(?zL`M@cVto_FU$m!59M^Pv-Nv`T2-M#q{64AIyS?pe-SE zB#^*lAs|Be38aRukHFu}hA@le&d<+pCpd|;fbmc{4#5?mUqjZJ`h_XFdsDYd`F`Fo zXc?L8896yZvb*RdOYqeq*Vm+H3x2M}gi9?!`aIn%H8pi!;gI|6C-=X@=gQY^0W8EO zItt-Aj^{Vo<^Cl(DmR*v`y=%5ui;kcO^yUKjnCXxwE*b#wUR0qUj~}WWoF}KiIVbD z9&3jC9*78bB_WFQPeGt9as!^DaV4QeDU2WUeDx(TgWUuuNh{rQfoC4^$v zrBO%Tv0rgY&@Z6kUvs5a9&CED?E@;ncejXr@7p_^LED3rV}hRy!;nP|ro2vebUXBf z)k0iQLtZ$QD3=EVwN4U^ygT1)i&~-|WGi7D^zOM@dCigzd>AUgLE`8sr9{V}eii15 z(?J+}c$6Sd7ktmp2I~#igfM$uxuLH=TN1|lGiU5rogd!R4i-(7iyTATS%U)%&*MU^ z)g4*vqz~|T3o;BJAJ>-MKp+)*EEIzA_Y=)mEEM&THpwunz$2Ean zMwaP5^w}NLW_P|*^Y;bxygUHaQfH%S4k2@=T#4N>wY3%KM@&>s%i%vtBX z-qqb5bhcmd_wvaKh|bDU4n~g@LzdRFy@g4t#}I$-R^B1taoOA^A5CaBs-OzA`fV9! z1uf7XoGj%>p^5d7t&ehT1>O%-v~3WeBiJ`NPCrKTVh*6-KF(xiW*)LBe7Zy0pV!0& z#9O9d5j4Dcymw_UiNbm+5~^8EskPn3fNn zfRVs*C{~z{Z!{rsO*x?L{`cKT+Pe}P)3y(q#LV|SSMpwd4j12I2EwgEDOwDcgWm~WP$ zxTC{oI6$4)tcwq^E=W>Dn}`+ur}7iD0~8MYIuop&1{7YNl2#%60sA*WH!XI-s1a@U zUHpE!2kdL9QpnsnUnn^l=sQPciCycrJ@Ugo+}eO_o$uQ+0g;c8_}}l&1OUGCnj+E; zfmGpFv?N=b?Y-BFxBj+9Cy%`?EY#7pK|FC8`Ed5PmgH{>t8NPO4)X^+B0xK??V>)= zvmXf6$Mjgzv?sN{7oK+j7SeYRr0>AB-n%aN`}^y8>V2nJ{RzQOUVWewRS|FI@9Q49 zUjU;`DWwoWLBpV$Xh@QVG>oKnIqR{*s1(7PK=}HETcm}X+j+JzY(LSG6p{}Jk#1aQ z0UF`Bk>(M`MN4;(B;i-UkDoL8)^{3O{4Ti;l>p1H?-j66AiAw4ButQoo#+YdFG?b+ z=&hm&3GaB$?|0zGobW$cVLT$u)XMyi~8ez{|_$?48{NFcyVHY6OD`w3je%VP454yem*@lh!p=W^ zA{btQ$9s?9QKieO|F1ABiQ^DjlD^*FFV`D@3@7v#v)v-Xj+p=usl!*b>)-Hq#FtkY zMo%>KuB|x(Fh)<-#=?S)^JHVp8s=-Fz5-4mz5o9I;^c4*qAAz&1gMaA_n}xpb;j8Y zRq%4r5k%zFczhHv2b3!%Z6%;^zNa~vW5+ndmZ+*6hZH~^2*U7P@2@c5#G{a<9)Yjm zyp_WM7d>>g04N_?7(fxdnNC3gO!gu3K#Lt+_neES-5EpWSSvFGP!h5JFROo|?v6At zTm^`$i-INe9O|+#2Eo38fjK5fF|p`lb;9xy)6OB_ISkH7^Z6X_x=q( z-*lJ~Mh{9eL5iY_SY@xJ&XKTHTwb1VN=!91R?s+J_Dde^fg^yaDWQpW#1TULi!DAUkPyfb-v; zPrN(*ApihtXikucCGN%M<|Yw4lLzD@FhO$!kTdONMIadIN}Zu==d=xe`IC5p-~d0E z$XaNNY#tRo1?*lgu^2NxKK@-jNR5$ENCW)4J>$R1HgG|{kP(OS9mhT6WjLC6|v_D~+qD zi{7<{Pv8K}_2UsO{L1j!d_NJmQ*w1_Z#fv?do6ks6!UN62mh&1pe`Ub@8psbNdlvy zl>d|}3@W7&#L>Um*{QZ=J3>wouFP@1A7G!#-R;72 zi{`lwVolmVXc!nH)ym%Wm44aEbgAE06JF*7PX6;Oby^77$rFNda&oU+r~03KzFS0uYa5P5RjWQ^q0Tf1axLyTU*OrpDB!=(CS*xvs(G%7GAJa@62_g<6jG5kAcGppT}&=00ArRl1+lKF(?8a)j}M0 z9xv^7W)?Hhb=z`jV>Y|+fS#V7c?7$dFL8EOE<5%qd!6k1@Ppeyd;@Yef-S(2@tPEd z*A01~%DS3NMo7OO@_A3wjKl%dZ{Q$dZehWsMRwEbFvdQos%$`~933&2>OGTMb2_RI zZH-*B&RljS>+K0V^m8TVe!A$Db+wnl1fQ^+WQM4ijypKhtGV?6B5|FVI*fd`Ok?&I z+So`hyfPl8Uua`YoYR}l)V~={02Ag~?2zU?%aiP%V_i(ie;$rBkjK%n&0V#hnRY0z(9yMvj zCj~lX3iNjv#EE`7?i3fYJo-=$F9uo$S}n?Gn=7Sff2{}l+iZdYmNB{Q+L(%GKt67!U?3l*J)m&q(0M2^xL594lli^4RSf2hvDc;`1v6;n;j5OWgB#i&I}iP~2Nh|M zExcjhdP~~JP+(Fk7Qt;RVbpbb_+irV++|sNg%7`m}j})^gSGMm0}-SJnM|V z?{4tjJCCBPH*9#%{bW=Q(J_n@l0yhuX&1C3z}O55&%55<_$&ElfgrS|ejNt5vD}H+ zjf|K`27%e~O;t<(bCq%$@K`Zh6F~Cz^ykYv|^NFl207RAa3_k3v=Ot zpU+D>09X06hQVwWv>*6c;Cw5L0J*|Wl>))~`*4z212N|?egaE6^T^kak}x+ncNbLA zXV?i?Y^S_hwO|zDwc$g+sr90)KL}wui6ern!DO`z+s(OOT_owlg81d0kD&Q8MYsZp z9P`V(U?a2Oe_~Se`Z9%C&tl6&LzLx!$4+Mz5d#hrvo}|X->5a>jed;>`W+F(3zx=l zf-Mrf1Wfg%8D0R;DS&F*Kpy$@8BFeg>l06@N6I#%o3K}7!tp5QL25UKGQZ5vxUly}f) zj2X%BxN5yc{f6^N=--hb(2@Sq5Lt6mld&Og^oYJ8NE7sa_;8L`t3mTD76FT%+fHkV z+_?4E(ZqG~Az~Ffakv4o;R(=BVvA*Qi)xhu%1uA{aj=-Z$`&{-Z^L__8Q+}0&$@pe zV3aur_~wmSmpyjn>x1VI)$3|oG42$PRKI~Fi?xjJLZT_-4TqvK>7Ne*(mo1xHD?& zQx`Go#OcgGP^3}$7TuVf(Ash)awAiz@1PG2+qpzTT(w~%QTcNAqmc}RI!jkIUno6r z37@w%^j%DR2pKD<#6=ilBj$Qd;a)G3g#uRcv%NB>48uyW9`_q}j04mVX6NVo*%0lN zAmSZ^`Fusd7!;*^!8S;bz*R+&KYQ`8UaDXR99NC&oq}PThPr25`obh-t6lnoXH9E- zigR@@`9hn3+ifxQ;ki;`BV??YGFT66q~T!Xd(vPfm{SR<<8rr3qvZC>RosG%QIUeC zlI5ZvMumBxA!MQVY+Z+Zqq}Z3T78VkSF10+w8Q&R?6Wj`Mu@Z ziWmrA4laS}5fxh3be{dhRvh&__r_{Mtbh~Uy-4$wWFRH3gZ_CZiMOT@K`KUe7 z-BA_{*T06Anwy)Uwbs_w`S^`2hpA}NG&4}+W17&zk6R`SJoLk6fE`TKf{ZZ`Bvc;+ z6Ihg*K>bw*QSeR^+v4!@3(m(v#e5Ex>+Zx>x#KIu&qFIkF0Y$1oZ9?;Nc_eLy_5`u zn0Vq)gH~dw!-Ux01^nP0QQR(fukvm)aR;z+M>$5|OgH$S|EvH)=mhcYX$xJt5Hb^f z;I)q(xChEP*!C)K0CAhj$#>b|IlH~el4Qqm!V7GgY(H@qO?0U568ww!*Q52$*c}K&gQUd8KY?tK#{vR@fMX$p_V&~ns z!@!czE04e!Fm=Ok(W|aPx9f^vE;5+SL@&IyFBfpZ?&t2rNJl0t`DXv}>?0pE)7N|{ zky%2y7VG)4?}B!Utkc8Z7>liiRWuhZ{;QPyBwY1ew{QTd^HLG)q7o?VS^aY5)KPUl z*zLcDmvqJyh)f=Iwy=Dl@hrhB`K?n{W|KQkDr*sFSUvwHn{}Xt0PYL zaRSA(=^oUU>>&50JO(y5b@WK!%(i?cABt9_xVWDBZZP}KFH1=UcSsO93)pSA2dLxP z6bBAcNIPBhxJu$;%RBcrw{4x2LZy5L@JD+`#^sWo#eKb1;sNk7Gp#{=z=*{F{Zik- zJagU`lu}(9AfqCzj2G;r$ONy!E?pQ}WTEsB zI(&?}Dfcg()59-Ttg8h|UeE^7Jy6!BK2favh?3?aku?A#iS95G%${N&@eVvn@Ym$O zh^rsiQFCsoJ%A^OnI@xb3wqdY%6<0q+_?6{z2`N*?H=qhA$3i8G^ta zas+uFlED0km8x$lMWdjs!+$Rr{C zRvn*D6A)Vg0+&Q3;jz|hs*kcADvbeE=$VJ-+bOom0)mCd$PQi^XaMag@)stK;g&nI zz4M!!y8aPVCyKu;^eotH4z6#omI2Jr&#LohqWtZmsvm`=@@na5|9a5+!NK_ZVrb$8 zpt*Nd75*$VKk@G0U!@-h;kM+VtG_tKKePJx%fBbi|8Tb-7o>gyD1YA7$m0L>j?G_OW?^{24r{+I?1=bL9=|X0mp`EXfF<_?*#4vd{{2reC1J%AR=m!A&pC118<-R?!^(e?8k&bS+QT`u4(57=OZj6ZE>`}@? zEEj3^&Md?0WwC)+tC1G((#B40v29Is2jWkd56g4GkFnp~BrIX_4Dkq`lGW=uC&P=2 z?Wi$jspXvd_(m^JwJ&^TOA4P338}$mtZMzo!27exNUpn9tPUT&>Tf z&VII#o?2QQwvFAjt!yA5^uV^#@}pm$Aj}r#!LiBRgAMy@O}-T+3u*(6r8(gYCT$QG zFsw%Y{0v^Rn4}fk0;Ylt+j@r~XfR3Jda_Y@*c2but^YttI0O1fjpYNJeJfVr+I#Yy zbgq^j2mYhL!UVoLyG(DwB<;tk<106u+jWfZFNs!cB25FE1PNUJ0x6ZIi?G z`-nweb@sAHDkpUHfWVOdAmcX9-Y`yg<9 z(B4c7263i0VxvdgKFU4N>)lS;N0&lz=LJl5ZDb<71hLN~c5M@z8~6j?(FM2aAx>-r zH;ej61GOV9o*}i~-@ydNbOB0w)x%Gdbvc5i*D`uOUE6-K4Tzmj?qXn=8lkFro3ii7 zgMaWJE+1F#UV>jG^M#19<=x&nK833cvu_RN=W4%pT*ZFLY>CKH-SPft0+6Cx&)B!5 zw-xCvKk)J<0Vv{dHlWL(FQBZoJ|=7=k5@29@G~xlK7Mav6&+E5F7(XP^QBruxU5R$ zzK7@S2u?lsy&3Eju1$|oO7UyQx*_p*rvzbQQq*mVzLo8!8jnn)jzuq5K1N>U^8$070(&C_lV8v`C8$+5% zjiMkZFnM1OLMD_|{9=&{5Ub*nZzCYVAy(302clmb9v}cT0cW= z<1Q!?dkUU5ot)UWO$1G{N!c}xu+Ztxy*G*zb+_GtaMj^w@Mr;pcrD^&U=Rg42h8s` zxIK|_`Q$Tan}wE1)P1k-sAJ6JJ2aVo%5d`EF7ghh0s=Y;YWX9CBc7J34f78m~;K~S$NfX7;_a^WEx{hFA zTg_M=*Z(UWc3$(pb{+i2H+CS47;-^_@n4_6!|5i#Y>RMy?`;3?G!#4lEU_Io@Bf%@ z`R&0K!N91QkfGlFr*+f-J0)^J!FXp~|GJRqryy?>JRJXv`}q6cg2FyB-$hjqGO)oANf?9zGrZ->7j3Wm*vckvWpG@53?GG|FgTR&VA3-HQL(7#$>6_R)}8x zYbfuVj#@no&QmS~i|@U)exX_=A|12fqDZVtV zLL)3YL@|2;c;JDg{`P6@pV}8@K}A8Tormn$Nq28gE|l8#3V)-iZ;_E6{jtqeS`m0g z)7Lr8zt_RA4GId<&QyW?orzR}#2&+T7QDX)HWD{o@HTh`wx*ULwX zszU=uwSQviBSfC#Csun%o*t%+;ALkmb_0_R!?VeGachxUJ-!4Vu-Fa&`;5-rEq zzTmtf3EA-vbI*X_MiSUPMh}H}3YH5THj{X)0Ah%WaF7-6CE%@7fPI8ox`I1){96}J zC^c-ox^{~2fzUO?^r6piy{_p6!C>nJd>uO{H+5egv4Oz1T!?DRs(Og@+(P1SIYl1R80@ohXm19@43C?TG;GYq%HsP?3`T}@0!cINt7b$5zljeOQ>K4jU?Rg@aRQa5l|()*>W3$>etIR z!h8gmYj0hu8;ve4%w8$CJKC%2jZku*zMeqgamXJEkEd!!n75>=V<$ew|b zSl35h^$U){^X~fLqg(>#cFU`bfn`RSYkqzY=Xlhw^$GW<6 z)Okn}$Wwl`Jjtq%Jd#!tGiSiu0B?KeaQXUdI>K&J>yf_3R=UzO*Fw;4tb&fI-%;^P7+PD%25P7LdUo>rh|nq_uuXY4j!3gy1B zoj{#0Zo2%nN`mZbl|))P^q~)aWYwoPf5Qi_#xBL~!|@p_^=zF`jUY_B@E{hS`Lu{@ z=*SQU1gpyc%`meX!$faJxk5$o#Gq5zWSU3&^IG`jFw=hXNts@RWe@7S^(%c~$1i^f z@1facEVnfMlugd-OLpPfGW$2zc-R#}%Ykil@wON?O=Cy#ht8aMvJfQc^Yjo#!DqhB zu?tyN+94~l5ad19#`zJym1Jl?feLy_JvMadNruXVN(h;!VrQDL=pzzXJyq2{nI+dO z-DA;td3nNJ1uSfAq4}kD2c5SzR{7bLXZVHhpA(X51wDx*#X4s;qYS+zV&QPx{4W3& z*9#)rnSDAP(I*SbJ{dFhKlDv`F~>qyf0PD6U$itG_Sf<9G0)w&|{@1OGNcx zzmYOG8+D|Q$Tb#13#v510QVu^llEItj{-S>O842Ns|yLu;1_zFK`9|v=l6a(C==?;Jxx^-EY*TFI|+!%UUK0p^U$l=E+=58>;i zszZ=rGMDXK@WviEg+-#O_tx3Q(!q|Bk7lQhwuwf5p~7t>^?ITa(~lQhbO=kx876#P z_nXzdIfMz6jF4sfS_`rXH7Nt@qgW44SJAN`&T%cKVqsyq3+kjAZn};<;%CbgojpPB z`I7Cm4ak1gjm`qJu@XcM>t$+SJH-W1k01+sH}Y-t(^&u*)RR_RMC7>?<~SI*yg+d! z7mu{o$O>?)&q!04T`|gsQgvKQI$lf>3%zXEQ8SU=bVx{Xe(!tdvB(GN2BU0&=^=Ip zE|QI;C>2t_Jo>uh>g6nh_#>|mR*$rfv$>D9UQ?j7OQfDs7<;$JJv+;NN@@WGNAjam zV|s-NF<76dyC^KmKfilpPDa=gwHlkRLdE@75{!Ib>d?JSx%iq2U&l9ihJ|su2JT^; zAnN|Os%IZT9ZE`mQ1=q463O+tN|P6b#T9$}lL+JTMwu4l@KRN86`Iy0HR+u?oKE}!z>L>rseV_TI@~5)cS_G>= zZAv&P=n$a$5_m`TTkfO~)zLOh+?^S}1MtrF zxl^*W4XD@ac(3`zZha@DrqgD|Cf+z;+Pu7F*?XqEC5u@$ayF9rrm&r!!w`0Lk3XzY zqAg+Mh_ylc9zRa!*im$aIoT?k)xCW`CzrI`@^E!+?!;M! zd4z82>jXEwo``wwP^GiY{Z+Fq)V9yBhF&bC7PPorOPI?(d)VPYzWJvW&mm>a@(@;) z^*eg9NzY|9XZk*Ci83#{yi2CUH=qz=gwL}~*^CS$e0^Qx?T$8fZ|tF5Q*LgV@@!9M zWKr)Uzo3V>F-N)$8cp9_n~|{GEF0Kkj93q-nskt!nbrbyi`L>`bE$C-#%bQ0!W@7d<;ebPz`Ldr8g ziOr~%p}aAfn(WMCT2NRf=Pi=fE{U!#M-?)&MlbxpGo$>G$<~UZLQXp z5ZL}JHWu`yS3^bQlb$fIr24fyJVno{=Qt<$k|y5V`ka&%J3>B1Nb{t3Q;VmKJN6irQ)sAe6!w@9|#^5A^3uaJgj@QjRUOp6mKHDmuGpUoXs9a)IPE{SDv&N5|BLQ z8c)CJih9f9cJP62!l~)?B!|~oMmaO@+uW+hH0n!Pu{;msc@uph?Uajp$}~0$+>=rh zNvO@t8j`;*t<|_tB1@!`%6O|W0>(uXHQhJRUN`eqG!vHMD=}F3ex=VbB2HM@Bw5eG zT9uGX7~0wPFA0eLQP(Q;Y0Uz+c|mR-pD;N|E6YyuPoKL+HYM+uqh&v4UFn}F8c``2 zP<(GTylS=63qUtCV-HGlDJsJHFY)GqPacH%Jxndx1qCYO)w1TA@5m2{PDITr_nJ*T zvDN17w;XuGshzJ8ttKHP`3~jRwOl-3%DnG%XyT<_@$8KH_0HSAjP6K4%ly9c|`XbIixc``L*HMNtD?Y`g;*kcA8k$?z!6$SLi^U zdqW(>!8e<<B-<*bJN| zl~>FBA(l{b-k$V)CVn zP8%;$H`voaLcLC+Q*MeYmGy;c&mo$hj@9ObjrBwtHgk15fk}lq8EqoXnK?1Sc#VYA zyR}KR;yV3o`8AxqFGJ(@I!&E*^ApO31AIVR(tzt={z6=m_S9~-=#3-8u5=wnH0p$c z$+U`G%6D67JoOD;VZ|QoIjY?}-KYWPF{U)$iA@@(?niD?SxM9W=_MGt?pEvW>k?`= z9jtqtsb6MMr!@vivLZIyjk@)6RLgZfcNJ>i^!Z#Un(ouKN6f?ZT6pto2-TYsUsC9- z_rpwNALU}hc$}HEmeZ|qr}Ufc=briOZ&bO>BxmhL8*<|i?iuEstSz=oj$MIMOd(dx z>ESrewQ0XAQD)jn5E_y`&k_u4&gzpSwAo)7EwyS*yNXp{cVM z?K;K2HKDu5T)VtklW0kzRehOvh3!Wqt$Utal3D*SqTt?sk;4F^)d~82o!U%(5N~KJ zI$vU$Fs>xneo74QICxqhMy?K{=ULc7$FD%5uF4W*{`slCkDWMrs9WspgpRhh`ST4G zwAY|x9)JA>k7)z0h0LwaBej{9{eDiA;_ifb#5HEJs{NG-_zU0LDc5x_)&4A7+CHNUj)M@P5OyN2=`ia;HFO5j!7}=yyhZTCiP$Y!P6N}$Rv zxC}KsrN9?$_UQY=9wOQE;&|UWL%@^;7V@6)il7^p?%TQ^>;zbW<++ew=3-8B5uiQUT?|JOP}$)KJK4zpj5e zFUX@guB5KuhO?J-D`8=&#L4dR6-P?!p;$}5629E};sP`2tqFG)zWR%!(fQ;gIkJHc z;>smmTlDbthon%>vg$2Un%2=bgU=vA2NS*EJ{=w1GOx3o5?cZ+%T5Pc-t3xCYP8KN z@)vH(zZPX_wq}6YHFv{#!}p>JrYlVtbFX-SFL2Y)0Ck}i)84fws$td=nBl~Of+T4N zmu$%+ZzPs7dKokrQ9e(v%+6XCxGhJhlG|_d8e4r9yO;)f`ScPQ)zYj%3UUKZkSu*6 zEeoNZZKYjq!?33B<9%Fn&4!~*-FO^=9zRlQ$TsxK zVJ}XEINg6axgq1-g*Kp%OCu>{;xl;;+iaQaSSy6(-XFO=3X6HNvFZ7b3 z3Pyb%Fuy`-v(XD@k*N<*(n1fe4fYL#S-j!`;vZZeuOwchN zdFUR_PRC2yNYkeRm2C8L8mnGi2OB>ZAQ{pnx-30zXui==HGi;}&7)0zPHAg>=%fY$ zS0;6-|IzKtF%Mbvi57+#$w;BFvGX9Dc9p09Y+Fj={P~h`xc|A&#{GW7zTNmb)QvOg zG!s7EftzQWE!R}M=c1mn&GuQ6vAGms6{W)66*k8Y=@_zvyh=B?1ZJYj@F*RxckNz` zYo?S_bUrEv88WLxQw$2;Wxn5kSjh40CU3u+K|N-uL{5~dv3~XW#A>N*Qhfzo=n3mP zLu&63ujRtNOe`Ozt3?;<7znTF_7@k3^s{*MGaI$%@JtCu+9kz$EeQ;Txfu^d8IP8x zZ5mr)d(SZK=QUPeb&JY2*3F+;4js-rjS9g@(4nZ&aVcBPMG0F=*oyhh{dm%M7mi&V zsS2b|f`$}B|0=MWKFAgD(z+ugj`|$A`*(+_+2z9dC0=M^{HWMGtvz&TD4`HyM>lp-LUBZsH$IfN|(dVAi4|C&e)V!-DSc z!qe+q^^d8O2Et+TIi=S1LdFjnu9B54$_|U7`0AmfECCfy+kzSKoQ8^T%`e;W$2e2o zJBCOfhWG5jN-tLIN_whIk=64?UZO9Q$2bU`RbSLM$p}B!VfnlYdj#%fpu~B{Fwa{| zS@T*mAjq>)!($9VUqk|1oSXUB_nZw$=;wYbPMU=oD-I~D@j99nl#vy-!Oz%zYrwQV z(Cc&Ihcb?<1Jw0i{5Ou^>lgQ!PKj_0ZX3jqw$rSd@E7;$(~^kJdraqU|QWHkwOlKjhj`jQ#X(^u;)Yov50UWk>ZI6s@_KYpfUraOP1O0-n` z^t~4v+M?GF%qnp4NZswfuxZ26FP5}ttNyK>QtkPHcPVzA$wEqc=2khVVvT@l@u1xd zQYKsJ+*So#t&d};o=(4Nh_`WLXUA1NV0Q9YdQ1N>F(oCO+3}6e#Fbr9#ioXMS?oxA zM7f*K2rg6~(}k~>$VL&B3KUjHJ{{mqkn*-(m)OFMiBb+>?<%M(bPr$?j0aT>iq=sg%M+D};cmyv9pu)_G|Y|>cni}voUBV)PrkM+d9vr6C>g&zZ~w8zDcuNb zW}DM?-imkFbsaA7LOz)1JvGFNAMdl1kn59G!eeBNuuqRn?VW%1KBharnFUk4cBCAb zU`=Lg-g?53%8JF@{q>(t8yT|{usZZFCs)p&-fe>GNMk)DreG?8`k33Wk}Ze97-d9E zsf44zIc{QlnKM+#8d*FXp0iI1FO=PqW|pKGJY(dxI&J4OVR~??Ar-$~D0Uf+Bov)$ zqEfLoha4G;&OJ@~bi%C>Cyk;I=DYNdsYx0nEx)@$qaHOJ@u{SRXZ3h141F!c+7l3 zY|Ugkg2rU=dh|}8KFHDDT3M{54$BRtb?addT>Y|Q+b`yH!fqd>RUnkKAdFUBSJj~B z$mWS0*@Rb^(kU6{n{S>edFwe2e*!~Fl_tZ(Zb9lBB%p!{2M!K<-aQx%It?CUC2-o} z6}-tV(w066qsIEF)mmBVB)wq?yBKZ%hI^M2Uzk@E30DE9A>DzcY4J(=frGUPh}Xy_ zF=XlO>W#VB@LdUuCVI%gty(vgA+r3p0xZ$&6UlFmH+R;u`;@Y5yCy z;xY);3SWh&I$nde$YFnt*NFwq0YRM0Qt>3#W0zNi@8<3{0yIGzvmbcd^_w)>ThilE znuDSut94!{*a@)+)|-15ipTp-O;zW!VfLgeUdSxo^MP6zoo_K-?d`zZ@aarJiE&SN zBx>Sb%Vft}RGPK?K+5I?H$?e0;DybQ-Bd5Ez=G3%97N=*{l-)F;`N|q>Fn9rBZP_x zqYS!0fVwrm;apmEmfr&-IDC@JM!enaDLw0HVeJdC$Kp1dP|r*~-oojPE{{>-a*tuap5Q7+&XhlR zO`T-#cnJt$JLUkg4g9aI*S{8It2!=~E&t>~cfpkofv86XS9;m%8^%O!w5^Yz$8)Yr zv>oPI=2wzr$v3>Zs5XReEoW8?+Zrvs{qPWk$Nptg%&tA_oxM4?WBK>cPpk^;HZaT+ z%CL-|?E~j;=~ScR^&#>548xc2$k&vrmQH$xI^cIz)+Pu&ZY$Mfgw|5r)TZp_v>lY< zWw$<8FUH_mXwmt4s%csH@tC#?KaU`YpXT{3XN0YZh&^@SEz_$h#o?Q*zbL_7&)CYk z8_1r1Bb{2`Qete4a2Shg)8fo$P10@ovJ7$LS5m?*JEZz3cv}am4zjSc72u^KHT_Pr z&X8SiWgOJG1E=LaW1SE#b6@n+gHQ{}6-vlaiu*ofCtCxXW-rdgTF25ge7VOZ2jM&R z{*i{3{UXhjvTRrQ(XOU*A}yeca*b!?l(EXhv?XKh=^6SDOdWiscjP*_6qf?so+&dL z@XRRQ#NtB7+LfTBwOmt8OUb7@LU3a}!UCwU<>W73&1cXlY`7q!mT)8Z13_H@giDVa z%cfQ2R9Y z2A3nN$v@F79J*?9vcA=ENU?WAx_mASdX^Y3NG0ihHeg!k#T_1A!ICwLh4l%r;3|es z5k@+ExwuyBP93JZAz-)Pqn-QH^z$p;A_r%MPWcRItX>E~m)FGlxal^y&1XrRA%u z2bgqK0w+XT)7q5?Z#OiI-8^g-rnGh&O&A*(`b>UUGJGT^qZ293hpmIT*{zRwhjfS6Th}}3saDvl zA~)6$lXCu*7GZk~#PYSx*h_sF?tTo(btSWG4sLU{cUQ9GOenyMVEthxJ6#H=#fB1A;g1x@~L(Z=e@z>{Jo>E7=V` z*UC+h5&@Yw<@ABHN@c&iFvq23Gu-mC+WQK{Hm>te`#)=c@Gr7%T570$iz_wimF`2y zZTvN_F&>x-f;wVv_v2%t?cSX`;2V9~zpn6lWa87NG^y)(U2<%-@;AwhX(%+JbR&0( z_1#aPK$~B7JI>J6U*_S`J`>V#j62C%GDs)OLysZUr=Y*wtGMNOeJ#at?E9*62GZ9p z%lsa-X>vK)_Lm18w)#V5K3rDe*(0torFZ{$U#J^@OKk#2SZPafMONAcx6{w2cvECs z;Fo6L^IW}IC?`1Ro>-n|)nuzTGjv^DNB!2`z4w}`!(?3t>?JPS)_q(R#bgYf^^DUt z>^7N@tw)s1O-)pLQD?m({m_eOE*|Xx-kG!6_dAa=NoV#IWsqIp=kHrB`3qI5E z77z*IR_9v$SWMom1BG?cB)*8Wy{vY1=5!dY9_zIKIFFQ%z3Kjtlz~8y1aWm{Fqg&O z=q_z>jfZnKl*TE&X|D~>rE|k~YM3JPDh)pq`iHNmd0Vec`^utQ+@9E~8c5=G5p#Dd zT3`8sC%u)1c&aYXCNNffP^;tKyGZ#kyc+`tyy-t;F&N-#$-~oiICdbx1PVnyFL$w55?@F0xU2QO0D|RYFJC1@Cj|i5Z$!;kebXZiQC!E<<}KF52R# zT>56K(=gZK&s7SZ5#pjo9BxY?-$_gp_DO*8SU(J7HmqsdgY-7(C6=TMhM%K)=lHr-mvkAWYz=bi5ZZ<-5Eka4sTO?&_?s_BSo3=^6G&T!%WXv1YO2QxH|7F9l)q35k)2j}z~K}Ad@{KLLS{BS?)P~*3Mmj{Q)_%7X};#3 zzLG}x%elrnR?~?P@A@bHsbSK6Db}o)t^21Y96z;@ObU@18}Vn0p}Ldx+wtq7o^g97 z?5z5ljlX)MLA1j;mqAW!oI4;~25nHeKUjLsR`XK*Q-k8&@i&%U`iMoI65~1+5$0cC z$y6(;-Oi5vQ0Kj>V$grzAbm}P zUl(7hLBGm`a~|Gn&f1n?sC4s_qSR3|U)Wx;HHw6y3BMVohKaLn@8u(o0IHeBb;{S2xkVIW97YzsA5TfZMbH=%iZ<$chA@*gu9yG@F4}L!|GJb6>;xKln9r#E6&WeSM+1-9a50k}q z$K9n_o~>WxtUT-1{Vrh5gAsS3H%+tOOy6MkZD-e*&^c-lyJ$@M{IH+ZWq?+|GS#tB z_^-yNoJtnmF&KzsNJE=d`r4(|UGWpN@*4Tg$Cq9C+upElxH&#P?KYud!_CJz*g;Teu}_!`Rv5B%LhA;*Og6IdEI$1l8It6@^qhSd1^1=6E!O`p_9M$W;x}x+y_qD^}O^c-l+p{W-s!wd#dk3)~a$w zOYe51efH`a6R%tIRE(N3=v?zeIqv7p=#y<3%X^A>3$lLK)O=?-vMfjY{McnYJ=Z^> z(0zd3c>BdVIc)rE)a!c&s~<)vs>n8~M0-T8-zX7urW0@^)<2<-#O~p8(`%nS15a@>NCB*>OiWCPSlA?0oTL(jK)tOFBFg8{7yZ(Y zMIi=GDedPj{KAxYKFMC24)NDKo$pHDh`^+Iv_fl>^bBV7kbSJuR+A>M@48~7Ho7zc zv|_RhkBo;ny_D(Rw0C2i7&(OJ?cz={_zq7@m2Y*17@$ABRv29G?J=(mlCHm4X;F6i z<2WZuN@RTMTeKmf^UgZ%^OnqM(qxH(W6t*DxiOwCmIvTC(=D zyjQ!?%FHBHOLOjXc`ffYXRpKmpZ4B5uIcZ490#NXrBp&n!2n5VkWvYYmXdslC`g0E z=z)a-6X`}#K)O4IMT`(Zx)jEck(=~j-#ZlS*ZcMR{r&L|A7jtw?mhS1)92iKEKOn9 z6Y@f1330{M6V$}v~fJgeV=s}+Z&%vW;0Fg&S@8yuUEubY;c#& zCdzocc)>47$bRT)!HH6IWp747E>cIpJ~q}lChng=yy4F1C-JY`6sZd~w?A0o&$>|O z{7OUP%9qnRNhYFMRGUGKPW+R%f_Nc%8*#Ph+RALnrVN$NTaVp%1JMNknd-}NxLV{0 z+py-xyJSJ__P+IBI{tA~G4fn^DCl7H+Y>7Vik7+4Yu%?6zl=<mqHX%^NL$(d`I7UG z$qPwXKr<8@#^mZdAS^XUNOs;CIuwj?3Xe2Xl8A&^&as7Bbs>wmro>t!M3C(~7-qTL zd4u&aDul(u^#cmR@+5s?mT_+J@pqG@>EFG&VMMyinMSGda7gy+Nqy3{kEA$P?x}7r zUXN~nT-x5yE7^&Q9ndGb-lkwG`tn2F9#ugEYV#C+Qi#d3R@Jckp9?qmh^Zf{lM>(B zOKNbd53@PBKW&$Mz$r{&tFN3|y6obMi{m}T2{Rb^8@rDkrnHIFp%ELEQ_?hgMtS6Z zM+s%W`E7=P8MnD%_AFPL7>htmx$t8hqvnPOt~aDjEls;1aY}M&(o4wA%7m=Gj4$zN z)A1F1>jNLs9D0qL>_|=Zi#J>rTn{~zQY~2TRz273BAK@@Z%i&;we^9e`TayesQ*m>T?& z8LLUw8*#B^35;AyZ_J@XzLX+!Hj@|`d(rjMr?X&WMd%6QUH+Mu6Vl-f)PwUydCYw5 zAz9`B$d#rbVRanxvnTLe38CxA4zamL1Kif|U1AKhf?w6#ON2ZZ8wd zdfM5EcePDigBAnUX_j>RLb?Ql+5!h5`aP%njtcc?Z6*5h;CUms%6io1>_t}gf?I;B zikh8CAWt$j(vpGeE;kMfij^PBHl@KcTs`n z&Wg0C^YRU=!9JfQlh|!6Q2tMNZm!DD_WPo|5i`pWLwcOc^h})$m4qkSPpZVJreSl$ z_0w>{vPW0b3lj7Z#dPmr5{AySH6;BFgSg?eKnfgTHu~X)pF6l)OQy4G445fQ3c}DS%N=4^|Hja zwr!%}!yIR*SbSd1)gkNjt~-7vxledClGg)(apLQ`r6%Z>HvM#keTKWe<*UJ z%1$NB%gjO zbDW4rMt~7m7A98Z?|DL%Vm*vc8#Tj3%!ew?&}GOm=dD1<8%jc6=&k%5{u5O7ZEaxY z1+CB({YC2Z%$XJO%jyboBtafZ>gp%2s3hqx$|x|T-&{R9a)W)>l`Kr$Ee=Vy$=5KY zlnc?LcMOtw?ZViOI2PnAR_hun*z>rq)=n_pl&-11uJok3jmvbORLfXsId8NMOXDNX zlMl6`Wl*@N&*G@vsk2gM(Ei;KAiZi+gONuPQWsef5T<1Qlp2OGkttUogT_)*L}$b>}?hZH5z8rkjbw zA+dP}449zzIDI`*Ppr~kW)*$NBwNYPkZL4G1{v3~yBaSm5Pj;(TjfMll9!;UUG>}J zFt_l)bjr=x*RZjuU3W1~L%shjaKR*$9%<`*3cIfM!q}92T+D8uq40gCEEB?q4i_F6 zB~R#HnDcmq0#Z{DS-Y${fXTE=D1(rY<86~_y!6bINMw=zT0UbVM|!Rgo^5(j-G)G9 znz{@{c7~PBTJM4LE=QLoj=LJ4#ZKlWH2a2EXTy4oZy>!ijV)pcITcHgspAX2>CmI!Ep*TmCnd$cV7OI0km5)$6CwB7+v{(pTbD%#4z3 zFV+s`*Y;ht#Gz1(3fV}#Yr8fKKdRXrJ-BH-;MR}q zx@Yl#FSSAJsC^8QRUa8r-orRkCTO>RX{6z#-3j*!{+@~)wNtn$ zV5P-diC*6@t}R_Qf`sPJaJ?5x){;HLmo7NByBjqjMq>##Y_sX_S#`PJ9xdB{!<2FL zh64$g3%iHksCFdAjeia^Jn9ps6RT?4-D5KaPwjL5RB*#YJ{c=ft7jfbim~XVt|o-ugDc~1p?F7YVJyVLir8>U9#m`U9UT9n#x^ZE8$_V&-=Cn(3ml6f6! zbFKLlQg@*)yYGzvDM!TmWxE0Q-S3)u-XUpuF|`mAQ++ke{Rxr0TtAe}KeN2WL-!7i z)Xw_33Z$ZWfh|K$1fk){4K+c%ex2`O$!%NhTQ{rk*r!CJ)p>!m) zA-B$Vo_yAM1g7op&@-A-ZDvMDWK!|#`8P}p_Z#BVHu zEqkX1MDC`_T^|mb8!5PCv+02qs-0!Krs@io622Ers}fm z?vgUgeiNh~IZH{2$x5vtR$$<)vbg7J_*ymkwOOb!NSxJ*xqfjOM2JkBam*bLqq0sF zDY<$Y(8UixF(fEX+brV76kif*-#xBqJx`i;|_H)+7NC-@&&#m zAe5X3UxH;s@ONEUQDfvMJ8iOJ z-FAX>&nB-TP6sN!k0)=a$~gYC6ab4CA2i~Mk)ENigPAEggWzEFJ6vE8T zO5!II7hP^rD>kFm*VhPDoTb(S8;7S%hVhwIw31N;egxB6l#-c9Y*$kJiTqd+*~%y0 zU-l)^Ik39n9Ce~uhsFG3-p6n|-hHdH%0nzuacu9Q!$Mr~*40W}X7&@&b+>b;lv0b1 zQV569B6A-^rO`Mj_*$a|vlFev$j9y~xSA^XyD8rnx6L zb4d>%i!%;|^`f$qwf@oV7V0k`y@$LU+PELlteAc3F6tzTe>KYPbMIYVl;h9>PrE}m z3fGkCbyDcPT3!xq?~y#VcNN*m6k5@k@=q(d`B-r964&)-!Z#L#vim0B z&|Rzpmut+~r_RC|2ec)c+lysk&7LUS1z3gxWGSrIordIPq>pBsZ5#SrLRP}MC08EK z7mjJic?u}eAd2T6|Nr2t3`9HJ6#blpVugP=yJf|IB~8` zGbius4Z1ZWj2v+;*{8VDYWetygSEI0s-7m~;-PMnxsy&gk#jToCPTD6<#z?wU7AM| zAZ#}hu74hQ8an=uHZaB~c;{b2uG(NpddRM-4OBl;;W3s3|96tkS~dq z)qa-DWJx^_)lYV(hks0+^V&??kIi2h%-1;~bGV3XuZK22>&8M2K5h4K^T0YGx1niX zK;oTNw{BM_18(mwykSo{kGmBh#YKM`URQotdy#$ci;1D`-Id{wUgbE`<3a&fKY1xM zYl-{dXVxhk*h9sbM-0OpyvwjFt$wSVBZDiZA+i~$Y~q}8r{#Pf&CtoSESqcf_A^i2 z2P@sPbrhPJbOM5%H!FM*7#}r%CYW4vgMXNwl^+QHAykQJi|wvLx^iZjg*_?jMjyH% z-zbJYOvNcB>CT8_R} z{Uq-`H(RsFa!6v0i&31N{9;3$NlBj-$3T=MG@i`?*QamnT-~SY>wWb@8)fFIb}J~- z@a9me;H6D&4<=;|V*MO!?>ebAlt*S^lupH81+Q>Hshnn)zJHr2k?q+&*F)ujpqB7; zxIt1n>Ei3jqojeCkCJf3kz8Q5yF(x3{O)4JLpWt~t>EXCg|XDkarzj?timxVI`cP` zQgkV2&0AVQ@dSO#hc|cN2RJz2-WLyxTWTt3h6)y_C231wp2e&~$_DH%*omR~}a>pI9X$I5ao$ZTFe)otzBVrtuSc^PB?ou|Vr z3PL)4fuR4~&|$pafQ;CeKWY66kIP`uq_%G^H0`s?$18lxv5p(9i&8y-vQVfz<67>g zxJMiFF}Z0rR6>*Lu9Y&n<~_@+jDvy7*ff7`qMQk* zqAsdn((>4KaQ{S3ki+16P=e5Nq+a2)>E_EMnrE5h0|Kw&=fq^u43@|JUrc)(7P3Ew z&C7Io&?a~AfzOeHcaWRb%$+CCrth-6U)dq-^*O(z|7h5aSO;5Gi!wH6{L?o^LM$CY zJ{FoXkC-fzCsvfD69@(2RJNf+lKdE-+_E=>_+kH+@7>Ig-6Sp-r6>rwo<$$o7HhAT*g}E9I%vEbhsY@?pnYWz{ zCqeVjomLE1*Jc`3%%>ziX2Q{Tn|_r}SX=zlZUnQ>9UP#)I7}}f)hp~&Q=VTYJGAC6 znV9cG)2#%u*7L2105@;%Y2)x?p(wN&eyw>-$rO_8iafE*QZJGqYRRP#>Lch?LE~3s zIt$wX;x&#?Qhwb?@#YSs@teD2+L5%_jEnkp${&0f(sS~2>lKVX@))U-y(OEp zc~R-cX@-7ZSFUGOdED_cQbB4Xy5CS@vFUaeH>#}LwCF89?@QG1M#qb9ewImmL6kjh0NY-Be((A9vcaTZz znoM>%@#cit=4yu;I!2*M?BEI6r-$$&l9-hGa}-yi+awQOxOIQtwy)iLcJ7{JN^-F6 z+q7Fe)vM8_k~$R)BA5K~{bVE*MA>MHY$FN|vMb>VV?+H_lP7%xWvAZcy+nA|tt~px zepG~8Ez zLN=?meYF-<6}+MT{&b65GFxa{$C6LCYW$I@q;xI&{!`v%J&=OEBQJvcd!Dy4tWRF4 zYsJ;31#_~5N~{o$T=XyPU5p)Prt500fOBg}rq(cSy6V`|`F^3L6|Yt8Ek&P1$z2#- zZouc&v2R{+lJ&QITxuUtHnYKqwwr+Lvox3ad}BYLk&Lb{EXuXvT|%L!b*o}5UalHM z=CZb-u)==o-8qP*YBLX^@g+}PP0bIry>5~Hb8P7iwM>-Ei~roNFtlG%l~+Gtph6rV zL8Ihb73+IL#AS(!o($z`_L8pp$gpJITXG}%YY;d6w< zc|}z&wRW5p)E+WduYJgCD@yZZZwS)5-+Lptf8Tn-Qm+NWs#fxOEFVHyI!00r5@-HW zwyne1uX#{hOJa(xAvd$yNw5rQ*qyi-+n}I>CrL<0QmVUpeJodJc&8f;!-A*^7Es*B z{zNp(?7hoF=%(k>o<0d>FFada=@pc+&_Y*Pmy}cJ&~E3AX;u96b>xm!yqzu_Ip19$ z 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 diff --git a/docs/assets/images/restaking_accounts.png b/docs/assets/images/restaking_accounts.png deleted file mode 100644 index f58e2f684861cfd6739f95c2c676a5c986680577..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45427 zcmeFZc|4SB|2U2~CGDFerjCS&qR2LsB4mk>b(Cz`vJ3_@L!Ih$iYW%kGL{CG_L7WRo9%)m4qIDcg3;Bh`iNY-m2NS9`eiDpBuPEa9<^6s*vYIVAFnB*0-Gx=yMSo+w@{qmhD^)b#D2p?r--4Qk` zt1D9jE`>w9l3o^1Cp|a3faeexb9&Of?d+%8wch91PmX6$;PWeOaPOsV(33RDx*2qb z95Fm>X^DQkWnM}^aLcYEy3S@IDYQn9r6rzb7z@n{9?T)GuJJq{ndg4G2!>rp(Pk`J@^aX-Rs}>8Dcx@`U)g((jJD*`RrHCg z<4}=b0}r3^A+S>Z6T~AM!@`Z7J2?xM?0pnoWgd4dCmTs4)l6e&kduDDAyS_&V5Y-a zT7dF%c$``utHR68(QSaPzQb<$fI0X-p!*HDptadLbocHa?S(*_I^St^D-9NE#&f~u7gWSKRM%Ou2Fo37Y zA4(~-%wC#yqlEz z_jdN3bzjuwOXK`!sr*@B3P+Cc`SG85{QXD&el&mR^8eAC|A<{tWF%j$P`kq4Yw`E+ z1jhz8|Gi>;M?$@TfWX%Sx~2ccl)r!Ftl+r~?iR8rn;V@nwImO8;P%3O+@-ZQ9E#oD9Ayzb?J! z3NLx7v*VT?On7ral-bIxuy$I14kgDz`T#kz~SsOEj&?NsJ1rDNv}n$HVVmGsdE;Le0IdIFAq^C z_W=YP&C37w>JxxrVy{~CHzth=bLSowL~6|E^QZXz8+-tqJISagwNk6TlBel=fN$1o zY^_}x-fVsQ7{C;b`Aaib)<@E}P9Z7#RdhDS^w&mmL+B3zzF|n;ns3CD=kpsq>5kZt z5dh=Bmf3ZIuN-3fiX3x2zYc+V1hW#cHvn>wX zd;^23T_yP$JjaCEfOQ|mISGK{IwPOF4s3JgCV>60|gROxxc2_yUTBgR`ACnUK!}*_6xk4Ghsp-AYz4<@W!Kj zzibFeTka)qBrbWx=(k4+0{{!{X`gJgz9~@Air{i8E3U4xv-g|h6C@y-x;&n#4v?si zbzVpKdI`X=CUM=xb#4^N0MNq&O6@(rJ*rn`L-<3DM^`sK_YLNTHEghMok4%Th+^#9 z6%8ud9z3(YC+AvcQ zAqV{Eees?X1?WgkJ^6C=Yk!QZpWoAH$f;alFHW^<^T1(WKg%@8hb$)%4@$h9uuT^Q zFD#V{TNA>DgQ)muFq;fYDT*qLn8S5|oEU-Wcy5i1K5{L4XS5`IEjwKv!k+aV3^aXC z1g(GgR89R`V(3)PqJR>U?B3dKGx5VzYJ)%qao+bQ+)So>o6}hAF9SpZucb#H zs=dP_KDk!|30<5P@2a)k88utU8Bc`_B3T~2@KEhui<82;`VFJ1{RmZEA~mY62}>Wb zIu)mf#GU+?Ke1iwa@Y)!Hl4YjK&MUeEbA2W+S9minJS-;Qzn+69?n2*C&Dhl zwa%2a>2*TDwF zi7#tdYW!sxS7V|yLoHbCr_s^WSuE>~Kd6QYF@UVH(m6c`>t^aZ_)tpRRu^$<(h+U` zP?@Vqz={=>B!mlPyNLVv`rbxNK7gcz-DAH~bY9q`rhP_HijW_`=j7`hu|&k_?C8}H zBYU)8^!@$sjHY)&G##3>QK_NzC+gb<>Fp=ha6 z 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π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 diff --git a/docs/assets/images/vault_interactions.png b/docs/assets/images/vault_interactions.png deleted file mode 100644 index fa533cfcac7a07d3676cf60344c5864223cc41e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180683 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

-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 bPAtAOaK9;Au?!Hu?b5eq-;|znzW;v!MHf}> diff --git a/docs/index.md b/docs/index.md index bc20f00a..8bcd51b2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,217 +1,5 @@ ---- -title: Jito (Re)staking Overview -category: Jekyll -layout: post -weight: 1 ---- +# Restaking Documentation Moved -**Jito (Re)staking** is a universal staking protocol for decentralized networks called node consensus networks (NCNs) built on Solana. It provides a framework for deploying NCNs and staking SPL tokens to them, with flexibility baked in at every level. Altogether, Jito (Re)staking is a coordination protocol for developers to build external networks that use the Solana runtime to enforce custom proof of stake protocol. +The Restaking documentation has been moved to the [Jito Omnidocs](https://github.com/jito-foundation/jito-omnidocs/tree/master/restaking). -The protocol coordinates stake and rewards between three main participants: NCNs, Vaults, and Operators. Developers register NCNs with custom rules. Operators perform arbitrary logic defined by the NCN, whether that’s processing data, orchestrating or coordination tasks, running models, or verifying offchain inputs. Vaults hold staked SPL tokens and delegate them to Operators. NCNs, Operators, and Vaults can define who they interact with and under what conditions. - -The system consists of two programs: the **Restaking Program** (`RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q`), which acts as an onchain registry for NCNs and Operators, and the **Vault Program** (`Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8`), which manages tokenized stake through Vault Receipt Tokens (VRTs) between participants. Both of these programs gives developers the flexibility to customize network operations and various administrative authorities, including appointing stake delegators, setting fees, making protocol upgrades, and updating supported tokens. Together, the Restaking program and the Vault Program manage stake delegations 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 on Solana. Its design separates core network services and coordination: - -- All core activity (computation, validation, data collection) happens offchain. -- All coordination (stake delegation, voting, rewards distributions) 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 distributed 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 (e.g. one set of staked tokens can secure multiple services) and allows teams to allocate moreresources toward core development. - -## Core Components - -### Node Consensus Network (NCN) - -An NCN is a decentralized network that reaches consensus on offchain data or workloads and updates onchain state. These networks may include oracle networks, DePIN services, bridges, co-processors, or new chains. In most cases, NCNs will include their own custom onchain programs to handle proof submissions, verify work, and distribute rewards. Consensus can take place onchain or offchain. - -### 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: - -- **Token Utility**: The restaking protocol is completely non-custodial and requires multiple parties to opt-in and coordinate network connections, operations, and rewards distributions, unlocking a path for NCNs to build decentralized networks and install token utility. - -- **Access to professional node operators**: NCNs require different hardware requirements and software competencies. Jito Network is deeply integrated with Solana’s validator ecosystem, which includes a wide range of sophisticated independent operators and institutional operators. This makes it very trivial for NCNs to connect with the industry’s best node operators to participate in their networks, regardless of the underlying network’s hardware and software requirements. - -- **Wide Distribution**: JitoSOL is the largest stakepool and is deeply integrated with the Solana DeFi ecosystem. NCNs can immediately tap into Jito’s network effects without having to attract native stake from scratch. This means, by registering with the Jito (Re)staking framework, bootstrapping and building staked networks is very cost-effective and extremely trivial. - -- **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. - -- **Instant access to Internet Capital Markets**: NCNs have instant access to [Internet Capital Markets](https://multicoin.capital/2025/01/22/the-solana-thesis-internet-capital-markets/). Vaults have the incentive to integrate VRTs across DeFi, creating market structures for native tokens. - -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/makefile b/makefile index 193702e3..4bcf582a 100644 --- a/makefile +++ b/makefile @@ -9,7 +9,6 @@ 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 # Default target .PHONY: all @@ -25,7 +24,6 @@ lint: # Code generation .PHONY: generate-code generate-code: build-release - $(RESTAKING_CLI) --markdown-help > ./docs/_tools/00_cli.md $(SHANK_CLI) $(YARN) install $(YARN) generate-clients From bbc5abe64d5bbbeab1d6f33f5a63a97cc3b9638a Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Wed, 23 Jul 2025 03:18:01 +0900 Subject: [PATCH 05/12] Bump CLI version to v1.0.1 (#257) --- Cargo.lock | 2 +- cli/Cargo.toml | 4 ++-- cli/src/bin/main.rs | 10 +++++++++- cli/src/cli_config.rs | 3 +++ cli/src/cli_signer.rs | 1 + 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3557edb..485665fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2607,7 +2607,7 @@ dependencies = [ [[package]] name = "jito-restaking-cli" -version = "1.0.0" +version = "1.0.1" dependencies = [ "anyhow", "base64 0.22.1", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 13b7ebcf..d36387ae 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,13 +1,13 @@ [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" [dependencies] anyhow = { workspace = true } 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)), From af95116cf8b0d4c638fa8557f6c54f90d2936995 Mon Sep 17 00:00:00 2001 From: Eric Gonzalez <117293923+gzalz@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:58:45 -0600 Subject: [PATCH 06/12] cranker: emit cluster name in metrics (#224) **Problem** We would like to differentiate between instances of restaking cranker running against different Solana clusters but lack the ability to do so. **Solution** - Wire through the `cluster` arg to `datapoint_info!` calls as a tag --------- Co-authored-by: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> --- crankers/src/bin/main.rs | 5 ++++- crankers/src/metrics.rs | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crankers/src/bin/main.rs b/crankers/src/bin/main.rs index 8d0315be..3b94f692 100644 --- a/crankers/src/bin/main.rs +++ b/crankers/src/bin/main.rs @@ -148,7 +148,10 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { 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; 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(()) From d411ec2dae0ac0e9c94f23b1e6e8c090716f63a0 Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Thu, 7 Aug 2025 07:24:24 +0900 Subject: [PATCH 07/12] Fix: Cranker when cranking, check vault_operator_delegation state (#245) - While running cranker on devnet, I've noticed some errors like below: [2025-06-01T23:27:29Z ERROR jito_vault_cranker::vault_handler] Transaction failed after 10 retries: Error { request: Some(SendTransaction), kind: RpcError(RpcResponseError { code: -32002, message: "Transaction simulation failed: Error processing Instruction 1: custom program error: 0x3ff", data: SendTransactionPreflightFailure(RpcSimulateTransactionResult { err: Some(InstructionError(1, Custom(1023))), logs: Some(["Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 invoke [1]", "Program log: Instruction: CrankVaultUpdateStateTracker", "Program log: VaultOperatorDelegationUpdate is not needed", "Program Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 consumed 13514 of 202850 compute units", "Program Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 failed: custom program error: 0x3ff"]), accounts: None, units_consumed: Some(13664), return_data: None, inner_instructions: None, replacement_blockhash: None }) }) } - To avoid sending failed transaction, we should check the state of `VaultOperatorDelegation` before cranking - Add checking `vault_operator_delegation.check_is_already_updated` before sending crank ix https://github.com/jito-foundation/restaking/blob/8469519a42205b8609f08b5734746635ddd8692f/vault_core/src/vault_operator_delegation.rs#L63-L77 --------- Co-authored-by: Coach Chuck <169060940+coachchucksol@users.noreply.github.com> --- crankers/src/bin/main.rs | 38 +++-- crankers/src/vault_handler.rs | 258 ++++++++++++++++++++++++++++++---- 2 files changed, 255 insertions(+), 41 deletions(-) diff --git a/crankers/src/bin/main.rs b/crankers/src/bin/main.rs index 3b94f692..8651600d 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,7 +144,9 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { // Track vault metrics in separate thread tokio::spawn({ - let epoch_length = config.epoch_length(); + let config: Config = vault_handler + .get_vault_program_account(&config_address) + .await?; async move { let metrics_client = RpcClient::new_with_timeout(args.rpc_url, Duration::from_secs(60)); loop { @@ -160,6 +162,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(); @@ -194,6 +200,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)| { @@ -212,7 +220,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(_) => { @@ -233,6 +241,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/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(()) } From fe404165994d7cd8b32541ce6a7a8b2f32b9b95d Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Thu, 7 Aug 2025 07:34:07 +0900 Subject: [PATCH 08/12] CI/CD: Create Restaking CLI binaries for different platforms (#237) - can deploy `jito-restaking-cli`, `jito-shank-cli` (in the future) - support `x86_64-unknown-linux-gnu`, `x86_64-apple-darwin`, `x86_64-pc-windows-msvc` --------- Co-authored-by: Coach Chuck <169060940+coachchucksol@users.noreply.github.com> --- .github/workflows/build-upload-binaries.yaml | 203 +++++++++++++++++++ .github/workflows/publish-cli.yaml | 14 +- 2 files changed, 208 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/build-upload-binaries.yaml 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/publish-cli.yaml b/.github/workflows/publish-cli.yaml index 462ea8de..1ad8103a 100644 --- a/.github/workflows/publish-cli.yaml +++ b/.github/workflows/publish-cli.yaml @@ -84,6 +84,8 @@ jobs: name: Publish CLI runs-on: ubuntu-latest needs: test_sbf + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} permissions: contents: write steps: @@ -103,6 +105,9 @@ jobs: with: key: "restaking-publish-${{ inputs.package_path }}" + - 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 with: @@ -131,15 +136,6 @@ jobs: 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 - - name: Create CLI Release if: inputs.dry_run != 'true' uses: ncipollo/release-action@v1 From 1f5b318a6e0061c43afda27dd7f1f7f7500e4a34 Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Fri, 8 Aug 2025 07:56:57 +0900 Subject: [PATCH 09/12] Add log meesage in both program for indexing & analytics (#232) - Fixes #221 --- crankers/src/bin/main.rs | 1 + restaking_program/src/cooldown_ncn_vault_ticket.rs | 6 ++++++ restaking_program/src/cooldown_operator_vault_ticket.rs | 6 ++++++ restaking_program/src/ncn_cooldown_operator.rs | 6 ++++++ restaking_program/src/ncn_warmup_operator.rs | 6 ++++++ restaking_program/src/operator_cooldown_ncn.rs | 6 ++++++ restaking_program/src/operator_warmup_ncn.rs | 6 ++++++ restaking_program/src/warmup_ncn_vault_ticket.rs | 6 ++++++ restaking_program/src/warmup_operator_vault_ticket.rs | 6 ++++++ vault_program/src/add_delegation.rs | 7 +++++++ vault_program/src/burn_withdrawal_ticket.rs | 9 +++++++++ vault_program/src/cooldown_delegation.rs | 7 +++++++ vault_program/src/cooldown_vault_ncn_ticket.rs | 6 ++++++ vault_program/src/enqueue_withdrawal.rs | 6 ++++++ vault_program/src/mint_to.rs | 8 ++++++++ vault_program/src/warmup_vault_ncn_ticket.rs | 6 ++++++ 16 files changed, 98 insertions(+) diff --git a/crankers/src/bin/main.rs b/crankers/src/bin/main.rs index 8651600d..fd9d95a1 100644 --- a/crankers/src/bin/main.rs +++ b/crankers/src/bin/main.rs @@ -147,6 +147,7 @@ async fn main() -> anyhow::Result<(), anyhow::Error> { 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 { 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/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/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/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(()) } From 417dcacbd9e8fac18df0dd0697cfba714ada49d5 Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:00:28 +0900 Subject: [PATCH 10/12] Vault: Add SetConfigSecondaryAdmin Instruction (#222) - being able to set `fee_admin` to new_admin --------- Co-authored-by: Christian Krueger Co-authored-by: Evan B --- Cargo.lock | 320 +------------- Cargo.toml | 30 +- cli/Cargo.toml | 8 +- cli/src/vault.rs | 9 + cli/src/vault_handler.rs | 66 ++- clients/js/vault_client/instructions/index.ts | 1 + .../instructions/setConfigSecondaryAdmin.ts | 216 ++++++++++ clients/js/vault_client/programs/jitoVault.ts | 10 +- .../js/vault_client/types/configAdminRole.ts | 37 ++ clients/js/vault_client/types/index.ts | 1 + .../src/generated/instructions/mod.rs | 2 + .../set_config_secondary_admin.rs | 406 ++++++++++++++++++ .../src/generated/types/config_admin_role.rs | 27 ++ .../vault_client/src/generated/types/mod.rs | 2 + idl/jito_vault.json | 43 ++ integration_tests/Cargo.toml | 6 +- .../tests/fixtures/vault_client.rs | 25 +- integration_tests/tests/vault/mod.rs | 1 + .../tests/vault/set_config_secondary_admin.rs | 75 ++++ vault_core/src/config.rs | 21 + vault_program/src/lib.rs | 6 + .../src/set_config_secondary_admin.rs | 40 ++ vault_sdk/src/instruction.rs | 12 + vault_sdk/src/sdk.rs | 23 +- 24 files changed, 1046 insertions(+), 341 deletions(-) create mode 100644 clients/js/vault_client/instructions/setConfigSecondaryAdmin.ts create mode 100644 clients/js/vault_client/types/configAdminRole.ts create mode 100644 clients/rust/vault_client/src/generated/instructions/set_config_secondary_admin.rs create mode 100644 clients/rust/vault_client/src/generated/types/config_admin_role.rs create mode 100644 integration_tests/tests/vault/set_config_secondary_admin.rs create mode 100644 vault_program/src/set_config_secondary_admin.rs diff --git a/Cargo.lock b/Cargo.lock index 485665fd..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.1" -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/cli/Cargo.toml b/cli/Cargo.toml index d36387ae..f6d3126c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,6 +9,10 @@ license = { workspace = true } edition = { workspace = true } readme = "./README.md" +[[bin]] +name = "jito-restaking-cli" +path = "src/bin/main.rs" + [dependencies] anyhow = { workspace = true } base64 = { 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/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/index.ts b/clients/js/vault_client/instructions/index.ts index 7bc28cbe..1efbeb40 100644 --- a/clients/js/vault_client/instructions/index.ts +++ b/clients/js/vault_client/instructions/index.ts @@ -28,6 +28,7 @@ export * from './initializeVaultWithMint'; export * from './mintTo'; 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/setConfigSecondaryAdmin.ts b/clients/js/vault_client/instructions/setConfigSecondaryAdmin.ts new file mode 100644 index 00000000..1c6b0eb6 --- /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 = 32; + +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/programs/jitoVault.ts b/clients/js/vault_client/programs/jitoVault.ts index 83cd3290..342e0b4f 100644 --- a/clients/js/vault_client/programs/jitoVault.ts +++ b/clients/js/vault_client/programs/jitoVault.ts @@ -35,6 +35,7 @@ import { type ParsedMintToInstruction, type ParsedSetAdminInstruction, type ParsedSetConfigAdminInstruction, + type ParsedSetConfigSecondaryAdminInstruction, type ParsedSetDepositCapacityInstruction, type ParsedSetFeesInstruction, type ParsedSetIsPausedInstruction, @@ -94,6 +95,7 @@ export enum JitoVaultInstruction { CreateTokenMetadata, UpdateTokenMetadata, SetConfigAdmin, + SetConfigSecondaryAdmin, } export function identifyJitoVaultInstruction( @@ -196,6 +198,9 @@ export function identifyJitoVaultInstruction( if (containsBytes(data, getU8Encoder().encode(31), 0)) { return JitoVaultInstruction.SetConfigAdmin; } + if (containsBytes(data, getU8Encoder().encode(32), 0)) { + return JitoVaultInstruction.SetConfigSecondaryAdmin; + } throw new Error( 'The provided instruction could not be identified as a jitoVault instruction.' ); @@ -299,4 +304,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/mod.rs b/clients/rust/vault_client/src/generated/instructions/mod.rs index 3529db29..ad460411 100644 --- a/clients/rust/vault_client/src/generated/instructions/mod.rs +++ b/clients/rust/vault_client/src/generated/instructions/mod.rs @@ -27,6 +27,7 @@ pub(crate) mod r#initialize_vault_with_mint; pub(crate) mod r#mint_to; 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; @@ -60,6 +61,7 @@ pub use self::r#initialize_vault_with_mint::*; pub use self::r#mint_to::*; 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/set_config_secondary_admin.rs b/clients/rust/vault_client/src/generated/instructions/set_config_secondary_admin.rs new file mode 100644 index 00000000..2e16a013 --- /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: 32 } + } +} + +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/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/idl/jito_vault.json b/idl/jito_vault.json index 38b8e4fe..2f62ef34 100644 --- a/idl/jito_vault.json +++ b/idl/jito_vault.json @@ -1391,6 +1391,38 @@ "type": "u8", "value": 31 } + }, + { + "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": 32 + } } ], "accounts": [ @@ -2077,6 +2109,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..57371ba8 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, @@ -1931,6 +1931,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..a45d3883 100644 --- a/integration_tests/tests/vault/mod.rs +++ b/integration_tests/tests/vault/mod.rs @@ -18,6 +18,7 @@ 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/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/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/lib.rs b/vault_program/src/lib.rs index ad620244..4934ab90 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -21,6 +21,7 @@ mod mint_to; 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 +34,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, @@ -280,5 +282,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/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_sdk/src/instruction.rs b/vault_sdk/src/instruction.rs index e29892d6..5a0f33e6 100644 --- a/vault_sdk/src/instruction.rs +++ b/vault_sdk/src/instruction.rs @@ -320,6 +320,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..2bb7374c 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( @@ -819,3 +819,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(), + } +} From 3e5999551577ac1f78a336825b865f4e642d3d6e Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa <62386689+aoikurokawa@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:22:08 +0900 Subject: [PATCH 11/12] CLI: Refactor `jito-shank-cli` to support dynamic configuration via CLI (#238) - `jito-tip-router`, `ncn-template` use mostly same logic for generating IDL, so after publishing `jito-shank-idl` crate, we can remove these code from each repo. --- .github/workflows/ci.yaml | 43 ++---- .github/workflows/publish-cli.yaml | 43 ++++-- makefile | 35 ++++- shank-cli/README.md | 93 +++++++++++++ shank-cli/src/main.rs | 207 ++++++++++++++++------------- 5 files changed, 280 insertions(+), 141 deletions(-) create mode 100644 shank-cli/README.md diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 53712518..87c7e846 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,48 +35,27 @@ jobs: - 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: 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/publish-cli.yaml b/.github/workflows/publish-cli.yaml index 1ad8103a..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,8 +88,8 @@ 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: @@ -103,7 +111,7 @@ 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 @@ -118,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) @@ -134,19 +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 + 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/makefile b/makefile index 4bcf582a..ef26df1c 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,4 @@ # Makefile for Jito Restaking Project - # Commands CARGO := cargo CARGO_SORT := cargo sort @@ -10,6 +9,10 @@ CARGO_NEXTEST := cargo nextest YARN := yarn SHANK_CLI := ./target/release/jito-shank-cli +# Environment and paths +ENV_PATH := ./config/program.env +IDL_OUTPUT_PATH := ./idl + # Default target .PHONY: all all: build test @@ -23,12 +26,38 @@ lint: # Code generation .PHONY: generate-code -generate-code: build-release - $(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/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(()) From 4190d695534bc9767aed1d62d13b0431bfef77b1 Mon Sep 17 00:00:00 2001 From: Coach Chuck <169060940+coachchucksol@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:00:40 -0600 Subject: [PATCH 12/12] AUDIT: 36675 - Add Revoke Token Delegation (#262) Cleaned up version of #193 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../instructions/addDelegation.ts | 2 +- .../closeVaultUpdateStateTracker.ts | 2 +- .../instructions/cooldownDelegation.ts | 2 +- .../crankVaultUpdateStateTracker.ts | 2 +- .../instructions/createTokenMetadata.ts | 2 +- clients/js/vault_client/instructions/index.ts | 1 + .../initializeVaultUpdateStateTracker.ts | 2 +- .../revokeDelegateTokenAccount.ts | 249 +++++++++ .../js/vault_client/instructions/setAdmin.ts | 2 +- .../instructions/setConfigAdmin.ts | 2 +- .../instructions/setConfigSecondaryAdmin.ts | 2 +- .../instructions/setSecondaryAdmin.ts | 2 +- .../instructions/updateTokenMetadata.ts | 2 +- .../instructions/updateVaultBalance.ts | 2 +- clients/js/vault_client/programs/jitoVault.ts | 30 +- .../generated/instructions/add_delegation.rs | 2 +- .../close_vault_update_state_tracker.rs | 2 +- .../instructions/cooldown_delegation.rs | 2 +- .../crank_vault_update_state_tracker.rs | 2 +- .../instructions/create_token_metadata.rs | 2 +- .../initialize_vault_update_state_tracker.rs | 2 +- .../src/generated/instructions/mod.rs | 2 + .../revoke_delegate_token_account.rs | 488 ++++++++++++++++++ .../src/generated/instructions/set_admin.rs | 2 +- .../instructions/set_config_admin.rs | 2 +- .../set_config_secondary_admin.rs | 2 +- .../instructions/set_secondary_admin.rs | 2 +- .../instructions/update_token_metadata.rs | 2 +- .../instructions/update_vault_balance.rs | 2 +- idl/jito_vault.json | 64 ++- .../tests/fixtures/vault_client.rs | 27 + integration_tests/tests/vault/mod.rs | 1 + .../vault/revoke_delegate_token_account.rs | 253 +++++++++ vault_program/src/lib.rs | 6 + .../src/revoke_delegate_token_account.rs | 84 +++ vault_sdk/src/instruction.rs | 9 + vault_sdk/src/sdk.rs | 27 + 37 files changed, 1242 insertions(+), 47 deletions(-) create mode 100644 clients/js/vault_client/instructions/revokeDelegateTokenAccount.ts create mode 100644 clients/rust/vault_client/src/generated/instructions/revoke_delegate_token_account.rs create mode 100644 integration_tests/tests/vault/revoke_delegate_token_account.rs create mode 100644 vault_program/src/revoke_delegate_token_account.rs 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 1efbeb40..8c7050f9 100644 --- a/clients/js/vault_client/instructions/index.ts +++ b/clients/js/vault_client/instructions/index.ts @@ -26,6 +26,7 @@ export * from './initializeVaultOperatorDelegation'; export * from './initializeVaultUpdateStateTracker'; export * from './initializeVaultWithMint'; export * from './mintTo'; +export * from './revokeDelegateTokenAccount'; export * from './setAdmin'; export * from './setConfigAdmin'; export * from './setConfigSecondaryAdmin'; 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 index 1c6b0eb6..1db2782e 100644 --- a/clients/js/vault_client/instructions/setConfigSecondaryAdmin.ts +++ b/clients/js/vault_client/instructions/setConfigSecondaryAdmin.ts @@ -35,7 +35,7 @@ import { type ConfigAdminRoleArgs, } from '../types'; -export const SET_CONFIG_SECONDARY_ADMIN_DISCRIMINATOR = 32; +export const SET_CONFIG_SECONDARY_ADMIN_DISCRIMINATOR = 33; export function getSetConfigSecondaryAdminDiscriminatorBytes() { return getU8Encoder().encode(SET_CONFIG_SECONDARY_ADMIN_DISCRIMINATOR); 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 342e0b4f..44a1e635 100644 --- a/clients/js/vault_client/programs/jitoVault.ts +++ b/clients/js/vault_client/programs/jitoVault.ts @@ -33,6 +33,7 @@ import { type ParsedInitializeVaultUpdateStateTrackerInstruction, type ParsedInitializeVaultWithMintInstruction, type ParsedMintToInstruction, + type ParsedRevokeDelegateTokenAccountInstruction, type ParsedSetAdminInstruction, type ParsedSetConfigAdminInstruction, type ParsedSetConfigSecondaryAdminInstruction, @@ -84,6 +85,7 @@ export enum JitoVaultInstruction { SetProgramFeeWallet, SetIsPaused, DelegateTokenAccount, + RevokeDelegateTokenAccount, SetAdmin, SetSecondaryAdmin, AddDelegation, @@ -166,39 +168,42 @@ 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.SetConfigAdmin; + 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( @@ -272,6 +277,9 @@ export type ParsedJitoVaultInstruction< | ({ instructionType: JitoVaultInstruction.DelegateTokenAccount; } & ParsedDelegateTokenAccountInstruction) + | ({ + instructionType: JitoVaultInstruction.RevokeDelegateTokenAccount; + } & ParsedRevokeDelegateTokenAccountInstruction) | ({ instructionType: JitoVaultInstruction.SetAdmin; } & ParsedSetAdminInstruction) 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 ad460411..57f6404a 100644 --- a/clients/rust/vault_client/src/generated/instructions/mod.rs +++ b/clients/rust/vault_client/src/generated/instructions/mod.rs @@ -25,6 +25,7 @@ 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; @@ -59,6 +60,7 @@ 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::*; 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 index 2e16a013..00cd38f4 100644 --- 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 @@ -65,7 +65,7 @@ pub struct SetConfigSecondaryAdminInstructionData { impl SetConfigSecondaryAdminInstructionData { pub fn new() -> Self { - Self { discriminator: 32 } + Self { discriminator: 33 } } } 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/idl/jito_vault.json b/idl/jito_vault.json index 2f62ef34..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,7 @@ "args": [], "discriminant": { "type": "u8", - "value": 31 + "value": 32 } }, { @@ -1421,7 +1461,7 @@ ], "discriminant": { "type": "u8", - "value": 32 + "value": 33 } } ], diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 57371ba8..1c7f78cd 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -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, diff --git a/integration_tests/tests/vault/mod.rs b/integration_tests/tests/vault/mod.rs index a45d3883..0fba3278 100644 --- a/integration_tests/tests/vault/mod.rs +++ b/integration_tests/tests/vault/mod.rs @@ -14,6 +14,7 @@ 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; 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/vault_program/src/lib.rs b/vault_program/src/lib.rs index 4934ab90..755da580 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -18,6 +18,7 @@ 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; @@ -61,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, @@ -164,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, 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_sdk/src/instruction.rs b/vault_sdk/src/instruction.rs index 5a0f33e6..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")] diff --git a/vault_sdk/src/sdk.rs b/vault_sdk/src/sdk.rs index 2bb7374c..aa31a3e1 100644 --- a/vault_sdk/src/sdk.rs +++ b/vault_sdk/src/sdk.rs @@ -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,