├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── run-tests.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── mod.rs ├── package-lock.json ├── rust-toolchain.toml ├── security_audits └── Halborn_MagicBlock_Delegation_Program.pdf ├── src ├── args │ ├── commit_state.rs │ ├── delegate.rs │ ├── delegate_ephemeral_balance.rs │ ├── mod.rs │ ├── top_up_ephemeral_balance.rs │ ├── validator_claim_fees.rs │ └── whitelist_validator_for_program.rs ├── consts.rs ├── discriminator.rs ├── error.rs ├── instruction_builder │ ├── close_ephemeral_balance.rs │ ├── close_validator_fees_vault.rs │ ├── commit_state.rs │ ├── commit_state_from_buffer.rs │ ├── delegate.rs │ ├── delegate_ephemeral_balance.rs │ ├── finalize.rs │ ├── init_protocol_fees_vault.rs │ ├── init_validator_fees_vault.rs │ ├── mod.rs │ ├── protocol_claim_fees.rs │ ├── top_up_ephemeral_balance.rs │ ├── undelegate.rs │ ├── validator_claim_fees.rs │ └── whitelist_validator_for_program.rs ├── lib.rs ├── pda.rs ├── processor │ ├── close_ephemeral_balance.rs │ ├── close_validator_fees_vault.rs │ ├── commit_state.rs │ ├── commit_state_from_buffer.rs │ ├── delegate.rs │ ├── delegate_ephemeral_balance.rs │ ├── finalize.rs │ ├── init_protocol_fees_vault.rs │ ├── init_validator_fees_vault.rs │ ├── mod.rs │ ├── protocol_claim_fees.rs │ ├── top_up_ephemeral_balance.rs │ ├── undelegate.rs │ ├── utils │ │ ├── curve.rs │ │ ├── loaders.rs │ │ ├── mod.rs │ │ └── pda.rs │ ├── validator_claim_fees.rs │ └── whitelist_validator_for_program.rs └── state │ ├── commit_record.rs │ ├── delegation_metadata.rs │ ├── delegation_record.rs │ ├── mod.rs │ ├── program_config.rs │ └── utils │ ├── discriminator.rs │ ├── mod.rs │ ├── to_bytes.rs │ └── try_from_bytes.rs └── tests ├── buffers └── test_delegation.so ├── fixtures ├── accounts.rs └── mod.rs ├── integration ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── crates │ └── types │ │ └── Cargo.toml ├── migrations │ └── deploy.ts ├── package.json ├── programs │ └── test-delegation │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ └── lib.rs ├── target │ └── deploy │ │ └── test_delegation-keypair.json ├── tests │ ├── fixtures │ │ ├── consts.ts │ │ └── provider.json │ ├── test-delegation.ts │ └── test-increment-delegated.ts ├── tsconfig.json └── yarn.lock ├── test_close_validator_fees_vault.rs ├── test_commit_on_curve.rs ├── test_commit_state.rs ├── test_commit_state_from_buffer.rs ├── test_commit_state_with_program_config.rs ├── test_delegate.rs ├── test_delegate_on_curve.rs ├── test_finalize.rs ├── test_init_fees_vault.rs ├── test_init_validator_fees_vault.rs ├── test_lamports_settlement.rs ├── test_protocol_claim_fees.rs ├── test_top_up.rs ├── test_undelegate.rs ├── test_undelegate_on_curve.rs ├── test_undelegate_without_commit.rs ├── test_validator_claim_fees.rs └── test_whitelist_validator_for_program.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug]" 5 | labels: bug 6 | 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | > ⚠️ NOTE: Use notes like this to emphasize something important about the PR. 3 | > 4 | > This could include other PRs this PR is built on top of; API breaking changes; reasons for why the PR is on hold; or anything else you would like to draw attention to. 5 | 6 | | Status | Type | ⚠️ Core Change | Issue | 7 | | :---: | :---: | :---: | :--: | 8 | | Ready/Hold | Feature/Bug/Tooling/Refactor/Hotfix | Yes/No | [Link]() | 9 | 10 | ## Problem 11 | 12 | _What problem are you trying to solve?_ 13 | 14 | 15 | ## Solution 16 | 17 | _How did you solve the problem?_ 18 | 19 | 20 | ## Before & After Screenshots 21 | 22 | _Insert screenshots of example code output_ 23 | 24 | **BEFORE**: 25 | [insert screenshot here] 26 | 27 | **AFTER**: 28 | [insert screenshot here] 29 | 30 | 31 | ## Other changes (e.g. bug fixes, small refactors) 32 | 33 | 34 | ## Deploy Notes 35 | 36 | _Notes regarding deployment of the contained body of work. These should note any 37 | new dependencies, new scripts, etc._ 38 | 39 | **New scripts**: 40 | 41 | - `script` : script details 42 | 43 | **New dependencies**: 44 | 45 | - `dependency` : dependency details -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | env: 9 | solana_version: v2.1.21 10 | 11 | jobs: 12 | install: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/cache@v4 17 | name: cache solana cli 18 | id: cache-solana 19 | with: 20 | path: | 21 | ~/.cache/solana/ 22 | ~/.local/share/solana/ 23 | key: solana-${{ runner.os }}-v0000-${{ env.solana_version }} 24 | 25 | - name: install essentials 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install -y pkg-config build-essential libudev-dev 29 | npm install --global yarn 30 | 31 | - name: install rust 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | toolchain: stable 35 | 36 | - name: Cache rust 37 | uses: Swatinem/rust-cache@v2 38 | 39 | - name: install solana 40 | if: steps.cache-solana.outputs.cache-hit != 'true' 41 | run: | 42 | sh -c "$(curl -sSfL https://release.anza.xyz/${{ env.solana_version }}/install)" 43 | export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" 44 | solana --version 45 | 46 | lint: 47 | needs: install 48 | runs-on: ubuntu-latest 49 | 50 | steps: 51 | - uses: actions/checkout@v4 52 | - name: Run fmt 53 | run: cargo fmt -- --check 54 | - name: Run clippy 55 | run: cargo clippy -- --deny=warnings 56 | 57 | test: 58 | needs: [install, lint] 59 | runs-on: ubuntu-latest 60 | 61 | steps: 62 | - uses: actions/checkout@v4 63 | - uses: actions/cache@v4 64 | name: cache solana cli 65 | id: cache-solana 66 | with: 67 | path: | 68 | ~/.cache/solana/ 69 | ~/.local/share/solana/ 70 | key: solana-${{ runner.os }}-v0000-${{ env.solana_version }} 71 | 72 | - name: setup solana 73 | run: | 74 | export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" 75 | solana --version 76 | solana-keygen new --silent --no-bip39-passphrase 77 | 78 | - name: run build 79 | run: | 80 | cargo build 81 | 82 | - name: run tests 83 | run: | 84 | export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" 85 | cargo test-sbf --features unit_test_config 86 | 87 | - name: clean up before integration tests 88 | run: | 89 | rm -rf sdk/ts/node_modules 90 | cp target/deploy/dlp.so /tmp/dlp.so 91 | rm -rf target 92 | mkdir -p target/deploy && cp /tmp/dlp.so target/deploy/dlp.so 93 | rm -rf ~/.cache 94 | rm -rf ~/.cargo/registry 95 | df -h 96 | 97 | - name: run integration tests 98 | run: | 99 | export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" 100 | npm install -g @magicblock-labs/bolt-cli 101 | cd sdk/ts && npm install --legacy-peer-deps && npm run build && npm run lint:fix && cd ../.. 102 | cd tests/integration 103 | npm install 104 | bolt test 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.idea 3 | target 4 | test-ledger 5 | 6 | *.anchor 7 | *.bolt 8 | *.yarn 9 | tests/integration/target 10 | !tests/integration/target/deploy/test_delegation-keypair.json 11 | tests/integration/node_modules 12 | tests/integration/test-ledger 13 | **/*.rs.bk 14 | 15 | sdk/ts/lib 16 | sdk/ts/node_modules 17 | 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "magicblock-delegation-program" 3 | description = "Delegation program for the Ephemeral Rollups" 4 | version = "1.0.0" 5 | authors = ["Magicblock Labs "] 6 | edition = "2021" 7 | license = "MIT" 8 | homepage = "https://www.magicblock.gg/" 9 | documentation = "https://docs.magicblock.gg/" 10 | repository = "https://github.com/magicblock-labs/delegation-program" 11 | readme = "./README.md" 12 | keywords = ["solana", "crypto", "delegation", "ephemeral-rollups", "magicblock"] 13 | 14 | [lib] 15 | crate-type = ["cdylib", "lib"] 16 | name = "dlp" 17 | 18 | [features] 19 | no-entrypoint = [] 20 | default = ["solana-security-txt"] 21 | unit_test_config = [] 22 | 23 | [dependencies] 24 | borsh = { version = "1.5.3", features = [ "derive" ] } 25 | paste = "^1.0" 26 | solana-program = "2.2" 27 | bytemuck = { version = "1.21", features = [ "derive" ] } 28 | num_enum = "^0.7.2" 29 | thiserror = "^1.0.57" 30 | solana-security-txt = { version = "1.1.1", optional = true } 31 | solana-curve25519 = "2.2" 32 | bincode = "1.3.3" 33 | 34 | [dev-dependencies] 35 | base64 = "0.22.1" 36 | rand = "0.8.5" 37 | solana-program-test = "2.2" 38 | solana-sdk = "2.2" 39 | tokio = { version = "1.0", features = ["full"] } 40 | magicblock-delegation-program = { path = ".", features = ["unit_test_config"] } 41 | 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## Business Source License 1.1 2 | 3 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. "Business Source License" is a trademark of 4 | MariaDB Corporation Ab. 5 | 6 | ### Parameters 7 | 8 | Licensor: MagicBlock Labs Pte. Ltd. 9 | 10 | Licensed Work: MagicBlock Validator 11 | 12 | The Licensed Work is (c) 2024 MagicBlock Labs Pte. Ltd. 13 | 14 | Additional Use Grant: None 15 | 16 | Change Date: Dec 1, 2027 17 | 18 | Change License: MIT 19 | 20 | ### Terms 21 | 22 | The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production 23 | use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use. 24 | 25 | Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific 26 | version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the 27 | terms of the Change License, and the rights granted in the paragraph above terminate. 28 | 29 | If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, 30 | you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must 31 | refrain from using the Licensed Work. 32 | 33 | All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this 34 | License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each 35 | version of the Licensed Work released by Licensor. 36 | 37 | You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the 38 | Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply 39 | to your use of that work. 40 | 41 | Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License 42 | for the current and all other versions of the Licensed Work. 43 | 44 | This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may 45 | use a trademark or logo of Licensor as expressly required by this License). TO THE EXTENT PERMITTED BY APPLICABLE LAW, 46 | THE LICENSED WORK IS PROVIDED ON AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR 47 | IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, 48 | NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works, and 49 | to refer to it using the trademark "Business Source License", as long as you comply with the Covenants of Licensor 50 | below. 51 | 52 | ### Covenants of Licensor 53 | 54 | In consideration of the right to use this License’s text and the “Business Source License” name and trademark, Licensor 55 | covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor: 56 | 57 | To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL 58 | Version 2.0 or a later version, where “compatible” means that software provided under the Change License can be included 59 | in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change 60 | Licenses without limitation. 61 | To either: (1) specify an additional grant of rights to use that does not impose any additional restriction on the right 62 | granted in this License, as the Additional Use Grant; or (2) insert the text “None”. (3) to specify a Change Date. (4) Not to 63 | modify this License in any other way. 64 | 65 | ### Notice 66 | 67 | The Business Source License (this document, or the "License") is not an Open Source license. However, the Licensed Work 68 | will eventually be made available under an Open Source License, as stated in this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Delegation program 2 | 3 | Delegation module for https://arxiv.org/pdf/2311.02650.pdf 4 | 5 | ## Public Api 6 | 7 | - [`Instruction Builders`](src/instruction_builder/*.rs) – utilities to generate Instructions. 8 | - [`Args`](src/args/*.rs) – Instructions arguments structures. 9 | - [`Consts`](src/consts.rs) – Program constants. 10 | - [`Errors`](src/error.rs) – Custom program errors. 11 | 12 | ## Program 13 | 14 | - [`Entrypoint`](src/lib.rs) – The program entrypoint. 15 | - [`Processors`](src/processors/) – Instruction implementations. 16 | 17 | ## Important Instructions 18 | 19 | - [`Delegate`](src/processor/delegate.rs) - Delegate an account 20 | - [`CommitState`](src/processor/commit_state.rs) – Commit a new state 21 | - [`Finalize`](src/processor/finalize.rs) – Finalize a new state 22 | - [`Undelegate`](src/processor/undelegate.rs) – Undelegate an account 23 | 24 | ## Tests 25 | 26 | To run the test suite, use the Solana toolchain: 27 | 28 | ```bash 29 | cargo test-sbf --features unit_test_config 30 | ``` 31 | 32 | For line coverage, use llvm-cov: 33 | 34 | ```bash 35 | cargo llvm-cov --test test_commit_state 36 | ``` 37 | 38 | (llvm-cov currently does not work with instructions with CPIs e.g.: delegate, undelegate) 39 | 40 | ## Integration Tests 41 | 42 | The integration tests are located in the `tests/integration` directory. 43 | The tests consist of a Bolt/Anchor program that uses the delegation program to delegate, commit, and undelegate accounts. 44 | This can be also used a reference for how to interact with the program. 45 | 46 | To run the integration test, use Bolt or Anchor: 47 | 48 | ```bash 49 | cd tests/integration && bolt test 50 | ``` 51 | 52 | or: 53 | 54 | ```bash 55 | cd tests/integration && anchor test 56 | ``` 57 | -------------------------------------------------------------------------------- /mod.rs: -------------------------------------------------------------------------------- 1 | mod delegate; 2 | 3 | pub use delegate::*; 4 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delegation-program", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.84.1" 3 | components = [ "rustfmt", "rust-analyzer", "clippy"] 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /security_audits/Halborn_MagicBlock_Delegation_Program.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicblock-labs/delegation-program/9570abf24ab6d03ba75ecdbf8a6a787bb0d69d3a/security_audits/Halborn_MagicBlock_Delegation_Program.pdf -------------------------------------------------------------------------------- /src/args/commit_state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(Default, Debug, BorshSerialize, BorshDeserialize)] 4 | pub struct CommitStateArgs { 5 | /// The ephemeral slot at which the account data is committed 6 | pub slot: u64, 7 | /// The lamports that the account holds in the ephemeral validator 8 | pub lamports: u64, 9 | /// Whether the account can be undelegated after the commit completes 10 | pub allow_undelegation: bool, 11 | /// The account data 12 | pub data: Vec, 13 | } 14 | 15 | #[derive(Default, Debug, BorshSerialize, BorshDeserialize)] 16 | pub struct CommitStateFromBufferArgs { 17 | /// The ephemeral slot at which the account data is committed 18 | pub slot: u64, 19 | /// The lamports that the account holds in the ephemeral validator 20 | pub lamports: u64, 21 | /// Whether the account can be undelegated after the commit completes 22 | pub allow_undelegation: bool, 23 | } 24 | -------------------------------------------------------------------------------- /src/args/delegate.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | 4 | #[derive(Default, Debug, BorshSerialize, BorshDeserialize)] 5 | pub struct DelegateArgs { 6 | /// The frequency at which the validator should commit the account data 7 | /// if no commit is triggered by the owning program 8 | pub commit_frequency_ms: u32, 9 | /// The seeds used to derive the PDA of the delegated account 10 | pub seeds: Vec>, 11 | /// The validator authority that is added to the delegation record 12 | pub validator: Option, 13 | } 14 | -------------------------------------------------------------------------------- /src/args/delegate_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | use crate::args::DelegateArgs; 4 | 5 | #[derive(Default, Debug, BorshSerialize, BorshDeserialize)] 6 | pub struct DelegateEphemeralBalanceArgs { 7 | pub delegate_args: DelegateArgs, 8 | pub index: u8, 9 | } 10 | -------------------------------------------------------------------------------- /src/args/mod.rs: -------------------------------------------------------------------------------- 1 | mod commit_state; 2 | mod delegate; 3 | mod delegate_ephemeral_balance; 4 | mod top_up_ephemeral_balance; 5 | mod validator_claim_fees; 6 | mod whitelist_validator_for_program; 7 | 8 | pub use commit_state::*; 9 | pub use delegate::*; 10 | pub use delegate_ephemeral_balance::*; 11 | pub use top_up_ephemeral_balance::*; 12 | pub use validator_claim_fees::*; 13 | pub use whitelist_validator_for_program::*; 14 | -------------------------------------------------------------------------------- /src/args/top_up_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(Debug, BorshSerialize, BorshDeserialize)] 4 | pub struct TopUpEphemeralBalanceArgs { 5 | /// The amount to add to the ephemeral balance. 6 | pub amount: u64, 7 | /// The index of the ephemeral balance account to top up which allows 8 | /// one payer to have multiple ephemeral balance accounts. 9 | pub index: u8, 10 | } 11 | -------------------------------------------------------------------------------- /src/args/validator_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(Default, Debug, BorshSerialize, BorshDeserialize)] 4 | pub struct ValidatorClaimFeesArgs { 5 | /// The amount to claim from the fees vault. 6 | /// If `None`, almost the entire amount is claimed. The remaining amount 7 | /// is needed to keep the fees vault rent-exempt. 8 | pub amount: Option, 9 | } 10 | -------------------------------------------------------------------------------- /src/args/whitelist_validator_for_program.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(Debug, BorshSerialize, BorshDeserialize)] 4 | pub struct WhitelistValidatorForProgramArgs { 5 | /// If `true`, insert the validator identity into the program whitelist, 6 | /// otherwise remove it. 7 | pub insert: bool, 8 | } 9 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | use solana_program::pubkey::Pubkey; 2 | 3 | /// The delegation session fees (extracted in percentage from the delegation PDAs rent on closure). 4 | pub const RENT_FEES_PERCENTAGE: u8 = 10; 5 | 6 | /// The fees extracted from the validator earnings (extracted in percentage from the validator fees claims). 7 | pub const PROTOCOL_FEES_PERCENTAGE: u8 = 10; 8 | 9 | /// The discriminator for the external undelegate instruction. 10 | pub const EXTERNAL_UNDELEGATE_DISCRIMINATOR: [u8; 8] = [196, 28, 41, 206, 48, 37, 51, 167]; 11 | 12 | /// The program ID of the delegation program. 13 | pub const DELEGATION_PROGRAM_ID: Pubkey = crate::id(); 14 | -------------------------------------------------------------------------------- /src/discriminator.rs: -------------------------------------------------------------------------------- 1 | use num_enum::TryFromPrimitive; 2 | use solana_program::program_error::ProgramError; 3 | 4 | #[repr(u8)] 5 | #[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] 6 | #[rustfmt::skip] 7 | pub enum DlpDiscriminator { 8 | /// See [crate::processor::process_delegate] for docs. 9 | Delegate = 0, 10 | /// See [crate::processor::process_commit_state] for docs. 11 | CommitState = 1, 12 | /// See [crate::processor::process_finalize] for docs. 13 | Finalize = 2, 14 | /// See [crate::processor::process_undelegate] for docs. 15 | Undelegate = 3, 16 | /// See [crate::processor::process_init_protocol_fees_vault] for docs. 17 | InitProtocolFeesVault = 5, 18 | /// See [crate::processor::process_init_validator_fees_vault] for docs. 19 | InitValidatorFeesVault = 6, 20 | /// See [crate::processor::process_validator_claim_fees] for docs. 21 | ValidatorClaimFees = 7, 22 | /// See [crate::processor::process_whitelist_validator_for_program] for docs. 23 | WhitelistValidatorForProgram = 8, 24 | /// See [crate::processor::process_top_up_ephemeral_balance] for docs. 25 | TopUpEphemeralBalance = 9, 26 | /// See [crate::processor::process_delegate_ephemeral_balance] for docs. 27 | DelegateEphemeralBalance = 10, 28 | /// See [crate::processor::process_close_ephemeral_balance] for docs. 29 | CloseEphemeralBalance = 11, 30 | /// See [crate::processor::process_protocol_claim_fees] for docs. 31 | ProtocolClaimFees = 12, 32 | /// See [crate::processor::process_commit_state_from_buffer] for docs. 33 | CommitStateFromBuffer = 13, 34 | /// See [crate::processor::process_close_validator_fees_vault] for docs. 35 | CloseValidatorFeesVault = 14, 36 | } 37 | 38 | impl DlpDiscriminator { 39 | pub fn to_vec(self) -> Vec { 40 | let num = self as u64; 41 | num.to_le_bytes().to_vec() 42 | } 43 | } 44 | 45 | impl TryFrom<[u8; 8]> for DlpDiscriminator { 46 | type Error = ProgramError; 47 | fn try_from(bytes: [u8; 8]) -> Result { 48 | match bytes[0] { 49 | 0x0 => Ok(DlpDiscriminator::Delegate), 50 | 0x1 => Ok(DlpDiscriminator::CommitState), 51 | 0x2 => Ok(DlpDiscriminator::Finalize), 52 | 0x3 => Ok(DlpDiscriminator::Undelegate), 53 | 0x5 => Ok(DlpDiscriminator::InitProtocolFeesVault), 54 | 0x6 => Ok(DlpDiscriminator::InitValidatorFeesVault), 55 | 0x7 => Ok(DlpDiscriminator::ValidatorClaimFees), 56 | 0x8 => Ok(DlpDiscriminator::WhitelistValidatorForProgram), 57 | 0x9 => Ok(DlpDiscriminator::TopUpEphemeralBalance), 58 | 0xa => Ok(DlpDiscriminator::DelegateEphemeralBalance), 59 | 0xb => Ok(DlpDiscriminator::CloseEphemeralBalance), 60 | 0xc => Ok(DlpDiscriminator::ProtocolClaimFees), 61 | 0xd => Ok(DlpDiscriminator::CommitStateFromBuffer), 62 | 0xe => Ok(DlpDiscriminator::CloseValidatorFeesVault), 63 | _ => Err(ProgramError::InvalidInstructionData), 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use num_enum::IntoPrimitive; 2 | use solana_program::program_error::ProgramError; 3 | use thiserror::Error; 4 | 5 | #[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] 6 | #[repr(u32)] 7 | pub enum DlpError { 8 | #[error("Invalid Authority")] 9 | InvalidAuthority = 0, 10 | #[error("Account cannot be undelegated, is_undelegatable is false")] 11 | NotUndelegatable = 1, 12 | #[error("Unauthorized Operation")] 13 | Unauthorized = 2, 14 | #[error("Invalid Authority for the current target program")] 15 | InvalidAuthorityForProgram = 3, 16 | #[error("Delegated account does not match the expected account")] 17 | InvalidDelegatedAccount = 4, 18 | #[error("Delegated account is not in a valid state")] 19 | InvalidDelegatedState = 5, 20 | #[error("Reimbursement account does not match the expected account")] 21 | InvalidReimbursementAccount = 6, 22 | #[error("Invalid account data after CPI")] 23 | InvalidAccountDataAfterCPI = 7, 24 | #[error("Invalid validator balance after CPI")] 25 | InvalidValidatorBalanceAfterCPI = 8, 26 | #[error("Invalid reimbursement address for delegation rent")] 27 | InvalidReimbursementAddressForDelegationRent = 9, 28 | #[error("Authority is invalid for the delegated account program owner")] 29 | InvalidWhitelistProgramConfig = 10, 30 | #[error("Account already undelegated")] 31 | AlreadyUndelegated = 11, 32 | #[error("Committed state slot is outdated")] 33 | OutdatedSlot = 12, 34 | #[error("Computation overflow detected")] 35 | Overflow = 13, 36 | } 37 | 38 | impl From for ProgramError { 39 | fn from(e: DlpError) -> Self { 40 | ProgramError::Custom(e as u32) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/instruction_builder/close_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use solana_program::instruction::Instruction; 2 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey, system_program}; 3 | 4 | use crate::discriminator::DlpDiscriminator; 5 | use crate::pda::ephemeral_balance_pda_from_payer; 6 | 7 | /// Creates instruction to close an ephemeral balance account 8 | /// See [crate::processor::process_close_ephemeral_balance] for docs. 9 | pub fn close_ephemeral_balance(payer: Pubkey, index: u8) -> Instruction { 10 | let ephemeral_balance_pda = ephemeral_balance_pda_from_payer(&payer, index); 11 | Instruction { 12 | program_id: crate::id(), 13 | accounts: vec![ 14 | AccountMeta::new(payer, true), 15 | AccountMeta::new(ephemeral_balance_pda, false), 16 | AccountMeta::new_readonly(system_program::id(), false), 17 | ], 18 | data: [ 19 | DlpDiscriminator::CloseEphemeralBalance.to_vec(), 20 | vec![index], 21 | ] 22 | .concat(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/instruction_builder/close_validator_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use solana_program::instruction::Instruction; 2 | use solana_program::{bpf_loader_upgradeable, instruction::AccountMeta, pubkey::Pubkey}; 3 | 4 | use crate::discriminator::DlpDiscriminator; 5 | use crate::pda::validator_fees_vault_pda_from_validator; 6 | 7 | /// Close a validator fees vault PDA. 8 | /// See [crate::processor::process_close_validator_fees_vault] for docs. 9 | pub fn close_validator_fees_vault( 10 | payer: Pubkey, 11 | admin: Pubkey, 12 | validator_identity: Pubkey, 13 | ) -> Instruction { 14 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator_identity); 15 | let delegation_program_data = 16 | Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::id()).0; 17 | Instruction { 18 | program_id: crate::id(), 19 | accounts: vec![ 20 | AccountMeta::new(payer, true), 21 | AccountMeta::new(admin, true), 22 | AccountMeta::new_readonly(delegation_program_data, false), 23 | AccountMeta::new(validator_identity, false), 24 | AccountMeta::new(validator_fees_vault_pda, false), 25 | ], 26 | data: DlpDiscriminator::CloseValidatorFeesVault.to_vec(), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/instruction_builder/commit_state.rs: -------------------------------------------------------------------------------- 1 | use borsh::to_vec; 2 | use solana_program::instruction::Instruction; 3 | use solana_program::system_program; 4 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 5 | 6 | use crate::args::CommitStateArgs; 7 | use crate::discriminator::DlpDiscriminator; 8 | use crate::pda::{ 9 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 10 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 11 | program_config_from_program_id, validator_fees_vault_pda_from_validator, 12 | }; 13 | 14 | /// Builds a commit state instruction. 15 | /// See [crate::processor::process_commit_state] for docs. 16 | pub fn commit_state( 17 | validator: Pubkey, 18 | delegated_account: Pubkey, 19 | delegated_account_owner: Pubkey, 20 | commit_args: CommitStateArgs, 21 | ) -> Instruction { 22 | let commit_args = to_vec(&commit_args).unwrap(); 23 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account); 24 | let commit_state_pda = commit_state_pda_from_delegated_account(&delegated_account); 25 | let commit_record_pda = commit_record_pda_from_delegated_account(&delegated_account); 26 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); 27 | let delegation_metadata_pda = 28 | delegation_metadata_pda_from_delegated_account(&delegated_account); 29 | let program_config_pda = program_config_from_program_id(&delegated_account_owner); 30 | Instruction { 31 | program_id: crate::id(), 32 | accounts: vec![ 33 | AccountMeta::new_readonly(validator, true), 34 | AccountMeta::new_readonly(delegated_account, false), 35 | AccountMeta::new(commit_state_pda, false), 36 | AccountMeta::new(commit_record_pda, false), 37 | AccountMeta::new_readonly(delegation_record_pda, false), 38 | AccountMeta::new(delegation_metadata_pda, false), 39 | AccountMeta::new_readonly(validator_fees_vault_pda, false), 40 | AccountMeta::new_readonly(program_config_pda, false), 41 | AccountMeta::new_readonly(system_program::id(), false), 42 | ], 43 | data: [DlpDiscriminator::CommitState.to_vec(), commit_args].concat(), 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/instruction_builder/commit_state_from_buffer.rs: -------------------------------------------------------------------------------- 1 | use borsh::to_vec; 2 | use solana_program::instruction::Instruction; 3 | use solana_program::system_program; 4 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 5 | 6 | use crate::args::CommitStateFromBufferArgs; 7 | use crate::discriminator::DlpDiscriminator; 8 | use crate::pda::{ 9 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 10 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 11 | program_config_from_program_id, validator_fees_vault_pda_from_validator, 12 | }; 13 | 14 | /// Builds a commit state from buffer instruction. 15 | /// See [crate::processor::process_commit_state_from_buffer] for docs. 16 | pub fn commit_state_from_buffer( 17 | validator: Pubkey, 18 | delegated_account: Pubkey, 19 | delegated_account_owner: Pubkey, 20 | commit_state_buffer: Pubkey, 21 | commit_args: CommitStateFromBufferArgs, 22 | ) -> Instruction { 23 | let commit_args = to_vec(&commit_args).unwrap(); 24 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account); 25 | let commit_state_pda = commit_state_pda_from_delegated_account(&delegated_account); 26 | let commit_record_pda = commit_record_pda_from_delegated_account(&delegated_account); 27 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); 28 | let delegation_metadata_pda = 29 | delegation_metadata_pda_from_delegated_account(&delegated_account); 30 | let program_config_pda = program_config_from_program_id(&delegated_account_owner); 31 | Instruction { 32 | program_id: crate::id(), 33 | accounts: vec![ 34 | AccountMeta::new_readonly(validator, true), 35 | AccountMeta::new_readonly(delegated_account, false), 36 | AccountMeta::new(commit_state_pda, false), 37 | AccountMeta::new(commit_record_pda, false), 38 | AccountMeta::new_readonly(delegation_record_pda, false), 39 | AccountMeta::new(delegation_metadata_pda, false), 40 | AccountMeta::new_readonly(commit_state_buffer, false), 41 | AccountMeta::new_readonly(validator_fees_vault_pda, false), 42 | AccountMeta::new_readonly(program_config_pda, false), 43 | AccountMeta::new_readonly(system_program::id(), false), 44 | ], 45 | data: [ 46 | DlpDiscriminator::CommitStateFromBuffer.to_vec(), 47 | commit_args, 48 | ] 49 | .concat(), 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/instruction_builder/delegate.rs: -------------------------------------------------------------------------------- 1 | use borsh::to_vec; 2 | use solana_program::instruction::Instruction; 3 | use solana_program::system_program; 4 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 5 | 6 | use crate::args::DelegateArgs; 7 | use crate::discriminator::DlpDiscriminator; 8 | use crate::pda::{ 9 | delegate_buffer_pda_from_delegated_account_and_owner_program, 10 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 11 | }; 12 | 13 | /// Builds a delegate instruction 14 | /// See [crate::processor::process_delegate] for docs. 15 | pub fn delegate( 16 | payer: Pubkey, 17 | delegated_account: Pubkey, 18 | owner: Option, 19 | args: DelegateArgs, 20 | ) -> Instruction { 21 | let owner = owner.unwrap_or(system_program::id()); 22 | let delegate_buffer_pda = 23 | delegate_buffer_pda_from_delegated_account_and_owner_program(&delegated_account, &owner); 24 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account); 25 | let delegation_metadata_pda = 26 | delegation_metadata_pda_from_delegated_account(&delegated_account); 27 | let mut data = DlpDiscriminator::Delegate.to_vec(); 28 | data.extend_from_slice(&to_vec(&args).unwrap()); 29 | 30 | Instruction { 31 | program_id: crate::id(), 32 | accounts: vec![ 33 | AccountMeta::new(payer, true), 34 | AccountMeta::new(delegated_account, true), 35 | AccountMeta::new_readonly(owner, false), 36 | AccountMeta::new(delegate_buffer_pda, false), 37 | AccountMeta::new(delegation_record_pda, false), 38 | AccountMeta::new(delegation_metadata_pda, false), 39 | AccountMeta::new_readonly(system_program::id(), false), 40 | ], 41 | data, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/instruction_builder/delegate_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use borsh::to_vec; 2 | use solana_program::instruction::Instruction; 3 | use solana_program::system_program; 4 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 5 | 6 | use crate::args::DelegateEphemeralBalanceArgs; 7 | use crate::discriminator::DlpDiscriminator; 8 | use crate::pda::{ 9 | delegate_buffer_pda_from_delegated_account_and_owner_program, 10 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 11 | ephemeral_balance_pda_from_payer, 12 | }; 13 | 14 | /// Delegate ephemeral balance 15 | /// See [crate::processor::process_delegate_ephemeral_balance] for docs. 16 | pub fn delegate_ephemeral_balance( 17 | payer: Pubkey, 18 | pubkey: Pubkey, 19 | args: DelegateEphemeralBalanceArgs, 20 | ) -> Instruction { 21 | let delegated_account = ephemeral_balance_pda_from_payer(&pubkey, args.index); 22 | let delegate_buffer_pda = delegate_buffer_pda_from_delegated_account_and_owner_program( 23 | &delegated_account, 24 | &system_program::id(), 25 | ); 26 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account); 27 | let delegation_metadata_pda = 28 | delegation_metadata_pda_from_delegated_account(&delegated_account); 29 | let mut data = DlpDiscriminator::DelegateEphemeralBalance.to_vec(); 30 | data.extend_from_slice(&to_vec(&args).unwrap()); 31 | 32 | Instruction { 33 | program_id: crate::id(), 34 | accounts: vec![ 35 | AccountMeta::new(payer, true), 36 | AccountMeta::new_readonly(pubkey, true), 37 | AccountMeta::new(delegated_account, false), 38 | AccountMeta::new(delegate_buffer_pda, false), 39 | AccountMeta::new(delegation_record_pda, false), 40 | AccountMeta::new(delegation_metadata_pda, false), 41 | AccountMeta::new_readonly(system_program::id(), false), 42 | AccountMeta::new_readonly(crate::id(), false), 43 | ], 44 | data, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/instruction_builder/finalize.rs: -------------------------------------------------------------------------------- 1 | use solana_program::instruction::Instruction; 2 | use solana_program::system_program; 3 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 4 | 5 | use crate::discriminator::DlpDiscriminator; 6 | use crate::pda::{ 7 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 8 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 9 | validator_fees_vault_pda_from_validator, 10 | }; 11 | 12 | /// Builds a finalize state instruction. 13 | /// See [crate::processor::process_finalize] for docs. 14 | pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { 15 | let commit_state_pda = commit_state_pda_from_delegated_account(&delegated_account); 16 | let commit_record_pda = commit_record_pda_from_delegated_account(&delegated_account); 17 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account); 18 | let delegation_metadata_pda = 19 | delegation_metadata_pda_from_delegated_account(&delegated_account); 20 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); 21 | Instruction { 22 | program_id: crate::id(), 23 | accounts: vec![ 24 | AccountMeta::new_readonly(validator, true), 25 | AccountMeta::new(delegated_account, false), 26 | AccountMeta::new(commit_state_pda, false), 27 | AccountMeta::new(commit_record_pda, false), 28 | AccountMeta::new(delegation_record_pda, false), 29 | AccountMeta::new(delegation_metadata_pda, false), 30 | AccountMeta::new(validator_fees_vault_pda, false), 31 | AccountMeta::new_readonly(system_program::id(), false), 32 | ], 33 | data: DlpDiscriminator::Finalize.to_vec(), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/instruction_builder/init_protocol_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use solana_program::instruction::Instruction; 2 | use solana_program::system_program; 3 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 4 | 5 | use crate::discriminator::DlpDiscriminator; 6 | use crate::pda::fees_vault_pda; 7 | 8 | /// Initialize the fees vault PDA. 9 | /// See [crate::processor::process_init_protocol_fees_vault] for docs. 10 | pub fn init_protocol_fees_vault(payer: Pubkey) -> Instruction { 11 | let fees_vault_pda = fees_vault_pda(); 12 | Instruction { 13 | program_id: crate::id(), 14 | accounts: vec![ 15 | AccountMeta::new(payer, true), 16 | AccountMeta::new(fees_vault_pda, false), 17 | AccountMeta::new_readonly(system_program::id(), false), 18 | ], 19 | data: DlpDiscriminator::InitProtocolFeesVault.to_vec(), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/instruction_builder/init_validator_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use solana_program::instruction::Instruction; 2 | use solana_program::{bpf_loader_upgradeable, system_program}; 3 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 4 | 5 | use crate::discriminator::DlpDiscriminator; 6 | use crate::pda::validator_fees_vault_pda_from_validator; 7 | 8 | /// Initialize a validator fees vault PDA. 9 | /// See [crate::processor::process_init_validator_fees_vault] for docs. 10 | pub fn init_validator_fees_vault( 11 | payer: Pubkey, 12 | admin: Pubkey, 13 | validator_identity: Pubkey, 14 | ) -> Instruction { 15 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator_identity); 16 | let delegation_program_data = 17 | Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::id()).0; 18 | Instruction { 19 | program_id: crate::id(), 20 | accounts: vec![ 21 | AccountMeta::new(payer, true), 22 | AccountMeta::new(admin, true), 23 | AccountMeta::new_readonly(delegation_program_data, false), 24 | AccountMeta::new(validator_identity, false), 25 | AccountMeta::new(validator_fees_vault_pda, false), 26 | AccountMeta::new_readonly(system_program::id(), false), 27 | ], 28 | data: DlpDiscriminator::InitValidatorFeesVault.to_vec(), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/instruction_builder/mod.rs: -------------------------------------------------------------------------------- 1 | mod close_ephemeral_balance; 2 | mod commit_state; 3 | 4 | mod close_validator_fees_vault; 5 | mod commit_state_from_buffer; 6 | mod delegate; 7 | mod delegate_ephemeral_balance; 8 | mod finalize; 9 | mod init_protocol_fees_vault; 10 | mod init_validator_fees_vault; 11 | mod protocol_claim_fees; 12 | mod top_up_ephemeral_balance; 13 | mod undelegate; 14 | mod validator_claim_fees; 15 | mod whitelist_validator_for_program; 16 | 17 | pub use close_ephemeral_balance::*; 18 | pub use close_validator_fees_vault::*; 19 | pub use commit_state::*; 20 | pub use commit_state_from_buffer::*; 21 | pub use delegate::*; 22 | pub use delegate_ephemeral_balance::*; 23 | pub use finalize::*; 24 | pub use init_protocol_fees_vault::*; 25 | pub use init_validator_fees_vault::*; 26 | pub use protocol_claim_fees::*; 27 | pub use top_up_ephemeral_balance::*; 28 | pub use undelegate::*; 29 | pub use validator_claim_fees::*; 30 | pub use whitelist_validator_for_program::*; 31 | -------------------------------------------------------------------------------- /src/instruction_builder/protocol_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use solana_program::instruction::Instruction; 2 | use solana_program::{bpf_loader_upgradeable, instruction::AccountMeta, pubkey::Pubkey}; 3 | 4 | use crate::discriminator::DlpDiscriminator; 5 | use crate::pda::fees_vault_pda; 6 | 7 | /// Claim the accrued fees from the protocol fees vault. 8 | /// See [crate::processor::process_protocol_claim_fees] for docs. 9 | pub fn protocol_claim_fees(admin: Pubkey) -> Instruction { 10 | let fees_vault_pda = fees_vault_pda(); 11 | let delegation_program_data = 12 | Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::id()).0; 13 | Instruction { 14 | program_id: crate::id(), 15 | accounts: vec![ 16 | AccountMeta::new(admin, true), 17 | AccountMeta::new(fees_vault_pda, false), 18 | AccountMeta::new_readonly(delegation_program_data, false), 19 | ], 20 | data: DlpDiscriminator::ProtocolClaimFees.to_vec(), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/instruction_builder/top_up_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use borsh::to_vec; 2 | use solana_program::instruction::Instruction; 3 | use solana_program::system_program; 4 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 5 | 6 | use crate::args::TopUpEphemeralBalanceArgs; 7 | use crate::discriminator::DlpDiscriminator; 8 | use crate::pda::ephemeral_balance_pda_from_payer; 9 | 10 | /// Builds a top-up ephemeral balance instruction. 11 | /// See [crate::processor::process_top_up_ephemeral_balance] for docs. 12 | pub fn top_up_ephemeral_balance( 13 | payer: Pubkey, 14 | pubkey: Pubkey, 15 | amount: Option, 16 | index: Option, 17 | ) -> Instruction { 18 | let args = TopUpEphemeralBalanceArgs { 19 | amount: amount.unwrap_or(10000), 20 | index: index.unwrap_or(0), 21 | }; 22 | let ephemeral_balance_pda = ephemeral_balance_pda_from_payer(&pubkey, args.index); 23 | Instruction { 24 | program_id: crate::id(), 25 | accounts: vec![ 26 | AccountMeta::new(payer, true), 27 | AccountMeta::new_readonly(pubkey, false), 28 | AccountMeta::new(ephemeral_balance_pda, false), 29 | AccountMeta::new_readonly(system_program::id(), false), 30 | ], 31 | data: [ 32 | DlpDiscriminator::TopUpEphemeralBalance.to_vec(), 33 | to_vec(&args).unwrap(), 34 | ] 35 | .concat(), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/instruction_builder/undelegate.rs: -------------------------------------------------------------------------------- 1 | use solana_program::instruction::Instruction; 2 | use solana_program::system_program; 3 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 4 | 5 | use crate::discriminator::DlpDiscriminator; 6 | use crate::pda::{ 7 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 8 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 9 | fees_vault_pda, undelegate_buffer_pda_from_delegated_account, 10 | validator_fees_vault_pda_from_validator, 11 | }; 12 | 13 | /// Builds an undelegate instruction. 14 | /// See [crate::processor::process_undelegate] for docs. 15 | #[allow(clippy::too_many_arguments)] 16 | pub fn undelegate( 17 | validator: Pubkey, 18 | delegated_account: Pubkey, 19 | owner_program: Pubkey, 20 | rent_reimbursement: Pubkey, 21 | ) -> Instruction { 22 | let undelegate_buffer_pda = undelegate_buffer_pda_from_delegated_account(&delegated_account); 23 | let commit_state_pda = commit_state_pda_from_delegated_account(&delegated_account); 24 | let commit_record_pda = commit_record_pda_from_delegated_account(&delegated_account); 25 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account); 26 | let delegation_metadata_pda = 27 | delegation_metadata_pda_from_delegated_account(&delegated_account); 28 | let fees_vault_pda = fees_vault_pda(); 29 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); 30 | Instruction { 31 | program_id: crate::id(), 32 | accounts: vec![ 33 | AccountMeta::new(validator, true), 34 | AccountMeta::new(delegated_account, false), 35 | AccountMeta::new_readonly(owner_program, false), 36 | AccountMeta::new(undelegate_buffer_pda, false), 37 | AccountMeta::new_readonly(commit_state_pda, false), 38 | AccountMeta::new_readonly(commit_record_pda, false), 39 | AccountMeta::new(delegation_record_pda, false), 40 | AccountMeta::new(delegation_metadata_pda, false), 41 | AccountMeta::new(rent_reimbursement, false), 42 | AccountMeta::new(fees_vault_pda, false), 43 | AccountMeta::new(validator_fees_vault_pda, false), 44 | AccountMeta::new_readonly(system_program::id(), false), 45 | ], 46 | data: DlpDiscriminator::Undelegate.to_vec(), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/instruction_builder/validator_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use borsh::to_vec; 2 | use solana_program::instruction::Instruction; 3 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 4 | 5 | use crate::args::ValidatorClaimFeesArgs; 6 | use crate::discriminator::DlpDiscriminator; 7 | use crate::pda::{fees_vault_pda, validator_fees_vault_pda_from_validator}; 8 | 9 | /// Claim the accrued fees from the fees vault. 10 | /// See [crate::processor::process_validator_claim_fees] for docs. 11 | pub fn validator_claim_fees(validator: Pubkey, amount: Option) -> Instruction { 12 | let args = ValidatorClaimFeesArgs { amount }; 13 | let fees_vault_pda = fees_vault_pda(); 14 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); 15 | Instruction { 16 | program_id: crate::id(), 17 | accounts: vec![ 18 | AccountMeta::new(validator, true), 19 | AccountMeta::new(fees_vault_pda, false), 20 | AccountMeta::new(validator_fees_vault_pda, false), 21 | ], 22 | data: [ 23 | DlpDiscriminator::ValidatorClaimFees.to_vec(), 24 | to_vec(&args).unwrap(), 25 | ] 26 | .concat(), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/instruction_builder/whitelist_validator_for_program.rs: -------------------------------------------------------------------------------- 1 | use borsh::to_vec; 2 | use solana_program::bpf_loader_upgradeable; 3 | use solana_program::instruction::Instruction; 4 | use solana_program::system_program; 5 | use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; 6 | 7 | use crate::args::WhitelistValidatorForProgramArgs; 8 | use crate::discriminator::DlpDiscriminator; 9 | use crate::pda::program_config_from_program_id; 10 | 11 | /// Whitelist validator for program 12 | /// 13 | /// See [crate::processor::process_whitelist_validator_for_program] for docs. 14 | pub fn whitelist_validator_for_program( 15 | authority: Pubkey, 16 | validator_identity: Pubkey, 17 | program: Pubkey, 18 | insert: bool, 19 | ) -> Instruction { 20 | let args = WhitelistValidatorForProgramArgs { insert }; 21 | let program_data = 22 | Pubkey::find_program_address(&[program.as_ref()], &bpf_loader_upgradeable::id()).0; 23 | let delegation_program_data = 24 | Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::id()).0; 25 | let program_config_pda = program_config_from_program_id(&program); 26 | Instruction { 27 | program_id: crate::id(), 28 | accounts: vec![ 29 | AccountMeta::new(authority, true), 30 | AccountMeta::new_readonly(validator_identity, false), 31 | AccountMeta::new_readonly(program, false), 32 | AccountMeta::new_readonly(program_data, false), 33 | AccountMeta::new_readonly(delegation_program_data, false), 34 | AccountMeta::new(program_config_pda, false), 35 | AccountMeta::new_readonly(system_program::id(), false), 36 | ], 37 | data: [ 38 | DlpDiscriminator::WhitelistValidatorForProgram.to_vec(), 39 | to_vec(&args).unwrap(), 40 | ] 41 | .concat(), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] // silence clippy for target_os solana and other solana program custom features 2 | 3 | use solana_program::{ 4 | account_info::AccountInfo, declare_id, entrypoint::ProgramResult, msg, 5 | program_error::ProgramError, pubkey::Pubkey, 6 | }; 7 | 8 | pub mod args; 9 | pub mod consts; 10 | mod discriminator; 11 | pub mod error; 12 | pub mod instruction_builder; 13 | pub mod pda; 14 | mod processor; 15 | pub mod state; 16 | 17 | declare_id!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"); 18 | 19 | #[cfg(not(feature = "no-entrypoint"))] 20 | solana_program::entrypoint!(process_instruction); 21 | 22 | #[cfg(all(not(feature = "no-entrypoint"), feature = "solana-security-txt"))] 23 | solana_security_txt::security_txt! { 24 | name: "MagicBlock Delegation Program", 25 | project_url: "https://magicblock.gg", 26 | contacts: "email:dev@magicblock.gg,twitter:@magicblock", 27 | policy: "https://github.com/magicblock-labs/delegation-program/blob/master/LICENSE.md", 28 | preferred_languages: "en", 29 | source_code: "https://github.com/magicblock-labs/delegation-program" 30 | } 31 | 32 | pub fn process_instruction( 33 | program_id: &Pubkey, 34 | accounts: &[AccountInfo], 35 | data: &[u8], 36 | ) -> ProgramResult { 37 | if program_id.ne(&id()) { 38 | return Err(ProgramError::IncorrectProgramId); 39 | } 40 | 41 | if data.len() < 8 { 42 | return Err(ProgramError::InvalidInstructionData); 43 | } 44 | 45 | let (tag, data) = data.split_at(8); 46 | let tag_array: [u8; 8] = tag 47 | .try_into() 48 | .map_err(|_| ProgramError::InvalidInstructionData)?; 49 | 50 | let ix = discriminator::DlpDiscriminator::try_from(tag_array) 51 | .or(Err(ProgramError::InvalidInstructionData))?; 52 | msg!("Processing instruction: {:?}", ix); 53 | match ix { 54 | discriminator::DlpDiscriminator::Delegate => { 55 | processor::process_delegate(program_id, accounts, data)? 56 | } 57 | discriminator::DlpDiscriminator::CommitState => { 58 | processor::process_commit_state(program_id, accounts, data)? 59 | } 60 | discriminator::DlpDiscriminator::CommitStateFromBuffer => { 61 | processor::process_commit_state_from_buffer(program_id, accounts, data)? 62 | } 63 | discriminator::DlpDiscriminator::Finalize => { 64 | processor::process_finalize(program_id, accounts, data)? 65 | } 66 | discriminator::DlpDiscriminator::Undelegate => { 67 | processor::process_undelegate(program_id, accounts, data)? 68 | } 69 | discriminator::DlpDiscriminator::InitValidatorFeesVault => { 70 | processor::process_init_validator_fees_vault(program_id, accounts, data)? 71 | } 72 | discriminator::DlpDiscriminator::InitProtocolFeesVault => { 73 | processor::process_init_protocol_fees_vault(program_id, accounts, data)? 74 | } 75 | discriminator::DlpDiscriminator::ValidatorClaimFees => { 76 | processor::process_validator_claim_fees(program_id, accounts, data)? 77 | } 78 | discriminator::DlpDiscriminator::WhitelistValidatorForProgram => { 79 | processor::process_whitelist_validator_for_program(program_id, accounts, data)? 80 | } 81 | discriminator::DlpDiscriminator::TopUpEphemeralBalance => { 82 | processor::process_top_up_ephemeral_balance(program_id, accounts, data)? 83 | } 84 | discriminator::DlpDiscriminator::DelegateEphemeralBalance => { 85 | processor::process_delegate_ephemeral_balance(program_id, accounts, data)? 86 | } 87 | discriminator::DlpDiscriminator::CloseEphemeralBalance => { 88 | processor::process_close_ephemeral_balance(program_id, accounts, data)? 89 | } 90 | discriminator::DlpDiscriminator::ProtocolClaimFees => { 91 | processor::process_protocol_claim_fees(program_id, accounts, data)? 92 | } 93 | discriminator::DlpDiscriminator::CloseValidatorFeesVault => { 94 | processor::process_close_validator_fees_vault(program_id, accounts, data)? 95 | } 96 | } 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /src/pda.rs: -------------------------------------------------------------------------------- 1 | use solana_program::pubkey::Pubkey; 2 | 3 | #[macro_export] 4 | macro_rules! delegation_record_seeds_from_delegated_account { 5 | ($delegated_account: expr) => { 6 | &[b"delegation", &$delegated_account.as_ref()] 7 | }; 8 | } 9 | 10 | #[macro_export] 11 | macro_rules! delegation_metadata_seeds_from_delegated_account { 12 | ($delegated_account: expr) => { 13 | &[b"delegation-metadata", &$delegated_account.as_ref()] 14 | }; 15 | } 16 | 17 | #[macro_export] 18 | macro_rules! commit_state_seeds_from_delegated_account { 19 | ($delegated_account: expr) => { 20 | &[b"state-diff", &$delegated_account.as_ref()] 21 | }; 22 | } 23 | 24 | #[macro_export] 25 | macro_rules! commit_record_seeds_from_delegated_account { 26 | ($delegated_account: expr) => { 27 | &[b"commit-state-record", &$delegated_account.as_ref()] 28 | }; 29 | } 30 | 31 | #[macro_export] 32 | macro_rules! delegate_buffer_seeds_from_delegated_account { 33 | ($delegated_account: expr) => { 34 | &[b"buffer", &$delegated_account.as_ref()] 35 | }; 36 | } 37 | 38 | #[macro_export] 39 | macro_rules! undelegate_buffer_seeds_from_delegated_account { 40 | ($delegated_account: expr) => { 41 | &[b"undelegate-buffer", &$delegated_account.as_ref()] 42 | }; 43 | } 44 | 45 | #[macro_export] 46 | macro_rules! fees_vault_seeds { 47 | () => { 48 | &[b"fees-vault"] 49 | }; 50 | } 51 | 52 | #[macro_export] 53 | macro_rules! validator_fees_vault_seeds_from_validator { 54 | ($validator: expr) => { 55 | &[b"v-fees-vault", &$validator.as_ref()] 56 | }; 57 | } 58 | 59 | #[macro_export] 60 | macro_rules! program_config_seeds_from_program_id { 61 | ($program_id: expr) => { 62 | &[b"p-conf", &$program_id.as_ref()] 63 | }; 64 | } 65 | 66 | #[macro_export] 67 | macro_rules! ephemeral_balance_seeds_from_payer { 68 | ($payer: expr, $index: expr) => { 69 | &[b"balance", &$payer.as_ref(), &[$index]] 70 | }; 71 | } 72 | 73 | pub fn delegation_record_pda_from_delegated_account(delegated_account: &Pubkey) -> Pubkey { 74 | Pubkey::find_program_address( 75 | delegation_record_seeds_from_delegated_account!(delegated_account), 76 | &crate::id(), 77 | ) 78 | .0 79 | } 80 | 81 | pub fn delegation_metadata_pda_from_delegated_account(delegated_account: &Pubkey) -> Pubkey { 82 | Pubkey::find_program_address( 83 | delegation_metadata_seeds_from_delegated_account!(delegated_account), 84 | &crate::id(), 85 | ) 86 | .0 87 | } 88 | 89 | pub fn commit_state_pda_from_delegated_account(delegated_account: &Pubkey) -> Pubkey { 90 | Pubkey::find_program_address( 91 | commit_state_seeds_from_delegated_account!(delegated_account), 92 | &crate::id(), 93 | ) 94 | .0 95 | } 96 | 97 | pub fn commit_record_pda_from_delegated_account(delegated_account: &Pubkey) -> Pubkey { 98 | Pubkey::find_program_address( 99 | commit_record_seeds_from_delegated_account!(delegated_account), 100 | &crate::id(), 101 | ) 102 | .0 103 | } 104 | 105 | pub fn delegate_buffer_pda_from_delegated_account_and_owner_program( 106 | delegated_account: &Pubkey, 107 | owner_program: &Pubkey, 108 | ) -> Pubkey { 109 | Pubkey::find_program_address( 110 | delegate_buffer_seeds_from_delegated_account!(delegated_account), 111 | owner_program, 112 | ) 113 | .0 114 | } 115 | 116 | pub fn undelegate_buffer_pda_from_delegated_account(delegated_account: &Pubkey) -> Pubkey { 117 | Pubkey::find_program_address( 118 | undelegate_buffer_seeds_from_delegated_account!(delegated_account), 119 | &crate::id(), 120 | ) 121 | .0 122 | } 123 | 124 | pub fn fees_vault_pda() -> Pubkey { 125 | Pubkey::find_program_address(fees_vault_seeds!(), &crate::id()).0 126 | } 127 | 128 | pub fn validator_fees_vault_pda_from_validator(validator: &Pubkey) -> Pubkey { 129 | Pubkey::find_program_address( 130 | validator_fees_vault_seeds_from_validator!(validator), 131 | &crate::id(), 132 | ) 133 | .0 134 | } 135 | 136 | pub fn program_config_from_program_id(program_id: &Pubkey) -> Pubkey { 137 | Pubkey::find_program_address( 138 | program_config_seeds_from_program_id!(program_id), 139 | &crate::id(), 140 | ) 141 | .0 142 | } 143 | 144 | pub fn ephemeral_balance_pda_from_payer(payer: &Pubkey, index: u8) -> Pubkey { 145 | Pubkey::find_program_address( 146 | ephemeral_balance_seeds_from_payer!(payer, index), 147 | &crate::id(), 148 | ) 149 | .0 150 | } 151 | -------------------------------------------------------------------------------- /src/processor/close_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use crate::ephemeral_balance_seeds_from_payer; 2 | use crate::processor::utils::loaders::{load_pda, load_signer}; 3 | use solana_program::msg; 4 | use solana_program::program::invoke_signed; 5 | use solana_program::program_error::ProgramError; 6 | use solana_program::system_instruction::transfer; 7 | use solana_program::{ 8 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program, 9 | }; 10 | 11 | /// Process the closing of an ephemeral balance account 12 | /// 13 | /// Accounts: 14 | /// 15 | /// 0: `[signer]` payer to pay for the transaction and receive the refund 16 | /// 1: `[writable]` ephemeral balance account we are closing 17 | /// 2: `[]` the system program 18 | /// 19 | /// Requirements: 20 | /// 21 | /// - ephemeral balance account is initialized 22 | /// 23 | /// Steps: 24 | /// 25 | /// 1. Closes the ephemeral balance account and refunds the payer with the 26 | /// escrowed lamports 27 | pub fn process_close_ephemeral_balance( 28 | _program_id: &Pubkey, 29 | accounts: &[AccountInfo], 30 | data: &[u8], 31 | ) -> ProgramResult { 32 | let index = *data.first().ok_or(ProgramError::InvalidInstructionData)?; 33 | 34 | // Load Accounts 35 | let [payer, ephemeral_balance_account, system_program] = accounts else { 36 | return Err(ProgramError::NotEnoughAccountKeys); 37 | }; 38 | 39 | load_signer(payer, "payer")?; 40 | 41 | let ephemeral_balance_seeds: &[&[u8]] = ephemeral_balance_seeds_from_payer!(payer.key, index); 42 | let ephemeral_balance_bump = load_pda( 43 | ephemeral_balance_account, 44 | ephemeral_balance_seeds, 45 | &crate::id(), 46 | true, 47 | "ephemeral balance", 48 | )?; 49 | if ephemeral_balance_account.owner != &system_program::id() { 50 | msg!( 51 | "ephemeral balance expected to be owned by system program. got: {}", 52 | ephemeral_balance_account.owner 53 | ); 54 | return Err(ProgramError::InvalidAccountOwner); 55 | } 56 | 57 | let amount = ephemeral_balance_account.lamports(); 58 | if amount == 0 { 59 | return Ok(()); 60 | } 61 | 62 | let ephemeral_balance_bump_slice: &[u8] = &[ephemeral_balance_bump]; 63 | let ephemeral_balance_signer_seeds = 64 | [ephemeral_balance_seeds, &[ephemeral_balance_bump_slice]].concat(); 65 | invoke_signed( 66 | &transfer(ephemeral_balance_account.key, payer.key, amount), 67 | &[ 68 | ephemeral_balance_account.clone(), 69 | payer.clone(), 70 | system_program.clone(), 71 | ], 72 | &[&ephemeral_balance_signer_seeds], 73 | )?; 74 | 75 | Ok(()) 76 | } 77 | -------------------------------------------------------------------------------- /src/processor/close_validator_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use solana_program::msg; 2 | use solana_program::program_error::ProgramError; 3 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 4 | 5 | use crate::error::DlpError::Unauthorized; 6 | use crate::processor::utils::loaders::{ 7 | load_initialized_pda, load_program_upgrade_authority, load_signer, 8 | }; 9 | use crate::processor::utils::pda::close_pda; 10 | use crate::validator_fees_vault_seeds_from_validator; 11 | 12 | /// Process the close of the validator fees vault 13 | /// 14 | /// Accounts: 15 | /// 16 | /// 0; `[signer]` payer 17 | /// 1; `[signer]` admin that controls the vault 18 | /// 2; `[]` validator_identity 19 | /// 3; `[]` validator_fees_vault_pda 20 | /// 21 | /// Requirements: 22 | /// 23 | /// - validator admin need to be signer since the existence of the validator fees vault 24 | /// is used as proof later that the validator is whitelisted 25 | /// - validator fees vault is closed 26 | /// 27 | /// 1. Close the validator fees vault PDA 28 | pub fn process_close_validator_fees_vault( 29 | _program_id: &Pubkey, 30 | accounts: &[AccountInfo], 31 | _data: &[u8], 32 | ) -> ProgramResult { 33 | // Load Accounts 34 | let [payer, admin, delegation_program_data, validator_identity, validator_fees_vault] = 35 | accounts 36 | else { 37 | return Err(ProgramError::NotEnoughAccountKeys); 38 | }; 39 | 40 | // Check if the payer and admin are signers 41 | load_signer(payer, "payer")?; 42 | load_signer(admin, "admin")?; 43 | 44 | // Check if the admin is the correct one 45 | let admin_pubkey = 46 | load_program_upgrade_authority(&crate::ID, delegation_program_data)?.ok_or(Unauthorized)?; 47 | if !admin.key.eq(&admin_pubkey) { 48 | msg!( 49 | "Expected admin pubkey: {} but got {}", 50 | admin_pubkey, 51 | admin.key 52 | ); 53 | return Err(Unauthorized.into()); 54 | } 55 | 56 | load_initialized_pda( 57 | validator_fees_vault, 58 | validator_fees_vault_seeds_from_validator!(validator_identity.key), 59 | &crate::id(), 60 | true, 61 | "validator fees vault", 62 | )?; 63 | 64 | // Close the fees vault PDA 65 | close_pda(validator_fees_vault, validator_identity)?; 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/processor/commit_state_from_buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::args::CommitStateFromBufferArgs; 2 | use crate::processor::{process_commit_state_internal, CommitStateInternalArgs}; 3 | use borsh::BorshDeserialize; 4 | use solana_program::program_error::ProgramError; 5 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 6 | 7 | /// Commit a new state of a delegated Pda 8 | /// 9 | /// It is identical to [crate::processor::process_commit_state] but it takes the new state 10 | /// from a buffer account 11 | /// 12 | /// Accounts: 13 | /// 14 | /// 0: `[signer]` the validator requesting the commit 15 | /// 1: `[]` the delegated account 16 | /// 2: `[writable]` the PDA storing the new state temporarily 17 | /// 3: `[writable]` the PDA storing the commit record 18 | /// 4: `[]` the delegation record 19 | /// 5: `[writable]` the delegation metadata 20 | /// 6: `[]` the buffer account storing the data to be committed 21 | /// 7: `[]` the validator fees vault 22 | /// 8: `[]` the program config account 23 | /// 9: `[]` the system program 24 | /// 25 | /// Requirements: 26 | /// 27 | /// - delegation record is initialized 28 | /// - delegation metadata is initialized 29 | /// - validator fees vault is initialized 30 | /// - program config is initialized 31 | /// - commit state is uninitialized 32 | /// - commit record is uninitialized 33 | /// - delegated account holds at least the lamports indicated in the delegation record 34 | /// - account was not committed at a later slot 35 | /// 36 | /// Steps: 37 | /// 1. Check that the pda is delegated 38 | /// 2. Init a new PDA to store the new state 39 | /// 3. Copy the new state to the new PDA 40 | /// 4. Init a new PDA to store the record of the new state commitment 41 | pub fn process_commit_state_from_buffer( 42 | _program_id: &Pubkey, 43 | accounts: &[AccountInfo], 44 | data: &[u8], 45 | ) -> ProgramResult { 46 | let args = CommitStateFromBufferArgs::try_from_slice(data)?; 47 | 48 | let commit_record_lamports = args.lamports; 49 | let commit_record_slot = args.slot; 50 | let allow_undelegation = args.allow_undelegation; 51 | 52 | let [validator, delegated_account, commit_state_account, commit_record_account, delegation_record_account, delegation_metadata_account, state_buffer_account, validator_fees_vault, program_config_account, system_program] = 53 | accounts 54 | else { 55 | return Err(ProgramError::NotEnoughAccountKeys); 56 | }; 57 | let state = state_buffer_account.try_borrow_data()?; 58 | let commit_state_bytes: &[u8] = *state; 59 | 60 | let commit_args = CommitStateInternalArgs { 61 | commit_state_bytes, 62 | commit_record_lamports, 63 | commit_record_slot, 64 | allow_undelegation, 65 | validator, 66 | delegated_account, 67 | commit_state_account, 68 | commit_record_account, 69 | delegation_record_account, 70 | delegation_metadata_account, 71 | validator_fees_vault, 72 | program_config_account, 73 | system_program, 74 | }; 75 | process_commit_state_internal(commit_args) 76 | } 77 | -------------------------------------------------------------------------------- /src/processor/delegate.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use solana_program::msg; 3 | use solana_program::program_error::ProgramError; 4 | use solana_program::sysvar::Sysvar; 5 | use solana_program::{ 6 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program, 7 | }; 8 | 9 | use crate::args::DelegateArgs; 10 | use crate::processor::utils::curve::is_on_curve; 11 | use crate::processor::utils::loaders::{ 12 | load_owned_pda, load_pda, load_program, load_signer, load_uninitialized_pda, 13 | }; 14 | use crate::processor::utils::pda::create_pda; 15 | use crate::state::{DelegationMetadata, DelegationRecord}; 16 | use crate::{ 17 | delegate_buffer_seeds_from_delegated_account, delegation_metadata_seeds_from_delegated_account, 18 | delegation_record_seeds_from_delegated_account, 19 | }; 20 | 21 | /// Delegates an account 22 | /// 23 | /// Accounts: 24 | /// 0: `[signer]` the account paying for the transaction 25 | /// 1: `[signer]` the account to delegate 26 | /// 2: `[]` the owner of the account to delegate 27 | /// 3: `[writable]` the buffer account we use to temporarily store the account data 28 | /// during owner change 29 | /// 4: `[writable]` the delegation record account 30 | /// 5: `[writable]` the delegation metadata account 31 | /// 6: `[]` the system program 32 | /// 33 | /// Requirements: 34 | /// 35 | /// - delegation buffer is initialized 36 | /// - delegation record is uninitialized 37 | /// - delegation metadata is uninitialized 38 | /// 39 | /// Steps: 40 | /// 1. Checks that the account is owned by the delegation program, that the buffer is initialized and derived correctly from the PDA 41 | /// - Also checks that the delegated_account is a signer (enforcing that the instruction is being called from CPI) & other constraints 42 | /// 2. Copies the data from the buffer into the original account 43 | /// 3. Creates a Delegation Record to store useful information about the delegation event 44 | /// 4. Creates a Delegated Account Seeds to store the seeds used to derive the delegate account. Needed for undelegation. 45 | /// 46 | /// Usage: 47 | /// 48 | /// This instruction is meant to be called via CPI with the owning program signing for the 49 | /// delegated account. 50 | pub fn process_delegate( 51 | _program_id: &Pubkey, 52 | accounts: &[AccountInfo], 53 | data: &[u8], 54 | ) -> ProgramResult { 55 | let [payer, delegated_account, owner_program, delegate_buffer_account, delegation_record_account, delegation_metadata_account, system_program] = 56 | accounts 57 | else { 58 | return Err(ProgramError::NotEnoughAccountKeys); 59 | }; 60 | 61 | let args = DelegateArgs::try_from_slice(data)?; 62 | 63 | load_owned_pda(delegated_account, &crate::id(), "delegated account")?; 64 | load_program(system_program, system_program::id(), "system program")?; 65 | 66 | msg!("Delegating: {}", delegated_account.key); 67 | 68 | // Validate seeds if the delegate account is not on curve, i.e. is a PDA 69 | // If the owner is the system program, we check if the account is derived from the delegation program, 70 | // allowing delegation of escrow accounts 71 | if !is_on_curve(delegated_account.key) { 72 | let seeds_to_validate: Vec<&[u8]> = args.seeds.iter().map(|v| v.as_slice()).collect(); 73 | let program_id = if owner_program.key.eq(&system_program::id()) { 74 | crate::id() 75 | } else { 76 | *owner_program.key 77 | }; 78 | let (derived_pda, _) = 79 | Pubkey::find_program_address(seeds_to_validate.as_ref(), &program_id); 80 | 81 | if derived_pda.ne(delegated_account.key) { 82 | msg!( 83 | "Expected delegated PDA to be {}, but got {}", 84 | derived_pda, 85 | delegated_account.key 86 | ); 87 | return Err(ProgramError::InvalidSeeds); 88 | } 89 | } 90 | 91 | // Check that the buffer PDA is initialized and derived correctly from the PDA 92 | load_pda( 93 | delegate_buffer_account, 94 | delegate_buffer_seeds_from_delegated_account!(delegated_account.key), 95 | owner_program.key, 96 | true, 97 | "delegate buffer", 98 | )?; 99 | 100 | // Check that the delegation record PDA is uninitialized 101 | let delegation_record_bump = load_uninitialized_pda( 102 | delegation_record_account, 103 | delegation_record_seeds_from_delegated_account!(delegated_account.key), 104 | &crate::id(), 105 | true, 106 | "delegation record", 107 | )?; 108 | 109 | // Check that the delegation metadata PDA is uninitialized 110 | let delegation_metadata_bump = load_uninitialized_pda( 111 | delegation_metadata_account, 112 | delegation_metadata_seeds_from_delegated_account!(delegated_account.key), 113 | &crate::id(), 114 | true, 115 | "delegation metadata", 116 | )?; 117 | 118 | // Check that payer and delegated_account are signers, this ensures the instruction is being called from CPI 119 | load_signer(payer, "payer")?; 120 | load_signer(delegated_account, "delegated account")?; 121 | 122 | // Initialize the delegation record PDA 123 | create_pda( 124 | delegation_record_account, 125 | &crate::id(), 126 | DelegationRecord::size_with_discriminator(), 127 | delegation_record_seeds_from_delegated_account!(delegated_account.key), 128 | delegation_record_bump, 129 | system_program, 130 | payer, 131 | )?; 132 | 133 | // Initialize the delegation record 134 | let delegation_record = DelegationRecord { 135 | owner: *owner_program.key, 136 | authority: args.validator.unwrap_or(Pubkey::default()), 137 | commit_frequency_ms: args.commit_frequency_ms as u64, 138 | delegation_slot: solana_program::clock::Clock::get()?.slot, 139 | lamports: delegated_account.lamports(), 140 | }; 141 | let mut delegation_record_data = delegation_record_account.try_borrow_mut_data()?; 142 | delegation_record.to_bytes_with_discriminator(&mut delegation_record_data)?; 143 | 144 | // Initialize the account seeds PDA 145 | let mut delegation_metadata_bytes = vec![]; 146 | let delegation_metadata = DelegationMetadata { 147 | seeds: args.seeds, 148 | last_update_external_slot: 0, 149 | is_undelegatable: false, 150 | rent_payer: *payer.key, 151 | }; 152 | delegation_metadata.to_bytes_with_discriminator(&mut delegation_metadata_bytes)?; 153 | 154 | // Initialize the delegation metadata PDA 155 | create_pda( 156 | delegation_metadata_account, 157 | &crate::id(), 158 | delegation_metadata_bytes.len(), 159 | delegation_metadata_seeds_from_delegated_account!(delegated_account.key), 160 | delegation_metadata_bump, 161 | system_program, 162 | payer, 163 | )?; 164 | 165 | // Copy the seeds to the delegated metadata PDA 166 | let mut delegation_metadata_data = delegation_metadata_account.try_borrow_mut_data()?; 167 | delegation_metadata_data.copy_from_slice(&delegation_metadata_bytes); 168 | 169 | // Copy the data from the buffer into the original account 170 | if !delegate_buffer_account.data_is_empty() { 171 | let mut delegated_data = delegated_account.try_borrow_mut_data()?; 172 | let delegate_buffer_data = delegate_buffer_account.try_borrow_data()?; 173 | (*delegated_data).copy_from_slice(&delegate_buffer_data); 174 | } 175 | 176 | Ok(()) 177 | } 178 | -------------------------------------------------------------------------------- /src/processor/delegate_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use crate::args::DelegateEphemeralBalanceArgs; 2 | use crate::ephemeral_balance_seeds_from_payer; 3 | use crate::processor::utils::loaders::{load_program, load_signer}; 4 | use borsh::BorshDeserialize; 5 | use solana_program::program::invoke_signed; 6 | use solana_program::program_error::ProgramError; 7 | use solana_program::system_program; 8 | use solana_program::{ 9 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_instruction, 10 | }; 11 | 12 | /// Delegates an account to transfer lamports which are used to fund it inside 13 | /// the ephemeral. 14 | /// 15 | /// Accounts: 16 | /// 17 | /// 0: `[writable]` payer account 18 | /// 1: `[signer]` delegatee account from which the delegated account is derived 19 | /// 2: `[writable]` ephemeral balance account 20 | /// 3: `[writable]` delegate buffer PDA 21 | /// 4: `[writable]` delegation record PDA 22 | /// 5: `[writable]` delegation metadata PDA 23 | /// 6: `[]` system program 24 | /// 7: `[]` this program 25 | /// 26 | /// Requirements: 27 | /// 28 | /// - same as [crate::processor::delegate::process_delegate] 29 | /// 30 | /// Steps: 31 | /// 32 | /// 1. Delegates the ephemeral balance account to the delegation program so it can 33 | /// act as an escrow 34 | pub fn process_delegate_ephemeral_balance( 35 | _program_id: &Pubkey, 36 | accounts: &[AccountInfo], 37 | data: &[u8], 38 | ) -> ProgramResult { 39 | let mut args = DelegateEphemeralBalanceArgs::try_from_slice(data)?; 40 | let [payer, pubkey, ephemeral_balance_account, delegate_buffer, delegation_record, delegation_metadata, system_program, delegation_program] = 41 | accounts 42 | else { 43 | return Err(ProgramError::NotEnoughAccountKeys); 44 | }; 45 | 46 | load_signer(payer, "payer")?; 47 | load_signer(pubkey, "delegatee")?; 48 | load_program(system_program, system_program::id(), "system program")?; 49 | load_program(delegation_program, crate::id(), "delegation program")?; 50 | 51 | // Check seeds and derive bump 52 | let ephemeral_balance_seeds: &[&[u8]] = 53 | ephemeral_balance_seeds_from_payer!(pubkey.key, args.index); 54 | let (ephemeral_balance_key, ephemeral_balance_bump) = 55 | Pubkey::find_program_address(ephemeral_balance_seeds, &crate::id()); 56 | if !ephemeral_balance_key.eq(ephemeral_balance_account.key) { 57 | return Err(ProgramError::InvalidSeeds); 58 | } 59 | 60 | // Set the delegation seeds 61 | args.delegate_args.seeds = ephemeral_balance_seeds.iter().map(|s| s.to_vec()).collect(); 62 | 63 | // Generate the ephemeral balance PDA's signer seeds 64 | let ephemeral_balance_bump_slice = &[ephemeral_balance_bump]; 65 | let ephemeral_balance_signer_seeds = 66 | [ephemeral_balance_seeds, &[ephemeral_balance_bump_slice]].concat(); 67 | 68 | // Assign as owner the delegation program 69 | invoke_signed( 70 | &system_instruction::assign(ephemeral_balance_account.key, &crate::id()), 71 | &[ephemeral_balance_account.clone(), system_program.clone()], 72 | &[&ephemeral_balance_signer_seeds], 73 | )?; 74 | 75 | // Create the delegation ix 76 | let ix = crate::instruction_builder::delegate( 77 | *payer.key, 78 | *ephemeral_balance_account.key, 79 | Some(system_program::id()), 80 | args.delegate_args, 81 | ); 82 | 83 | // Invoke signed delegation instruction 84 | invoke_signed( 85 | &ix, 86 | &[ 87 | delegation_program.clone(), 88 | payer.clone(), 89 | ephemeral_balance_account.clone(), 90 | delegate_buffer.clone(), 91 | delegation_record.clone(), 92 | delegation_metadata.clone(), 93 | system_program.clone(), 94 | ], 95 | &[&ephemeral_balance_signer_seeds], 96 | )?; 97 | 98 | Ok(()) 99 | } 100 | -------------------------------------------------------------------------------- /src/processor/finalize.rs: -------------------------------------------------------------------------------- 1 | use crate::error::DlpError; 2 | use crate::processor::utils::loaders::{ 3 | is_uninitialized_account, load_initialized_commit_record, load_initialized_commit_state, 4 | load_initialized_delegation_metadata, load_initialized_delegation_record, 5 | load_initialized_validator_fees_vault, load_owned_pda, load_program, load_signer, 6 | }; 7 | use crate::processor::utils::pda::close_pda; 8 | use crate::state::{CommitRecord, DelegationMetadata, DelegationRecord}; 9 | use solana_program::program_error::ProgramError; 10 | use solana_program::{ 11 | account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey, system_program, 12 | }; 13 | 14 | /// Finalize a committed state, after validation, to a delegated account 15 | /// 16 | /// Accounts: 17 | /// 18 | /// 0: `[signer]` the validator account 19 | /// 1: `[writable]` the delegated account 20 | /// 2: `[writable]` the commit state account 21 | /// 3: `[writable]` the commit record account 22 | /// 4: `[writable]` the delegation record account 23 | /// 5: `[writable]` the delegation metadata account 24 | /// 6: `[writable]` the validator fees vault account 25 | /// 7: `[]` the system program 26 | /// 27 | /// Requirements: 28 | /// 29 | /// - delegated account is owned by delegation program 30 | /// - delegation record is initialized 31 | /// - delegation metadata is initialized 32 | /// - validator fees vault is initialized 33 | /// - commit state is initialized and derived from the delegated account key 34 | /// - commit record is initialized and derived from the delegated account key 35 | /// - account mentioned in commit record is the same as the delegated account 36 | /// - identity mentioned in commit record is the same as the validator 37 | /// 38 | /// NOTE: that if neither commit state nor commit record are as required then 39 | /// we skip the finalize without an error in order to not affect other finalize 40 | /// instructions that may be bundled in the same transaction. 41 | /// 42 | /// Steps: 43 | /// 44 | /// 1. Validate the new state (currently state is valid if committed from a whitelisted validator) 45 | /// 2. If the state is valid, copy the committed state to the delegated account 46 | /// 3. Close the state diff account 47 | /// 4. Close the commit state record 48 | pub fn process_finalize( 49 | _program_id: &Pubkey, 50 | accounts: &[AccountInfo], 51 | _data: &[u8], 52 | ) -> ProgramResult { 53 | let [validator, delegated_account, commit_state_account, commit_record_account, delegation_record_account, delegation_metadata_account, validator_fees_vault, system_program] = 54 | accounts 55 | else { 56 | return Err(ProgramError::NotEnoughAccountKeys); 57 | }; 58 | 59 | load_signer(validator, "validator")?; 60 | load_owned_pda(delegated_account, &crate::id(), "delegated account")?; 61 | load_initialized_delegation_record(delegated_account, delegation_record_account, true)?; 62 | load_initialized_delegation_metadata(delegated_account, delegation_metadata_account, true)?; 63 | load_initialized_validator_fees_vault(validator, validator_fees_vault, true)?; 64 | load_program(system_program, system_program::id(), "system program")?; 65 | let load_cs = load_initialized_commit_state(delegated_account, commit_state_account, true); 66 | let load_cr = load_initialized_commit_record(delegated_account, commit_record_account, true); 67 | 68 | // Since finalize instructions are typically bundled, we return without error 69 | // if there is nothing to be finalized, so that correct finalizes are executed 70 | if let (Err(ProgramError::InvalidAccountOwner), Err(ProgramError::InvalidAccountOwner)) = 71 | (&load_cs, &load_cr) 72 | { 73 | if is_uninitialized_account(commit_state_account) 74 | && is_uninitialized_account(commit_record_account) 75 | { 76 | msg!("No state to be finalized. Skipping finalize."); 77 | return Ok(()); 78 | } 79 | } 80 | load_cs?; 81 | load_cr?; 82 | 83 | // Load delegation metadata 84 | let mut delegation_metadata_data = delegation_metadata_account.try_borrow_mut_data()?; 85 | let mut delegation_metadata = 86 | DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata_data)?; 87 | 88 | let mut delegation_record_data = delegation_record_account.try_borrow_mut_data()?; 89 | let delegation_record = 90 | DelegationRecord::try_from_bytes_with_discriminator_mut(&mut delegation_record_data)?; 91 | 92 | // Load commit record 93 | let commit_record_data = commit_record_account.try_borrow_data()?; 94 | let commit_record = CommitRecord::try_from_bytes_with_discriminator(&commit_record_data)?; 95 | 96 | // Check that the commit record is the right one 97 | if !commit_record.account.eq(delegated_account.key) { 98 | return Err(DlpError::InvalidDelegatedAccount.into()); 99 | } 100 | if !commit_record.identity.eq(validator.key) { 101 | return Err(DlpError::InvalidReimbursementAccount.into()); 102 | } 103 | 104 | // Settle accounts lamports 105 | settle_lamports_balance( 106 | delegated_account, 107 | commit_state_account, 108 | validator_fees_vault, 109 | delegation_record.lamports, 110 | commit_record.lamports, 111 | )?; 112 | 113 | // Update the delegation metadata 114 | delegation_metadata.last_update_external_slot = commit_record.slot; 115 | delegation_metadata.to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut())?; 116 | 117 | // Update the delegation record 118 | delegation_record.lamports = delegated_account.lamports(); 119 | 120 | // Load commit state 121 | let commit_state_data = commit_state_account.try_borrow_data()?; 122 | 123 | // Copying the new commit state to the delegated account 124 | delegated_account.realloc(commit_state_data.len(), false)?; 125 | let mut delegated_account_data = delegated_account.try_borrow_mut_data()?; 126 | (*delegated_account_data).copy_from_slice(&commit_state_data); 127 | 128 | // Drop remaining reference before closing accounts 129 | drop(commit_record_data); 130 | drop(commit_state_data); 131 | 132 | // Closing accounts 133 | close_pda(commit_state_account, validator)?; 134 | close_pda(commit_record_account, validator)?; 135 | 136 | Ok(()) 137 | } 138 | 139 | /// Settle the committed lamports to the delegated account 140 | fn settle_lamports_balance<'a, 'info>( 141 | delegated_account: &'a AccountInfo<'info>, 142 | commit_state_account: &'a AccountInfo<'info>, 143 | validator_fees_vault: &'a AccountInfo<'info>, 144 | delegation_record_lamports: u64, 145 | commit_record_lamports: u64, 146 | ) -> Result<(), ProgramError> { 147 | let (transfer_source, transfer_destination, transfer_lamports) = 148 | match delegation_record_lamports.cmp(&commit_record_lamports) { 149 | std::cmp::Ordering::Greater => ( 150 | delegated_account, 151 | validator_fees_vault, 152 | delegation_record_lamports 153 | .checked_sub(commit_record_lamports) 154 | .ok_or(DlpError::Overflow)?, 155 | ), 156 | std::cmp::Ordering::Less => ( 157 | commit_state_account, 158 | delegated_account, 159 | commit_record_lamports 160 | .checked_sub(delegation_record_lamports) 161 | .ok_or(DlpError::Overflow)?, 162 | ), 163 | std::cmp::Ordering::Equal => return Ok(()), 164 | }; 165 | 166 | **transfer_source.try_borrow_mut_lamports()? = transfer_source 167 | .lamports() 168 | .checked_sub(transfer_lamports) 169 | .ok_or(DlpError::Overflow)?; 170 | **transfer_destination.try_borrow_mut_lamports()? = transfer_destination 171 | .lamports() 172 | .checked_add(transfer_lamports) 173 | .ok_or(DlpError::Overflow)?; 174 | 175 | Ok(()) 176 | } 177 | -------------------------------------------------------------------------------- /src/processor/init_protocol_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program, 4 | }; 5 | 6 | use crate::fees_vault_seeds; 7 | use crate::processor::utils::loaders::{load_program, load_signer, load_uninitialized_pda}; 8 | use crate::processor::utils::pda::create_pda; 9 | 10 | /// Initialize the global fees vault 11 | /// 12 | /// Accounts: 13 | /// 0: `[signer]` the account paying for the transaction 14 | /// 1: `[writable]` the fees vault PDA we are initializing 15 | /// 2: `[]` the system program 16 | /// 17 | /// Requirements: 18 | /// 19 | /// - fees vault is uninitialized 20 | /// 21 | /// NOTE: this operation is permisionless and can be done by anyone 22 | /// 23 | /// Steps: 24 | /// 25 | /// 1. Create the protocol fees vault PDA 26 | pub fn process_init_protocol_fees_vault( 27 | _program_id: &Pubkey, 28 | accounts: &[AccountInfo], 29 | _data: &[u8], 30 | ) -> ProgramResult { 31 | // Load Accounts 32 | let [payer, protocol_fees_vault, system_program] = accounts else { 33 | return Err(ProgramError::NotEnoughAccountKeys); 34 | }; 35 | 36 | load_signer(payer, "payer")?; 37 | load_program(system_program, system_program::id(), "system program")?; 38 | 39 | let bump_fees_vault = load_uninitialized_pda( 40 | protocol_fees_vault, 41 | fees_vault_seeds!(), 42 | &crate::id(), 43 | true, 44 | "fees vault", 45 | )?; 46 | 47 | // Create the fees vault account 48 | create_pda( 49 | protocol_fees_vault, 50 | &crate::id(), 51 | 8, 52 | fees_vault_seeds!(), 53 | bump_fees_vault, 54 | system_program, 55 | payer, 56 | )?; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /src/processor/init_validator_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use solana_program::msg; 2 | use solana_program::program_error::ProgramError; 3 | use solana_program::{ 4 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program, 5 | }; 6 | 7 | use crate::error::DlpError::Unauthorized; 8 | use crate::processor::utils::loaders::{ 9 | load_program, load_program_upgrade_authority, load_signer, load_uninitialized_pda, 10 | }; 11 | use crate::processor::utils::pda::create_pda; 12 | use crate::validator_fees_vault_seeds_from_validator; 13 | 14 | /// Process the initialization of the validator fees vault 15 | /// 16 | /// Accounts: 17 | /// 18 | /// 0; `[signer]` payer 19 | /// 1; `[signer]` admin that controls the vault 20 | /// 2; `[]` validator_identity 21 | /// 3; `[]` validator_fees_vault_pda 22 | /// 4; `[]` system_program 23 | /// 24 | /// Requirements: 25 | /// 26 | /// - validator admin need to be signer since the existence of the validator fees vault 27 | /// is used as proof later that the validator is whitelisted 28 | /// - validator admin is whitelisted 29 | /// - validator fees vault is not initialized 30 | /// 31 | /// 1. Create the validator fees vault PDA 32 | /// 2. Currently, the existence of the validator fees vault also act as a flag to indicate that the validator is whitelisted (only the admin can create the vault) 33 | pub fn process_init_validator_fees_vault( 34 | _program_id: &Pubkey, 35 | accounts: &[AccountInfo], 36 | _data: &[u8], 37 | ) -> ProgramResult { 38 | // Load Accounts 39 | let [payer, admin, delegation_program_data, validator_identity, validator_fees_vault, system_program] = 40 | accounts 41 | else { 42 | return Err(ProgramError::NotEnoughAccountKeys); 43 | }; 44 | 45 | // Check if the payer and admin are signers 46 | load_signer(payer, "payer")?; 47 | load_signer(admin, "admin")?; 48 | load_program(system_program, system_program::id(), "system program")?; 49 | 50 | // Check if the admin is the correct one 51 | let admin_pubkey = 52 | load_program_upgrade_authority(&crate::ID, delegation_program_data)?.ok_or(Unauthorized)?; 53 | if !admin.key.eq(&admin_pubkey) { 54 | msg!( 55 | "Expected admin pubkey: {} but got {}", 56 | admin_pubkey, 57 | admin.key 58 | ); 59 | return Err(Unauthorized.into()); 60 | } 61 | 62 | let validator_fees_vault_bump = load_uninitialized_pda( 63 | validator_fees_vault, 64 | validator_fees_vault_seeds_from_validator!(validator_identity.key), 65 | &crate::id(), 66 | true, 67 | "validator fees vault", 68 | )?; 69 | 70 | // Create the fees vault PDA 71 | create_pda( 72 | validator_fees_vault, 73 | &crate::id(), 74 | 8, 75 | validator_fees_vault_seeds_from_validator!(validator_identity.key), 76 | validator_fees_vault_bump, 77 | system_program, 78 | payer, 79 | )?; 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /src/processor/mod.rs: -------------------------------------------------------------------------------- 1 | mod close_ephemeral_balance; 2 | mod close_validator_fees_vault; 3 | mod commit_state; 4 | mod commit_state_from_buffer; 5 | mod delegate; 6 | mod delegate_ephemeral_balance; 7 | mod finalize; 8 | mod init_protocol_fees_vault; 9 | mod init_validator_fees_vault; 10 | mod protocol_claim_fees; 11 | mod top_up_ephemeral_balance; 12 | mod undelegate; 13 | mod utils; 14 | mod validator_claim_fees; 15 | mod whitelist_validator_for_program; 16 | 17 | pub use close_ephemeral_balance::*; 18 | pub use close_validator_fees_vault::*; 19 | pub use commit_state::*; 20 | pub use commit_state_from_buffer::*; 21 | pub use delegate::*; 22 | pub use delegate_ephemeral_balance::*; 23 | pub use finalize::*; 24 | pub use init_protocol_fees_vault::*; 25 | pub use init_validator_fees_vault::*; 26 | pub use protocol_claim_fees::*; 27 | pub use top_up_ephemeral_balance::*; 28 | pub use undelegate::*; 29 | pub use validator_claim_fees::*; 30 | pub use whitelist_validator_for_program::*; 31 | -------------------------------------------------------------------------------- /src/processor/protocol_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use crate::error::DlpError::Unauthorized; 2 | use crate::processor::utils::loaders::{ 3 | load_initialized_protocol_fees_vault, load_program_upgrade_authority, load_signer, 4 | }; 5 | use solana_program::msg; 6 | use solana_program::program_error::ProgramError; 7 | use solana_program::rent::Rent; 8 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 9 | 10 | /// Process request to claim fees from the protocol fees vault 11 | /// 12 | /// Accounts: 13 | /// 14 | /// 1. `[signer]` admin account that can claim the fees 15 | /// 2. `[writable]` protocol fees vault PDA 16 | /// 17 | /// Requirements: 18 | /// 19 | /// - protocol fees vault is initialized 20 | /// - protocol fees vault has enough lamports to claim fees and still be 21 | /// rent exempt 22 | /// - admin is the protocol fees vault admin 23 | /// 24 | /// 1. Transfer lamports from protocol fees_vault PDA to the admin authority 25 | pub fn process_protocol_claim_fees( 26 | _program_id: &Pubkey, 27 | accounts: &[AccountInfo], 28 | _data: &[u8], 29 | ) -> ProgramResult { 30 | // Load Accounts 31 | let [admin, fees_vault, delegation_program_data] = accounts else { 32 | return Err(ProgramError::NotEnoughAccountKeys); 33 | }; 34 | 35 | // Check if the admin is signer 36 | load_signer(admin, "admin")?; 37 | load_initialized_protocol_fees_vault(fees_vault, true)?; 38 | 39 | // Check if the admin is the correct one 40 | let admin_pubkey = 41 | load_program_upgrade_authority(&crate::ID, delegation_program_data)?.ok_or(Unauthorized)?; 42 | if !admin.key.eq(&admin_pubkey) { 43 | msg!( 44 | "Expected admin pubkey: {} but got {}", 45 | admin_pubkey, 46 | admin.key 47 | ); 48 | return Err(Unauthorized.into()); 49 | } 50 | 51 | // Calculate the amount to transfer 52 | let min_rent = Rent::default().minimum_balance(8); 53 | if fees_vault.lamports() < min_rent { 54 | return Err(ProgramError::InsufficientFunds); 55 | } 56 | let amount = fees_vault.lamports() - min_rent; 57 | 58 | // Transfer fees to the admin pubkey 59 | **fees_vault.try_borrow_mut_lamports()? = fees_vault 60 | .lamports() 61 | .checked_sub(amount) 62 | .ok_or(ProgramError::InsufficientFunds)?; 63 | 64 | **admin.try_borrow_mut_lamports()? = admin 65 | .lamports() 66 | .checked_add(amount) 67 | .ok_or(ProgramError::ArithmeticOverflow)?; 68 | 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /src/processor/top_up_ephemeral_balance.rs: -------------------------------------------------------------------------------- 1 | use crate::args::TopUpEphemeralBalanceArgs; 2 | use crate::ephemeral_balance_seeds_from_payer; 3 | use crate::processor::utils::loaders::{load_pda, load_program, load_signer}; 4 | use crate::processor::utils::pda::create_pda; 5 | use borsh::BorshDeserialize; 6 | use solana_program::program::invoke; 7 | use solana_program::program_error::ProgramError; 8 | use solana_program::system_instruction::transfer; 9 | use solana_program::{ 10 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program, 11 | }; 12 | 13 | /// Tops up the ephemeral balance account. 14 | /// 15 | /// Accounts: 16 | /// 17 | /// 0: `[writable]` payer account who funds the topup 18 | /// 1: `[]` pubkey account that the ephemeral balance PDA was derived from 19 | /// 2: `[writable]` ephemeral balance account to top up 20 | /// 3: `[]` system program 21 | /// 22 | /// Requirements: 23 | /// 24 | /// - the payer account has enough lamports to fund the transfer 25 | /// 26 | /// Steps: 27 | /// 28 | /// 1. Create the ephemeral balance PDA if it does not exist 29 | /// 2. Transfer lamports from payer to ephemeral PDA 30 | pub fn process_top_up_ephemeral_balance( 31 | _program_id: &Pubkey, 32 | accounts: &[AccountInfo], 33 | data: &[u8], 34 | ) -> ProgramResult { 35 | // Parse args. 36 | let args = TopUpEphemeralBalanceArgs::try_from_slice(data)?; 37 | 38 | // Load Accounts 39 | let [payer, pubkey, ephemeral_balance_account, system_program] = accounts else { 40 | return Err(ProgramError::NotEnoughAccountKeys); 41 | }; 42 | 43 | load_signer(payer, "payer")?; 44 | load_program(system_program, system_program::id(), "system program")?; 45 | 46 | let bump_ephemeral_balance = load_pda( 47 | ephemeral_balance_account, 48 | ephemeral_balance_seeds_from_payer!(pubkey.key, args.index), 49 | &crate::id(), 50 | true, 51 | "ephemeral balance", 52 | )?; 53 | 54 | // Create the ephemeral balance PDA if it does not exist 55 | if ephemeral_balance_account.owner.eq(&system_program::id()) { 56 | create_pda( 57 | ephemeral_balance_account, 58 | &system_program::id(), 59 | 0, 60 | ephemeral_balance_seeds_from_payer!(pubkey.key, args.index), 61 | bump_ephemeral_balance, 62 | system_program, 63 | payer, 64 | )?; 65 | } 66 | 67 | // Transfer lamports from payer to ephemeral PDA (with a system program call) 68 | if args.amount > 0 { 69 | let transfer_instruction = transfer(payer.key, ephemeral_balance_account.key, args.amount); 70 | invoke( 71 | &transfer_instruction, 72 | &[ 73 | payer.clone(), 74 | ephemeral_balance_account.clone(), 75 | system_program.clone(), 76 | ], 77 | )?; 78 | } 79 | 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /src/processor/utils/curve.rs: -------------------------------------------------------------------------------- 1 | use solana_curve25519::edwards::{validate_edwards, PodEdwardsPoint}; 2 | use solana_program::pubkey::Pubkey; 3 | 4 | pub fn is_on_curve(key: &Pubkey) -> bool { 5 | validate_edwards(&PodEdwardsPoint(key.to_bytes())) 6 | } 7 | -------------------------------------------------------------------------------- /src/processor/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod curve; 2 | pub(crate) mod loaders; 3 | pub(crate) mod pda; 4 | -------------------------------------------------------------------------------- /src/processor/utils/pda.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program::invoke; 2 | use solana_program::program_error::ProgramError; 3 | use solana_program::{ 4 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, rent::Rent, 5 | system_instruction, sysvar::Sysvar, 6 | }; 7 | 8 | /// Creates a new pda 9 | #[inline(always)] 10 | pub(crate) fn create_pda<'a, 'info>( 11 | target_account: &'a AccountInfo<'info>, 12 | owner: &Pubkey, 13 | space: usize, 14 | pda_seeds: &[&[u8]], 15 | pda_bump: u8, 16 | system_program: &'a AccountInfo<'info>, 17 | payer: &'a AccountInfo<'info>, 18 | ) -> ProgramResult { 19 | // Generate the PDA's signer seeds 20 | let pda_bump_slice = &[pda_bump]; 21 | let pda_signer_seeds = [pda_seeds, &[pda_bump_slice]].concat(); 22 | // Create the account manually or using the create instruction 23 | let rent = Rent::get()?; 24 | if target_account.lamports().eq(&0) { 25 | // If balance is zero, create account 26 | solana_program::program::invoke_signed( 27 | &solana_program::system_instruction::create_account( 28 | payer.key, 29 | target_account.key, 30 | rent.minimum_balance(space), 31 | space as u64, 32 | owner, 33 | ), 34 | &[ 35 | payer.clone(), 36 | target_account.clone(), 37 | system_program.clone(), 38 | ], 39 | &[&pda_signer_seeds], 40 | )?; 41 | } else { 42 | // Otherwise, if balance is nonzero: 43 | // 1) transfer sufficient lamports for rent exemption 44 | let rent_exempt_balance = rent 45 | .minimum_balance(space) 46 | .saturating_sub(target_account.lamports()); 47 | if rent_exempt_balance.gt(&0) { 48 | solana_program::program::invoke( 49 | &solana_program::system_instruction::transfer( 50 | payer.key, 51 | target_account.key, 52 | rent_exempt_balance, 53 | ), 54 | &[ 55 | payer.as_ref().clone(), 56 | target_account.as_ref().clone(), 57 | system_program.as_ref().clone(), 58 | ], 59 | )?; 60 | } 61 | // 2) allocate space for the account 62 | solana_program::program::invoke_signed( 63 | &solana_program::system_instruction::allocate(target_account.key, space as u64), 64 | &[ 65 | target_account.as_ref().clone(), 66 | system_program.as_ref().clone(), 67 | ], 68 | &[&pda_signer_seeds], 69 | )?; 70 | // 3) assign our program as the owner 71 | solana_program::program::invoke_signed( 72 | &solana_program::system_instruction::assign(target_account.key, owner), 73 | &[ 74 | target_account.as_ref().clone(), 75 | system_program.as_ref().clone(), 76 | ], 77 | &[&pda_signer_seeds], 78 | )?; 79 | } 80 | 81 | Ok(()) 82 | } 83 | 84 | /// Resize PDA 85 | pub(crate) fn resize_pda<'a, 'info>( 86 | payer: &'a AccountInfo<'info>, 87 | pda: &'a AccountInfo<'info>, 88 | system_program: &'a AccountInfo<'info>, 89 | new_size: usize, 90 | ) -> Result<(), ProgramError> { 91 | let new_minimum_balance = Rent::default().minimum_balance(new_size); 92 | let lamports_diff = new_minimum_balance.saturating_sub(pda.lamports()); 93 | invoke( 94 | &system_instruction::transfer(payer.key, pda.key, lamports_diff), 95 | &[payer.clone(), pda.clone(), system_program.clone()], 96 | )?; 97 | 98 | pda.realloc(new_size, false)?; 99 | Ok(()) 100 | } 101 | 102 | /// Close PDA 103 | #[inline(always)] 104 | pub(crate) fn close_pda<'a, 'info>( 105 | target_account: &'a AccountInfo<'info>, 106 | destination: &'a AccountInfo<'info>, 107 | ) -> ProgramResult { 108 | // Transfer tokens from the account to the destination. 109 | let dest_starting_lamports = destination.lamports(); 110 | **destination.lamports.borrow_mut() = dest_starting_lamports 111 | .checked_add(target_account.lamports()) 112 | .unwrap(); 113 | **target_account.lamports.borrow_mut() = 0; 114 | 115 | target_account.assign(&solana_program::system_program::ID); 116 | target_account.realloc(0, false).map_err(Into::into) 117 | } 118 | 119 | /// Close PDA with fees, distributing the fees to the specified addresses in sequence 120 | /// The total fees are calculated as `fee_percentage` of the total lamports in the PDA 121 | /// Each fee address receives fee_percentage % of the previous fee address's amount 122 | pub(crate) fn close_pda_with_fees<'a, 'info>( 123 | target_account: &'a AccountInfo<'info>, 124 | destination: &'a AccountInfo<'info>, 125 | fees_addresses: &[&AccountInfo<'info>], 126 | fee_percentage: u8, 127 | ) -> ProgramResult { 128 | if fees_addresses.is_empty() || fee_percentage > 100 { 129 | return Err(ProgramError::InvalidArgument); 130 | } 131 | 132 | let init_lamports = target_account.lamports(); 133 | let total_fee_amount = target_account 134 | .lamports() 135 | .checked_mul(fee_percentage as u64) 136 | .and_then(|v| v.checked_div(100)) 137 | .ok_or(ProgramError::InsufficientFunds)?; 138 | 139 | let mut fees: Vec = vec![total_fee_amount; fees_addresses.len()]; 140 | 141 | let mut fee_amount = total_fee_amount; 142 | for fee in fees.iter_mut().take(fees_addresses.len()).skip(1) { 143 | fee_amount = fee_amount 144 | .checked_mul(fee_percentage as u64) 145 | .and_then(|v| v.checked_div(100)) 146 | .ok_or(ProgramError::InsufficientFunds)?; 147 | *fee = fee_amount; 148 | } 149 | 150 | for i in 0..fees.len() - 1 { 151 | fees[i] -= fees[i + 1]; 152 | } 153 | 154 | for (i, &fee_address) in fees_addresses.iter().enumerate() { 155 | **fee_address.lamports.borrow_mut() = fee_address 156 | .lamports() 157 | .checked_add(fees[i]) 158 | .ok_or(ProgramError::InsufficientFunds)?; 159 | } 160 | 161 | **destination.lamports.borrow_mut() = destination 162 | .lamports() 163 | .checked_add(init_lamports - total_fee_amount) 164 | .ok_or(ProgramError::InsufficientFunds)?; 165 | 166 | **target_account.lamports.borrow_mut() = 0; 167 | target_account.assign(&solana_program::system_program::ID); 168 | target_account.realloc(0, false).map_err(Into::into) 169 | } 170 | -------------------------------------------------------------------------------- /src/processor/validator_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use crate::args::ValidatorClaimFeesArgs; 2 | use crate::consts::PROTOCOL_FEES_PERCENTAGE; 3 | use crate::error::DlpError; 4 | use crate::processor::utils::loaders::{ 5 | load_initialized_protocol_fees_vault, load_initialized_validator_fees_vault, load_signer, 6 | }; 7 | use borsh::BorshDeserialize; 8 | use solana_program::msg; 9 | use solana_program::program_error::ProgramError; 10 | use solana_program::rent::Rent; 11 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 12 | 13 | /// Process validator request to claim fees from the fees vault 14 | /// 15 | /// Accounts: 16 | /// 17 | /// 0: `[signer]` the validator account. 18 | /// 1: `[writable]` the fees vault PDA. 19 | /// 2: `[writable]` the validator fees vault PDA. 20 | /// 21 | /// Requirements: 22 | /// 23 | /// - protocol fees vault is initialized 24 | /// - validator fees vault is initialized 25 | /// - validators fees vault needs to hold enough lamports to claim 26 | /// 27 | /// 1. Transfer lamports from validator fees_vault PDA to the validator authority 28 | pub fn process_validator_claim_fees( 29 | _program_id: &Pubkey, 30 | accounts: &[AccountInfo], 31 | data: &[u8], 32 | ) -> ProgramResult { 33 | let args = ValidatorClaimFeesArgs::try_from_slice(data)?; 34 | 35 | // Load Accounts 36 | let [validator, fees_vault, validator_fees_vault] = accounts else { 37 | return Err(ProgramError::NotEnoughAccountKeys); 38 | }; 39 | 40 | load_signer(validator, "validator")?; 41 | load_initialized_protocol_fees_vault(fees_vault, true)?; 42 | load_initialized_validator_fees_vault(validator, validator_fees_vault, true)?; 43 | 44 | // Calculate the amount to transfer 45 | let min_rent = Rent::default().minimum_balance(8); 46 | let amount = args 47 | .amount 48 | .unwrap_or(validator_fees_vault.lamports() - min_rent); 49 | 50 | // Ensure vault has enough lamports 51 | if validator_fees_vault.lamports() - min_rent < amount { 52 | msg!( 53 | "Vault ({}) has insufficient funds: {} < {}", 54 | validator_fees_vault.key, 55 | validator_fees_vault.lamports() - min_rent, 56 | amount 57 | ); 58 | return Err(ProgramError::InsufficientFunds); 59 | } 60 | 61 | // Calculate fees and remaining amount 62 | let protocol_fees = (amount * u64::from(PROTOCOL_FEES_PERCENTAGE)) / 100; 63 | let remaining_amount = amount.saturating_sub(protocol_fees); 64 | 65 | // Transfer fees to fees_vault 66 | **fees_vault.try_borrow_mut_lamports()? = fees_vault 67 | .lamports() 68 | .checked_add(protocol_fees) 69 | .ok_or(DlpError::Overflow)?; 70 | 71 | // Transfer remaining amount from validator_fees_vault to validator 72 | **validator_fees_vault.try_borrow_mut_lamports()? = validator_fees_vault 73 | .lamports() 74 | .checked_sub(amount) 75 | .ok_or(ProgramError::InsufficientFunds)?; 76 | 77 | **validator.try_borrow_mut_lamports()? = validator 78 | .lamports() 79 | .checked_add(remaining_amount) 80 | .ok_or(DlpError::Overflow)?; 81 | 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /src/processor/whitelist_validator_for_program.rs: -------------------------------------------------------------------------------- 1 | use crate::args::WhitelistValidatorForProgramArgs; 2 | use crate::error::DlpError::Unauthorized; 3 | use crate::processor::utils::loaders::{ 4 | load_pda, load_program, load_program_upgrade_authority, load_signer, 5 | }; 6 | use crate::processor::utils::pda::{create_pda, resize_pda}; 7 | use crate::program_config_seeds_from_program_id; 8 | use crate::state::ProgramConfig; 9 | use borsh::BorshDeserialize; 10 | use solana_program::msg; 11 | use solana_program::program_error::ProgramError; 12 | use solana_program::{ 13 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program, 14 | }; 15 | 16 | /// Whitelist a validator for a program 17 | /// 18 | /// Accounts: 19 | /// 20 | /// 0: `[signer]` authority that has rights to whitelist validators 21 | /// 1: `[]` validator identity to whitelist 22 | /// 2: `[]` program to whitelist the validator for 23 | /// 3: `[]` program data account 24 | /// 4: `[writable]` program config PDA 25 | /// 5: `[]` system program 26 | /// 27 | /// Requirements: 28 | /// 29 | /// - authority is either the ADMIN_PUBKEY or the program upgrade authority 30 | /// - program config is initialized or owned by the system program in 31 | /// which case it is created 32 | /// 33 | /// Steps: 34 | /// 35 | /// 1. Load the authority and validate it 36 | /// 2. Load the program config or create it and insert the validator to the `approved_validators` 37 | /// set, resizing the account if necessary 38 | pub fn process_whitelist_validator_for_program( 39 | _program_id: &Pubkey, 40 | accounts: &[AccountInfo], 41 | data: &[u8], 42 | ) -> ProgramResult { 43 | let args = WhitelistValidatorForProgramArgs::try_from_slice(data)?; 44 | 45 | // Load Accounts 46 | let [authority, validator_identity, program, program_data, delegation_program_data, program_config_account, system_program] = 47 | accounts 48 | else { 49 | return Err(ProgramError::NotEnoughAccountKeys); 50 | }; 51 | 52 | load_signer(authority, "authority")?; 53 | validate_authority(authority, program, program_data, delegation_program_data)?; 54 | load_program(system_program, system_program::id(), "system program")?; 55 | 56 | let program_config_bump = load_pda( 57 | program_config_account, 58 | program_config_seeds_from_program_id!(program.key), 59 | &crate::id(), 60 | true, 61 | "program config", 62 | )?; 63 | 64 | // Get the program config. If the account doesn't exist, create it 65 | let mut program_config = if program_config_account.owner.eq(system_program.key) { 66 | create_pda( 67 | program_config_account, 68 | &crate::id(), 69 | 0, // It will be resized later to the proper size 70 | program_config_seeds_from_program_id!(program.key), 71 | program_config_bump, 72 | system_program, 73 | authority, 74 | )?; 75 | ProgramConfig::default() 76 | } else { 77 | let program_config_data = program_config_account.try_borrow_data()?; 78 | ProgramConfig::try_from_bytes_with_discriminator(&program_config_data)? 79 | }; 80 | if args.insert { 81 | program_config 82 | .approved_validators 83 | .insert(*validator_identity.key); 84 | } else { 85 | program_config 86 | .approved_validators 87 | .remove(validator_identity.key); 88 | } 89 | resize_pda( 90 | authority, 91 | program_config_account, 92 | system_program, 93 | program_config.size_with_discriminator(), 94 | )?; 95 | let mut program_config_data = program_config_account.try_borrow_mut_data()?; 96 | program_config.to_bytes_with_discriminator(&mut program_config_data.as_mut())?; 97 | 98 | Ok(()) 99 | } 100 | 101 | /// Authority is valid if either the authority is the ADMIN_PUBKEY or the program upgrade authority 102 | fn validate_authority( 103 | authority: &AccountInfo, 104 | program: &AccountInfo, 105 | program_data: &AccountInfo, 106 | delegation_program_data: &AccountInfo, 107 | ) -> Result<(), ProgramError> { 108 | let admin_pubkey = 109 | load_program_upgrade_authority(&crate::ID, delegation_program_data)?.ok_or(Unauthorized)?; 110 | if authority.key.eq(&admin_pubkey) 111 | || authority 112 | .key 113 | .eq(&load_program_upgrade_authority(program.key, program_data)?.ok_or(Unauthorized)?) 114 | { 115 | Ok(()) 116 | } else { 117 | msg!( 118 | "Expected authority to be {} or program upgrade authority, but got {}", 119 | admin_pubkey, 120 | authority.key 121 | ); 122 | Err(Unauthorized.into()) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/state/commit_record.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | use bytemuck::{Pod, Zeroable}; 4 | use solana_program::pubkey::Pubkey; 5 | 6 | use crate::{ 7 | impl_to_bytes_with_discriminator_zero_copy, impl_try_from_bytes_with_discriminator_zero_copy, 8 | }; 9 | 10 | use super::discriminator::{AccountDiscriminator, AccountWithDiscriminator}; 11 | 12 | /// The Commit State Record 13 | #[repr(C)] 14 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 15 | pub struct CommitRecord { 16 | /// The identity committing the state 17 | pub identity: Pubkey, 18 | 19 | /// The account for which the state is committed 20 | pub account: Pubkey, 21 | 22 | /// The external slot of the commit. This is used to enforce sequential commits 23 | pub slot: u64, 24 | 25 | /// The account committed lamports 26 | pub lamports: u64, 27 | } 28 | 29 | impl AccountWithDiscriminator for CommitRecord { 30 | fn discriminator() -> AccountDiscriminator { 31 | AccountDiscriminator::CommitRecord 32 | } 33 | } 34 | 35 | impl CommitRecord { 36 | pub fn size_with_discriminator() -> usize { 37 | 8 + size_of::() 38 | } 39 | } 40 | 41 | impl_to_bytes_with_discriminator_zero_copy!(CommitRecord); 42 | impl_try_from_bytes_with_discriminator_zero_copy!(CommitRecord); 43 | -------------------------------------------------------------------------------- /src/state/delegation_metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_to_bytes_with_discriminator_borsh, impl_try_from_bytes_with_discriminator_borsh}; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use solana_program::pubkey::Pubkey; 4 | 5 | use super::discriminator::{AccountDiscriminator, AccountWithDiscriminator}; 6 | 7 | /// The Delegated Metadata includes Account Seeds, max delegation time, seeds 8 | /// and other meta information about the delegated account. 9 | /// * Everything necessary at cloning time is instead stored in the delegation record. 10 | #[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq)] 11 | pub struct DelegationMetadata { 12 | /// The last slot at which the delegation was updated 13 | pub last_update_external_slot: u64, 14 | /// Whether the account can be undelegated or not 15 | pub is_undelegatable: bool, 16 | /// The seeds of the account, used to reopen it on undelegation 17 | pub seeds: Vec>, 18 | /// The account that paid the rent for the delegation PDAs 19 | pub rent_payer: Pubkey, 20 | } 21 | 22 | impl AccountWithDiscriminator for DelegationMetadata { 23 | fn discriminator() -> AccountDiscriminator { 24 | AccountDiscriminator::DelegationMetadata 25 | } 26 | } 27 | 28 | impl_to_bytes_with_discriminator_borsh!(DelegationMetadata); 29 | impl_try_from_bytes_with_discriminator_borsh!(DelegationMetadata); 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use borsh::to_vec; 34 | 35 | use super::*; 36 | 37 | #[test] 38 | fn test_serialization_without_discriminator() { 39 | let original = DelegationMetadata { 40 | seeds: vec![ 41 | vec![], 42 | vec![ 43 | 215, 233, 74, 188, 162, 203, 12, 212, 106, 87, 189, 226, 48, 38, 129, 7, 34, 44 | 82, 254, 106, 161, 35, 74, 146, 30, 211, 164, 97, 139, 136, 136, 77, 45 | ], 46 | ], 47 | is_undelegatable: false, 48 | last_update_external_slot: 0, 49 | rent_payer: Pubkey::default(), 50 | }; 51 | 52 | // Serialize 53 | let serialized = to_vec(&original).expect("Serialization failed"); 54 | 55 | // Deserialize 56 | let deserialized: DelegationMetadata = 57 | DelegationMetadata::try_from_slice(&serialized).expect("Deserialization failed"); 58 | 59 | assert_eq!(deserialized, original); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/state/delegation_record.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | use crate::impl_to_bytes_with_discriminator_zero_copy; 4 | use crate::impl_try_from_bytes_with_discriminator_zero_copy; 5 | use bytemuck::{Pod, Zeroable}; 6 | use solana_program::pubkey::Pubkey; 7 | 8 | use super::discriminator::AccountDiscriminator; 9 | use super::discriminator::AccountWithDiscriminator; 10 | 11 | /// The Delegation Record stores information such as the authority, the owner and the commit frequency. 12 | /// This is used by the ephemeral validator to update the state of the delegated account. 13 | #[repr(C)] 14 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 15 | pub struct DelegationRecord { 16 | /// The delegated authority 17 | pub authority: Pubkey, 18 | 19 | /// The original owner of the account 20 | pub owner: Pubkey, 21 | 22 | /// The slot at which the delegation was created 23 | pub delegation_slot: u64, 24 | 25 | /// The lamports at the time of delegation or from the last state finalization, stored as lamports can be received even if the account is delegated 26 | pub lamports: u64, 27 | 28 | /// The state update frequency in milliseconds 29 | pub commit_frequency_ms: u64, 30 | } 31 | 32 | impl AccountWithDiscriminator for DelegationRecord { 33 | fn discriminator() -> AccountDiscriminator { 34 | AccountDiscriminator::DelegationRecord 35 | } 36 | } 37 | 38 | impl DelegationRecord { 39 | pub fn size_with_discriminator() -> usize { 40 | 8 + size_of::() 41 | } 42 | } 43 | 44 | impl_to_bytes_with_discriminator_zero_copy!(DelegationRecord); 45 | impl_try_from_bytes_with_discriminator_zero_copy!(DelegationRecord); 46 | -------------------------------------------------------------------------------- /src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod commit_record; 2 | mod delegation_metadata; 3 | mod delegation_record; 4 | mod program_config; 5 | mod utils; 6 | 7 | pub use commit_record::*; 8 | pub use delegation_metadata::*; 9 | pub use delegation_record::*; 10 | pub use program_config::*; 11 | pub use utils::*; 12 | -------------------------------------------------------------------------------- /src/state/program_config.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | use std::collections::BTreeSet; 4 | 5 | use crate::{impl_to_bytes_with_discriminator_borsh, impl_try_from_bytes_with_discriminator_borsh}; 6 | 7 | use super::discriminator::{AccountDiscriminator, AccountWithDiscriminator}; 8 | 9 | #[derive(BorshSerialize, BorshDeserialize, Default, Debug)] 10 | pub struct ProgramConfig { 11 | pub approved_validators: BTreeSet, 12 | } 13 | 14 | impl AccountWithDiscriminator for ProgramConfig { 15 | fn discriminator() -> AccountDiscriminator { 16 | AccountDiscriminator::ProgramConfig 17 | } 18 | } 19 | 20 | impl ProgramConfig { 21 | pub fn size_with_discriminator(&self) -> usize { 22 | 8 + 4 + 32 * self.approved_validators.len() 23 | } 24 | } 25 | 26 | impl_to_bytes_with_discriminator_borsh!(ProgramConfig); 27 | impl_try_from_bytes_with_discriminator_borsh!(ProgramConfig); 28 | -------------------------------------------------------------------------------- /src/state/utils/discriminator.rs: -------------------------------------------------------------------------------- 1 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 2 | 3 | #[repr(u8)] 4 | #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] 5 | pub enum AccountDiscriminator { 6 | DelegationRecord = 100, 7 | DelegationMetadata = 102, 8 | CommitRecord = 101, 9 | ProgramConfig = 103, 10 | } 11 | 12 | impl AccountDiscriminator { 13 | pub const fn to_bytes(&self) -> [u8; 8] { 14 | let num = (*self) as u64; 15 | num.to_le_bytes() 16 | } 17 | } 18 | 19 | pub trait AccountWithDiscriminator { 20 | fn discriminator() -> AccountDiscriminator; 21 | } 22 | -------------------------------------------------------------------------------- /src/state/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod discriminator; 2 | pub mod to_bytes; 3 | pub mod try_from_bytes; 4 | -------------------------------------------------------------------------------- /src/state/utils/to_bytes.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! impl_to_bytes_with_discriminator_zero_copy { 3 | ($struct_name:ident) => { 4 | impl $struct_name { 5 | pub fn to_bytes_with_discriminator( 6 | &self, 7 | data: &mut [u8], 8 | ) -> Result<(), ::solana_program::program_error::ProgramError> { 9 | if data.len() < 8 { 10 | return Err(::solana_program::program_error::ProgramError::InvalidAccountData); 11 | } 12 | data[..8].copy_from_slice(&Self::discriminator().to_bytes()); 13 | data[8..].copy_from_slice(bytemuck::bytes_of(self)); 14 | Ok(()) 15 | } 16 | } 17 | }; 18 | } 19 | 20 | #[macro_export] 21 | macro_rules! impl_to_bytes_with_discriminator_borsh { 22 | ($struct_name:ident) => { 23 | impl $struct_name { 24 | pub fn to_bytes_with_discriminator( 25 | &self, 26 | writer: &mut W, 27 | ) -> Result<(), ::solana_program::program_error::ProgramError> { 28 | writer.write_all(&Self::discriminator().to_bytes())?; 29 | self.serialize(writer)?; 30 | Ok(()) 31 | } 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/state/utils/try_from_bytes.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! impl_try_from_bytes_with_discriminator_zero_copy { 3 | ($struct_name:ident) => { 4 | impl $struct_name { 5 | pub fn try_from_bytes_with_discriminator( 6 | data: &[u8], 7 | ) -> Result<&Self, ::solana_program::program_error::ProgramError> { 8 | if data.len() < 8 { 9 | return Err(::solana_program::program_error::ProgramError::InvalidAccountData); 10 | } 11 | if Self::discriminator().to_bytes().ne(&data[..8]) { 12 | return Err(::solana_program::program_error::ProgramError::InvalidAccountData); 13 | } 14 | bytemuck::try_from_bytes::(&data[8..]).or(Err( 15 | ::solana_program::program_error::ProgramError::InvalidAccountData, 16 | )) 17 | } 18 | pub fn try_from_bytes_with_discriminator_mut( 19 | data: &mut [u8], 20 | ) -> Result<&mut Self, ::solana_program::program_error::ProgramError> { 21 | if data.len() < 8 { 22 | return Err(::solana_program::program_error::ProgramError::InvalidAccountData); 23 | } 24 | if Self::discriminator().to_bytes().ne(&data[..8]) { 25 | return Err(::solana_program::program_error::ProgramError::InvalidAccountData); 26 | } 27 | bytemuck::try_from_bytes_mut::(&mut data[8..]).or(Err( 28 | ::solana_program::program_error::ProgramError::InvalidAccountData, 29 | )) 30 | } 31 | } 32 | }; 33 | } 34 | 35 | #[macro_export] 36 | macro_rules! impl_try_from_bytes_with_discriminator_borsh { 37 | ($struct_name:ident) => { 38 | impl $struct_name { 39 | pub fn try_from_bytes_with_discriminator( 40 | data: &[u8], 41 | ) -> Result { 42 | if data.len() < 8 { 43 | return Err(::solana_program::program_error::ProgramError::InvalidAccountData); 44 | } 45 | if Self::discriminator().to_bytes().ne(&data[..8]) { 46 | return Err(::solana_program::program_error::ProgramError::InvalidAccountData); 47 | } 48 | Self::try_from_slice(&data[8..]).or(Err( 49 | ::solana_program::program_error::ProgramError::InvalidAccountData, 50 | )) 51 | } 52 | } 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /tests/buffers/test_delegation.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicblock-labs/delegation-program/9570abf24ab6d03ba75ecdbf8a6a787bb0d69d3a/tests/buffers/test_delegation.so -------------------------------------------------------------------------------- /tests/fixtures/accounts.rs: -------------------------------------------------------------------------------- 1 | use dlp::state::{CommitRecord, DelegationMetadata, DelegationRecord, ProgramConfig}; 2 | use solana_program::native_token::LAMPORTS_PER_SOL; 3 | use solana_program::pubkey::Pubkey; 4 | use solana_program::rent::Rent; 5 | use solana_program::system_program; 6 | use solana_sdk::pubkey; 7 | 8 | // Constants for default values 9 | const DEFAULT_DELEGATION_SLOT: u64 = 0; 10 | const DEFAULT_COMMIT_FREQUENCY_MS: u64 = 0; 11 | const DEFAULT_LAST_UPDATE_EXTERNAL_SLOT: u64 = 0; 12 | const DEFAULT_IS_UNDELEGATABLE: bool = false; 13 | const DEFAULT_SEEDS: &[&[u8]] = &[&[116, 101, 115, 116, 45, 112, 100, 97]]; 14 | 15 | #[allow(dead_code)] 16 | pub const COMMIT_STATE_AUTHORITY: Pubkey = pubkey!("Ec6jL2GVTzjfHz8RFP3mVyki9JRNmMu8E7YdNh45xNdk"); 17 | 18 | #[allow(dead_code)] 19 | pub const COMMIT_NEW_STATE_ACCOUNT_DATA: [u8; 11] = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 11]; 20 | 21 | #[allow(dead_code)] 22 | pub const DELEGATED_PDA_ID: Pubkey = pubkey!("8k2V7EzQtNg38Gi9HK5ZtQYp1YpGKNGrMcuGa737gZX4"); 23 | 24 | #[allow(dead_code)] 25 | pub const DELEGATED_PDA: [u8; 19] = [15, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]; 26 | 27 | #[allow(dead_code)] 28 | pub const DELEGATED_PDA_OWNER_ID: Pubkey = pubkey!("3vAK9JQiDsKoQNwmcfeEng4Cnv22pYuj1ASfso7U4ukF"); 29 | 30 | #[allow(dead_code)] 31 | pub const EXTERNAL_DELEGATE_INSTRUCTION_DISCRIMINATOR: [u8; 8] = [90, 147, 75, 178, 85, 88, 4, 137]; 32 | 33 | #[allow(dead_code)] 34 | pub const EXTERNAL_ALLOW_UNDELEGATION_INSTRUCTION_DISCRIMINATOR: [u8; 8] = 35 | [255, 66, 82, 208, 247, 5, 210, 126]; 36 | 37 | #[allow(dead_code)] 38 | pub const ON_CURVE_KEYPAIR: [u8; 64] = [ 39 | 74, 198, 48, 104, 119, 57, 255, 80, 67, 181, 191, 189, 85, 21, 235, 45, 185, 175, 48, 143, 13, 40 | 202, 92, 81, 211, 108, 61, 237, 183, 116, 207, 45, 170, 118, 238, 247, 128, 91, 3, 41, 33, 10, 41 | 241, 163, 185, 198, 228, 172, 200, 220, 225, 192, 149, 94, 106, 209, 65, 79, 210, 54, 191, 49, 42 | 115, 159, 43 | ]; 44 | 45 | #[allow(dead_code)] 46 | pub const TEST_AUTHORITY: [u8; 64] = [ 47 | 251, 62, 129, 184, 107, 49, 62, 184, 1, 147, 178, 128, 185, 157, 247, 92, 56, 158, 145, 53, 51, 48 | 226, 202, 96, 178, 248, 195, 133, 133, 237, 237, 146, 13, 32, 77, 204, 244, 56, 166, 172, 66, 49 | 113, 150, 218, 112, 42, 110, 181, 98, 158, 222, 194, 130, 93, 175, 100, 190, 106, 9, 69, 156, 50 | 80, 96, 72, 51 | ]; 52 | 53 | #[allow(dead_code)] 54 | pub fn get_delegation_record_data(authority: Pubkey, last_update_lamports: Option) -> Vec { 55 | create_delegation_record_data(authority, DELEGATED_PDA_OWNER_ID, last_update_lamports) 56 | } 57 | 58 | #[allow(dead_code)] 59 | pub fn get_delegation_record_on_curve_data( 60 | authority: Pubkey, 61 | last_update_lamports: Option, 62 | ) -> Vec { 63 | create_delegation_record_data(authority, system_program::id(), last_update_lamports) 64 | } 65 | 66 | #[allow(dead_code)] 67 | pub fn create_delegation_record_data( 68 | authority: Pubkey, 69 | owner: Pubkey, 70 | last_update_lamports: Option, 71 | ) -> Vec { 72 | let delegation_record = DelegationRecord { 73 | authority, 74 | owner, 75 | delegation_slot: DEFAULT_DELEGATION_SLOT, 76 | commit_frequency_ms: DEFAULT_COMMIT_FREQUENCY_MS, 77 | lamports: last_update_lamports.unwrap_or(Rent::default().minimum_balance(500)), 78 | }; 79 | let mut bytes = vec![0u8; DelegationRecord::size_with_discriminator()]; 80 | delegation_record 81 | .to_bytes_with_discriminator(&mut bytes) 82 | .unwrap(); 83 | bytes 84 | } 85 | 86 | #[allow(dead_code)] 87 | pub fn get_delegation_metadata_data_on_curve( 88 | rent_payer: Pubkey, 89 | is_undelegatable: Option, 90 | ) -> Vec { 91 | create_delegation_metadata_data( 92 | rent_payer, 93 | &[], 94 | is_undelegatable.unwrap_or(DEFAULT_IS_UNDELEGATABLE), 95 | ) 96 | } 97 | 98 | #[allow(dead_code)] 99 | pub fn get_delegation_metadata_data(rent_payer: Pubkey, is_undelegatable: Option) -> Vec { 100 | create_delegation_metadata_data( 101 | rent_payer, 102 | DEFAULT_SEEDS, 103 | is_undelegatable.unwrap_or(DEFAULT_IS_UNDELEGATABLE), 104 | ) 105 | } 106 | 107 | pub fn create_delegation_metadata_data( 108 | rent_payer: Pubkey, 109 | seeds: &[&[u8]], 110 | is_undelegatable: bool, 111 | ) -> Vec { 112 | let delegation_metadata = DelegationMetadata { 113 | last_update_external_slot: DEFAULT_LAST_UPDATE_EXTERNAL_SLOT, 114 | is_undelegatable, 115 | seeds: seeds.iter().map(|s| s.to_vec()).collect(), 116 | rent_payer, 117 | }; 118 | let mut bytes = vec![]; 119 | delegation_metadata 120 | .to_bytes_with_discriminator(&mut bytes) 121 | .unwrap(); 122 | bytes 123 | } 124 | 125 | #[allow(dead_code)] 126 | pub fn get_commit_record_account_data(authority: Pubkey) -> Vec { 127 | let commit_record = CommitRecord { 128 | slot: 100, 129 | identity: authority, 130 | account: DELEGATED_PDA_ID, 131 | lamports: LAMPORTS_PER_SOL, 132 | }; 133 | let mut bytes = vec![0u8; CommitRecord::size_with_discriminator()]; 134 | commit_record 135 | .to_bytes_with_discriminator(&mut bytes) 136 | .unwrap(); 137 | bytes 138 | } 139 | 140 | #[allow(dead_code)] 141 | pub fn create_program_config_data(approved_validator: Pubkey) -> Vec { 142 | let mut program_config = ProgramConfig { 143 | approved_validators: Default::default(), 144 | }; 145 | program_config 146 | .approved_validators 147 | .insert(approved_validator); 148 | let mut bytes = vec![]; 149 | program_config 150 | .to_bytes_with_discriminator(&mut bytes) 151 | .unwrap(); 152 | bytes 153 | } 154 | -------------------------------------------------------------------------------- /tests/fixtures/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod accounts; 2 | 3 | #[allow(unused_imports)] 4 | pub(crate) use accounts::*; 5 | -------------------------------------------------------------------------------- /tests/integration/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = false 6 | 7 | [programs.localnet] 8 | test_delegation = "3vAK9JQiDsKoQNwmcfeEng4Cnv22pYuj1ASfso7U4ukF" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | cluster = "Localnet" 15 | wallet = "./tests/fixtures/provider.json" 16 | 17 | [workspace] 18 | members = ["programs/*"] 19 | 20 | [scripts] 21 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/test-delegation.ts" 22 | 23 | [test] 24 | startup_wait = 5000 25 | shutdown_wait = 2000 26 | upgradeable = false 27 | 28 | [[test.genesis]] 29 | address = "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" 30 | program = "../../target/deploy/dlp.so" 31 | upgradeable = true 32 | -------------------------------------------------------------------------------- /tests/integration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*", 4 | ] 5 | resolver = "2" 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /tests/integration/crates/types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bolt-types" 3 | version = "0.1.4" 4 | description = "Autogenerate types for the bolt language" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "bolt_types" 10 | 11 | [dependencies] 12 | bolt-lang = "0.1.4" 13 | anchor-lang = "0.30.0" 14 | -------------------------------------------------------------------------------- /tests/integration/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@coral-xyz/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /tests/integration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@coral-xyz/anchor": "0.31.1" 8 | }, 9 | "devDependencies": { 10 | "@magicblock-labs/ephemeral-rollups-sdk-v2": "^0.1.4", 11 | "@metaplex-foundation/beet": "^0.7.1", 12 | "@metaplex-foundation/beet-solana": "^0.4.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "chai": "^4.3.4", 17 | "mocha": "^9.0.3", 18 | "prettier": "^2.6.2", 19 | "ts-mocha": "^10.0.0", 20 | "typescript": "^4.3.5" 21 | }, 22 | "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" 23 | } 24 | -------------------------------------------------------------------------------- /tests/integration/programs/test-delegation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-delegation" 3 | version = "0.1.4" 4 | description = "Created with Bolt" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "test_delegation" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | idl-build = ["anchor-lang/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = "0.31.1" 21 | ephemeral-rollups-sdk = { version = "0.2.5", features = ["anchor"] } -------------------------------------------------------------------------------- /tests/integration/programs/test-delegation/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /tests/integration/programs/test-delegation/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use ephemeral_rollups_sdk::anchor::{delegate, ephemeral}; 3 | use ephemeral_rollups_sdk::cpi::DelegateConfig; 4 | 5 | declare_id!("3vAK9JQiDsKoQNwmcfeEng4Cnv22pYuj1ASfso7U4ukF"); 6 | 7 | pub const TEST_PDA_SEED: &[u8] = b"test-pda"; 8 | pub const TEST_PDA_SEED_OTHER: &[u8] = b"test-pda-other"; 9 | 10 | #[ephemeral] 11 | #[program] 12 | pub mod test_delegation { 13 | use super::*; 14 | 15 | pub fn initialize(ctx: Context) -> Result<()> { 16 | let counter = &mut ctx.accounts.counter; 17 | counter.count = 0; 18 | Ok(()) 19 | } 20 | 21 | pub fn initialize_other(ctx: Context) -> Result<()> { 22 | let counter = &mut ctx.accounts.counter; 23 | counter.count = 0; 24 | Ok(()) 25 | } 26 | 27 | pub fn increment(ctx: Context) -> Result<()> { 28 | let counter = &mut ctx.accounts.counter; 29 | counter.count += 1; 30 | Ok(()) 31 | } 32 | 33 | /// Delegate the account to the delegation program 34 | pub fn delegate(ctx: Context) -> Result<()> { 35 | ctx.accounts.delegate_pda( 36 | &ctx.accounts.payer, 37 | &[TEST_PDA_SEED], 38 | DelegateConfig::default(), 39 | )?; 40 | Ok(()) 41 | } 42 | 43 | /// Delegate two accounts to the delegation program 44 | pub fn delegate_two(ctx: Context) -> Result<()> { 45 | ctx.accounts.delegate_pda( 46 | &ctx.accounts.payer, 47 | &[TEST_PDA_SEED], 48 | DelegateConfig::default(), 49 | )?; 50 | ctx.accounts.delegate_pda_other( 51 | &ctx.accounts.payer, 52 | &[TEST_PDA_SEED_OTHER], 53 | DelegateConfig::default(), 54 | )?; 55 | Ok(()) 56 | } 57 | } 58 | 59 | #[delegate] 60 | #[derive(Accounts)] 61 | pub struct DelegateInput<'info> { 62 | pub payer: Signer<'info>, 63 | /// CHECK: The pda to delegate 64 | #[account(mut, del, seeds = [TEST_PDA_SEED], bump)] 65 | pub pda: AccountInfo<'info>, 66 | } 67 | 68 | #[delegate] 69 | #[derive(Accounts)] 70 | pub struct DelegateInputTwo<'info> { 71 | pub payer: Signer<'info>, 72 | /// CHECK: The pda to delegate 73 | #[account(mut, del, seeds = [TEST_PDA_SEED], bump)] 74 | pub pda: AccountInfo<'info>, 75 | /// CHECK: The other pda to delegate 76 | #[account(mut, del, seeds = [TEST_PDA_SEED_OTHER], bump)] 77 | pub pda_other: AccountInfo<'info>, 78 | } 79 | 80 | #[derive(Accounts)] 81 | pub struct Initialize<'info> { 82 | #[account(init, payer = user, space = 8 + 8, seeds = [TEST_PDA_SEED], bump)] 83 | pub counter: Account<'info, Counter>, 84 | #[account(mut)] 85 | pub user: Signer<'info>, 86 | pub system_program: Program<'info, System>, 87 | } 88 | 89 | #[derive(Accounts)] 90 | pub struct InitializeOther<'info> { 91 | #[account(init, payer = user, space = 8 + 8, seeds = [TEST_PDA_SEED_OTHER], bump)] 92 | pub counter: Account<'info, Counter>, 93 | #[account(mut)] 94 | pub user: Signer<'info>, 95 | pub system_program: Program<'info, System>, 96 | } 97 | 98 | #[derive(Accounts)] 99 | pub struct Increment<'info> { 100 | #[account(mut, seeds = [TEST_PDA_SEED], bump)] 101 | pub counter: Account<'info, Counter>, 102 | } 103 | 104 | #[account] 105 | pub struct Counter { 106 | pub count: u64, 107 | } 108 | -------------------------------------------------------------------------------- /tests/integration/target/deploy/test_delegation-keypair.json: -------------------------------------------------------------------------------- 1 | [63,50,121,252,202,15,215,66,105,123,222,131,60,225,133,25,112,251,212,207,96,83,93,218,11,161,62,9,170,171,154,69,43,85,175,207,195,148,154,129,218,62,110,177,81,112,72,172,141,157,3,211,24,26,191,79,101,191,48,19,105,181,70,132] -------------------------------------------------------------------------------- /tests/integration/tests/fixtures/consts.ts: -------------------------------------------------------------------------------- 1 | import { web3 } from "@coral-xyz/anchor"; 2 | 3 | export const ON_CURVE_ACCOUNT = web3.Keypair.fromSecretKey( 4 | Uint8Array.from([ 5 | 74, 198, 48, 104, 119, 57, 255, 80, 67, 181, 191, 189, 85, 21, 235, 45, 185, 6 | 175, 48, 143, 13, 202, 92, 81, 211, 108, 61, 237, 183, 116, 207, 45, 170, 7 | 118, 238, 247, 128, 91, 3, 41, 33, 10, 241, 163, 185, 198, 228, 172, 200, 8 | 220, 225, 192, 149, 94, 106, 209, 65, 79, 210, 54, 191, 49, 115, 159, 9 | ]) 10 | ); // CURVek2Zcmv5HUt34CVDnWMeSLAJfrXSrU2mYBuiZvvS 11 | -------------------------------------------------------------------------------- /tests/integration/tests/fixtures/provider.json: -------------------------------------------------------------------------------- 1 | [251,62,129,184,107,49,62,184,1,147,178,128,185,157,247,92,56,158,145,53,51,226,202,96,178,248,195,133,133,237,237,146,13,32,77,204,244,56,166,172,66,113,150,218,112,42,110,181,98,158,222,194,130,93,175,100,190,106,9,69,156,80,96,72] -------------------------------------------------------------------------------- /tests/integration/tests/test-increment-delegated.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program } from "@coral-xyz/anchor"; 3 | import { TestDelegation } from "../target/types/test_delegation"; 4 | import { assert } from "chai"; 5 | 6 | describe("TestDelegation", () => { 7 | const connection = new anchor.web3.Connection( 8 | anchor.web3.clusterApiUrl("devnet") 9 | ); 10 | 11 | const provider = new anchor.AnchorProvider( 12 | connection, 13 | anchor.Wallet.local(), 14 | { preflightCommitment: "processed" } 15 | ); 16 | anchor.setProvider(provider); 17 | 18 | const testDelegation = anchor.workspace 19 | .TestDelegation as Program; 20 | 21 | it("Increase the counter after undelegation", async () => { 22 | const counter = new anchor.web3.PublicKey( 23 | "C89kNYAztTjg3qiPeztai2Ua9ucES41MLTuQS44rYqTP" 24 | ); 25 | let failed = false; 26 | try { 27 | const tx = await testDelegation.methods 28 | .increment() 29 | .accounts({ 30 | counter: counter, 31 | }) 32 | .rpc(); 33 | console.log("Increment Tx: ", tx); 34 | } catch (e) { 35 | failed = true; 36 | } 37 | assert.isTrue(failed); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/test_close_validator_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use crate::fixtures::TEST_AUTHORITY; 2 | use dlp::pda::validator_fees_vault_pda_from_validator; 3 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 4 | use solana_program_test::{processor, BanksClient, ProgramTest}; 5 | use solana_sdk::{ 6 | account::Account, 7 | signature::{Keypair, Signer}, 8 | transaction::Transaction, 9 | }; 10 | 11 | mod fixtures; 12 | 13 | #[tokio::test] 14 | async fn test_close_validator_fees_vault() { 15 | // Setup 16 | let (banks, admin, validator, blockhash) = setup_program_test_env().await; 17 | 18 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator.pubkey()); 19 | 20 | // Submit the close vault tx 21 | let ix = dlp::instruction_builder::close_validator_fees_vault( 22 | admin.pubkey(), 23 | admin.pubkey(), 24 | validator.pubkey(), 25 | ); 26 | let tx = Transaction::new_signed_with_payer(&[ix], Some(&admin.pubkey()), &[&admin], blockhash); 27 | let res = banks.process_transaction(tx).await; 28 | assert!(res.is_ok()); 29 | 30 | // Assert the validator fees vault now has been closed 31 | let validator_fees_vault_account = banks.get_account(validator_fees_vault_pda).await.unwrap(); 32 | assert!(validator_fees_vault_account.is_none()); 33 | } 34 | 35 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 36 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 37 | program_test.prefer_bpf(true); 38 | 39 | let admin_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 40 | let validator = Keypair::new(); 41 | 42 | program_test.add_account( 43 | admin_keypair.pubkey(), 44 | Account { 45 | lamports: LAMPORTS_PER_SOL, 46 | data: vec![], 47 | owner: system_program::id(), 48 | executable: false, 49 | rent_epoch: 0, 50 | }, 51 | ); 52 | 53 | // Setup the validator fees vault 54 | program_test.add_account( 55 | validator_fees_vault_pda_from_validator(&validator.pubkey()), 56 | Account { 57 | lamports: LAMPORTS_PER_SOL, 58 | data: vec![], 59 | owner: dlp::id(), 60 | executable: false, 61 | rent_epoch: 0, 62 | }, 63 | ); 64 | 65 | let (banks, _, blockhash) = program_test.start().await; 66 | (banks, admin_keypair, validator, blockhash) 67 | } 68 | -------------------------------------------------------------------------------- /tests/test_commit_on_curve.rs: -------------------------------------------------------------------------------- 1 | use dlp::args::CommitStateArgs; 2 | use dlp::pda::{ 3 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 4 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 5 | validator_fees_vault_pda_from_validator, 6 | }; 7 | use dlp::state::{CommitRecord, DelegationMetadata}; 8 | use solana_program::rent::Rent; 9 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 10 | use solana_program_test::{processor, BanksClient, ProgramTest}; 11 | use solana_sdk::{ 12 | account::Account, 13 | signature::{Keypair, Signer}, 14 | transaction::Transaction, 15 | }; 16 | 17 | use crate::fixtures::{ 18 | get_delegation_metadata_data_on_curve, get_delegation_record_on_curve_data, ON_CURVE_KEYPAIR, 19 | TEST_AUTHORITY, 20 | }; 21 | 22 | mod fixtures; 23 | 24 | #[tokio::test] 25 | async fn test_commit_on_curve() { 26 | // Setup 27 | let (banks, payer_delegated, validator, blockhash) = setup_program_test_env().await; 28 | 29 | let new_account_balance = 1_000_000; 30 | let commit_args = CommitStateArgs { 31 | data: vec![], 32 | slot: 100, 33 | allow_undelegation: true, 34 | lamports: new_account_balance, 35 | }; 36 | 37 | // Commit the state for the delegated account 38 | let ix = dlp::instruction_builder::commit_state( 39 | validator.pubkey(), 40 | payer_delegated.pubkey(), 41 | system_program::ID, 42 | commit_args, 43 | ); 44 | let tx = Transaction::new_signed_with_payer( 45 | &[ix], 46 | Some(&validator.pubkey()), 47 | &[&validator], 48 | blockhash, 49 | ); 50 | let res = banks.process_transaction(tx).await; 51 | println!("{:?}", res); 52 | assert!(res.is_ok()); 53 | 54 | // Assert the state commitment was created and contains the new state 55 | let commit_state_pda = commit_state_pda_from_delegated_account(&payer_delegated.pubkey()); 56 | let commit_state_account = banks.get_account(commit_state_pda).await.unwrap().unwrap(); 57 | assert!(commit_state_account.data.is_empty()); 58 | 59 | // Assert the record about the commitment exists 60 | let commit_record_pda = commit_record_pda_from_delegated_account(&payer_delegated.pubkey()); 61 | let commit_record_account = banks.get_account(commit_record_pda).await.unwrap().unwrap(); 62 | let commit_record = 63 | CommitRecord::try_from_bytes_with_discriminator(&commit_record_account.data).unwrap(); 64 | assert_eq!(commit_record.account, payer_delegated.pubkey()); 65 | assert_eq!(commit_record.identity, validator.pubkey()); 66 | assert_eq!(commit_record.slot, 100); 67 | 68 | let delegation_metadata_pda = 69 | delegation_metadata_pda_from_delegated_account(&payer_delegated.pubkey()); 70 | let delegation_metadata_account = banks 71 | .get_account(delegation_metadata_pda) 72 | .await 73 | .unwrap() 74 | .unwrap(); 75 | let delegation_metadata = 76 | DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata_account.data) 77 | .unwrap(); 78 | assert!(delegation_metadata.is_undelegatable); 79 | } 80 | 81 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 82 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 83 | program_test.prefer_bpf(true); 84 | 85 | // Setup the validator authority 86 | let validator_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 87 | program_test.add_account( 88 | validator_keypair.pubkey(), 89 | Account { 90 | lamports: 10 * LAMPORTS_PER_SOL, 91 | data: vec![], 92 | owner: system_program::id(), 93 | executable: false, 94 | rent_epoch: 0, 95 | }, 96 | ); 97 | 98 | // Setup a delegated account 99 | let payer_alt = Keypair::from_bytes(&ON_CURVE_KEYPAIR).unwrap(); 100 | program_test.add_account( 101 | payer_alt.pubkey(), 102 | Account { 103 | lamports: 10 * LAMPORTS_PER_SOL, 104 | data: vec![], 105 | owner: dlp::id(), 106 | executable: false, 107 | rent_epoch: 0, 108 | }, 109 | ); 110 | 111 | // Setup the delegated record PDA 112 | let delegation_record_data = 113 | get_delegation_record_on_curve_data(validator_keypair.pubkey(), Some(LAMPORTS_PER_SOL)); 114 | program_test.add_account( 115 | delegation_record_pda_from_delegated_account(&payer_alt.pubkey()), 116 | Account { 117 | lamports: LAMPORTS_PER_SOL, 118 | data: delegation_record_data, 119 | owner: dlp::id(), 120 | executable: false, 121 | rent_epoch: 0, 122 | }, 123 | ); 124 | 125 | // Setup the delegated account metadata PDA 126 | let delegation_metadata_data = 127 | get_delegation_metadata_data_on_curve(validator_keypair.pubkey(), None); 128 | program_test.add_account( 129 | delegation_metadata_pda_from_delegated_account(&payer_alt.pubkey()), 130 | Account { 131 | lamports: Rent::default().minimum_balance(delegation_metadata_data.len()), 132 | data: delegation_metadata_data, 133 | owner: dlp::id(), 134 | executable: false, 135 | rent_epoch: 0, 136 | }, 137 | ); 138 | 139 | // Setup the validator fees vault 140 | program_test.add_account( 141 | validator_fees_vault_pda_from_validator(&validator_keypair.pubkey()), 142 | Account { 143 | lamports: LAMPORTS_PER_SOL, 144 | data: vec![], 145 | owner: dlp::id(), 146 | executable: false, 147 | rent_epoch: 0, 148 | }, 149 | ); 150 | 151 | let (banks, _, blockhash) = program_test.start().await; 152 | (banks, payer_alt, validator_keypair, blockhash) 153 | } 154 | -------------------------------------------------------------------------------- /tests/test_commit_state.rs: -------------------------------------------------------------------------------- 1 | use dlp::args::CommitStateArgs; 2 | use dlp::pda::{ 3 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 4 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 5 | validator_fees_vault_pda_from_validator, 6 | }; 7 | use dlp::state::{CommitRecord, DelegationMetadata}; 8 | use solana_program::rent::Rent; 9 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 10 | use solana_program_test::{processor, BanksClient, ProgramTest}; 11 | use solana_sdk::{ 12 | account::Account, 13 | signature::{Keypair, Signer}, 14 | transaction::Transaction, 15 | }; 16 | 17 | use crate::fixtures::{ 18 | get_delegation_metadata_data, get_delegation_record_data, DELEGATED_PDA_ID, 19 | DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, 20 | }; 21 | 22 | mod fixtures; 23 | 24 | #[tokio::test] 25 | async fn test_commit_new_state() { 26 | // Setup 27 | let (banks, _, authority, blockhash) = setup_program_test_env().await; 28 | let new_state = vec![0, 1, 2, 9, 9, 9, 6, 7, 8, 9]; 29 | 30 | let new_account_balance = 1_000_000; 31 | let commit_args = CommitStateArgs { 32 | data: new_state.clone(), 33 | slot: 100, 34 | allow_undelegation: true, 35 | lamports: new_account_balance, 36 | }; 37 | 38 | // Commit the state for the delegated account 39 | let ix = dlp::instruction_builder::commit_state( 40 | authority.pubkey(), 41 | DELEGATED_PDA_ID, 42 | DELEGATED_PDA_OWNER_ID, 43 | commit_args, 44 | ); 45 | let tx = Transaction::new_signed_with_payer( 46 | &[ix], 47 | Some(&authority.pubkey()), 48 | &[&authority], 49 | blockhash, 50 | ); 51 | let res = banks.process_transaction(tx).await; 52 | println!("{:?}", res); 53 | assert!(res.is_ok()); 54 | 55 | // Assert the state commitment was created and contains the new state 56 | let commit_state_pda = commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID); 57 | let commit_state_account = banks.get_account(commit_state_pda).await.unwrap().unwrap(); 58 | assert_eq!(commit_state_account.data, new_state.clone()); 59 | 60 | // Check that the commit has enough collateral to finalize the proposed state diff 61 | let delegated_account = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 62 | assert!(new_account_balance < commit_state_account.lamports + delegated_account.lamports); 63 | 64 | // Assert the record about the commitment exists 65 | let commit_record_pda = commit_record_pda_from_delegated_account(&DELEGATED_PDA_ID); 66 | let commit_record_account = banks.get_account(commit_record_pda).await.unwrap().unwrap(); 67 | let commit_record = 68 | CommitRecord::try_from_bytes_with_discriminator(&commit_record_account.data).unwrap(); 69 | assert_eq!(commit_record.account, DELEGATED_PDA_ID); 70 | assert_eq!(commit_record.identity, authority.pubkey()); 71 | assert_eq!(commit_record.slot, 100); 72 | 73 | let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID); 74 | let delegation_metadata_account = banks 75 | .get_account(delegation_metadata_pda) 76 | .await 77 | .unwrap() 78 | .unwrap(); 79 | let delegation_metadata = 80 | DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata_account.data) 81 | .unwrap(); 82 | assert!(delegation_metadata.is_undelegatable); 83 | } 84 | 85 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 86 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 87 | program_test.prefer_bpf(true); 88 | 89 | let validator_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 90 | 91 | program_test.add_account( 92 | validator_keypair.pubkey(), 93 | Account { 94 | lamports: 10 * LAMPORTS_PER_SOL, 95 | data: vec![], 96 | owner: system_program::id(), 97 | executable: false, 98 | rent_epoch: 0, 99 | }, 100 | ); 101 | 102 | // Setup a delegated PDA 103 | program_test.add_account( 104 | DELEGATED_PDA_ID, 105 | Account { 106 | lamports: LAMPORTS_PER_SOL, 107 | data: vec![], 108 | owner: dlp::id(), 109 | executable: false, 110 | rent_epoch: 0, 111 | }, 112 | ); 113 | 114 | // Setup the delegated account metadata PDA 115 | let delegation_metadata_data = get_delegation_metadata_data(validator_keypair.pubkey(), None); 116 | program_test.add_account( 117 | delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID), 118 | Account { 119 | lamports: Rent::default().minimum_balance(delegation_metadata_data.len()), 120 | data: delegation_metadata_data, 121 | owner: dlp::id(), 122 | executable: false, 123 | rent_epoch: 0, 124 | }, 125 | ); 126 | 127 | // Setup the delegated record PDA 128 | let delegation_record_data = get_delegation_record_data(validator_keypair.pubkey(), None); 129 | program_test.add_account( 130 | delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 131 | Account { 132 | lamports: Rent::default().minimum_balance(delegation_record_data.len()), 133 | data: delegation_record_data, 134 | owner: dlp::id(), 135 | executable: false, 136 | rent_epoch: 0, 137 | }, 138 | ); 139 | 140 | // Setup the validator fees vault 141 | program_test.add_account( 142 | validator_fees_vault_pda_from_validator(&validator_keypair.pubkey()), 143 | Account { 144 | lamports: LAMPORTS_PER_SOL, 145 | data: vec![], 146 | owner: dlp::id(), 147 | executable: false, 148 | rent_epoch: 0, 149 | }, 150 | ); 151 | 152 | let (banks, payer, blockhash) = program_test.start().await; 153 | (banks, payer, validator_keypair, blockhash) 154 | } 155 | -------------------------------------------------------------------------------- /tests/test_commit_state_from_buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::fixtures::{ 2 | get_delegation_metadata_data, get_delegation_record_data, DELEGATED_PDA_ID, 3 | DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, 4 | }; 5 | use dlp::args::CommitStateFromBufferArgs; 6 | use dlp::pda::{ 7 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 8 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 9 | validator_fees_vault_pda_from_validator, 10 | }; 11 | use dlp::state::{CommitRecord, DelegationMetadata}; 12 | use solana_program::rent::Rent; 13 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 14 | use solana_program_test::{processor, BanksClient, ProgramTest}; 15 | use solana_sdk::pubkey::Pubkey; 16 | use solana_sdk::{ 17 | account::Account, 18 | signature::{Keypair, Signer}, 19 | transaction::Transaction, 20 | }; 21 | 22 | mod fixtures; 23 | 24 | const NEW_STATE: [u8; 10] = [0, 1, 2, 9, 9, 9, 6, 7, 8, 9]; 25 | 26 | #[tokio::test] 27 | async fn test_commit_new_state_from_buffer() { 28 | // Setup 29 | let (banks, _, authority, blockhash) = setup_program_test_env().await; 30 | let new_account_balance = 1_000_000; 31 | let state_buffer_pda = Pubkey::find_program_address(&[b"state_buffer"], &authority.pubkey()).0; 32 | 33 | let commit_args = CommitStateFromBufferArgs { 34 | slot: 100, 35 | allow_undelegation: true, 36 | lamports: new_account_balance, 37 | }; 38 | 39 | // Commit the state for the delegated account 40 | let ix = dlp::instruction_builder::commit_state_from_buffer( 41 | authority.pubkey(), 42 | DELEGATED_PDA_ID, 43 | DELEGATED_PDA_OWNER_ID, 44 | state_buffer_pda, 45 | commit_args, 46 | ); 47 | let tx = Transaction::new_signed_with_payer( 48 | &[ix], 49 | Some(&authority.pubkey()), 50 | &[&authority], 51 | blockhash, 52 | ); 53 | let res = banks.process_transaction(tx).await; 54 | println!("{:?}", res); 55 | assert!(res.is_ok()); 56 | 57 | // Assert the state commitment was created and contains the new state 58 | let commit_state_pda = commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID); 59 | let commit_state_account = banks.get_account(commit_state_pda).await.unwrap().unwrap(); 60 | assert_eq!(commit_state_account.data, NEW_STATE.to_vec()); 61 | 62 | // Check that the commit has enough collateral to finalize the proposed state diff 63 | let delegated_account = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 64 | assert!(new_account_balance < commit_state_account.lamports + delegated_account.lamports); 65 | 66 | // Assert the record about the commitment exists 67 | let commit_record_pda = commit_record_pda_from_delegated_account(&DELEGATED_PDA_ID); 68 | let commit_record_account = banks.get_account(commit_record_pda).await.unwrap().unwrap(); 69 | let commit_record = 70 | CommitRecord::try_from_bytes_with_discriminator(&commit_record_account.data).unwrap(); 71 | assert_eq!(commit_record.account, DELEGATED_PDA_ID); 72 | assert_eq!(commit_record.identity, authority.pubkey()); 73 | assert_eq!(commit_record.slot, 100); 74 | 75 | let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID); 76 | let delegation_metadata_account = banks 77 | .get_account(delegation_metadata_pda) 78 | .await 79 | .unwrap() 80 | .unwrap(); 81 | let delegation_metadata = 82 | DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata_account.data) 83 | .unwrap(); 84 | assert!(delegation_metadata.is_undelegatable); 85 | } 86 | 87 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 88 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 89 | program_test.prefer_bpf(true); 90 | 91 | let validator_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 92 | 93 | program_test.add_account( 94 | validator_keypair.pubkey(), 95 | Account { 96 | lamports: 10 * LAMPORTS_PER_SOL, 97 | data: vec![], 98 | owner: system_program::id(), 99 | executable: false, 100 | rent_epoch: 0, 101 | }, 102 | ); 103 | 104 | // Setup a delegated PDA 105 | program_test.add_account( 106 | DELEGATED_PDA_ID, 107 | Account { 108 | lamports: LAMPORTS_PER_SOL, 109 | data: vec![], 110 | owner: dlp::id(), 111 | executable: false, 112 | rent_epoch: 0, 113 | }, 114 | ); 115 | 116 | // Setup the delegated account metadata PDA 117 | let delegation_metadata_data = get_delegation_metadata_data(validator_keypair.pubkey(), None); 118 | program_test.add_account( 119 | delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID), 120 | Account { 121 | lamports: Rent::default().minimum_balance(delegation_metadata_data.len()), 122 | data: delegation_metadata_data, 123 | owner: dlp::id(), 124 | executable: false, 125 | rent_epoch: 0, 126 | }, 127 | ); 128 | 129 | // Setup the delegated record PDA 130 | let delegation_record_data = get_delegation_record_data(validator_keypair.pubkey(), None); 131 | program_test.add_account( 132 | delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 133 | Account { 134 | lamports: Rent::default().minimum_balance(delegation_record_data.len()), 135 | data: delegation_record_data, 136 | owner: dlp::id(), 137 | executable: false, 138 | rent_epoch: 0, 139 | }, 140 | ); 141 | 142 | // Setup the validator fees vault 143 | program_test.add_account( 144 | validator_fees_vault_pda_from_validator(&validator_keypair.pubkey()), 145 | Account { 146 | lamports: LAMPORTS_PER_SOL, 147 | data: vec![], 148 | owner: dlp::id(), 149 | executable: false, 150 | rent_epoch: 0, 151 | }, 152 | ); 153 | 154 | // Setup a state buffer account 155 | program_test.add_account( 156 | Pubkey::find_program_address(&[b"state_buffer"], &validator_keypair.pubkey()).0, 157 | Account { 158 | lamports: LAMPORTS_PER_SOL, 159 | data: NEW_STATE.to_vec(), 160 | owner: dlp::id(), 161 | executable: false, 162 | rent_epoch: 0, 163 | }, 164 | ); 165 | 166 | let (banks, payer, blockhash) = program_test.start().await; 167 | (banks, payer, validator_keypair, blockhash) 168 | } 169 | -------------------------------------------------------------------------------- /tests/test_commit_state_with_program_config.rs: -------------------------------------------------------------------------------- 1 | use dlp::args::CommitStateArgs; 2 | use dlp::pda::{ 3 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 4 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 5 | program_config_from_program_id, validator_fees_vault_pda_from_validator, 6 | }; 7 | use dlp::state::{CommitRecord, DelegationMetadata}; 8 | use fixtures::create_program_config_data; 9 | use solana_program::rent::Rent; 10 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 11 | use solana_program_test::{processor, BanksClient, ProgramTest}; 12 | use solana_sdk::{ 13 | account::Account, 14 | signature::{Keypair, Signer}, 15 | transaction::Transaction, 16 | }; 17 | 18 | use crate::fixtures::{ 19 | get_delegation_metadata_data, get_delegation_record_data, DELEGATED_PDA_ID, 20 | DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, 21 | }; 22 | 23 | mod fixtures; 24 | 25 | #[tokio::test] 26 | async fn test_commit_new_state_valid_config() { 27 | test_commit_new_state(true).await 28 | } 29 | 30 | #[tokio::test] 31 | async fn test_commit_new_state_invalid_config() { 32 | test_commit_new_state(false).await 33 | } 34 | 35 | async fn test_commit_new_state(valid_config: bool) { 36 | // Setup 37 | let (banks, _, authority, blockhash) = setup_program_test_env(valid_config).await; 38 | let new_state = vec![0, 1, 2, 9, 9, 9, 6, 7, 8, 9]; 39 | 40 | let new_account_balance = 1_000_000; 41 | let commit_args = CommitStateArgs { 42 | data: new_state.clone(), 43 | slot: 100, 44 | allow_undelegation: true, 45 | lamports: new_account_balance, 46 | }; 47 | 48 | // Commit the state for the delegated account 49 | let ix = dlp::instruction_builder::commit_state( 50 | authority.pubkey(), 51 | DELEGATED_PDA_ID, 52 | DELEGATED_PDA_OWNER_ID, 53 | commit_args, 54 | ); 55 | let tx = Transaction::new_signed_with_payer( 56 | &[ix], 57 | Some(&authority.pubkey()), 58 | &[&authority], 59 | blockhash, 60 | ); 61 | let res = banks.process_transaction(tx).await; 62 | println!("{:?}", res); 63 | if !valid_config { 64 | assert!(res.is_err()) 65 | } else { 66 | assert!(res.is_ok()); 67 | 68 | // Assert the state commitment was created and contains the new state 69 | let commit_state_pda = commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID); 70 | let commit_state_account = banks.get_account(commit_state_pda).await.unwrap().unwrap(); 71 | assert_eq!(commit_state_account.data, new_state.clone()); 72 | 73 | // Check that the commit has enough collateral to finalize the proposed state diff 74 | let delegated_account = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 75 | assert!(new_account_balance < commit_state_account.lamports + delegated_account.lamports); 76 | 77 | // Assert the record about the commitment exists 78 | let commit_record_pda = commit_record_pda_from_delegated_account(&DELEGATED_PDA_ID); 79 | let commit_record_account = banks.get_account(commit_record_pda).await.unwrap().unwrap(); 80 | let commit_record = 81 | CommitRecord::try_from_bytes_with_discriminator(&commit_record_account.data).unwrap(); 82 | assert_eq!(commit_record.account, DELEGATED_PDA_ID); 83 | assert_eq!(commit_record.identity, authority.pubkey()); 84 | assert_eq!(commit_record.slot, 100); 85 | 86 | let delegation_metadata_pda = 87 | delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID); 88 | let delegation_metadata_account = banks 89 | .get_account(delegation_metadata_pda) 90 | .await 91 | .unwrap() 92 | .unwrap(); 93 | let delegation_metadata = DelegationMetadata::try_from_bytes_with_discriminator( 94 | &delegation_metadata_account.data, 95 | ) 96 | .unwrap(); 97 | assert!(delegation_metadata.is_undelegatable); 98 | } 99 | } 100 | 101 | async fn setup_program_test_env(valid_config: bool) -> (BanksClient, Keypair, Keypair, Hash) { 102 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 103 | program_test.prefer_bpf(true); 104 | 105 | let validator_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 106 | 107 | program_test.add_account( 108 | validator_keypair.pubkey(), 109 | Account { 110 | lamports: 10 * LAMPORTS_PER_SOL, 111 | data: vec![], 112 | owner: system_program::id(), 113 | executable: false, 114 | rent_epoch: 0, 115 | }, 116 | ); 117 | 118 | // Setup a delegated PDA 119 | program_test.add_account( 120 | DELEGATED_PDA_ID, 121 | Account { 122 | lamports: LAMPORTS_PER_SOL, 123 | data: vec![], 124 | owner: dlp::id(), 125 | executable: false, 126 | rent_epoch: 0, 127 | }, 128 | ); 129 | 130 | // Setup the delegated account metadata PDA 131 | let delegation_metadata_data = get_delegation_metadata_data(validator_keypair.pubkey(), None); 132 | program_test.add_account( 133 | delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID), 134 | Account { 135 | lamports: Rent::default().minimum_balance(delegation_metadata_data.len()), 136 | data: delegation_metadata_data, 137 | owner: dlp::id(), 138 | executable: false, 139 | rent_epoch: 0, 140 | }, 141 | ); 142 | 143 | // Setup the delegated record PDA 144 | let delegation_record_data = get_delegation_record_data(validator_keypair.pubkey(), None); 145 | program_test.add_account( 146 | delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 147 | Account { 148 | lamports: Rent::default().minimum_balance(delegation_record_data.len()), 149 | data: delegation_record_data, 150 | owner: dlp::id(), 151 | executable: false, 152 | rent_epoch: 0, 153 | }, 154 | ); 155 | 156 | // Setup the validator fees vault 157 | program_test.add_account( 158 | validator_fees_vault_pda_from_validator(&validator_keypair.pubkey()), 159 | Account { 160 | lamports: LAMPORTS_PER_SOL, 161 | data: vec![], 162 | owner: dlp::id(), 163 | executable: false, 164 | rent_epoch: 0, 165 | }, 166 | ); 167 | 168 | // Setup the program config 169 | let program_config_data = create_program_config_data(if valid_config { 170 | validator_keypair.pubkey() 171 | } else { 172 | Keypair::new().pubkey() 173 | }); 174 | program_test.add_account( 175 | program_config_from_program_id(&DELEGATED_PDA_OWNER_ID), 176 | Account { 177 | lamports: Rent::default().minimum_balance(program_config_data.len()), 178 | data: program_config_data, 179 | owner: dlp::id(), 180 | executable: false, 181 | rent_epoch: 0, 182 | }, 183 | ); 184 | 185 | let (banks, payer, blockhash) = program_test.start().await; 186 | (banks, payer, validator_keypair, blockhash) 187 | } 188 | -------------------------------------------------------------------------------- /tests/test_delegate.rs: -------------------------------------------------------------------------------- 1 | use solana_program::pubkey::Pubkey; 2 | use solana_program::rent::Rent; 3 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 4 | use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; 5 | use solana_sdk::instruction::{AccountMeta, Instruction}; 6 | use solana_sdk::{ 7 | account::Account, 8 | signature::{Keypair, Signer}, 9 | transaction::Transaction, 10 | }; 11 | 12 | use dlp::pda::{ 13 | delegate_buffer_pda_from_delegated_account_and_owner_program, 14 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 15 | }; 16 | use dlp::state::DelegationRecord; 17 | 18 | use crate::fixtures::{ 19 | DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, EXTERNAL_DELEGATE_INSTRUCTION_DISCRIMINATOR, 20 | }; 21 | 22 | mod fixtures; 23 | 24 | #[tokio::test] 25 | async fn test_delegate() { 26 | // Setup 27 | let (banks, payer, _, blockhash) = setup_program_test_env().await; 28 | 29 | // Save the PDA before delegation 30 | let pda_before_delegation = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 31 | let pda_data_before_delegation = pda_before_delegation.data.clone(); 32 | 33 | // Submit the delegate tx 34 | let ix = delegate_from_wrapper_program(payer.pubkey(), DELEGATED_PDA_ID); 35 | 36 | let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); 37 | let res = banks.process_transaction(tx).await; 38 | 39 | println!("{:?}", res); 40 | assert!(res.is_ok()); 41 | 42 | // Assert the buffer was closed 43 | let delegate_buffer_pda = delegate_buffer_pda_from_delegated_account_and_owner_program( 44 | &DELEGATED_PDA_ID, 45 | &DELEGATED_PDA_OWNER_ID, 46 | ); 47 | let buffer_account = banks.get_account(delegate_buffer_pda).await.unwrap(); 48 | assert!(buffer_account.is_none()); 49 | 50 | // Assert the PDA was delegated => owner is set to the delegation program 51 | let pda_account = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 52 | assert!(pda_account.owner.eq(&dlp::id())); 53 | 54 | // Assert the PDA data was not changed 55 | assert_eq!(pda_data_before_delegation, pda_account.data); 56 | 57 | // Assert that the PDA seeds account exists 58 | let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID); 59 | let delegation_metadata_account = banks 60 | .get_account(delegation_metadata_pda) 61 | .await 62 | .unwrap() 63 | .unwrap(); 64 | assert!(delegation_metadata_account.owner.eq(&dlp::id())); 65 | 66 | // Assert that the delegation record exists and can be parsed 67 | let delegation_record = banks 68 | .get_account(delegation_record_pda_from_delegated_account( 69 | &DELEGATED_PDA_ID, 70 | )) 71 | .await 72 | .unwrap() 73 | .unwrap(); 74 | let delegation_record = 75 | DelegationRecord::try_from_bytes_with_discriminator(&delegation_record.data).unwrap(); 76 | assert_eq!(delegation_record.owner, DELEGATED_PDA_OWNER_ID); 77 | } 78 | 79 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 80 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 81 | program_test.prefer_bpf(true); 82 | let payer_alt = Keypair::new(); 83 | 84 | program_test.add_account( 85 | payer_alt.pubkey(), 86 | Account { 87 | lamports: LAMPORTS_PER_SOL, 88 | data: vec![], 89 | owner: system_program::id(), 90 | executable: false, 91 | rent_epoch: 0, 92 | }, 93 | ); 94 | 95 | // Setup a PDA 96 | program_test.add_account( 97 | DELEGATED_PDA_ID, 98 | Account { 99 | lamports: LAMPORTS_PER_SOL, 100 | data: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 101 | owner: DELEGATED_PDA_OWNER_ID, 102 | executable: false, 103 | rent_epoch: 0, 104 | }, 105 | ); 106 | 107 | // Setup program to test delegation 108 | let data = read_file("tests/buffers/test_delegation.so"); 109 | program_test.add_account( 110 | DELEGATED_PDA_OWNER_ID, 111 | Account { 112 | lamports: Rent::default().minimum_balance(data.len()).max(1), 113 | data, 114 | owner: solana_sdk::bpf_loader::id(), 115 | executable: true, 116 | rent_epoch: 0, 117 | }, 118 | ); 119 | 120 | let (banks, payer, blockhash) = program_test.start().await; 121 | (banks, payer, payer_alt, blockhash) 122 | } 123 | 124 | /// Builds a delegate instruction for the test program 125 | fn delegate_from_wrapper_program(payer: Pubkey, delegated_account: Pubkey) -> Instruction { 126 | let delegate_buffer_pda = delegate_buffer_pda_from_delegated_account_and_owner_program( 127 | &delegated_account, 128 | &DELEGATED_PDA_OWNER_ID, 129 | ); 130 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account); 131 | let delegation_metadata_pda = 132 | delegation_metadata_pda_from_delegated_account(&delegated_account); 133 | Instruction { 134 | program_id: DELEGATED_PDA_OWNER_ID, 135 | accounts: vec![ 136 | AccountMeta::new(payer, true), 137 | AccountMeta::new(delegate_buffer_pda, false), 138 | AccountMeta::new(delegation_record_pda, false), 139 | AccountMeta::new(delegation_metadata_pda, false), 140 | AccountMeta::new(delegated_account, false), 141 | AccountMeta::new_readonly(DELEGATED_PDA_OWNER_ID, false), 142 | AccountMeta::new_readonly(dlp::id(), false), 143 | AccountMeta::new_readonly(system_program::id(), false), 144 | ], 145 | data: EXTERNAL_DELEGATE_INSTRUCTION_DISCRIMINATOR.to_vec(), 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/test_delegate_on_curve.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 2 | use solana_program_test::{processor, BanksClient, ProgramTest}; 3 | use solana_sdk::{ 4 | account::Account, 5 | signature::{Keypair, Signer}, 6 | transaction::Transaction, 7 | }; 8 | 9 | use crate::fixtures::ON_CURVE_KEYPAIR; 10 | use dlp::args::DelegateArgs; 11 | use dlp::pda::{ 12 | delegate_buffer_pda_from_delegated_account_and_owner_program, 13 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 14 | }; 15 | use dlp::state::{DelegationMetadata, DelegationRecord}; 16 | 17 | mod fixtures; 18 | 19 | #[tokio::test] 20 | async fn test_delegate_on_curve() { 21 | // Setup 22 | let (banks, payer, alt_payer, blockhash) = setup_program_test_env().await; 23 | 24 | // Save the PDA before delegation 25 | let delegated_account = alt_payer.pubkey(); 26 | 27 | // Create transaction to change the owner of alt_payer 28 | let change_owner_ix = 29 | solana_program::system_instruction::assign(&alt_payer.pubkey(), &dlp::id()); 30 | 31 | let change_owner_tx = Transaction::new_signed_with_payer( 32 | &[change_owner_ix], 33 | Some(&alt_payer.pubkey()), 34 | &[&alt_payer], 35 | blockhash, 36 | ); 37 | 38 | // Process the transaction 39 | let change_owner_res = banks.process_transaction(change_owner_tx).await; 40 | assert!(change_owner_res.is_ok()); 41 | 42 | // Verify the owner change 43 | let updated_alt_payer_account = banks 44 | .get_account(alt_payer.pubkey()) 45 | .await 46 | .unwrap() 47 | .unwrap(); 48 | assert_eq!(updated_alt_payer_account.owner, dlp::id()); 49 | 50 | // Submit the delegate tx 51 | let ix = dlp::instruction_builder::delegate( 52 | payer.pubkey(), 53 | delegated_account, 54 | None, 55 | DelegateArgs { 56 | commit_frequency_ms: u32::MAX, 57 | seeds: vec![], 58 | validator: Some(alt_payer.pubkey()), 59 | }, 60 | ); 61 | 62 | let tx = Transaction::new_signed_with_payer( 63 | &[ix], 64 | Some(&payer.pubkey()), 65 | &[&payer, &alt_payer], 66 | blockhash, 67 | ); 68 | let res = banks.process_transaction(tx).await; 69 | 70 | println!("{:?}", res); 71 | assert!(res.is_ok()); 72 | 73 | // Assert the buffer doesn't exist 74 | let delegate_buffer_pda = delegate_buffer_pda_from_delegated_account_and_owner_program( 75 | &delegated_account, 76 | &system_program::id(), 77 | ); 78 | let buffer_account = banks.get_account(delegate_buffer_pda).await.unwrap(); 79 | assert!(buffer_account.is_none()); 80 | 81 | // Assert the PDA was delegated => owner is set to the delegation program 82 | let pda_account = banks.get_account(delegated_account).await.unwrap().unwrap(); 83 | assert!(pda_account.owner.eq(&dlp::id())); 84 | 85 | // Assert that the PDA seeds account exists 86 | let delegation_metadata_pda = 87 | delegation_metadata_pda_from_delegated_account(&delegated_account); 88 | let delegation_metadata_account = banks 89 | .get_account(delegation_metadata_pda) 90 | .await 91 | .unwrap() 92 | .unwrap(); 93 | assert!(delegation_metadata_account.owner.eq(&dlp::id())); 94 | 95 | // Assert that the delegation record exists and can be parsed 96 | let delegation_record_account = banks 97 | .get_account(delegation_record_pda_from_delegated_account( 98 | &delegated_account, 99 | )) 100 | .await 101 | .unwrap() 102 | .unwrap(); 103 | let delegation_record = 104 | DelegationRecord::try_from_bytes_with_discriminator(&delegation_record_account.data) 105 | .unwrap(); 106 | assert_eq!(delegation_record.owner, system_program::id()); 107 | assert_eq!(delegation_record.authority, alt_payer.pubkey()); 108 | 109 | // Assert that the delegation metadata exists and can be parsed 110 | let delegation_metadata = banks 111 | .get_account(delegation_metadata_pda_from_delegated_account( 112 | &delegated_account, 113 | )) 114 | .await 115 | .unwrap() 116 | .unwrap(); 117 | assert!(delegation_metadata.owner.eq(&dlp::id())); 118 | let delegation_metadata = 119 | DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata.data).unwrap(); 120 | assert!(!delegation_metadata.is_undelegatable); 121 | } 122 | 123 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 124 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 125 | program_test.prefer_bpf(true); 126 | let payer_alt = Keypair::from_bytes(&ON_CURVE_KEYPAIR).unwrap(); 127 | 128 | program_test.add_account( 129 | payer_alt.pubkey(), 130 | Account { 131 | lamports: LAMPORTS_PER_SOL, 132 | data: vec![], 133 | owner: system_program::id(), 134 | executable: false, 135 | rent_epoch: 0, 136 | }, 137 | ); 138 | 139 | let (banks, payer, blockhash) = program_test.start().await; 140 | (banks, payer, payer_alt, blockhash) 141 | } 142 | -------------------------------------------------------------------------------- /tests/test_finalize.rs: -------------------------------------------------------------------------------- 1 | use crate::fixtures::{ 2 | get_commit_record_account_data, get_delegation_metadata_data, get_delegation_record_data, 3 | COMMIT_NEW_STATE_ACCOUNT_DATA, DELEGATED_PDA_ID, TEST_AUTHORITY, 4 | }; 5 | use dlp::pda::{ 6 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 7 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 8 | validator_fees_vault_pda_from_validator, 9 | }; 10 | use dlp::state::{CommitRecord, DelegationMetadata}; 11 | use solana_program::rent::Rent; 12 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 13 | use solana_program_test::{processor, BanksClient, ProgramTest}; 14 | use solana_sdk::{ 15 | account::Account, 16 | signature::{Keypair, Signer}, 17 | transaction::Transaction, 18 | }; 19 | 20 | mod fixtures; 21 | 22 | #[tokio::test] 23 | async fn test_finalize() { 24 | // Setup 25 | let (banks, _, authority, blockhash) = setup_program_test_env().await; 26 | 27 | // Retrieve the accounts 28 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID); 29 | let commit_state_pda = commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID); 30 | let commit_record_pda = commit_record_pda_from_delegated_account(&DELEGATED_PDA_ID); 31 | 32 | // Commit state record data 33 | let commit_record = banks.get_account(commit_record_pda).await.unwrap().unwrap(); 34 | let commit_record = 35 | CommitRecord::try_from_bytes_with_discriminator(&commit_record.data).unwrap(); 36 | 37 | // Save the new state data before finalizing 38 | let new_state_before_finalize = banks.get_account(commit_state_pda).await.unwrap().unwrap(); 39 | let new_state_data_before_finalize = new_state_before_finalize.data.clone(); 40 | 41 | // Submit the finalize tx 42 | let ix = dlp::instruction_builder::finalize(authority.pubkey(), DELEGATED_PDA_ID); 43 | let tx = Transaction::new_signed_with_payer( 44 | &[ix], 45 | Some(&authority.pubkey()), 46 | &[&authority], 47 | blockhash, 48 | ); 49 | let res = banks.process_transaction(tx).await; 50 | println!("{:?}", res); 51 | assert!(res.is_ok()); 52 | 53 | // Assert the state_diff was closed 54 | let commit_state_account = banks.get_account(commit_state_pda).await.unwrap(); 55 | assert!(commit_state_account.is_none()); 56 | 57 | // Assert the delegation_record was not closed 58 | let delegation_record = banks.get_account(delegation_record_pda).await.unwrap(); 59 | assert!(delegation_record.is_some()); 60 | 61 | // Assert the commit_record_pda was closed 62 | let commit_record_account = banks.get_account(commit_record_pda).await.unwrap(); 63 | assert!(commit_record_account.is_none()); 64 | 65 | // Assert that the account owner is still the delegation program 66 | let pda_account = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 67 | assert!(pda_account.owner.eq(&dlp::id())); 68 | 69 | // Assert the delegated account contains the data from the new state 70 | assert_eq!(new_state_data_before_finalize, pda_account.data); 71 | 72 | // Assert the delegation metadata contains the correct slot of the commitment 73 | let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID); 74 | let delegation_metadata_account = banks 75 | .get_account(delegation_metadata_pda) 76 | .await 77 | .unwrap() 78 | .unwrap(); 79 | let delegation_metadata = 80 | DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata_account.data) 81 | .unwrap(); 82 | assert_eq!( 83 | commit_record.slot, 84 | delegation_metadata.last_update_external_slot 85 | ); 86 | } 87 | 88 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 89 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 90 | program_test.prefer_bpf(true); 91 | 92 | let authority = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 93 | 94 | program_test.add_account( 95 | authority.pubkey(), 96 | Account { 97 | lamports: LAMPORTS_PER_SOL, 98 | data: vec![], 99 | owner: system_program::id(), 100 | executable: false, 101 | rent_epoch: 0, 102 | }, 103 | ); 104 | 105 | // Setup a delegated PDA 106 | program_test.add_account( 107 | DELEGATED_PDA_ID, 108 | Account { 109 | lamports: LAMPORTS_PER_SOL, 110 | data: vec![], 111 | owner: dlp::id(), 112 | executable: false, 113 | rent_epoch: 0, 114 | }, 115 | ); 116 | 117 | // Setup the delegation record PDA 118 | let delegation_record_data = get_delegation_record_data(authority.pubkey(), None); 119 | program_test.add_account( 120 | delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 121 | Account { 122 | lamports: Rent::default().minimum_balance(delegation_record_data.len()), 123 | data: delegation_record_data.clone(), 124 | owner: dlp::id(), 125 | executable: false, 126 | rent_epoch: 0, 127 | }, 128 | ); 129 | 130 | // Setup the delegated account metadata PDA 131 | let delegation_metadata_data = get_delegation_metadata_data(authority.pubkey(), None); 132 | program_test.add_account( 133 | delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID), 134 | Account { 135 | lamports: Rent::default().minimum_balance(delegation_metadata_data.len()), 136 | data: delegation_metadata_data, 137 | owner: dlp::id(), 138 | executable: false, 139 | rent_epoch: 0, 140 | }, 141 | ); 142 | 143 | // Setup the commit state PDA 144 | program_test.add_account( 145 | commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID), 146 | Account { 147 | lamports: LAMPORTS_PER_SOL, 148 | data: COMMIT_NEW_STATE_ACCOUNT_DATA.into(), 149 | owner: dlp::id(), 150 | executable: false, 151 | rent_epoch: 0, 152 | }, 153 | ); 154 | 155 | let commit_record_data = get_commit_record_account_data(authority.pubkey()); 156 | program_test.add_account( 157 | commit_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 158 | Account { 159 | lamports: Rent::default().minimum_balance(commit_record_data.len()), 160 | data: commit_record_data, 161 | owner: dlp::id(), 162 | executable: false, 163 | rent_epoch: 0, 164 | }, 165 | ); 166 | 167 | // Setup the validator fees vault 168 | program_test.add_account( 169 | validator_fees_vault_pda_from_validator(&authority.pubkey()), 170 | Account { 171 | lamports: LAMPORTS_PER_SOL, 172 | data: vec![], 173 | owner: dlp::id(), 174 | executable: false, 175 | rent_epoch: 0, 176 | }, 177 | ); 178 | 179 | let (banks, payer, blockhash) = program_test.start().await; 180 | (banks, payer, authority, blockhash) 181 | } 182 | -------------------------------------------------------------------------------- /tests/test_init_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use dlp::pda::fees_vault_pda; 2 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 3 | use solana_program_test::{processor, BanksClient, ProgramTest}; 4 | use solana_sdk::{ 5 | account::Account, 6 | signature::{Keypair, Signer}, 7 | transaction::Transaction, 8 | }; 9 | 10 | mod fixtures; 11 | 12 | #[tokio::test] 13 | async fn test_init_fees_vault() { 14 | // Setup 15 | let (banks, payer, _, blockhash) = setup_program_test_env().await; 16 | 17 | let ix = dlp::instruction_builder::init_protocol_fees_vault(payer.pubkey()); 18 | let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); 19 | let res = banks.process_transaction(tx).await; 20 | assert!(res.is_ok()); 21 | 22 | // Assert the fees vault was created 23 | let fees_vault_pda = fees_vault_pda(); 24 | let fees_vault_account = banks.get_account(fees_vault_pda).await.unwrap(); 25 | assert!(fees_vault_account.is_some()); 26 | } 27 | 28 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 29 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 30 | program_test.prefer_bpf(true); 31 | let payer_alt = Keypair::new(); 32 | 33 | program_test.add_account( 34 | payer_alt.pubkey(), 35 | Account { 36 | lamports: LAMPORTS_PER_SOL, 37 | data: vec![], 38 | owner: system_program::id(), 39 | executable: false, 40 | rent_epoch: 0, 41 | }, 42 | ); 43 | 44 | let (banks, payer, blockhash) = program_test.start().await; 45 | (banks, payer, payer_alt, blockhash) 46 | } 47 | -------------------------------------------------------------------------------- /tests/test_init_validator_fees_vault.rs: -------------------------------------------------------------------------------- 1 | use crate::fixtures::TEST_AUTHORITY; 2 | use dlp::pda::validator_fees_vault_pda_from_validator; 3 | use solana_program::pubkey::Pubkey; 4 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 5 | use solana_program_test::{processor, BanksClient, ProgramTest}; 6 | use solana_sdk::{ 7 | account::Account, 8 | signature::{Keypair, Signer}, 9 | transaction::Transaction, 10 | }; 11 | 12 | mod fixtures; 13 | 14 | #[tokio::test] 15 | async fn test_init_validator_fees_vault() { 16 | // Setup 17 | let (banks, payer, admin, blockhash) = setup_program_test_env().await; 18 | 19 | let validator_identity = Pubkey::new_unique(); 20 | let ix = dlp::instruction_builder::init_validator_fees_vault( 21 | payer.pubkey(), 22 | admin.pubkey(), 23 | validator_identity, 24 | ); 25 | let tx = Transaction::new_signed_with_payer( 26 | &[ix], 27 | Some(&payer.pubkey()), 28 | &[&payer, &admin], 29 | blockhash, 30 | ); 31 | let res = banks.process_transaction(tx).await; 32 | assert!(res.is_ok()); 33 | 34 | // Assert the fees vault was created successfully 35 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator_identity); 36 | let validator_fees_vault_account = banks.get_account(validator_fees_vault_pda).await.unwrap(); 37 | assert!(validator_fees_vault_account.is_some()); 38 | 39 | // Assert record cannot be created if the admin is not the correct one 40 | let validator_identity = Pubkey::new_unique(); 41 | let ix = dlp::instruction_builder::init_validator_fees_vault( 42 | payer.pubkey(), 43 | payer.pubkey(), 44 | validator_identity, 45 | ); 46 | let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); 47 | let res = banks.process_transaction(tx).await; 48 | assert!(res.is_err()); 49 | } 50 | 51 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 52 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 53 | program_test.prefer_bpf(true); 54 | 55 | let admin_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 56 | 57 | program_test.add_account( 58 | admin_keypair.pubkey(), 59 | Account { 60 | lamports: LAMPORTS_PER_SOL, 61 | data: vec![], 62 | owner: system_program::id(), 63 | executable: false, 64 | rent_epoch: 0, 65 | }, 66 | ); 67 | 68 | let (banks, payer, blockhash) = program_test.start().await; 69 | (banks, payer, admin_keypair, blockhash) 70 | } 71 | -------------------------------------------------------------------------------- /tests/test_protocol_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use crate::fixtures::TEST_AUTHORITY; 2 | use dlp::pda::fees_vault_pda; 3 | use solana_program::rent::Rent; 4 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 5 | use solana_program_test::{processor, BanksClient, ProgramTest}; 6 | use solana_sdk::{ 7 | account::Account, 8 | signature::{Keypair, Signer}, 9 | transaction::Transaction, 10 | }; 11 | 12 | mod fixtures; 13 | 14 | #[tokio::test] 15 | async fn test_protocol_claim_fees() { 16 | // Setup 17 | let (banks, payer, admin, blockhash) = setup_program_test_env().await; 18 | 19 | let fees_vault_pda = fees_vault_pda(); 20 | 21 | // Submit the claim fees tx 22 | let ix = dlp::instruction_builder::protocol_claim_fees(admin.pubkey()); 23 | let tx = Transaction::new_signed_with_payer( 24 | &[ix], 25 | Some(&payer.pubkey()), 26 | &[&payer, &admin], 27 | blockhash, 28 | ); 29 | let res = banks.process_transaction(tx).await; 30 | assert!(res.is_ok()); 31 | 32 | // Assert that fees vault now only have the rent exemption amount 33 | let fees_vault_account = banks.get_account(fees_vault_pda).await.unwrap(); 34 | assert!(fees_vault_account.is_some()); 35 | assert_eq!( 36 | fees_vault_account.unwrap().lamports, 37 | Rent::default().minimum_balance(8) 38 | ); 39 | 40 | // Assert that the admin account now has the fees 41 | let admin_account = banks.get_account(admin.pubkey()).await.unwrap(); 42 | assert_eq!( 43 | admin_account.unwrap().lamports, 44 | LAMPORTS_PER_SOL * 2 - Rent::default().minimum_balance(8) 45 | ); 46 | } 47 | 48 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 49 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 50 | program_test.prefer_bpf(true); 51 | 52 | let admin_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 53 | 54 | program_test.add_account( 55 | admin_keypair.pubkey(), 56 | Account { 57 | lamports: LAMPORTS_PER_SOL, 58 | data: vec![], 59 | owner: system_program::id(), 60 | executable: false, 61 | rent_epoch: 0, 62 | }, 63 | ); 64 | 65 | // Setup the fees vault account 66 | program_test.add_account( 67 | fees_vault_pda(), 68 | Account { 69 | lamports: LAMPORTS_PER_SOL, 70 | data: vec![], 71 | owner: dlp::id(), 72 | executable: false, 73 | rent_epoch: 0, 74 | }, 75 | ); 76 | 77 | let (banks, payer, blockhash) = program_test.start().await; 78 | (banks, payer, admin_keypair, blockhash) 79 | } 80 | -------------------------------------------------------------------------------- /tests/test_undelegate.rs: -------------------------------------------------------------------------------- 1 | use dlp::pda::{ 2 | commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, 3 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 4 | fees_vault_pda, validator_fees_vault_pda_from_validator, 5 | }; 6 | use solana_program::rent::Rent; 7 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 8 | use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; 9 | use solana_sdk::{ 10 | account::Account, 11 | signature::{Keypair, Signer}, 12 | transaction::Transaction, 13 | }; 14 | 15 | use crate::fixtures::{ 16 | get_commit_record_account_data, get_delegation_metadata_data, get_delegation_record_data, 17 | COMMIT_NEW_STATE_ACCOUNT_DATA, DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, 18 | }; 19 | 20 | mod fixtures; 21 | 22 | #[tokio::test] 23 | async fn test_finalize_and_undelegate() { 24 | // Setup 25 | let (banks, _, authority, blockhash) = setup_program_test_env().await; 26 | 27 | // Retrieve the accounts 28 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID); 29 | let commit_state_pda = commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID); 30 | 31 | // Save the new state data before undelegating 32 | let new_state_before_finalize = banks.get_account(commit_state_pda).await.unwrap().unwrap(); 33 | let new_state_data_before_finalize = new_state_before_finalize.data.clone(); 34 | 35 | // Create the finalize tx 36 | let ix_finalize = dlp::instruction_builder::finalize(authority.pubkey(), DELEGATED_PDA_ID); 37 | 38 | // Create the undelegate tx 39 | let ix_undelegate = dlp::instruction_builder::undelegate( 40 | authority.pubkey(), 41 | DELEGATED_PDA_ID, 42 | DELEGATED_PDA_OWNER_ID, 43 | authority.pubkey(), 44 | ); 45 | 46 | // Submit the transaction 47 | let tx = Transaction::new_signed_with_payer( 48 | &[ix_finalize, ix_undelegate], 49 | Some(&authority.pubkey()), 50 | &[&authority], 51 | blockhash, 52 | ); 53 | let res = banks.process_transaction(tx).await; 54 | println!("{:?}", res); 55 | assert!(res.is_ok()); 56 | 57 | // Assert the state_diff was closed 58 | let commit_state_account = banks.get_account(commit_state_pda).await.unwrap(); 59 | assert!(commit_state_account.is_none()); 60 | 61 | // Assert the delegation_record_pda was closed 62 | let delegation_record_account = banks.get_account(delegation_record_pda).await.unwrap(); 63 | assert!(delegation_record_account.is_none()); 64 | 65 | // Assert the delegated account seeds pda was closed 66 | let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID); 67 | let delegation_metadata_account = banks.get_account(delegation_metadata_pda).await.unwrap(); 68 | assert!(delegation_metadata_account.is_none()); 69 | 70 | // Assert that the account owner is now set to the owner program 71 | let pda_account = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 72 | assert!(pda_account.owner.eq(&DELEGATED_PDA_OWNER_ID)); 73 | 74 | // Assert the delegated account contains the data from the new state 75 | assert_eq!(new_state_data_before_finalize, pda_account.data); 76 | } 77 | 78 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 79 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 80 | program_test.prefer_bpf(true); 81 | let authority = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 82 | 83 | program_test.add_account( 84 | authority.pubkey(), 85 | Account { 86 | lamports: LAMPORTS_PER_SOL, 87 | data: vec![], 88 | owner: system_program::id(), 89 | executable: false, 90 | rent_epoch: 0, 91 | }, 92 | ); 93 | 94 | // Setup a delegated PDA 95 | program_test.add_account( 96 | DELEGATED_PDA_ID, 97 | Account { 98 | lamports: LAMPORTS_PER_SOL, 99 | data: vec![], 100 | owner: dlp::id(), 101 | executable: false, 102 | rent_epoch: 0, 103 | }, 104 | ); 105 | 106 | // Setup the delegated record PDA 107 | let delegation_record_data = get_delegation_record_data(authority.pubkey(), None); 108 | program_test.add_account( 109 | delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 110 | Account { 111 | lamports: Rent::default().minimum_balance(delegation_record_data.len()), 112 | data: delegation_record_data, 113 | owner: dlp::id(), 114 | executable: false, 115 | rent_epoch: 0, 116 | }, 117 | ); 118 | 119 | // Setup the delegated metadata PDA 120 | let delegation_metadata_data = get_delegation_metadata_data(authority.pubkey(), Some(true)); 121 | program_test.add_account( 122 | delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID), 123 | Account { 124 | lamports: Rent::default().minimum_balance(delegation_metadata_data.len()), 125 | data: delegation_metadata_data, 126 | owner: dlp::id(), 127 | executable: false, 128 | rent_epoch: 0, 129 | }, 130 | ); 131 | 132 | // Setup the committed state PDA 133 | program_test.add_account( 134 | commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID), 135 | Account { 136 | lamports: LAMPORTS_PER_SOL, 137 | data: COMMIT_NEW_STATE_ACCOUNT_DATA.into(), 138 | owner: dlp::id(), 139 | executable: false, 140 | rent_epoch: 0, 141 | }, 142 | ); 143 | 144 | // Setup the commit state record PDA 145 | let commit_record_data = get_commit_record_account_data(authority.pubkey()); 146 | program_test.add_account( 147 | commit_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 148 | Account { 149 | lamports: Rent::default().minimum_balance(commit_record_data.len()), 150 | data: commit_record_data, 151 | owner: dlp::id(), 152 | executable: false, 153 | rent_epoch: 0, 154 | }, 155 | ); 156 | 157 | // Setup program to test undelegation 158 | let data = read_file("tests/buffers/test_delegation.so"); 159 | program_test.add_account( 160 | DELEGATED_PDA_OWNER_ID, 161 | Account { 162 | lamports: Rent::default().minimum_balance(data.len()), 163 | data, 164 | owner: solana_sdk::bpf_loader::id(), 165 | executable: true, 166 | rent_epoch: 0, 167 | }, 168 | ); 169 | 170 | // Setup the protocol fees vault 171 | program_test.add_account( 172 | fees_vault_pda(), 173 | Account { 174 | lamports: Rent::default().minimum_balance(0), 175 | data: vec![], 176 | owner: dlp::id(), 177 | executable: false, 178 | rent_epoch: 0, 179 | }, 180 | ); 181 | 182 | // Setup the validator fees vault 183 | program_test.add_account( 184 | validator_fees_vault_pda_from_validator(&authority.pubkey()), 185 | Account { 186 | lamports: LAMPORTS_PER_SOL, 187 | data: vec![], 188 | owner: dlp::id(), 189 | executable: false, 190 | rent_epoch: 0, 191 | }, 192 | ); 193 | 194 | let (banks, payer, blockhash) = program_test.start().await; 195 | (banks, payer, authority, blockhash) 196 | } 197 | -------------------------------------------------------------------------------- /tests/test_undelegate_on_curve.rs: -------------------------------------------------------------------------------- 1 | use dlp::pda::{ 2 | delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, 3 | fees_vault_pda, validator_fees_vault_pda_from_validator, 4 | }; 5 | use solana_program::rent::Rent; 6 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 7 | use solana_program_test::{processor, BanksClient, ProgramTest}; 8 | use solana_sdk::{ 9 | account::Account, 10 | signature::{Keypair, Signer}, 11 | transaction::Transaction, 12 | }; 13 | 14 | use crate::fixtures::{ 15 | get_delegation_metadata_data_on_curve, get_delegation_record_on_curve_data, ON_CURVE_KEYPAIR, 16 | TEST_AUTHORITY, 17 | }; 18 | 19 | mod fixtures; 20 | 21 | #[tokio::test] 22 | async fn test_undelegate_on_curve() { 23 | // Setup 24 | let (banks, validator, delegated_on_curve, blockhash) = setup_program_test_env().await; 25 | 26 | // Retrieve the accounts 27 | let delegation_record_pda = 28 | delegation_record_pda_from_delegated_account(&delegated_on_curve.pubkey()); 29 | 30 | // Submit the undelegate tx 31 | let ix = dlp::instruction_builder::undelegate( 32 | validator.pubkey(), 33 | delegated_on_curve.pubkey(), 34 | system_program::id(), 35 | validator.pubkey(), 36 | ); 37 | let tx = Transaction::new_signed_with_payer( 38 | &[ix], 39 | Some(&validator.pubkey()), 40 | &[&validator], 41 | blockhash, 42 | ); 43 | let res = banks.process_transaction(tx).await; 44 | println!("{:?}", res); 45 | assert!(res.is_ok()); 46 | 47 | // Assert the delegation_record_pda was closed 48 | let delegation_record_account = banks.get_account(delegation_record_pda).await.unwrap(); 49 | assert!(delegation_record_account.is_none()); 50 | 51 | // Assert the delegated metadata account pda was closed 52 | let delegation_metadata_pda = 53 | delegation_metadata_pda_from_delegated_account(&delegated_on_curve.pubkey()); 54 | let delegation_metadata_account = banks.get_account(delegation_metadata_pda).await.unwrap(); 55 | assert!(delegation_metadata_account.is_none()); 56 | 57 | // Assert that the account owner is now set to the system program 58 | let pda_account = banks 59 | .get_account(delegated_on_curve.pubkey()) 60 | .await 61 | .unwrap() 62 | .unwrap(); 63 | assert!(pda_account.owner.eq(&system_program::id())); 64 | } 65 | 66 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 67 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 68 | program_test.prefer_bpf(true); 69 | let validator = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 70 | let payer_alt = Keypair::from_bytes(&ON_CURVE_KEYPAIR).unwrap(); 71 | 72 | // Setup a delegated on curve account 73 | program_test.add_account( 74 | payer_alt.pubkey(), 75 | Account { 76 | lamports: LAMPORTS_PER_SOL, 77 | data: vec![], 78 | owner: dlp::id(), 79 | executable: false, 80 | rent_epoch: 0, 81 | }, 82 | ); 83 | 84 | // Setup the delegated record PDA 85 | let delegation_record_data = 86 | get_delegation_record_on_curve_data(payer_alt.pubkey(), Some(LAMPORTS_PER_SOL)); 87 | program_test.add_account( 88 | delegation_record_pda_from_delegated_account(&payer_alt.pubkey()), 89 | Account { 90 | lamports: LAMPORTS_PER_SOL, 91 | data: delegation_record_data, 92 | owner: dlp::id(), 93 | executable: false, 94 | rent_epoch: 0, 95 | }, 96 | ); 97 | 98 | // Setup the delegated account metadata PDA 99 | let delegation_metadata_data = 100 | get_delegation_metadata_data_on_curve(validator.pubkey(), Some(true)); 101 | program_test.add_account( 102 | delegation_metadata_pda_from_delegated_account(&payer_alt.pubkey()), 103 | Account { 104 | lamports: LAMPORTS_PER_SOL, 105 | data: delegation_metadata_data, 106 | owner: dlp::id(), 107 | executable: false, 108 | rent_epoch: 0, 109 | }, 110 | ); 111 | 112 | // Setup the validator keypair 113 | program_test.add_account( 114 | validator.pubkey(), 115 | Account { 116 | lamports: LAMPORTS_PER_SOL, 117 | data: vec![], 118 | owner: system_program::id(), 119 | executable: false, 120 | rent_epoch: 0, 121 | }, 122 | ); 123 | 124 | // Setup the protocol fees vault 125 | program_test.add_account( 126 | fees_vault_pda(), 127 | Account { 128 | lamports: Rent::default().minimum_balance(0), 129 | data: vec![], 130 | owner: dlp::id(), 131 | executable: false, 132 | rent_epoch: 0, 133 | }, 134 | ); 135 | 136 | // Setup the validator fees vault 137 | program_test.add_account( 138 | validator_fees_vault_pda_from_validator(&validator.pubkey()), 139 | Account { 140 | lamports: LAMPORTS_PER_SOL, 141 | data: vec![], 142 | owner: dlp::id(), 143 | executable: false, 144 | rent_epoch: 0, 145 | }, 146 | ); 147 | 148 | let (banks, _, blockhash) = program_test.start().await; 149 | (banks, validator, payer_alt, blockhash) 150 | } 151 | -------------------------------------------------------------------------------- /tests/test_undelegate_without_commit.rs: -------------------------------------------------------------------------------- 1 | use dlp::pda::{ 2 | commit_state_pda_from_delegated_account, delegation_metadata_pda_from_delegated_account, 3 | delegation_record_pda_from_delegated_account, fees_vault_pda, 4 | validator_fees_vault_pda_from_validator, 5 | }; 6 | use solana_program::rent::Rent; 7 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 8 | use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; 9 | use solana_sdk::{ 10 | account::Account, 11 | signature::{Keypair, Signer}, 12 | transaction::Transaction, 13 | }; 14 | 15 | use crate::fixtures::{ 16 | get_delegation_metadata_data, get_delegation_record_data, DELEGATED_PDA_ID, 17 | DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, 18 | }; 19 | 20 | mod fixtures; 21 | 22 | #[tokio::test] 23 | async fn test_undelegate_without_commit() { 24 | // Setup 25 | let (banks, _, validator, blockhash) = setup_program_test_env().await; 26 | 27 | // Retrieve the accounts 28 | let delegation_record_pda = delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID); 29 | let commit_state_pda = commit_state_pda_from_delegated_account(&DELEGATED_PDA_ID); 30 | 31 | // Save the new state data before undelegating 32 | let delegated_pda_state_before_undelegation = 33 | banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 34 | let new_state_data_before_finalize = delegated_pda_state_before_undelegation.data.clone(); 35 | 36 | // Submit the undelegate tx 37 | let ix = dlp::instruction_builder::undelegate( 38 | validator.pubkey(), 39 | DELEGATED_PDA_ID, 40 | DELEGATED_PDA_OWNER_ID, 41 | validator.pubkey(), 42 | ); 43 | let tx = Transaction::new_signed_with_payer( 44 | &[ix], 45 | Some(&validator.pubkey()), 46 | &[&validator], 47 | blockhash, 48 | ); 49 | let res = banks.process_transaction(tx).await; 50 | println!("{:?}", res); 51 | assert!(res.is_ok()); 52 | 53 | // Assert the state_diff was closed 54 | let commit_state_account = banks.get_account(commit_state_pda).await.unwrap(); 55 | assert!(commit_state_account.is_none()); 56 | 57 | // Assert the delegation_record_pda was closed 58 | let delegation_record_account = banks.get_account(delegation_record_pda).await.unwrap(); 59 | assert!(delegation_record_account.is_none()); 60 | 61 | // Assert the delegation_record_pda was closed 62 | let delegation_record_account = banks.get_account(delegation_record_pda).await.unwrap(); 63 | assert!(delegation_record_account.is_none()); 64 | 65 | // Assert the delegated account seeds pda was closed 66 | let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID); 67 | let delegation_metadata_account = banks.get_account(delegation_metadata_pda).await.unwrap(); 68 | assert!(delegation_metadata_account.is_none()); 69 | 70 | // Assert that the account owner is now set to the owner program 71 | let pda_account = banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); 72 | assert!(pda_account.owner.eq(&DELEGATED_PDA_OWNER_ID)); 73 | 74 | // Assert the delegated account contains the data from the new state 75 | assert_eq!(new_state_data_before_finalize, pda_account.data); 76 | } 77 | 78 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 79 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 80 | program_test.prefer_bpf(true); 81 | let validator = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 82 | 83 | program_test.add_account( 84 | validator.pubkey(), 85 | Account { 86 | lamports: LAMPORTS_PER_SOL, 87 | data: vec![], 88 | owner: system_program::id(), 89 | executable: false, 90 | rent_epoch: 0, 91 | }, 92 | ); 93 | 94 | // Setup a delegated PDA 95 | program_test.add_account( 96 | DELEGATED_PDA_ID, 97 | Account { 98 | lamports: LAMPORTS_PER_SOL, 99 | data: vec![], 100 | owner: dlp::id(), 101 | executable: false, 102 | rent_epoch: 0, 103 | }, 104 | ); 105 | 106 | // Setup the delegated record PDA 107 | let delegation_record_data = get_delegation_record_data(validator.pubkey(), None); 108 | program_test.add_account( 109 | delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID), 110 | Account { 111 | lamports: Rent::default().minimum_balance(delegation_record_data.len()), 112 | data: delegation_record_data, 113 | owner: dlp::id(), 114 | executable: false, 115 | rent_epoch: 0, 116 | }, 117 | ); 118 | 119 | // Setup the delegated account metadata PDA 120 | let delegation_metadata_data = get_delegation_metadata_data(validator.pubkey(), Some(true)); 121 | program_test.add_account( 122 | delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID), 123 | Account { 124 | lamports: Rent::default().minimum_balance(delegation_metadata_data.len()), 125 | data: delegation_metadata_data, 126 | owner: dlp::id(), 127 | executable: false, 128 | rent_epoch: 0, 129 | }, 130 | ); 131 | 132 | // Setup program to test undelegation 133 | let data = read_file("tests/buffers/test_delegation.so"); 134 | program_test.add_account( 135 | DELEGATED_PDA_OWNER_ID, 136 | Account { 137 | lamports: Rent::default().minimum_balance(data.len()).max(1), 138 | data, 139 | owner: solana_sdk::bpf_loader::id(), 140 | executable: true, 141 | rent_epoch: 0, 142 | }, 143 | ); 144 | 145 | // Setup the protocol fees vault 146 | program_test.add_account( 147 | fees_vault_pda(), 148 | Account { 149 | lamports: Rent::default().minimum_balance(0), 150 | data: vec![], 151 | owner: dlp::id(), 152 | executable: false, 153 | rent_epoch: 0, 154 | }, 155 | ); 156 | 157 | // Setup the validator fees vault 158 | program_test.add_account( 159 | validator_fees_vault_pda_from_validator(&validator.pubkey()), 160 | Account { 161 | lamports: LAMPORTS_PER_SOL, 162 | data: vec![], 163 | owner: dlp::id(), 164 | executable: false, 165 | rent_epoch: 0, 166 | }, 167 | ); 168 | 169 | let (banks, payer, blockhash) = program_test.start().await; 170 | (banks, payer, validator, blockhash) 171 | } 172 | -------------------------------------------------------------------------------- /tests/test_validator_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use crate::fixtures::TEST_AUTHORITY; 2 | use dlp::consts::PROTOCOL_FEES_PERCENTAGE; 3 | use dlp::pda::{fees_vault_pda, validator_fees_vault_pda_from_validator}; 4 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 5 | use solana_program_test::{processor, BanksClient, ProgramTest}; 6 | use solana_sdk::{ 7 | account::Account, 8 | signature::{Keypair, Signer}, 9 | transaction::Transaction, 10 | }; 11 | 12 | mod fixtures; 13 | 14 | #[tokio::test] 15 | async fn test_validator_claim_fees() { 16 | // Setup 17 | let (banks, payer, validator, blockhash) = setup_program_test_env().await; 18 | 19 | let fees_vault_pda = fees_vault_pda(); 20 | let fees_vault_init_lamports = banks 21 | .get_account(fees_vault_pda) 22 | .await 23 | .unwrap() 24 | .unwrap() 25 | .lamports; 26 | 27 | let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator.pubkey()); 28 | let validator_fees_vault_init_lamports = banks 29 | .get_account(validator_fees_vault_pda) 30 | .await 31 | .unwrap() 32 | .unwrap() 33 | .lamports; 34 | 35 | let validator_init_lamports = banks 36 | .get_account(validator.pubkey()) 37 | .await 38 | .unwrap() 39 | .unwrap() 40 | .lamports; 41 | 42 | // Submit the withdrawal tx 43 | let withdrawal_amount = 100000; 44 | let ix = 45 | dlp::instruction_builder::validator_claim_fees(validator.pubkey(), Some(withdrawal_amount)); 46 | let tx = Transaction::new_signed_with_payer( 47 | &[ix], 48 | Some(&payer.pubkey()), 49 | &[&payer, &validator], 50 | blockhash, 51 | ); 52 | let res = banks.process_transaction(tx).await; 53 | assert!(res.is_ok()); 54 | 55 | // Assert the validator fees vault now has less lamports 56 | let validator_fees_vault_account = banks.get_account(validator_fees_vault_pda).await.unwrap(); 57 | assert!(validator_fees_vault_account.is_some()); 58 | assert_eq!( 59 | validator_fees_vault_account.unwrap().lamports, 60 | validator_fees_vault_init_lamports - withdrawal_amount 61 | ); 62 | 63 | // Assert the fees vault now has prev lamports + fees 64 | let protocol_fees = (withdrawal_amount * u64::from(PROTOCOL_FEES_PERCENTAGE)) / 100; 65 | let fees_vault_account = banks.get_account(fees_vault_pda).await.unwrap(); 66 | assert!(fees_vault_account.is_some()); 67 | assert_eq!( 68 | fees_vault_account.unwrap().lamports, 69 | fees_vault_init_lamports + protocol_fees 70 | ); 71 | 72 | let claim_amount = withdrawal_amount.saturating_sub(protocol_fees); 73 | let validator_account = banks.get_account(validator.pubkey()).await.unwrap(); 74 | assert_eq!( 75 | validator_account.unwrap().lamports, 76 | validator_init_lamports + claim_amount 77 | ); 78 | } 79 | 80 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 81 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 82 | program_test.prefer_bpf(true); 83 | let validator = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 84 | 85 | program_test.add_account( 86 | validator.pubkey(), 87 | Account { 88 | lamports: LAMPORTS_PER_SOL, 89 | data: vec![], 90 | owner: system_program::id(), 91 | executable: false, 92 | rent_epoch: 0, 93 | }, 94 | ); 95 | 96 | // Setup the fees vault account 97 | program_test.add_account( 98 | fees_vault_pda(), 99 | Account { 100 | lamports: LAMPORTS_PER_SOL, 101 | data: vec![], 102 | owner: dlp::id(), 103 | executable: false, 104 | rent_epoch: 0, 105 | }, 106 | ); 107 | 108 | // Setup the validator fees vault 109 | program_test.add_account( 110 | validator_fees_vault_pda_from_validator(&validator.pubkey()), 111 | Account { 112 | lamports: LAMPORTS_PER_SOL, 113 | data: vec![], 114 | owner: dlp::id(), 115 | executable: false, 116 | rent_epoch: 0, 117 | }, 118 | ); 119 | 120 | let (banks, payer, blockhash) = program_test.start().await; 121 | (banks, payer, validator, blockhash) 122 | } 123 | -------------------------------------------------------------------------------- /tests/test_whitelist_validator_for_program.rs: -------------------------------------------------------------------------------- 1 | use crate::fixtures::{DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY}; 2 | use dlp::pda::program_config_from_program_id; 3 | use dlp::state::ProgramConfig; 4 | use solana_program::rent::Rent; 5 | use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; 6 | use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; 7 | use solana_sdk::{ 8 | account::Account, 9 | signature::{Keypair, Signer}, 10 | transaction::Transaction, 11 | }; 12 | 13 | mod fixtures; 14 | 15 | #[tokio::test] 16 | async fn test_whitelist_validator_for_program() { 17 | // Setup 18 | let (banks, _, validator, blockhash) = setup_program_test_env().await; 19 | 20 | let ix = dlp::instruction_builder::whitelist_validator_for_program( 21 | validator.pubkey(), 22 | validator.pubkey(), 23 | DELEGATED_PDA_OWNER_ID, 24 | true, 25 | ); 26 | let tx = Transaction::new_signed_with_payer( 27 | &[ix], 28 | Some(&validator.pubkey()), 29 | &[&validator], 30 | blockhash, 31 | ); 32 | let res = banks.process_transaction(tx).await; 33 | println!("{:?}", res); 34 | assert!(res.is_ok()); 35 | 36 | // Check that the validator is whitelisted 37 | let program_config_account = banks 38 | .get_account(program_config_from_program_id(&DELEGATED_PDA_OWNER_ID)) 39 | .await; 40 | let program_config = ProgramConfig::try_from_bytes_with_discriminator( 41 | &program_config_account.unwrap().unwrap().data, 42 | ) 43 | .unwrap(); 44 | assert!(program_config 45 | .approved_validators 46 | .contains(&validator.pubkey())); 47 | } 48 | 49 | #[tokio::test] 50 | async fn test_remove_validator_for_program() { 51 | // Setup 52 | let (banks, _, validator, blockhash) = setup_program_test_env().await; 53 | 54 | let ix = dlp::instruction_builder::whitelist_validator_for_program( 55 | validator.pubkey(), 56 | validator.pubkey(), 57 | DELEGATED_PDA_OWNER_ID, 58 | true, 59 | ); 60 | let tx = Transaction::new_signed_with_payer( 61 | &[ix], 62 | Some(&validator.pubkey()), 63 | &[&validator], 64 | blockhash, 65 | ); 66 | let res = banks.process_transaction(tx).await; 67 | println!("{:?}", res); 68 | assert!(res.is_ok()); 69 | 70 | // Remove the validator 71 | let ix = dlp::instruction_builder::whitelist_validator_for_program( 72 | validator.pubkey(), 73 | validator.pubkey(), 74 | DELEGATED_PDA_OWNER_ID, 75 | false, 76 | ); 77 | let tx = Transaction::new_signed_with_payer( 78 | &[ix], 79 | Some(&validator.pubkey()), 80 | &[&validator], 81 | blockhash, 82 | ); 83 | let res = banks.process_transaction(tx).await; 84 | println!("{:?}", res); 85 | assert!(res.is_ok()); 86 | 87 | // Check that the validator is NOT whitelisted 88 | let program_config_account = banks 89 | .get_account(program_config_from_program_id(&DELEGATED_PDA_OWNER_ID)) 90 | .await; 91 | let program_config = ProgramConfig::try_from_bytes_with_discriminator( 92 | &program_config_account.unwrap().unwrap().data, 93 | ) 94 | .unwrap(); 95 | assert!(!program_config 96 | .approved_validators 97 | .contains(&validator.pubkey())); 98 | } 99 | 100 | async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { 101 | let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); 102 | program_test.prefer_bpf(true); 103 | let validator = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); 104 | 105 | program_test.add_account( 106 | validator.pubkey(), 107 | Account { 108 | lamports: LAMPORTS_PER_SOL, 109 | data: vec![], 110 | owner: system_program::id(), 111 | executable: false, 112 | rent_epoch: 0, 113 | }, 114 | ); 115 | 116 | // Setup program to test undelegation 117 | let data = read_file("tests/buffers/test_delegation.so"); 118 | program_test.add_account( 119 | DELEGATED_PDA_OWNER_ID, 120 | Account { 121 | lamports: Rent::default().minimum_balance(data.len()), 122 | data, 123 | owner: solana_sdk::bpf_loader::id(), 124 | executable: true, 125 | rent_epoch: 0, 126 | }, 127 | ); 128 | 129 | let (banks, payer, blockhash) = program_test.start().await; 130 | (banks, payer, validator, blockhash) 131 | } 132 | --------------------------------------------------------------------------------