├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── api ├── Cargo.toml └── src │ ├── consts.rs │ ├── cpis.rs │ ├── cvm │ ├── account │ │ ├── mod.rs │ │ ├── nonce.rs │ │ ├── relay.rs │ │ ├── timelock.rs │ │ └── virtual_account.rs │ ├── messages │ │ ├── airdrop.rs │ │ ├── mod.rs │ │ ├── transfer.rs │ │ └── withdraw.rs │ ├── mod.rs │ ├── pool.rs │ └── state │ │ ├── memory.rs │ │ ├── mod.rs │ │ ├── relay.rs │ │ ├── storage.rs │ │ ├── unlock.rs │ │ ├── vm.rs │ │ └── withdraw.rs │ ├── external │ ├── mod.rs │ ├── splitter.rs │ └── timelock.rs │ ├── helpers.rs │ ├── instruction.rs │ ├── lib.rs │ ├── opcode.rs │ ├── pdas.rs │ ├── sdk.rs │ ├── state.rs │ ├── types │ ├── circular_buffer.rs │ ├── hash.rs │ ├── merkle_tree.rs │ ├── mod.rs │ ├── signature.rs │ └── slice_allocator.rs │ └── utils │ ├── hash.rs │ ├── mod.rs │ └── signature.rs ├── docs ├── .gitkeep └── code_audit_final.pdf ├── idl ├── Makefile ├── README.md ├── code_vm.accounts.hexpat ├── code_vm.instructions.hexpat ├── code_vm.json └── src │ ├── .gitignore │ ├── .prettierignore │ ├── Anchor.toml │ ├── Cargo.lock │ ├── Cargo.toml │ ├── anchor-cli │ ├── package-lock.json │ ├── package.json │ ├── programs │ └── code-vm │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ ├── args.rs │ │ ├── consts.rs │ │ ├── instructions.rs │ │ ├── lib.rs │ │ ├── state.rs │ │ └── types.rs │ ├── scripts │ └── update-discriminators.ts │ ├── tests │ └── vm.test.ts │ └── tsconfig.json └── program ├── Cargo.toml ├── src ├── instruction │ ├── compress.rs │ ├── decompress.rs │ ├── deposit.rs │ ├── exec.rs │ ├── init_memory.rs │ ├── init_nonce.rs │ ├── init_relay.rs │ ├── init_storage.rs │ ├── init_timelock.rs │ ├── init_unlock.rs │ ├── init_vm.rs │ ├── mod.rs │ ├── resize.rs │ ├── snapshot.rs │ ├── unlock.rs │ └── withdraw.rs ├── lib.rs ├── opcode │ ├── airdrop.rs │ ├── conditional_transfer.rs │ ├── external_relay.rs │ ├── external_transfer.rs │ ├── external_withdraw.rs │ ├── mod.rs │ ├── relay.rs │ ├── transfer.rs │ └── withdraw.rs └── security.rs └── tests ├── relay_init.rs ├── relay_save_root.rs ├── relay_transfer.rs ├── system_compress.rs ├── system_decompress.rs ├── system_nonce_init.rs ├── system_timelock_init.rs ├── timelock_deposit.rs ├── timelock_unlock.rs ├── timelock_withdraw.rs ├── utils ├── context.rs ├── mod.rs ├── state.rs └── svm.rs ├── vm_exec.rs ├── vm_exec_airdrop.rs ├── vm_init.rs ├── vm_legacy_mem.rs ├── vm_memory_init.rs ├── vm_memory_resize.rs └── vm_storage_init.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | keypair-owner.json 8 | keypair-program.json -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["api", "program"] 4 | 5 | [workspace.package] 6 | version = "0.1.0" 7 | edition = "2021" 8 | license = "Apache-2.0" 9 | homepage = "" 10 | documentation = "" 11 | respository = "" 12 | readme = "./README.md" 13 | keywords = ["solana"] 14 | 15 | [workspace.dependencies] 16 | code-vm-api = { path = "./api", version = "0.1.0" } 17 | borsh = "0.10.3" 18 | bs58 = "0.4.0" 19 | bytemuck = "1.14" 20 | num_enum = "0.7" 21 | sha2 = "0.10.8" 22 | solana-ed25519-sha512 = { version = "0.1.2" } 23 | curve25519-dalek = { version = "4.1.3", default-features = false, features = ["zeroize"] } 24 | solana-curve25519 = "2.0.13" 25 | solana-program = "1.18" 26 | spl-associated-token-account = { version = "^2.3", features = [ "no-entrypoint" ] } 27 | spl-token = { version = "^4", features = ["no-entrypoint"] } 28 | steel = { version = "1.3", features = ["spl"] } 29 | thiserror = "1.0" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code VM 2 | ![license][license-image] 3 | ![version][version-image] 4 | 5 | [version-image]: https://img.shields.io/badge/version-0.2.0-blue.svg?style=flat 6 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat 7 | 8 | The Code Virtual Machine (Code VM) is a program that runs on the Solana 9 | blockchain. It is purpose built for payments and reduces transaction fees by up 10 | to `95%` and account rent by `80%` when compared to using non-virtualized 11 | accounts. The VM operates on compact representations of accounts but can further 12 | compress dormant accounts off-chain, effectively reducing rent to `zero`. 13 | Compressed accounts can be decompressed when needed — either automatically 14 | using the Code app or manually through a public indexer. 15 | 16 | > [!NOTE] 17 | > The Code VM is designed specifically to be used by the [Code](https://getcode.com) and [Flipchat](https://flipchat.xyz/) mobile apps, prioritizing **seamless payments**. As a result, this codebase is not intended as a generalized foundation for other projects. 18 | 19 | ## Audits 20 | 21 | | Network | Address | Audited By | Status | Audit Report | Commit | 22 | | --- | --- | --- | --- | --- | --- | 23 | | Mainnet | [vmZ1WU...5YMRTJ](https://explorer.solana.com/address/vmZ1WUq8SxjBWcaeTCvgJRZbS84R61uniFsQy5YMRTJ) | [OtterSec](https://osec.io/) | Completed | [Report](https://github.com/code-payments/code-vm/blob/main/docs/code_audit_final.pdf) | [c49f95d9](https://github.com/code-payments/code-vm/commit/c49f95d92095eb5955955f1bc888e25957b2f64d) | 24 | | Devnet | [J8FLfS...Rr3L24](https://explorer.solana.com/address/J8FLfS8rqBcQ3hH8KTfQF3zBNG3r3uaG2WqfNoRr3L24?cluster=devnet) | - | - | - | - | 25 | 26 | ## Dev Milestones 27 | 28 | :white_check_mark: The on-chain program itself is largely completed. 29 | 30 | :white_check_mark: Flipchat is using the program in mainnet. 31 | 32 | :white_check_mark: OtterSec has completed an audit of the program. 33 | 34 | We’re currently integrating the VM into a fork of the Code app called Flipchat. 35 | This allows us to make changes to backend services more efficiently without 36 | disrupting Code app users. 37 | 38 | A key obejctive for us has been that the Code mobile app could be migrated 39 | to the VM with minimal effort. From the app’s perspective, it should behave 40 | as if it’s using standard Solana accounts. All the VM complexity should be 41 | abstracted away by the backend services. 42 | 43 | We’re happy to report that this key objective has been achieved on Flipchat — 44 | our mobile app team migrated payments using only a handful of minor adjustments 45 | to the original transaction mechanics. To enable this from the VM side, we made 46 | intentional trade-offs that optimize for core objectives instead of on-chain 47 | compute units. 48 | 49 | #### Release Schedule 50 | 51 | | Milestone | Status | VM | Flipchat | Code | 52 | | --- | --- | --- | --- | --- | 53 | | Anchor Version | Done |Aug 9th, 2024 | - | - | 54 | | Steel Version | Done | Oct 24th, 2024 | - | - | 55 | | Audited Release | Done | Feb 18th, 2024 | - | - | 56 | | IDLs | [Done](https://github.com/code-payments/code-vm/blob/main/idl/code_vm.json) | Oct 30th, 2024 | - | - | 57 | | Indexer | [Done](https://github.com/code-payments/code-vm-indexer) | - | Aug 15th, 2024 | tbd | 58 | | Sequencer | Done | - | November 25th, 2024 | tbd | 59 | | Explorer | tbd | | | | 60 | | Documentation | tbd | | | | 61 | 62 | 63 | ## Quick Start 64 | 65 | 1. Install Solana CLI: https://docs.solana.com/de/cli/install-solana-cli-tools 66 | 2. Open Terminal: `cargo build-sbf & cargo test-sbf -- --nocapture` 67 | 68 | 69 | ## Versions 70 | 71 | Please make sure you have the following versions installed: 72 | 73 | ```bash 74 | % rustc --version 75 | rustc 1.76.0 (07dca489a 2024-02-04) 76 | 77 | % solana --version 78 | solana-cli 1.18.9 (src:9a7dd9ca; feat:3469865029, client:SolanaLabs) 79 | ``` 80 | 81 | ## Getting Help 82 | 83 | If you have any questions or need help, please reach out to us on [Discord](https://discord.gg/T8Tpj8DBFp) or [Twitter](https://twitter.com/getcode). 84 | 85 | ## Community Feedback & Contributions 86 | 87 | While we can't guarantee that all feedback will be implemented, we are always 88 | open to hearing from the community. If you have any suggestions or feedback, 89 | please reach out to us. 90 | 91 | ## Security and Issue Disclosures 92 | 93 | In the interest of protecting the security of our users and their funds, we ask 94 | that if you discover any security vulnerabilities please report them using this 95 | [Report a Vulnerability](https://github.com/code-wallet/code-program-library/security/advisories/new) 96 | link. 97 | 98 | -------------------------------------------------------------------------------- /api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "code-vm-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bytemuck.workspace = true 8 | num_enum.workspace = true 9 | solana-program.workspace = true 10 | steel.workspace = true 11 | thiserror.workspace = true 12 | spl-token.workspace = true 13 | borsh.workspace = true 14 | bs58.workspace = true 15 | sha2.workspace = true 16 | solana-curve25519.workspace = true 17 | curve25519-dalek.workspace = true 18 | solana-ed25519-sha512.workspace = true 19 | 20 | [dev-dependencies] 21 | solana-sdk = "1.18" -------------------------------------------------------------------------------- /api/src/consts.rs: -------------------------------------------------------------------------------- 1 | pub const CODE_VM: &[u8] = b"code_vm"; 2 | pub const VM_OMNIBUS: &[u8] = b"vm_omnibus"; 3 | pub const VM_MEMORY_ACCOUNT: &[u8] = b"vm_memory_account"; 4 | pub const VM_STORAGE_ACCOUNT: &[u8] = b"vm_storage_account"; 5 | pub const VM_DURABLE_NONCE: &[u8] = b"vm_durable_nonce"; 6 | pub const VM_UNLOCK_ACCOUNT: &[u8] = b"vm_unlock_pda_account"; 7 | pub const VM_WITHDRAW_RECEIPT: &[u8] = b"vm_withdraw_receipt_account"; 8 | pub const VM_DEPOSIT_PDA: &[u8] = b"vm_deposit_pda"; 9 | pub const VM_RELAY_ACCOUNT: &[u8] = b"vm_relay_account"; 10 | pub const VM_RELAY_PROOF: &[u8] = b"vm_proof_account"; 11 | pub const VM_RELAY_VAULT: &[u8] = b"vm_relay_vault"; 12 | pub const VM_RELAY_COMMITMENT: &[u8] = b"relay_commitment"; 13 | pub const VM_TIMELOCK_STATE: &[u8] = b"timelock_state"; 14 | pub const VM_TIMELOCK_VAULT: &[u8] = b"timelock_vault"; 15 | pub const MERKLE_TREE_SEED: &[u8] = b"merkletree"; 16 | 17 | pub const MAX_NAME_LEN: usize = 32; 18 | pub const NUM_ACCOUNTS: usize = 32_000; 19 | 20 | // Some (reasonable) virtual account limits without being perscriptive 21 | pub const MIN_ACCOUNT_SIZE: usize = 32; 22 | pub const MAX_ACCOUNT_SIZE: usize = 256; 23 | pub const MAX_NUM_ACCOUNTS: usize = 320_000; 24 | 25 | pub const COMPRESSED_STATE_DEPTH: usize = 20; 26 | pub const RELAY_STATE_DEPTH: usize = 63; 27 | pub const RELAY_HISTORY_ITEMS: usize = 32; 28 | -------------------------------------------------------------------------------- /api/src/cpis.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | program_pack::Pack, 3 | system_instruction, 4 | rent::Rent, 5 | }; 6 | use steel::*; 7 | 8 | use crate::helpers::check_condition; 9 | 10 | pub fn create_token_account<'info>( 11 | mint: &AccountInfo<'info>, 12 | target: &AccountInfo<'info>, 13 | seeds: &[&[u8]], 14 | payer: &AccountInfo<'info>, 15 | system_program: &AccountInfo<'info>, 16 | rent_sysvar: &AccountInfo<'info>, 17 | ) -> ProgramResult { 18 | // Create the PDA. 19 | allocate_account( 20 | target, 21 | &spl_token::id(), 22 | spl_token::state::Account::LEN, 23 | seeds, 24 | system_program, 25 | payer, 26 | )?; 27 | 28 | // Initialize the PDA. 29 | solana_program::program::invoke_signed( 30 | &spl_token::instruction::initialize_account( 31 | &spl_token::id(), 32 | target.key, 33 | mint.key, 34 | target.key, 35 | ).unwrap(), 36 | &[ 37 | target.clone(), 38 | mint.clone(), 39 | target.clone(), 40 | rent_sysvar.clone(), 41 | ], 42 | &[seeds], 43 | ) 44 | } 45 | 46 | pub fn create_account_with_size<'a, 'info, T: Discriminator + Pod>( 47 | target_account: &'a AccountInfo<'info>, 48 | size: usize, 49 | owner: &Pubkey, 50 | seeds: &[&[u8]], 51 | system_program: &'a AccountInfo<'info>, 52 | payer: &'a AccountInfo<'info>, 53 | ) -> ProgramResult { 54 | check_condition( 55 | size >= 8 + std::mem::size_of::(), 56 | "provided size is too small", 57 | )?; 58 | 59 | // Allocate space. 60 | allocate_account( 61 | target_account, 62 | owner, 63 | size, 64 | seeds, 65 | system_program, 66 | payer, 67 | )?; 68 | 69 | // Set discriminator. 70 | let mut data = target_account.data.borrow_mut(); 71 | data[0] = T::discriminator(); 72 | 73 | Ok(()) 74 | } 75 | 76 | pub fn resize_account<'info>( 77 | target_account: &AccountInfo<'info>, 78 | payer: &AccountInfo<'info>, 79 | new_size: usize, 80 | system_program: &AccountInfo<'info>, 81 | ) -> ProgramResult { 82 | let rent = Rent::get()?; 83 | let rent_exempt_balance = rent 84 | .minimum_balance(new_size) 85 | .saturating_sub(target_account.lamports()); 86 | 87 | if rent_exempt_balance.gt(&0) { 88 | solana_program::program::invoke( 89 | &system_instruction::transfer( 90 | payer.key, 91 | target_account.key, 92 | rent_exempt_balance, 93 | ), 94 | &[ 95 | payer.clone(), 96 | target_account.clone(), 97 | system_program.clone(), 98 | ], 99 | )?; 100 | } 101 | 102 | target_account.realloc(new_size, false)?; 103 | 104 | Ok(()) 105 | } -------------------------------------------------------------------------------- /api/src/cvm/account/mod.rs: -------------------------------------------------------------------------------- 1 | mod nonce; 2 | mod relay; 3 | mod timelock; 4 | mod virtual_account; 5 | 6 | pub use nonce::*; 7 | pub use relay::*; 8 | pub use timelock::*; 9 | pub use virtual_account::*; -------------------------------------------------------------------------------- /api/src/cvm/account/nonce.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use crate::types::Hash; 4 | 5 | #[repr(C)] 6 | #[derive(BorshDeserialize, BorshSerialize, Clone, Copy, PartialEq, Debug)] 7 | pub struct VirtualDurableNonce { 8 | pub address: Pubkey, // Unlike a real durable nonce, this value is off-curve and owned by the VM authority 9 | pub value: Hash, // The current nonce value (auto-advanced when used) 10 | } 11 | 12 | impl VirtualDurableNonce { 13 | pub const LEN: usize = // 64 bytes 14 | 32 + // address 15 | 32; // nonce 16 | 17 | pub fn pack(&self, mut writer: W) -> std::io::Result<()> { 18 | BorshSerialize::serialize(self, &mut writer) 19 | } 20 | 21 | pub fn unpack(buf: &[u8]) -> std::io::Result { 22 | let data = &buf[..VirtualDurableNonce::LEN]; 23 | BorshDeserialize::try_from_slice(data) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/src/cvm/account/relay.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | #[repr(C)] 5 | #[derive(BorshDeserialize, BorshSerialize, Clone, Copy, PartialEq, Debug)] 6 | pub struct VirtualRelayAccount { 7 | pub target: Pubkey, 8 | pub destination: Pubkey, 9 | } 10 | 11 | impl VirtualRelayAccount { 12 | pub const LEN: usize = // 64 bytes 13 | 32 + // address 14 | 32; // destination 15 | 16 | pub fn pack(&self, mut writer: W) -> std::io::Result<()> { 17 | BorshSerialize::serialize(self, &mut writer) 18 | } 19 | 20 | pub fn unpack(buf: &[u8]) -> std::io::Result { 21 | let data = &buf[..VirtualRelayAccount::LEN]; 22 | BorshDeserialize::try_from_slice(data) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/src/cvm/account/timelock.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | use crate::types::Hash; 5 | use crate::pdas; 6 | 7 | #[repr(C)] 8 | #[derive(BorshDeserialize, BorshSerialize, Clone, Copy, PartialEq, Debug)] 9 | pub struct VirtualTimelockAccount { 10 | pub owner: Pubkey, // wallet owner of the derived timelock/vault addresses 11 | pub instance: Hash, // unique identifier for this virtual instance 12 | 13 | pub token_bump: u8, // bump seed for the token account (derived from the owner) 14 | pub unlock_bump: u8, // bump seed for the unlock account (derived from the owner) 15 | pub withdraw_bump: u8, // bump seed for the withdraw receipt account (derived from the instance) 16 | 17 | pub balance: u64, 18 | pub bump: u8, 19 | } 20 | 21 | impl VirtualTimelockAccount { 22 | pub const LEN: usize = // 76 bytes 23 | 32 + // owner 24 | 32 + // nonce 25 | 1 + // token_bump 26 | 1 + // unlock_bump 27 | 1 + // withdraw_bump 28 | 8 + // balance 29 | 1; // bump 30 | 31 | pub fn get_timelock_address(&self, mint: &Pubkey, authority: &Pubkey, lock_duration: u8) -> Pubkey { 32 | pdas::create_virtual_timelock_address( 33 | mint, 34 | authority, 35 | &self.owner, 36 | lock_duration, 37 | self.bump 38 | ) 39 | } 40 | 41 | pub fn get_token_address(&self, timelock: &Pubkey) -> Pubkey { 42 | pdas::create_virtual_timelock_vault_address( 43 | timelock, 44 | self.token_bump 45 | ) 46 | } 47 | 48 | pub fn get_unlock_address(&self, timelock: &Pubkey, vm: &Pubkey) -> Pubkey { 49 | pdas::create_unlock_address( 50 | &self.owner, 51 | timelock, 52 | vm, 53 | self.unlock_bump 54 | ) 55 | } 56 | 57 | pub fn get_withdraw_receipt_address(&self, unlock_pda: &Pubkey, vm: &Pubkey) -> Pubkey { 58 | pdas::create_withdraw_receipt_address( 59 | unlock_pda, 60 | &self.instance, 61 | vm, 62 | self.withdraw_bump 63 | ) 64 | } 65 | 66 | pub fn pack(&self, mut writer: W) -> std::io::Result<()> { 67 | BorshSerialize::serialize(self, &mut writer) 68 | } 69 | 70 | pub fn unpack(buf: &[u8]) -> std::io::Result { 71 | let data = &buf[..VirtualTimelockAccount::LEN]; 72 | BorshDeserialize::try_from_slice(data) 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /api/src/cvm/account/virtual_account.rs: -------------------------------------------------------------------------------- 1 | 2 | use steel::*; 3 | use borsh::{BorshDeserialize, BorshSerialize}; 4 | 5 | use crate::{utils, types::Hash}; 6 | use super::{ 7 | VirtualDurableNonce, 8 | VirtualTimelockAccount, 9 | VirtualRelayAccount, 10 | }; 11 | 12 | 13 | #[derive(BorshDeserialize, BorshSerialize, Clone, Copy, PartialEq, Debug)] 14 | pub enum VirtualAccount { 15 | Nonce(VirtualDurableNonce), 16 | Timelock(VirtualTimelockAccount), 17 | Relay(VirtualRelayAccount), 18 | } 19 | 20 | impl VirtualAccount { 21 | /// Get the size of this enum 22 | pub fn get_size(&self) -> usize { 23 | 1 + (match self { 24 | VirtualAccount::Nonce(_) => VirtualDurableNonce::LEN, 25 | VirtualAccount::Timelock(_) => VirtualTimelockAccount::LEN, 26 | VirtualAccount::Relay(_) => VirtualRelayAccount::LEN, 27 | }) 28 | } 29 | 30 | pub fn is_timelock(&self) -> bool { 31 | matches!(self, VirtualAccount::Timelock(_)) 32 | } 33 | 34 | pub fn is_relay(&self) -> bool { 35 | matches!(self, VirtualAccount::Relay(_)) 36 | } 37 | 38 | pub fn is_nonce(&self) -> bool { 39 | matches!(self, VirtualAccount::Nonce(_)) 40 | } 41 | 42 | /// Get the hash of this VirtualAccount 43 | pub fn get_hash(&self) -> Hash { 44 | utils::hash(self.pack().as_ref()) 45 | } 46 | 47 | /// Pack this VirtualAccount into a byte array 48 | pub fn pack(&self) -> Vec { 49 | // The first byte is the variant, followed by the data 50 | 51 | let mut bytes = vec![0u8; self.get_size()]; 52 | bytes[0] = match self { 53 | VirtualAccount::Nonce(_) => 0, 54 | VirtualAccount::Timelock(_) => 1, 55 | VirtualAccount::Relay(_) => 2, 56 | }; 57 | 58 | match self { 59 | VirtualAccount::Nonce(account) => { 60 | account.pack(&mut bytes[1..]).unwrap(); 61 | }, 62 | VirtualAccount::Timelock(account) => { 63 | account.pack(&mut bytes[1..]).unwrap(); 64 | }, 65 | VirtualAccount::Relay(account) => { 66 | account.pack(&mut bytes[1..]).unwrap(); 67 | }, 68 | } 69 | bytes 70 | } 71 | 72 | /// Unpack a VirtualAccount from a byte array 73 | pub fn unpack(input: &[u8]) -> Result { 74 | // The first byte is the variant, followed by the data 75 | 76 | if input.len() < 1 { 77 | return Err(ProgramError::InvalidAccountData); 78 | } 79 | 80 | let variant = input[0]; 81 | let data = &input[1..]; 82 | let size = get_varient_size(variant); 83 | 84 | if data.len() < size { 85 | return Err(ProgramError::InvalidAccountData); 86 | } 87 | 88 | match variant { 89 | 0 => Ok(VirtualAccount::Nonce( 90 | VirtualDurableNonce::unpack(&data).unwrap() 91 | )), 92 | 1 => Ok(VirtualAccount::Timelock( 93 | VirtualTimelockAccount::unpack(&data).unwrap() 94 | )), 95 | 2 => Ok(VirtualAccount::Relay( 96 | VirtualRelayAccount::unpack(&data).unwrap() 97 | )), 98 | _ => Err(ProgramError::InvalidAccountData) 99 | } 100 | } 101 | } 102 | 103 | impl VirtualAccount { 104 | pub fn into_inner_nonce(self) -> Option { 105 | if let VirtualAccount::Nonce(inner) = self { 106 | Some(inner) 107 | } else { 108 | None 109 | } 110 | } 111 | 112 | pub fn into_inner_timelock(self) -> Option { 113 | if let VirtualAccount::Timelock(inner) = self { 114 | Some(inner) 115 | } else { 116 | None 117 | } 118 | } 119 | 120 | pub fn into_inner_relay(self) -> Option { 121 | if let VirtualAccount::Relay(inner) = self { 122 | Some(inner) 123 | } else { 124 | None 125 | } 126 | } 127 | } 128 | 129 | fn get_varient_size(variant: u8) -> usize { 130 | match variant { 131 | 0 => VirtualDurableNonce::LEN, 132 | 1 => VirtualTimelockAccount::LEN, 133 | 2 => VirtualRelayAccount::LEN, 134 | _ => 0, 135 | } 136 | } -------------------------------------------------------------------------------- /api/src/cvm/messages/airdrop.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | use crate::utils; 4 | use crate::types::Hash; 5 | use crate::cvm::{ 6 | CodeVmAccount, 7 | VirtualDurableNonce, 8 | VirtualTimelockAccount 9 | }; 10 | 11 | pub fn compact_airdrop_message( 12 | src_timelock_address: &Pubkey, 13 | dst_timelock_addresses: &[Pubkey], 14 | amount: u64, 15 | vdn: &VirtualDurableNonce, 16 | ) -> Hash { 17 | let mut msg = Vec::new(); 18 | 19 | msg.push(b"airdrop" as &[u8]); 20 | msg.push(src_timelock_address.as_ref()); 21 | msg.push(vdn.address.as_ref()); 22 | msg.push(vdn.value.as_ref()); 23 | 24 | // Store the little-endian bytes in a local variable so it won't go out of scope 25 | let amount_bytes = amount.to_le_bytes(); 26 | msg.push(&amount_bytes); 27 | 28 | // Push each destination pubkey 29 | for dst_pubkey in dst_timelock_addresses { 30 | msg.push(dst_pubkey.as_ref()); 31 | } 32 | 33 | utils::hashv(&msg) 34 | } 35 | 36 | pub fn create_airdrop_message( 37 | vm: &CodeVmAccount, 38 | src_vta: &VirtualTimelockAccount, 39 | destinations: &[Pubkey], 40 | amount: u64, 41 | vdn: &VirtualDurableNonce, 42 | ) -> Hash { 43 | 44 | let src_timelock_address = src_vta.get_timelock_address( 45 | &vm.get_mint(), 46 | &vm.get_authority(), 47 | vm.get_lock_duration(), 48 | ); 49 | 50 | let src_token_address = src_vta.get_token_address( 51 | &src_timelock_address, 52 | ); 53 | 54 | compact_airdrop_message( 55 | &src_token_address, 56 | destinations, 57 | amount, 58 | vdn, 59 | ) 60 | } -------------------------------------------------------------------------------- /api/src/cvm/messages/mod.rs: -------------------------------------------------------------------------------- 1 | mod airdrop; 2 | mod transfer; 3 | mod withdraw; 4 | 5 | pub use airdrop::*; 6 | pub use transfer::*; 7 | pub use withdraw::*; -------------------------------------------------------------------------------- /api/src/cvm/messages/transfer.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | use crate::utils; 4 | use crate::types::Hash; 5 | use crate::cvm::{ 6 | CodeVmAccount, 7 | VirtualDurableNonce, 8 | VirtualTimelockAccount 9 | }; 10 | 11 | pub fn compact_transfer_message( 12 | src_timelock_address: &Pubkey, 13 | dst_timelock_address: &Pubkey, 14 | amount: u64, 15 | vdn: &VirtualDurableNonce, 16 | ) -> Hash { 17 | let message = &[ 18 | b"transfer", 19 | src_timelock_address.as_ref(), 20 | dst_timelock_address.as_ref(), 21 | &amount.to_le_bytes(), 22 | vdn.address.as_ref(), 23 | vdn.value.as_ref(), // this value is auto-advanced upon use 24 | ]; 25 | 26 | utils::hashv(message) 27 | } 28 | 29 | pub fn create_transfer_message( 30 | vm: &CodeVmAccount, 31 | src_vta: &VirtualTimelockAccount, 32 | dst_vta: &VirtualTimelockAccount, 33 | vdn: &VirtualDurableNonce, 34 | amount: u64, 35 | ) -> Hash { 36 | 37 | let src_timelock_address = src_vta.get_timelock_address( 38 | &vm.get_mint(), 39 | &vm.get_authority(), 40 | vm.get_lock_duration(), 41 | ); 42 | let src_token_address = src_vta.get_token_address( 43 | &src_timelock_address, 44 | ); 45 | 46 | let dst_timelock_address = dst_vta.get_timelock_address( 47 | &vm.get_mint(), 48 | &vm.get_authority(), 49 | vm.get_lock_duration(), 50 | ); 51 | let dst_token_address = dst_vta.get_token_address( 52 | &dst_timelock_address, 53 | ); 54 | 55 | compact_transfer_message( 56 | &src_token_address, 57 | &dst_token_address, 58 | amount, 59 | vdn, 60 | ) 61 | } 62 | 63 | pub fn create_transfer_message_to_external( 64 | vm: &CodeVmAccount, 65 | src_vta: &VirtualTimelockAccount, 66 | dst_pubkey: &Pubkey, 67 | vdn: &VirtualDurableNonce, 68 | amount: u64, 69 | ) -> Hash { 70 | 71 | let src_timelock_address = src_vta.get_timelock_address( 72 | &vm.get_mint(), 73 | &vm.get_authority(), 74 | vm.get_lock_duration(), 75 | ); 76 | let src_token_address = src_vta.get_token_address( 77 | &src_timelock_address, 78 | ); 79 | 80 | compact_transfer_message( 81 | &src_token_address, 82 | dst_pubkey, 83 | amount, 84 | vdn, 85 | ) 86 | } 87 | 88 | 89 | -------------------------------------------------------------------------------- /api/src/cvm/messages/withdraw.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | use crate::utils; 4 | use crate::types::Hash; 5 | use crate::cvm::{ 6 | CodeVmAccount, 7 | VirtualDurableNonce, 8 | VirtualTimelockAccount 9 | }; 10 | 11 | pub fn compact_withdraw_message( 12 | src_timelock_address: &Pubkey, 13 | dst_timelock_address: &Pubkey, 14 | vdn: &VirtualDurableNonce, 15 | ) -> Hash { 16 | let message = &[ 17 | b"withdraw_and_close", 18 | src_timelock_address.as_ref(), 19 | dst_timelock_address.as_ref(), 20 | vdn.address.as_ref(), 21 | vdn.value.as_ref(), // this value is auto-advanced upon use 22 | ]; 23 | 24 | utils::hashv(message) 25 | } 26 | 27 | pub fn create_withdraw_message( 28 | vm: &CodeVmAccount, 29 | src_vta: &VirtualTimelockAccount, 30 | dst_vta: &VirtualTimelockAccount, 31 | vdn: &VirtualDurableNonce, 32 | ) -> Hash { 33 | 34 | let src_timelock_address = src_vta.get_timelock_address( 35 | &vm.get_mint(), 36 | &vm.get_authority(), 37 | vm.get_lock_duration(), 38 | ); 39 | let src_token_address = src_vta.get_token_address( 40 | &src_timelock_address, 41 | ); 42 | 43 | let dst_timelock_address = dst_vta.get_timelock_address( 44 | &vm.get_mint(), 45 | &vm.get_authority(), 46 | vm.get_lock_duration(), 47 | ); 48 | let dst_token_address = dst_vta.get_token_address( 49 | &dst_timelock_address, 50 | ); 51 | 52 | compact_withdraw_message( 53 | &src_token_address, 54 | &dst_token_address, 55 | vdn, 56 | ) 57 | } 58 | 59 | pub fn create_withdraw_message_to_external( 60 | vm: &CodeVmAccount, 61 | src_vta: &VirtualTimelockAccount, 62 | dst_pubkey: &Pubkey, 63 | vdn: &VirtualDurableNonce, 64 | ) -> Hash { 65 | 66 | let src_timelock_address = src_vta.get_timelock_address( 67 | &vm.get_mint(), 68 | &vm.get_authority(), 69 | vm.get_lock_duration(), 70 | ); 71 | let src_token_address = src_vta.get_token_address( 72 | &src_timelock_address, 73 | ); 74 | 75 | compact_withdraw_message( 76 | &src_token_address, 77 | dst_pubkey, 78 | vdn, 79 | ) 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /api/src/cvm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account; 2 | pub mod pool; 3 | pub mod state; 4 | pub mod messages; 5 | 6 | pub use account::*; 7 | pub use pool::*; 8 | pub use state::*; 9 | pub use messages::*; -------------------------------------------------------------------------------- /api/src/cvm/pool.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | #[repr(C)] 4 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 5 | pub struct TokenPool { 6 | pub vault: Pubkey, 7 | pub vault_bump: u8, 8 | } 9 | -------------------------------------------------------------------------------- /api/src/cvm/state/memory.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | use std::{cell::{Ref, RefMut}, marker::PhantomData}; 3 | use crate::{ 4 | consts::*, 5 | cvm::{ 6 | VirtualDurableNonce, 7 | VirtualRelayAccount, 8 | VirtualTimelockAccount 9 | }, 10 | types::SliceAllocator 11 | }; 12 | 13 | #[repr(u8)] 14 | #[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] 15 | pub enum MemoryVersion { 16 | Legacy = 0, 17 | V1 = 1, 18 | } 19 | 20 | #[repr(C, packed)] 21 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 22 | pub struct MemoryAccount { 23 | pub vm: Pubkey, 24 | pub name: [u8; MAX_NAME_LEN], 25 | pub bump: u8, 26 | 27 | pub version: u8, 28 | pub packed_info: [u8; 6], 29 | 30 | // Data starts at 72 bytes into the account 31 | _data: PhantomData<[u8]>, 32 | } 33 | 34 | impl MemoryAccount { 35 | pub const fn get_size() -> usize { 36 | 8 + std::mem::size_of::() 37 | } 38 | 39 | pub fn get_size_with_data(num_accounts: usize, account_size: usize) -> usize { 40 | Self::get_size() + SliceAllocator::get_size(num_accounts, account_size) 41 | } 42 | 43 | pub fn unpack(data: &[u8]) -> Self { 44 | let data = &data[..Self::get_size()]; 45 | Self::try_from_bytes(data).unwrap().clone() 46 | } 47 | 48 | pub fn get_capacity_and_size(info: &AccountInfo) -> (usize, usize) { 49 | let data = info.data.borrow(); 50 | let info = MemoryAccount::unpack(&data); 51 | (info.get_capacity(), info.get_account_size()) 52 | } 53 | 54 | pub fn get_data<'a>(info: &'a AccountInfo) 55 | -> Result, ProgramError> { 56 | 57 | let data = info.data.borrow(); 58 | let offset = MemoryAccount::get_size(); 59 | 60 | // Map the `Ref` to a subslice, preserving the borrow 61 | let data = Ref::map(data, |d| { 62 | let (_, data) = d.split_at(offset); 63 | data 64 | }); 65 | 66 | Ok(data) 67 | } 68 | 69 | pub fn get_data_mut<'a>(info: &'a AccountInfo) 70 | -> Result, ProgramError> { 71 | 72 | let data = info.data.borrow_mut(); 73 | let offset = MemoryAccount::get_size(); 74 | 75 | // Map the `RefMut` to a subslice, preserving the mutable borrow 76 | let data = RefMut::map(data, |d| { 77 | let (_, data) = d.split_at_mut(offset); 78 | data 79 | }); 80 | 81 | Ok(data) 82 | } 83 | 84 | pub fn get_version(&self) -> MemoryVersion { 85 | match self.version { 86 | 0 => MemoryVersion::Legacy, 87 | 1 => MemoryVersion::V1, 88 | _ => panic!("Invalid memory version"), 89 | } 90 | } 91 | 92 | pub fn get_capacity(&self) -> usize { 93 | match self.get_version() { 94 | MemoryVersion::Legacy => NUM_ACCOUNTS, 95 | MemoryVersion::V1 => { 96 | let packed: &PackedInfoV1 = bytemuck::from_bytes(&self.packed_info); 97 | packed.num_accounts as usize 98 | } 99 | } 100 | } 101 | 102 | pub fn get_account_size(&self) -> usize { 103 | match self.get_version() { 104 | MemoryVersion::Legacy => { 105 | let packed: &PackedInfoLegacy = bytemuck::from_bytes(&self.packed_info); 106 | 107 | // Values pulled from: 108 | // https://github.com/code-payments/code-vm/blob/acf276fce3e6858aa70e40dc99c6905f9bd655b9/api/src/cvm/state/memory.rs#L30 109 | 110 | match packed.layout { 111 | 1 => VirtualTimelockAccount::LEN + 1, 112 | 2 => VirtualDurableNonce::LEN + 1, 113 | 3 => VirtualRelayAccount::LEN + 1, 114 | _ => panic!("Invalid layout"), 115 | } 116 | } 117 | MemoryVersion::V1 => { 118 | let packed: &PackedInfoV1 = bytemuck::from_bytes(&self.packed_info); 119 | packed.account_size as usize 120 | } 121 | } 122 | } 123 | 124 | pub fn set_num_accounts(&mut self, num_accounts: u32) { 125 | if self.get_version() != MemoryVersion::V1 { 126 | panic!("Setting num_accounts is only valid for V1 memory version"); 127 | } 128 | let packed: &mut PackedInfoV1 = bytemuck::from_bytes_mut(&mut self.packed_info); 129 | packed.num_accounts = num_accounts; 130 | } 131 | 132 | pub fn set_account_size(&mut self, account_size: u16) { 133 | if self.get_version() != MemoryVersion::V1 { 134 | panic!("Setting account_size is only valid for V1 memory version"); 135 | } 136 | let packed: &mut PackedInfoV1 = bytemuck::from_bytes_mut(&mut self.packed_info); 137 | packed.account_size = account_size; 138 | } 139 | } 140 | 141 | #[repr(C, packed)] 142 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 143 | struct PackedInfoLegacy { 144 | _padding: [u8; 5], 145 | pub layout: u8, 146 | } 147 | 148 | #[repr(C, packed)] 149 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 150 | struct PackedInfoV1 { 151 | pub account_size: u16, 152 | pub num_accounts: u32, 153 | } -------------------------------------------------------------------------------- /api/src/cvm/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod memory; 2 | mod storage; 3 | mod relay; 4 | mod vm; 5 | mod unlock; 6 | mod withdraw; 7 | 8 | pub use memory::*; 9 | pub use storage::*; 10 | pub use relay::*; 11 | pub use vm::*; 12 | pub use unlock::*; 13 | pub use withdraw::*; -------------------------------------------------------------------------------- /api/src/cvm/state/relay.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | use crate::consts::*; 4 | use crate::cvm::TokenPool; 5 | use crate::types::{ 6 | MerkleTree, 7 | CircularBuffer, 8 | Hash 9 | }; 10 | 11 | #[repr(C, align(8))] 12 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 13 | pub struct RelayAccount { 14 | pub vm: Pubkey, 15 | pub name: [u8; MAX_NAME_LEN], 16 | 17 | pub treasury: TokenPool, 18 | pub bump: u8, 19 | pub num_levels: u8, 20 | pub num_history: u8, 21 | 22 | _padding: [u8; 4], 23 | 24 | pub recent_roots: CircularBuffer<{RELAY_HISTORY_ITEMS}, {Hash::LEN}>, 25 | pub history: MerkleTree<{RELAY_STATE_DEPTH}>, 26 | } 27 | 28 | impl RelayAccount { 29 | pub const fn get_size() -> usize { 30 | 8 + std::mem::size_of::() 31 | } 32 | 33 | pub fn get_recent_root(&self) -> Hash { 34 | self.recent_roots.first().unwrap().clone().into() 35 | } 36 | 37 | pub fn save_recent_root(&mut self) { 38 | let current = self.history.get_root(); 39 | let last = self.recent_roots.last(); 40 | 41 | match last { 42 | None => { 43 | // There is no last root, proceed to save the current root 44 | } 45 | Some(last) => { 46 | // We have a last root, check if it is the same as the current root 47 | if current.as_ref().eq(last) { 48 | // The root is already saved 49 | return; 50 | } 51 | }, 52 | }; 53 | 54 | self.recent_roots.push(current.as_ref()); 55 | } 56 | 57 | pub fn add_commitment(&mut self, commitment: &Pubkey) 58 | -> ProgramResult { 59 | self.history.try_insert(commitment.to_bytes().into()) 60 | } 61 | 62 | pub fn unpack(data: &[u8]) -> Self { 63 | let data = &data[..Self::get_size()]; 64 | Self::try_from_bytes(data).unwrap().clone() 65 | } 66 | 67 | pub fn unpack_mut(data: &mut [u8]) -> &mut Self { 68 | let data = &mut data[..Self::get_size()]; 69 | Self::try_from_bytes_mut(data).unwrap() 70 | } 71 | } -------------------------------------------------------------------------------- /api/src/cvm/state/storage.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | use crate::{consts::*, types::MerkleTree}; 4 | 5 | #[repr(C, align(8))] 6 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 7 | pub struct StorageAccount { 8 | pub vm: Pubkey, 9 | pub name: [u8; MAX_NAME_LEN], 10 | pub bump: u8, 11 | pub depth: u8, 12 | 13 | _padding: [u8; 6], 14 | pub compressed_state: MerkleTree<{COMPRESSED_STATE_DEPTH}>, 15 | } 16 | 17 | impl StorageAccount { 18 | pub const MERKLE_TREE_DEPTH: usize = COMPRESSED_STATE_DEPTH; 19 | 20 | pub const fn get_size() -> usize { 21 | 8 + std::mem::size_of::() 22 | } 23 | 24 | pub fn get_compressed_state_mut<'a>(info: &'a AccountInfo) 25 | -> Result<&'a mut MerkleTree<{COMPRESSED_STATE_DEPTH}>, ProgramError> { 26 | let storage = info.to_account_mut::(&crate::ID)?; 27 | let compressed_mem = &mut storage.compressed_state; 28 | Ok(compressed_mem) 29 | } 30 | 31 | pub fn unpack(data: &[u8]) -> Self { 32 | let data = &data[..Self::get_size()]; 33 | Self::try_from_bytes(data).unwrap().clone() 34 | } 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /api/src/cvm/state/unlock.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | #[repr(u8)] 4 | #[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] 5 | pub enum TimelockState { 6 | Unknown = 0, 7 | Unlocked, 8 | WaitingForTimeout 9 | } 10 | 11 | #[repr(C)] 12 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 13 | pub struct UnlockStateAccount { 14 | pub vm: Pubkey, 15 | pub owner: Pubkey, 16 | pub address: Pubkey, 17 | pub unlock_at: i64, 18 | pub bump: u8, 19 | pub state: u8, 20 | 21 | _padding: [u8; 6], 22 | } 23 | 24 | impl UnlockStateAccount { 25 | pub const fn get_size() -> usize { 26 | 8 + std::mem::size_of::() 27 | } 28 | 29 | pub fn unpack(data: &[u8]) -> Self { 30 | let data = &data[..Self::get_size()]; 31 | Self::try_from_bytes(data).unwrap().clone() 32 | } 33 | 34 | pub fn unpack_mut(data: &mut [u8]) -> &mut Self { 35 | let data = &mut data[..Self::get_size()]; 36 | Self::try_from_bytes_mut(data).unwrap() 37 | } 38 | 39 | pub fn is_unlocked(&self) -> bool { 40 | self.state == TimelockState::Unlocked as u8 41 | } 42 | 43 | pub fn is_waiting(&self) -> bool { 44 | self.state == TimelockState::WaitingForTimeout as u8 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /api/src/cvm/state/vm.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | 3 | use crate::{ 4 | cvm::TokenPool, 5 | instruction::CodeInstruction, 6 | types::Hash, 7 | utils 8 | }; 9 | 10 | #[repr(C, align(8))] 11 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 12 | pub struct CodeVmAccount { 13 | pub authority: Pubkey, 14 | pub mint: Pubkey, 15 | pub slot: u64, 16 | pub poh: Hash, 17 | pub omnibus: TokenPool, 18 | pub lock_duration: u8, // in days 19 | pub bump: u8, 20 | 21 | _padding: [u8; 5], 22 | } 23 | 24 | impl CodeVmAccount { 25 | pub const fn get_size() -> usize { 26 | 8 + std::mem::size_of::() 27 | } 28 | 29 | pub fn unpack(data: &[u8]) -> Self { 30 | let data = &data[..Self::get_size()]; 31 | Self::try_from_bytes(data).unwrap().clone() 32 | } 33 | 34 | pub fn unpack_mut(data: &mut [u8]) -> &mut Self { 35 | let data = &mut data[..Self::get_size()]; 36 | Self::try_from_bytes_mut(data).unwrap() 37 | } 38 | 39 | pub fn advance_slot(&mut self) { 40 | self.slot += 1; 41 | } 42 | 43 | pub fn advance_poh( 44 | &mut self, 45 | ix: CodeInstruction, 46 | accounts: &[AccountInfo], 47 | data: &[u8], 48 | ) { 49 | let mut message = Vec::new(); 50 | for account in accounts { 51 | message.extend_from_slice(account.key.as_ref()); 52 | } 53 | message.extend_from_slice(data); 54 | 55 | self.poh = utils::hashv(&[ 56 | self.poh.as_ref(), 57 | &[ix as u8], 58 | &message 59 | ]); 60 | 61 | self.advance_slot(); 62 | } 63 | 64 | #[inline] 65 | pub fn get_authority(&self) -> Pubkey { 66 | self.authority 67 | } 68 | 69 | #[inline] 70 | pub fn get_mint(&self) -> Pubkey { 71 | self.mint 72 | } 73 | 74 | #[inline] 75 | pub fn get_bump(&self) -> u8 { 76 | self.bump 77 | } 78 | 79 | #[inline] 80 | pub fn get_omnibus_bump(&self) -> u8 { 81 | self.omnibus.vault_bump 82 | } 83 | 84 | #[inline] 85 | pub fn get_lock_duration(&self) -> u8 { 86 | self.lock_duration 87 | } 88 | 89 | #[inline] 90 | pub fn get_current_poh(&self) -> Hash { 91 | self.poh 92 | } 93 | 94 | #[inline] 95 | pub fn get_current_slot(&self) -> u64 { 96 | self.slot 97 | } 98 | 99 | } 100 | 101 | -------------------------------------------------------------------------------- /api/src/cvm/state/withdraw.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | use crate::types::Hash; 3 | 4 | #[repr(C)] 5 | #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] 6 | pub struct WithdrawReceiptAccount { 7 | pub unlock_pda: Pubkey, 8 | pub nonce: Hash, 9 | pub amount: u64, 10 | pub bump: u8, 11 | 12 | _padding: [u8; 7], 13 | } -------------------------------------------------------------------------------- /api/src/external/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod timelock; 2 | pub mod splitter; -------------------------------------------------------------------------------- /api/src/external/splitter.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("spLit2eb13Tz93if6aJM136nUWki5PVUsoEjcUjwpwW"); -------------------------------------------------------------------------------- /api/src/external/timelock.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("time2Z2SCnn3qYg3ULKVtdkh8YmZ5jFdKicnA1W2YnJ"); -------------------------------------------------------------------------------- /api/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod consts; 2 | pub mod instruction; 3 | pub mod state; 4 | pub mod cpis; 5 | pub mod helpers; 6 | pub mod cvm; 7 | pub mod types; 8 | pub mod utils; 9 | pub mod opcode; 10 | pub mod pdas; 11 | pub mod external; 12 | 13 | #[cfg(not(target_os = "solana"))] 14 | pub mod sdk; 15 | 16 | pub mod prelude { 17 | pub use crate::consts::*; 18 | pub use crate::instruction::*; 19 | pub use crate::state::*; 20 | pub use crate::cpis::*; 21 | pub use crate::helpers::*; 22 | pub use crate::cvm::*; 23 | pub use crate::types::*; 24 | pub use crate::utils::*; 25 | pub use crate::opcode::*; 26 | pub use crate::pdas::*; 27 | pub use crate::external::*; 28 | 29 | #[cfg(not(target_os = "solana"))] 30 | pub use crate::sdk::*; 31 | } 32 | 33 | use steel::*; 34 | 35 | declare_id!("vmZ1WUq8SxjBWcaeTCvgJRZbS84R61uniFsQy5YMRTJ"); 36 | -------------------------------------------------------------------------------- /api/src/state.rs: -------------------------------------------------------------------------------- 1 | 2 | use steel::*; 3 | use crate::cvm::{ 4 | CodeVmAccount, 5 | MemoryAccount, 6 | RelayAccount, 7 | StorageAccount, 8 | UnlockStateAccount, 9 | WithdrawReceiptAccount 10 | }; 11 | 12 | #[repr(u8)] 13 | #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] 14 | pub enum AccountType { 15 | Unknown = 0, 16 | CodeVmAccount, 17 | MemoryAccount, 18 | StorageAccount, 19 | RelayAccount, 20 | UnlockStateAccount, 21 | WithdrawReceiptAccount, 22 | } 23 | 24 | 25 | account!(AccountType, CodeVmAccount); 26 | account!(AccountType, MemoryAccount); 27 | account!(AccountType, StorageAccount); 28 | account!(AccountType, RelayAccount); 29 | account!(AccountType, UnlockStateAccount); 30 | account!(AccountType, WithdrawReceiptAccount); -------------------------------------------------------------------------------- /api/src/types/hash.rs: -------------------------------------------------------------------------------- 1 | use steel::*; 2 | use std::fmt; 3 | use borsh::{BorshDeserialize, BorshSerialize}; 4 | use bytemuck::{ Pod, Zeroable }; 5 | 6 | pub const HASH_BYTES: usize = 32; 7 | 8 | #[repr(C)] 9 | #[derive(BorshSerialize, BorshDeserialize, Clone, Copy, PartialEq, Default, Pod, Zeroable)] 10 | pub struct Hash { 11 | pub(crate) value: [u8; 32] // Using an explicit "value" here to avoid IDL generation issues 12 | } 13 | 14 | impl From for Pubkey { 15 | fn from(from: Hash) -> Self { 16 | Pubkey::from(from.value) 17 | } 18 | } 19 | 20 | impl From for [u8; HASH_BYTES] { 21 | fn from(from: Hash) -> Self { 22 | from.value 23 | } 24 | } 25 | 26 | impl From<[u8; HASH_BYTES]> for Hash { 27 | fn from(from: [u8; 32]) -> Self { 28 | Self { value: from } 29 | } 30 | } 31 | 32 | impl AsRef<[u8]> for Hash { 33 | fn as_ref(&self) -> &[u8] { 34 | &self.value 35 | } 36 | } 37 | 38 | impl fmt::Debug for Hash { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | write!(f, "{}", bs58::encode(self.value).into_string()) 41 | } 42 | } 43 | 44 | impl fmt::Display for Hash { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | write!(f, "{}", bs58::encode(self.value).into_string()) 47 | } 48 | } 49 | 50 | impl Hash { 51 | pub const LEN: usize = HASH_BYTES; 52 | 53 | pub fn new(hash_slice: &[u8]) -> Self { 54 | Hash { value: <[u8; HASH_BYTES]>::try_from(hash_slice).unwrap() } 55 | } 56 | 57 | pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self { 58 | Self { value: hash_array } 59 | } 60 | 61 | pub fn to_bytes(self) -> [u8; HASH_BYTES] { 62 | self.value 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /api/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod circular_buffer; 2 | pub mod merkle_tree; 3 | pub mod signature; 4 | pub mod slice_allocator; 5 | pub mod hash; 6 | 7 | pub use circular_buffer::*; 8 | pub use merkle_tree::*; 9 | pub use signature::*; 10 | pub use slice_allocator::*; 11 | pub use hash::*; -------------------------------------------------------------------------------- /api/src/types/signature.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use bytemuck::{ Pod, Zeroable }; 4 | 5 | pub const SIGNATURE_BYTES: usize = 64; 6 | 7 | #[repr(C)] 8 | #[derive(BorshSerialize, BorshDeserialize, Clone, Copy, PartialEq, Pod, Zeroable)] 9 | pub struct Signature { 10 | value: [u8; 64] // Using an explicit "value" here to avoid IDL generation issues 11 | } 12 | 13 | impl Default for Signature { 14 | fn default() -> Self { 15 | Self { value: [0u8; SIGNATURE_BYTES] } 16 | } 17 | } 18 | 19 | impl From for [u8; SIGNATURE_BYTES] { 20 | fn from(from: Signature) -> Self { 21 | from.value 22 | } 23 | } 24 | 25 | impl From<[u8; SIGNATURE_BYTES]> for Signature { 26 | fn from(from: [u8; 64]) -> Self { 27 | Self { value: from } 28 | } 29 | } 30 | 31 | impl AsRef<[u8]> for Signature { 32 | fn as_ref(&self) -> &[u8] { 33 | &self.value 34 | } 35 | } 36 | 37 | impl fmt::Debug for Signature { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | write!(f, "{}", bs58::encode(self.value).into_string()) 40 | } 41 | } 42 | 43 | impl fmt::Display for Signature { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | write!(f, "{}", bs58::encode(self.value).into_string()) 46 | } 47 | } 48 | 49 | impl Signature { 50 | pub const LEN: usize = SIGNATURE_BYTES; 51 | 52 | pub fn new(signature_slice: &[u8]) -> Self { 53 | Signature { value: <[u8; SIGNATURE_BYTES]>::try_from(signature_slice).unwrap() } 54 | } 55 | 56 | pub const fn new_from_array(signature_array: [u8; SIGNATURE_BYTES]) -> Self { 57 | Self { value: signature_array } 58 | } 59 | 60 | pub fn to_bytes(self) -> [u8; SIGNATURE_BYTES] { 61 | self.value 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /api/src/utils/hash.rs: -------------------------------------------------------------------------------- 1 | use sha2::{ Digest, Sha256 }; 2 | use crate::types::Hash; 3 | 4 | #[derive(Clone, Default)] 5 | pub struct Hasher { 6 | hasher: Sha256, 7 | } 8 | 9 | impl Hasher { 10 | pub fn hash(&mut self, val: &[u8]) { 11 | self.hasher.update(val); 12 | } 13 | pub fn hashv(&mut self, vals: &[&[u8]]) { 14 | for val in vals { 15 | self.hash(val); 16 | } 17 | } 18 | pub fn result(self) -> Hash { 19 | Hash { value: self.hasher.finalize().into() } 20 | } 21 | } 22 | 23 | /// Return a Sha256 hash for the given data. 24 | pub fn hashv(vals: &[&[u8]]) -> Hash { 25 | // Perform the calculation inline, calling this from within a program is 26 | // not supported 27 | #[cfg(not(target_os = "solana"))] 28 | { 29 | let mut hasher = Hasher::default(); 30 | hasher.hashv(vals); 31 | hasher.result() 32 | } 33 | // Call via a system call to perform the calculation 34 | #[cfg(target_os = "solana")] 35 | { 36 | let mut hash_result = [0u8; 32]; 37 | unsafe { 38 | solana_program::syscalls::sol_sha256( 39 | vals as *const _ as *const u8, 40 | vals.len() as u64, 41 | &mut hash_result as *mut _ as *mut u8, 42 | ); 43 | } 44 | Hash::new_from_array(hash_result) 45 | } 46 | } 47 | 48 | /// Return a Sha256 hash for the given data. 49 | pub fn hash(val: &[u8]) -> Hash { 50 | hashv(&[val]) 51 | } -------------------------------------------------------------------------------- /api/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod hash; 2 | mod signature; 3 | 4 | pub use hash::*; 5 | pub use signature::*; -------------------------------------------------------------------------------- /docs/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/code_audit_final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-payments/code-vm/acb48ba7d5263dd91a1419021c816b83feaa8ffb/docs/code_audit_final.pdf -------------------------------------------------------------------------------- /idl/Makefile: -------------------------------------------------------------------------------- 1 | # A makefile for building the program and updating the discriminators in the IDL 2 | # to match the real program values. Anchor uses SHA256 hashes to identify the 3 | # discriminators, we use steel, which is a more human-readable format. 4 | 5 | ANCHOR_BUILD=anchor build --skip-lint 6 | 7 | UPDATE_IDL=bun ./src/scripts/update-discriminators.ts > ./src/target/idl/code_vm_updated.json 8 | COPY_IDL=cp -f ./src/target/idl/code_vm_updated.json ./src/target/idl/code_vm.json 9 | MOVE_IDL=cp -f ./src/target/idl/code_vm_updated.json ./code_vm.json 10 | 11 | anchor_build: 12 | cd src && $(ANCHOR_BUILD) 13 | 14 | update_idl: 15 | @ cd src && npm install 16 | @ $(UPDATE_IDL) 17 | 18 | copy_idl: 19 | @ $(COPY_IDL) 20 | 21 | move_idl: 22 | @ $(MOVE_IDL) 23 | 24 | idl: anchor_build update_idl copy_idl move_idl -------------------------------------------------------------------------------- /idl/README.md: -------------------------------------------------------------------------------- 1 | # Code VM IDL 2 | This is not the actual program, it is a dummy program that can be used to 3 | generate the IDL for the [real](https://github.com/code-payments/code-vm/tree/main/program) Code VM. 4 | 5 | ## What is an IDL? 6 | An IDL can be used to generate clients in various languages and is used by 7 | solana explorers to display the program's API. 8 | 9 | At the moment, developers can either use `shank` or `anchor` to create IDLs. 10 | Anchor is the most popular framework, so we're using it to generate the IDL for 11 | the Code VM. Using `shank` would have required marking up our actual program 12 | code, which we didn't want to do. 13 | 14 | Note, as of this writing, there is no "standard" way to generate IDLs for Solana 15 | programs. See [RFC-00008](https://forum.solana.com/t/srfc-00008-idl-standard/66) 16 | for more information. 17 | 18 | ## Requirements 19 | 20 | - Anchor 0.31.1 (for building the dummy program) 21 | - Bun.js (for running the discriminator update script) 22 | - Vitest (for testing the IDL) 23 | 24 | ## Quick Start 25 | 26 | To generate the IDL, run the following command after you've installed the npm 27 | dependencies: 28 | 29 | ```bash 30 | make idl 31 | ``` 32 | 33 | You'll find the generated IDL 34 | [here](https://github.com/code-payments/code-vm/blob/main/idl/code_vm.json) in 35 | the `target/idl` directory. Note that you'll see some errors thrown by Anchor, 36 | but you can ignore them. 37 | 38 | ## Using the IDL 39 | 40 | There are many ways to make use of the IDL, however, one way is to decode accounts. 41 | 42 | You can pull the IDL directly from the program if you'd like using something like this: 43 | 44 | ```js 45 | const url = "https://api.mainnet-beta.solana.com"; 46 | const idl = await anchor.Program.fetchIdl("vmZ1WUq8SxjBWcaeTCvgJRZbS84R61uniFsQy5YMRTJ", getProvider(url)); 47 | const coder = new BorshAccountsCoder(idl); 48 | 49 | const rawData = Buffer.from(/* your account data here */, "base64"); 50 | 51 | const account = idl.accounts?.find((accountType: any) => 52 | (rawData as Buffer).slice(0, 8).equals(coder.accountDiscriminator(accountType.name)) 53 | ); 54 | const accountDef = idl.types.find((type: any) => type.name === account.name); 55 | const decodedAccount = coder.decode(account.name, rawData); 56 | ``` 57 | 58 | ## Running tests 59 | 60 | Unlike with a standard Anchor program, you can't use `anchor test` to test the 61 | IDL. It would automatically replace our correct IDL with one that has different 62 | discriminators. See the `Makefile` for more context. 63 | 64 | Instead, you'll need to manually run a `solana-test-validator` and create a 65 | `keypair-owner.json` file. 66 | 67 | Once you have those, you can run a test script using the following command: 68 | 69 | ```bash 70 | vitest run ./tests/vm.test.ts --testTimeout 25000 --bail 1 71 | ``` 72 | 73 | ## ImHex Patterns 74 | 75 | In addition to the IDL, we're also including two ImHex pattern files that you 76 | can use to decode accounts and instructions inside the ImHex editor. You can 77 | learn more about ImHex [here](https://imhex.werwolv.net/). 78 | 79 | ### Instructions 80 | 81 | The first pattern file is for decoding instructions and opcodes. You can find the pattern 82 | [here](https://github.com/code-payments/code-vm/blob/main/idl/code_vm.instructions.hexpat). 83 | 84 | For example, here is a decoded [transfer](https://github.com/code-payments/code-vm/blob/main/program/src/opcode/transfer.rs): 85 | 86 | ![image](https://github.com/user-attachments/assets/111180c5-2652-4603-9d75-1c3fee3d267b) 87 | 88 | ### Accounts 89 | 90 | We also have a pattern file for decoding accounts. You can find the pattern 91 | [here](https://github.com/code-payments/code-vm/blob/main/idl/code_vm.accounts.hexpat). 92 | 93 | For example, here is a decoded [memory account](https://github.com/code-payments/code-vm/blob/main/api/src/cvm/state/memory.rs#L43C12-L43C25): 94 | 95 | ![image](https://github.com/user-attachments/assets/f2184e1f-8c1f-4774-a88b-8a169e72ebff) 96 | -------------------------------------------------------------------------------- /idl/code_vm.accounts.hexpat: -------------------------------------------------------------------------------- 1 | #pragma endian little 2 | 3 | #define MAX_NAME_LEN 32 4 | 5 | #define COMPRESSED_STATE_DEPTH 20 6 | #define RELAY_STATE_DEPTH 63 7 | #define RELAY_HISTORY_ITEMS 32 8 | 9 | enum AccountType : u8 { 10 | CodeVmAccount = 1, 11 | MemoryAccount = 2, 12 | StorageAccount = 3, 13 | RelayAccount = 4, 14 | UnlockStateAccount = 5, 15 | WithdrawReceiptAccount = 6, 16 | }; 17 | 18 | enum ItemState : u8 { 19 | Free = 0, 20 | Used = 1, 21 | }; 22 | 23 | enum AccountDataType : u8 { 24 | Nonce = 0, 25 | Timelock = 1, 26 | Relay = 2, 27 | }; 28 | 29 | // Type Definitions 30 | 31 | struct Pubkey { 32 | u8 value[32]; 33 | }; 34 | 35 | struct Hash { 36 | u8 value[32]; 37 | }; 38 | 39 | struct Signature { 40 | u8 value[64]; 41 | }; 42 | 43 | struct TokenPool { 44 | Pubkey vault; 45 | u8 vault_bump; 46 | }; 47 | 48 | struct SliceAllocator { 49 | ItemState state[N]; 50 | T data[N]; 51 | }; 52 | 53 | struct VirtualTimelockAccount { 54 | Pubkey owner; 55 | Hash instance; 56 | u8 token_bump; 57 | u8 unlock_bump; 58 | u8 withdraw_bump; 59 | u64 balance; 60 | u8 bump; 61 | }; 62 | 63 | struct VirtualDurableNonce { 64 | Pubkey address; 65 | Hash value; 66 | }; 67 | 68 | struct VirtualRelayAccount { 69 | Pubkey target; 70 | Pubkey destination; 71 | }; 72 | 73 | struct VirtualAccount { 74 | AccountDataType type; 75 | 76 | if (type == AccountDataType::Nonce) { 77 | VirtualDurableNonce data; 78 | } 79 | else if (type == AccountDataType::Timelock) { 80 | VirtualTimelockAccount data; 81 | } 82 | else if (type == AccountDataType::Relay) { 83 | VirtualRelayAccount data; 84 | } 85 | }; 86 | 87 | struct AccountDataHeader { 88 | u8 version; 89 | 90 | if (version == 0) { 91 | u8 _padding[5]; 92 | u8 layout; 93 | } else if (version == 1) { 94 | u16 account_size; 95 | u32 num_accounts; 96 | } 97 | }; 98 | 99 | struct AccountData { 100 | AccountDataHeader header; 101 | 102 | if (header.version == 0) { 103 | SliceAllocator allocator; 104 | } else if (header.version == 1) { 105 | SliceAllocator allocator; 106 | } 107 | }; 108 | 109 | struct CodeVmAccount { 110 | Pubkey authority; 111 | Pubkey mint; 112 | u64 slot; 113 | Hash poh; 114 | TokenPool omnibus; 115 | u8 lock_duration; // in days 116 | u8 bump; 117 | u8 _padding[5]; // To align to 8 bytes 118 | }; 119 | 120 | struct MemoryAccount { 121 | Pubkey vm; 122 | u8 name[MAX_NAME_LEN]; 123 | u8 bump; 124 | 125 | AccountData data; 126 | }; 127 | 128 | struct RelayHistory { 129 | Hash items[RELAY_HISTORY_ITEMS]; 130 | u8 offset; 131 | u8 num_items; 132 | u8 _padding[6]; 133 | }; 134 | 135 | struct RelayTree { 136 | Hash root; 137 | Hash filled_subtrees[RELAY_STATE_DEPTH]; 138 | Hash zero_values[RELAY_STATE_DEPTH]; 139 | u64 next_index; 140 | }; 141 | 142 | struct RelayAccount { 143 | Pubkey vm; 144 | u8 name[MAX_NAME_LEN]; 145 | TokenPool treasury; 146 | u8 bump; 147 | u8 num_levels; 148 | u8 num_history; 149 | u8 _padding[4]; 150 | RelayHistory recent_roots; 151 | RelayTree history; 152 | }; 153 | 154 | struct CompressedState { 155 | Hash root; 156 | Hash filled_subtrees[COMPRESSED_STATE_DEPTH]; 157 | Hash zero_values[COMPRESSED_STATE_DEPTH]; 158 | u64 next_index; 159 | }; 160 | 161 | struct StorageAccount { 162 | Pubkey vm; 163 | u8 name[MAX_NAME_LEN]; 164 | u8 bump; 165 | u8 depth; 166 | u8 _padding[6]; 167 | CompressedState state; 168 | }; 169 | 170 | struct UnlockStateAccount { 171 | Pubkey vm; 172 | Pubkey owner; 173 | Pubkey address; 174 | s64 unlock_at; 175 | u8 bump; 176 | u8 state; 177 | u8 _padding[6]; 178 | }; 179 | 180 | struct WithdrawReceiptAccount { 181 | Pubkey unlock_pda; 182 | Hash nonce; 183 | u64 amount; 184 | u8 bump; 185 | u8 _padding[7]; 186 | }; 187 | 188 | struct Account { 189 | AccountType _type; 190 | u8 _padding[7]; 191 | 192 | if (_type == AccountType::CodeVmAccount) { 193 | CodeVmAccount data; 194 | } 195 | else if (_type == AccountType::MemoryAccount) { 196 | MemoryAccount data; 197 | } 198 | else if (_type == AccountType::StorageAccount) { 199 | StorageAccount data; 200 | } 201 | else if (_type == AccountType::RelayAccount) { 202 | RelayAccount data; 203 | } 204 | else if (_type == AccountType::UnlockStateAccount) { 205 | UnlockStateAccount data; 206 | } 207 | else if (_type == AccountType::WithdrawReceiptAccount) { 208 | WithdrawReceiptAccount data; 209 | } 210 | else { 211 | // Unknown type, read the rest as raw bytes 212 | u8 data[]; 213 | } 214 | }; 215 | 216 | // Entry Point 217 | Account account @ 0x00; -------------------------------------------------------------------------------- /idl/code_vm.instructions.hexpat: -------------------------------------------------------------------------------- 1 | #pragma endian little 2 | 3 | #define MAX_NAME_LEN 32 4 | 5 | // Type Definitions 6 | struct Vec { 7 | u32 len; 8 | T data[len]; 9 | }; 10 | 11 | struct Pubkey { 12 | u8 value[32]; 13 | }; 14 | 15 | struct Hash { 16 | u8 value[32]; 17 | }; 18 | 19 | struct Signature { 20 | u8 value[64]; 21 | }; 22 | 23 | enum Instruction : u8 { 24 | Unknown = 0, 25 | InitVmIx = 1, 26 | InitMemoryIx = 2, 27 | InitStorageIx = 3, 28 | InitRelayIx = 4, 29 | InitNonceIx = 5, 30 | InitTimelockIx = 6, 31 | InitUnlockIx = 7, 32 | ExecIx = 8, 33 | CompressIx = 9, 34 | DecompressIx = 10, 35 | ResizeMemoryIx = 11, 36 | SnapshotIx = 12, 37 | DepositIx = 13, 38 | WithdrawIx = 14, 39 | UnlockIx = 15, 40 | }; 41 | 42 | enum Opcode : u8 { 43 | Unknown = 0, 44 | ExternalTransferOp = 10, 45 | TransferOp = 11, 46 | ConditionalTransferOp = 12, 47 | ExternalWithdrawOp = 13, 48 | WithdrawOp = 14, 49 | ExternalRelayOp = 20, 50 | RelayOp = 21, 51 | Airdrop = 30, 52 | }; 53 | 54 | 55 | struct TransferOp { 56 | Signature signature; 57 | u64 amount; 58 | }; 59 | 60 | struct WithdrawOp { 61 | Signature signature; 62 | }; 63 | 64 | struct RelayOp { 65 | u64 amount; 66 | Hash transcript; 67 | Hash recent_root; 68 | Pubkey commitment; 69 | }; 70 | 71 | struct ExternalTransferOp { 72 | Signature signature; 73 | u64 amount; 74 | }; 75 | 76 | struct ExternalWithdrawOp { 77 | Signature signature; 78 | }; 79 | 80 | struct ExternalRelayOp { 81 | u64 amount; 82 | Hash transcript; 83 | Hash recent_root; 84 | Pubkey commitment; 85 | }; 86 | 87 | struct ConditionalTransferOp { 88 | Signature signature; 89 | u64 amount; 90 | }; 91 | 92 | struct AirdropOp { 93 | Signature signature; 94 | u64 amount; 95 | u8 count; 96 | }; 97 | 98 | struct ExecArgs { 99 | Opcode opcode; 100 | Vec mem_indices; 101 | Vec mem_banks; 102 | u32 data_len; 103 | 104 | // Parsing the data field based on opcode 105 | if (opcode == Opcode::TransferOp) { 106 | TransferOp data; 107 | } 108 | else if (opcode == Opcode::WithdrawOp) { 109 | WithdrawOp data; 110 | } 111 | else if (opcode == Opcode::RelayOp) { 112 | RelayOp data; 113 | } 114 | else if (opcode == Opcode::ExternalTransferOp) { 115 | ExternalTransferOp data; 116 | } 117 | else if (opcode == Opcode::ExternalWithdrawOp) { 118 | ExternalWithdrawOp data; 119 | } 120 | else if (opcode == Opcode::ExternalRelayOp) { 121 | ExternalRelayOp data; 122 | } 123 | else if (opcode == Opcode::ConditionalTransferOp) { 124 | ConditionalTransferOp data; 125 | } 126 | else if (opcode == Opcode::Airdrop) { 127 | AirdropOp data; 128 | } 129 | else { 130 | // Unknown opcode, read data as bytes 131 | u8 data[data_bytes.len]; 132 | } 133 | }; 134 | 135 | struct InitVmArgs { 136 | u8 lock_duration; 137 | u8 vm_bump; 138 | u8 vm_omnibus_bump; 139 | }; 140 | 141 | struct InitMemoryArgs { 142 | u8 name[MAX_NAME_LEN]; 143 | u32 num_accounts; 144 | u16 account_size; 145 | u8 vm_memory_bump; 146 | }; 147 | 148 | struct ResizeMemoryArgs { 149 | u32 account_size; 150 | }; 151 | 152 | struct InitStorageArgs { 153 | u8 name[MAX_NAME_LEN]; 154 | u8 vm_storage_bump; 155 | }; 156 | 157 | struct InitNonceArgs { 158 | u16 account_index; 159 | }; 160 | 161 | struct InitTimelockArgs { 162 | u16 account_index; 163 | u8 virtual_timelock_bump; 164 | u8 virtual_vault_bump; 165 | u8 unlock_pda_bump; 166 | }; 167 | 168 | struct CompressArgs { 169 | u16 account_index; 170 | Signature signature; 171 | }; 172 | 173 | struct DecompressArgs { 174 | u16 account_index; 175 | Vec packed_va; 176 | Vec proof; 177 | Signature signature; 178 | }; 179 | 180 | struct InitRelayArgs { 181 | u8 name[MAX_NAME_LEN]; 182 | u8 relay_bump; 183 | u8 relay_vault_bump; 184 | }; 185 | 186 | struct SnapshotArgs { }; 187 | 188 | struct DepositArgs { 189 | u16 account_index; 190 | u64 amount; 191 | u8 bump; 192 | }; 193 | 194 | struct InitUnlockArgs { }; 195 | 196 | struct UnlockArgs { }; 197 | 198 | struct WithdrawArgsFromMemory { 199 | u16 account_index; 200 | }; 201 | 202 | struct WithdrawArgsFromStorage { 203 | Vec packed_va; 204 | Vec proof; 205 | Signature signature; 206 | }; 207 | 208 | struct WithdrawArgsFromDeposit { 209 | u8 bump; 210 | }; 211 | 212 | struct WithdrawArgs { 213 | u8 discriminator; 214 | if (discriminator == 0) { // FromMemory 215 | WithdrawArgsFromMemory data; 216 | } 217 | else if (discriminator == 1) { // FromStorage 218 | WithdrawArgsFromStorage data; 219 | } 220 | else if (discriminator == 2) { // FromDeposit 221 | WithdrawArgsFromDeposit data; 222 | } 223 | else { 224 | u8 data[]; 225 | } 226 | }; 227 | 228 | struct InstructionData { 229 | Instruction discriminator; 230 | 231 | if (discriminator == Instruction::InitVmIx) { 232 | InitVmArgs data; 233 | } 234 | else if (discriminator == Instruction::InitMemoryIx) { 235 | InitMemoryArgs data; 236 | } 237 | else if (discriminator == Instruction::InitStorageIx) { 238 | InitStorageArgs data; 239 | } 240 | else if (discriminator == Instruction::InitRelayIx) { 241 | InitRelayArgs data; 242 | } 243 | else if (discriminator == Instruction::InitNonceIx) { 244 | InitNonceArgs data; 245 | } 246 | else if (discriminator == Instruction::InitTimelockIx) { 247 | InitTimelockArgs data; 248 | } 249 | else if (discriminator == Instruction::InitUnlockIx) { 250 | InitUnlockArgs data; 251 | } 252 | else if (discriminator == Instruction::ExecIx) { 253 | ExecArgs data; 254 | } 255 | else if (discriminator == Instruction::CompressIx) { 256 | CompressArgs data; 257 | } 258 | else if (discriminator == Instruction::DecompressIx) { 259 | DecompressArgs data; 260 | } 261 | else if (discriminator == Instruction::ResizeMemoryIx) { 262 | ResizeMemoryArgs data; 263 | } 264 | else if (discriminator == Instruction::SnapshotIx) { 265 | SnapshotArgs data; 266 | } 267 | else if (discriminator == Instruction::DepositIx) { 268 | DepositArgs data; 269 | } 270 | else if (discriminator == Instruction::WithdrawIx) { 271 | WithdrawArgs data; 272 | } 273 | else if (discriminator == Instruction::UnlockIx) { 274 | UnlockArgs data; 275 | } 276 | else { 277 | u8 data[]; 278 | } 279 | }; 280 | 281 | // Entry Point 282 | InstructionData instruction_data @ 0x00; 283 | -------------------------------------------------------------------------------- /idl/src/.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | .yarn 8 | -------------------------------------------------------------------------------- /idl/src/.prettierignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | node_modules 5 | dist 6 | build 7 | test-ledger 8 | -------------------------------------------------------------------------------- /idl/src/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = true 6 | 7 | [programs.localnet] 8 | code_vm = "vmZ1WUq8SxjBWcaeTCvgJRZbS84R61uniFsQy5YMRTJ" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | wallet = "keypair-owner.json" 15 | cluster = "http://127.0.0.1:8899" 16 | 17 | [scripts] 18 | test = "echo \"Error: no test specified\" && exit 1" 19 | -------------------------------------------------------------------------------- /idl/src/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 | -------------------------------------------------------------------------------- /idl/src/anchor-cli: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-payments/code-vm/acb48ba7d5263dd91a1419021c816b83feaa8ffb/idl/src/anchor-cli -------------------------------------------------------------------------------- /idl/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "ISC", 3 | "scripts": { 4 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 5 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 6 | }, 7 | "dependencies": { 8 | "@coral-xyz/anchor": "^0.30.1", 9 | "@solana/spl-token": "^0.4.9", 10 | "@solana/web3.js": "^1.95.4" 11 | }, 12 | "devDependencies": { 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 | "vitest": "^2.0.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /idl/src/programs/code-vm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "code-vm" 3 | version = "0.1.0" 4 | description = "Code VM" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "code_vm" 10 | 11 | [features] 12 | default = [] 13 | cpi = ["no-entrypoint"] 14 | no-entrypoint = [] 15 | no-idl = [] 16 | no-log-ix-name = [] 17 | idl-build = ["anchor-lang/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = "0.30.1" 21 | anchor-spl = "0.30.1" 22 | -------------------------------------------------------------------------------- /idl/src/programs/code-vm/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /idl/src/programs/code-vm/src/args.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use crate::{Hash, Signature, MAX_NAME_LEN}; 3 | 4 | 5 | #[repr(C)] 6 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 7 | pub struct InitVmArgs { 8 | pub lock_duration: u8, 9 | pub vm_bump: u8, 10 | pub vm_omnibus_bump: u8, 11 | } 12 | 13 | #[repr(C)] 14 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 15 | pub struct InitMemoryArgs { 16 | pub name: [u8; MAX_NAME_LEN], 17 | pub num_accounts: u32, 18 | pub account_size: u16, 19 | pub vm_memory_bump: u8, 20 | } 21 | 22 | #[repr(C)] 23 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 24 | pub struct ResizeMemoryArgs { 25 | pub account_size: u32, 26 | } 27 | 28 | #[repr(C)] 29 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 30 | pub struct InitStorageArgs { 31 | pub name: [u8; MAX_NAME_LEN], 32 | pub vm_storage_bump: u8, 33 | } 34 | 35 | #[repr(C)] 36 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 37 | pub struct ExecArgs { 38 | pub data: ExecArgsData, 39 | } 40 | 41 | #[repr(C)] 42 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 43 | pub struct ExecArgsData { 44 | pub opcode: u8, 45 | pub mem_indicies: Vec, 46 | pub mem_banks: Vec, 47 | pub data: Vec, 48 | } 49 | 50 | #[repr(C)] 51 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 52 | pub struct InitNonceArgs { 53 | pub account_index: u16, 54 | } 55 | 56 | #[repr(C)] 57 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 58 | pub struct InitTimelockArgs { 59 | pub account_index: u16, 60 | pub virtual_timelock_bump: u8, 61 | pub virtual_vault_bump: u8, 62 | pub unlock_pda_bump: u8, 63 | } 64 | 65 | #[repr(C)] 66 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 67 | pub struct CompressArgs { 68 | pub account_index: u16, 69 | pub signature: Signature, 70 | } 71 | 72 | #[repr(C)] 73 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 74 | pub struct DecompressArgs { 75 | pub data: DecompressArgsData, 76 | } 77 | 78 | #[repr(C)] 79 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 80 | pub struct DecompressArgsData { 81 | pub account_index: u16, 82 | pub packed_va: Vec, 83 | pub proof: Vec, 84 | pub signature: Signature, 85 | } 86 | 87 | #[repr(C)] 88 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 89 | pub struct InitRelayArgs { 90 | pub name: [u8; MAX_NAME_LEN], 91 | pub relay_bump: u8, 92 | pub relay_vault_bump: u8, 93 | } 94 | 95 | 96 | #[repr(C)] 97 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 98 | pub struct SnapshotArgs { // SaveRecentRoot 99 | } 100 | 101 | #[repr(C)] 102 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 103 | pub struct DepositArgs { 104 | pub account_index: u16, 105 | pub amount: u64, 106 | pub bump: u8, 107 | } 108 | 109 | #[repr(C)] 110 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 111 | pub struct InitUnlockArgs { 112 | } 113 | 114 | #[repr(C)] 115 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 116 | pub struct UnlockArgs { 117 | } 118 | 119 | #[repr(C)] 120 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 121 | pub struct WithdrawArgs { 122 | pub data: WithdrawArgsData, 123 | } 124 | 125 | #[repr(u8)] 126 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Debug)] 127 | pub enum WithdrawArgsData { 128 | FromMemory { 129 | account_index: u16, 130 | } = 0, 131 | FromStorage { 132 | packed_va: Vec, 133 | proof: Vec, 134 | signature: Signature, 135 | } = 1, 136 | FromDeposit { 137 | bump: u8, 138 | } = 2, 139 | } -------------------------------------------------------------------------------- /idl/src/programs/code-vm/src/consts.rs: -------------------------------------------------------------------------------- 1 | pub const MAX_NAME_LEN: usize = 32; -------------------------------------------------------------------------------- /idl/src/programs/code-vm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | mod consts; 4 | mod types; 5 | mod state; 6 | mod args; 7 | mod instructions; 8 | 9 | use consts::*; 10 | use types::*; 11 | use args::*; 12 | use instructions::*; 13 | 14 | declare_id!("vmZ1WUq8SxjBWcaeTCvgJRZbS84R61uniFsQy5YMRTJ"); 15 | 16 | #[program] 17 | pub mod code_vm { 18 | use super::*; 19 | 20 | pub fn init_vm(_ctx: Context, _data: InitVmArgs) -> Result<()> { 21 | Ok(()) 22 | } 23 | pub fn init_memory(_ctx: Context, _data: InitMemoryArgs) -> Result<()> { 24 | Ok(()) 25 | } 26 | 27 | pub fn init_storage(_ctx: Context, _data: InitStorageArgs) -> Result<()> { 28 | Ok(()) 29 | } 30 | 31 | pub fn init_relay(_ctx: Context, _data: InitRelayArgs) -> Result<()> { 32 | Ok(()) 33 | } 34 | 35 | pub fn init_nonce(_ctx: Context, _data: InitNonceArgs) -> Result<()> { 36 | Ok(()) 37 | } 38 | 39 | pub fn init_timelock(_ctx: Context, _data: InitTimelockArgs) -> Result<()> { 40 | Ok(()) 41 | } 42 | 43 | pub fn init_unlock(_ctx: Context, _data: InitUnlockArgs) -> Result<()> { 44 | Ok(()) 45 | } 46 | 47 | pub fn exec(_ctx: Context, _data: ExecArgs) -> Result<()> { 48 | Ok(()) 49 | } 50 | 51 | pub fn compress(_ctx: Context, _data: CompressArgs) -> Result<()> { 52 | Ok(()) 53 | } 54 | 55 | pub fn decompress(_ctx: Context, _data: DecompressArgs) -> Result<()> { 56 | Ok(()) 57 | } 58 | 59 | pub fn resize_memory(_ctx: Context, _data: ResizeMemoryArgs) -> Result<()> { 60 | Ok(()) 61 | } 62 | 63 | pub fn snapshot(_ctx: Context, _data: SnapshotArgs) -> Result<()> { 64 | Ok(()) 65 | } 66 | 67 | pub fn deposit(_ctx: Context, _data: DepositArgs) -> Result<()> { 68 | Ok(()) 69 | } 70 | 71 | pub fn withdraw(_ctx: Context, _data: WithdrawArgs) -> Result<()> { 72 | Ok(()) 73 | } 74 | 75 | pub fn unlock(_ctx: Context, _data: UnlockArgs) -> Result<()> { 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /idl/src/programs/code-vm/src/state.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use crate::{types::*, MAX_NAME_LEN}; 3 | 4 | pub type RelayHistory = CircularBuffer<32, 32>; 5 | pub type RelayTree = MerkleTree<64>; 6 | pub type CompressedState = MerkleTree<24>; 7 | 8 | #[account] 9 | #[repr(C, align(8))] 10 | #[derive(Copy, Debug, PartialEq)] 11 | pub struct CodeVmAccount { 12 | pub authority: Pubkey, 13 | pub mint: Pubkey, 14 | pub slot: u64, 15 | pub poh: Hash, 16 | pub omnibus: TokenPool, 17 | pub lock_duration: u8, // in days 18 | pub bump: u8, 19 | 20 | _padding: [u8; 5], 21 | } 22 | 23 | 24 | #[account] 25 | #[repr(C, align(8))] 26 | #[derive(Copy, Debug, PartialEq)] 27 | pub struct MemoryAccount { 28 | pub vm: Pubkey, 29 | pub name: [u8; MAX_NAME_LEN], 30 | pub bump: u8, 31 | 32 | pub packed_info: [u8; 8], 33 | //pub _data: PhantomData<[u8]>, 34 | } 35 | 36 | #[account] 37 | #[repr(C, align(8))] 38 | #[derive(Copy, Debug, PartialEq)] 39 | pub struct RelayAccount { 40 | pub vm: Pubkey, 41 | pub name: [u8; MAX_NAME_LEN], 42 | 43 | pub treasury: TokenPool, 44 | pub bump: u8, 45 | pub num_levels: u8, 46 | pub num_history: u8, 47 | 48 | _padding: [u8; 4], 49 | 50 | pub recent_roots: RelayHistory, 51 | pub history: RelayTree, 52 | } 53 | 54 | #[account] 55 | #[repr(C, align(8))] 56 | #[derive(Copy, Debug, PartialEq)] 57 | pub struct StorageAccount { 58 | pub vm: Pubkey, 59 | pub name: [u8; MAX_NAME_LEN], 60 | pub bump: u8, 61 | pub depth: u8, 62 | 63 | _padding: [u8; 6], 64 | 65 | pub compressed_state: CompressedState, 66 | } 67 | 68 | #[account] 69 | #[repr(C, align(8))] 70 | #[derive(Copy, Debug, PartialEq)] 71 | pub struct UnlockStateAccount { 72 | pub vm: Pubkey, 73 | pub owner: Pubkey, 74 | pub address: Pubkey, 75 | pub unlock_at: i64, 76 | pub bump: u8, 77 | pub state: u8, 78 | 79 | _padding: [u8; 6], 80 | } 81 | 82 | #[account] 83 | #[repr(C, align(8))] 84 | #[derive(Copy, Debug, PartialEq)] 85 | pub struct WithdrawReceiptAccount { 86 | pub unlock_pda: Pubkey, 87 | pub nonce: Hash, 88 | pub amount: u64, 89 | pub bump: u8, 90 | 91 | _padding: [u8; 7], 92 | } -------------------------------------------------------------------------------- /idl/src/programs/code-vm/src/types.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[repr(C)] 4 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Debug)] 5 | pub struct Hash { 6 | pub(crate) value: [u8; 32] 7 | } 8 | 9 | #[repr(C)] 10 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Debug)] 11 | pub struct Signature { 12 | pub(crate) value: [u8; 64] 13 | } 14 | 15 | #[repr(C)] 16 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Debug)] 17 | pub struct TokenPool { 18 | pub vault: Pubkey, 19 | pub vault_bump: u8, 20 | } 21 | 22 | #[repr(C)] 23 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Debug)] 24 | pub struct CircularBuffer { 25 | pub items: [[u8; M]; N], 26 | pub offset: u8, 27 | pub num_items: u8, 28 | _padding: [u8; 6], 29 | } 30 | 31 | #[repr(C)] 32 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Debug)] 33 | pub struct MerkleTree { 34 | pub root: Hash, 35 | pub filled_subtrees: [Hash; N], 36 | pub zero_values: [Hash; N], 37 | pub next_index: u64, 38 | } 39 | -------------------------------------------------------------------------------- /idl/src/scripts/update-discriminators.ts: -------------------------------------------------------------------------------- 1 | import * as idl from "../target/idl/code_vm.json"; 2 | 3 | // Pulled from: 4 | // https://github.com/code-payments/code-vm/blob/aa4d916a1179feca99155ede5a6f11035eb0f2f2/api/src/instruction.rs#L11 5 | const instructionValues = { 6 | init_vm: [1], 7 | init_memory: [2], 8 | init_storage: [3], 9 | init_relay: [4], 10 | init_nonce: [5], 11 | init_timelock: [6], 12 | init_unlock: [7], 13 | exec: [8], 14 | compress: [9], 15 | decompress: [10], 16 | resize_memory: [11], 17 | snapshot: [12], 18 | deposit: [13], 19 | withdraw: [14], 20 | unlock: [15], 21 | }; 22 | 23 | // Pulled from: 24 | // https://github.com/code-payments/code-vm/blob/aa4d916a1179feca99155ede5a6f11035eb0f2f2/api/src/state.rs#L14 25 | const accountValues = { 26 | CodeVmAccount: [1, 0, 0, 0, 0, 0, 0, 0], 27 | MemoryAccount: [2, 0, 0, 0, 0, 0, 0, 0], 28 | StorageAccount: [3, 0, 0, 0, 0, 0, 0, 0], 29 | RelayAccount: [4, 0, 0, 0, 0, 0, 0, 0], 30 | UnlockStateAccount: [5, 0, 0, 0, 0, 0, 0, 0], 31 | WithdrawReceiptAccount: [6, 0, 0, 0, 0, 0, 0, 0], 32 | } 33 | 34 | function updateDiscriminators() { 35 | const instructions = idl.instructions; 36 | for (let ix of instructions) { 37 | const val = instructionValues[ix.name]; 38 | if (val === undefined) { 39 | throw new Error(`Instruction ${ix.name} not found`); 40 | } 41 | ix.discriminator = val; 42 | } 43 | 44 | const accounts = idl.accounts; 45 | for (const acc of accounts) { 46 | const val = accountValues[acc.name]; 47 | if (val === undefined) { 48 | throw new Error(`Account ${acc.name} not found`); 49 | } 50 | acc.discriminator = val; 51 | } 52 | 53 | return idl; 54 | } 55 | 56 | console.log(JSON.stringify(updateDiscriminators(), null, 2)); -------------------------------------------------------------------------------- /idl/src/tests/vm.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from "vitest"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | import { PublicKey, Keypair, Connection, LAMPORTS_PER_SOL } from '@solana/web3.js' 4 | import { CodeVm } from "../target/types/code_vm"; 5 | import { createMint } from "@solana/spl-token"; 6 | 7 | describe("vm-code", async () => { 8 | it("should run an instruction using the IDL", async () => { 9 | const { program, mint, signer } = await testSandbox(); 10 | 11 | const [vmAddress, vmBump] = await PublicKey.findProgramAddress( 12 | [ 13 | Buffer.from('code_vm'), 14 | mint.toBuffer(), 15 | signer.publicKey.toBuffer(), 16 | Buffer.from([21]), 17 | ], 18 | program.programId 19 | ) 20 | 21 | const [OmnibusAddress, vmOmnibusBump] = await PublicKey.findProgramAddress( 22 | [ 23 | Buffer.from('code_vm'), 24 | Buffer.from('vm_omnibus'), 25 | vmAddress.toBuffer() 26 | ], 27 | program.programId 28 | ) 29 | 30 | const tx = await program.methods.initVm( 31 | { 32 | lockDuration: 21, 33 | vmBump, 34 | vmOmnibusBump, 35 | } 36 | ).accountsPartial({ 37 | vmAuthority: signer.publicKey, 38 | vm: vmAddress, 39 | omnibus: OmnibusAddress, 40 | mint, 41 | }) 42 | .signers([signer]) 43 | .rpc({ skipPreflight: true }); 44 | 45 | console.log("Your transaction signature", tx); 46 | }); 47 | }); 48 | 49 | export type TestEnv = { 50 | program: anchor.Program; 51 | connection: Connection; 52 | }; 53 | 54 | export type TestSandbox = TestEnv & { 55 | signer: Keypair; 56 | mint: PublicKey; 57 | mint_key: Keypair; 58 | lock_duration: number; 59 | }; 60 | 61 | async function testEnv() : Promise { 62 | // Configure the client to use the local cluster and a provided wallet 63 | // keypair. We do this because "anchor test" runs a script to build the IDL, 64 | // this would fail because our discriminator values are not correct. 65 | 66 | process.env.ANCHOR_WALLET = "keypair-owner.json"; 67 | const provider = anchor.AnchorProvider.local(); 68 | anchor.setProvider(provider); 69 | 70 | const connection = anchor.getProvider().connection; 71 | const program = anchor.workspace.CodeVm as anchor.Program; 72 | return { program, connection }; 73 | } 74 | 75 | async function testSandbox() : Promise { 76 | const { program, connection } = await testEnv(); 77 | 78 | const signer = Keypair.generate(); 79 | const mint_key = Keypair.generate(); 80 | const mint = mint_key.publicKey; 81 | const lock_duration = 21; 82 | 83 | const tx = await connection.requestAirdrop(signer.publicKey, 1000 * LAMPORTS_PER_SOL); 84 | await connection.confirmTransaction(tx); 85 | await createMint(connection, signer, signer.publicKey, null, 5, mint_key); 86 | 87 | return { program, connection, signer, mint, mint_key, lock_duration }; 88 | } -------------------------------------------------------------------------------- /idl/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "NodeNext", 7 | "target": "es6", 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true, 10 | "resolvePackageJsonImports": true, 11 | "moduleResolution": "NodeNext" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "code-vm-program" 3 | description = "Purpose built VM for reduced fees on Solana" 4 | version = "0.2.0" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | test-sbf = [] 17 | 18 | [dependencies] 19 | code-vm-api.workspace = true 20 | solana-program.workspace = true 21 | steel.workspace = true 22 | spl-token.workspace = true 23 | spl-associated-token-account.workspace = true 24 | solana-security-txt = "1.1.1" 25 | 26 | [dev-dependencies] 27 | rand = "0.8.5" 28 | solana-sdk = "1.18" 29 | litesvm = "0.2.1" 30 | litesvm-token = "0.2.1" 31 | base64 = "0.13.0" 32 | pretty-hex = "0.4.1" 33 | -------------------------------------------------------------------------------- /program/src/instruction/compress.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | /* 5 | This instruction is used to compress an account in the VM's working memory 6 | (compact_mem) into the VM's cold storage (compressed_mem). This action takes 7 | the virtual account state off chain and stores it in a more efficient form 8 | on chain. 9 | 10 | Before an account is compressed, the data of that account is hashed and 11 | signed by the VM authority. This signature is used to prove that the account 12 | was witnessed by the VM authority as it currently exists in the VM's working 13 | memory before it is compressed. 14 | 15 | Accounts expected by this instruction: 16 | 17 | | # | R/W | Type | PDA | Name | Description | 18 | |---|-----|---------|-----|--------------|------------------------------------------| 19 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 20 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 21 | | 2 | mut | Memory | PDA | vm_memory | The memory account to pull from. | 22 | | 3 | mut | Storage | PDA | vm_storage | The storage account to push to. | 23 | 24 | 25 | Derived account seeds: 26 | 27 | 1. vm: [ "code_vm", , , ] 28 | 2. vm_memory: [ "code_vm", "vm_memory_account", , ] 29 | 3. vm_storage: [ "code_vm", "vm_storage_account", , ] 30 | 31 | Instruction data: 32 | 33 | 0. account_index: u16 - The index of the account in the VM's paged memory. 34 | 1. signature: [u8; 64] - A signature of the current account state signed by the VM authority. 35 | */ 36 | pub fn process_compress(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 37 | let args = CompressIx::try_from_bytes(data)?.to_struct()?; 38 | let [vm_authority_info, vm_info, vm_memory_info, vm_storage_info] = accounts else { 39 | return Err(ProgramError::NotEnoughAccountKeys); 40 | }; 41 | 42 | check_signer(vm_authority_info)?; 43 | check_mut(vm_info)?; 44 | check_mut(vm_memory_info)?; 45 | check_mut(vm_storage_info)?; 46 | 47 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 48 | 49 | check_memory(vm_memory_info, vm_info)?; 50 | check_storage(vm_storage_info, vm_info)?; 51 | 52 | let va = try_read(vm_memory_info, args.account_index)?; 53 | let va_hash = va.get_hash(); 54 | 55 | sig_verify( 56 | vm_authority_info.key.as_ref(), 57 | args.signature.as_ref(), 58 | va_hash.as_ref(), 59 | )?; 60 | 61 | let sig_hash = hashv(&[args.signature.as_ref(), va_hash.as_ref()]); 62 | 63 | try_compress(vm_storage_info, sig_hash)?; 64 | try_delete(vm_memory_info, args.account_index)?; 65 | 66 | vm.advance_poh(CodeInstruction::CompressIx, accounts, data); 67 | 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /program/src/instruction/decompress.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | /* 5 | This instruction is used to decompress a virtual account from the VM's cold 6 | storage (compressed_mem). The full account state along with a signature of 7 | the state is required in order to decompress it. The virtual account is 8 | decompressed into the VM's working memory (compact_mem) at the specified 9 | account_index. 10 | 11 | Accounts expected by this instruction: 12 | 13 | | # | R/W | Type | Req | PDA | Name | Description | 14 | |---|-----|-----------------|-----|-----|------------------|------------------------------------------| 15 | | 0 | mut | Signer | Yes | | vm_authority | The authority of the VM. | 16 | | 1 | mut | Vm | Yes | PDA | vm | The VM instance state account. | 17 | | 2 | mut | Memory | Yes | PDA | vm_memory | The memory account to pull from. | 18 | | 3 | mut | Storage | Yes | PDA | vm_storage | The storage account to push to. | 19 | | 4 | | UnlockState | | PDA | unlock_pda | State for unlocked timelock accounts. | 20 | | 5 | | WithdrawReceipt | | PDA | withdraw_receipt | State for withdrawn tokens. | 21 | 22 | 23 | Derived account seeds: 24 | 25 | 1. vm: [ "code_vm", , , ] 26 | 2. vm_memory: [ "code_vm", "vm_memory_account", , ] 27 | 3. vm_storage: [ "code_vm", "vm_storage_account", , ] 28 | 4. unlock_pda: [ "code_vm", "vm_unlock_pda_account", , , ] 29 | 30 | Instruction data: 31 | 32 | 0. account_index: u16 - The index of the account in the VM's paged memory. 33 | 1. signature: [u8; 64] - A signature of the current account state signed by the VM authority. 34 | 35 | Notes: 36 | 37 | * unlock_pda 38 | We expect this to be uninitialized in the happy path. If this account 39 | exists and has a non-locked state, then the decompress instruction will 40 | fail. 41 | 42 | * withdraw_receipt 43 | This account is used to prove that a user has not non-custodially 44 | withdrawn tokens from a virtual account. If this account exists, then 45 | the decompress instruction will fail. 46 | */ 47 | pub fn process_decompress(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 48 | let args = DecompressIx::try_from_slice(data)?; 49 | 50 | let ( 51 | vm_authority_info, 52 | vm_info, 53 | vm_memory_info, 54 | vm_storage_info, 55 | unlock_pda_info, // optional 56 | withdraw_receipt_info, // optional 57 | ) = match accounts { 58 | [a1, a2, a3, a4, a5, a6] => (a1, a2, a3, a4, get_optional(a5), get_optional(a6)), 59 | _ => return Err(ProgramError::NotEnoughAccountKeys), 60 | }; 61 | 62 | check_signer(vm_authority_info)?; 63 | check_mut(vm_info)?; 64 | check_mut(vm_memory_info)?; 65 | check_mut(vm_storage_info)?; 66 | 67 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 68 | 69 | check_memory(vm_memory_info, vm_info)?; 70 | check_storage(vm_storage_info, vm_info)?; 71 | check_is_empty(vm_memory_info, args.account_index)?; 72 | 73 | let unchecked_va = VirtualAccount::unpack(&args.packed_va)?; 74 | match unchecked_va { 75 | VirtualAccount::Timelock(vta) => { 76 | check_condition( 77 | unlock_pda_info.is_some(), 78 | "unlock_pda address is required for timelocked virtual accounts", 79 | )?; 80 | 81 | check_condition( 82 | withdraw_receipt_info.is_some(), 83 | "withdraw_receipt address is required for timelocked virtual accounts", 84 | )?; 85 | 86 | check_timelock_state( 87 | &vta, 88 | vm, 89 | vm_info, 90 | unlock_pda_info.unwrap(), 91 | withdraw_receipt_info.unwrap(), 92 | )?; 93 | } 94 | VirtualAccount::Nonce(_) => { 95 | // Nonce accounts are not timelocked 96 | } 97 | VirtualAccount::Relay(_) => { 98 | // Relay accounts are not timelocked 99 | } 100 | } 101 | 102 | let va = unchecked_va; 103 | let va_hash = va.get_hash(); 104 | 105 | sig_verify( 106 | vm_authority_info.key.as_ref(), 107 | args.signature.as_ref(), 108 | va_hash.as_ref(), 109 | )?; 110 | 111 | let sig_hash = hashv(&[args.signature.as_ref(), va_hash.as_ref()]); 112 | try_decompress(vm_storage_info, sig_hash, &args.proof)?; 113 | try_write(vm_memory_info, args.account_index, &va)?; 114 | 115 | vm.advance_poh(CodeInstruction::DecompressIx, accounts, data); 116 | 117 | Ok(()) 118 | } 119 | 120 | fn check_timelock_state( 121 | vta: &VirtualTimelockAccount, 122 | vm: &CodeVmAccount, 123 | vm_info: &AccountInfo<'_>, 124 | unlock_pda_info: &AccountInfo<'_>, 125 | withdraw_receipt_info: &AccountInfo<'_>, 126 | ) -> ProgramResult { 127 | let timelock_address = 128 | vta.get_timelock_address(&vm.get_mint(), &vm.get_authority(), vm.get_lock_duration()); 129 | 130 | let unlock_address = vta.get_unlock_address(&timelock_address, &vm_info.key); 131 | 132 | check_condition( 133 | unlock_pda_info.key.eq(&unlock_address), 134 | "unlock_pda does not match the expected unlock address", 135 | )?; 136 | 137 | let receipt_address = vta.get_withdraw_receipt_address(&unlock_address, &vm_info.key); 138 | 139 | check_condition( 140 | withdraw_receipt_info.key.eq(&receipt_address), 141 | "withdraw_receipt does not match the expected receipt address", 142 | )?; 143 | 144 | // Check that the receipt account is empty (no data; len == 0) 145 | check_condition( 146 | withdraw_receipt_info.data_is_empty(), 147 | "withdraw_receipt is not empty", 148 | )?; 149 | 150 | // If we have made it this far, then we can assume that the account has not 151 | // been non-custodially withdrawn from (yet). 152 | 153 | // The account might be unlocked, but we don't really care as long as the 154 | // withdraw_receipt is empty still. 155 | 156 | Ok(()) 157 | } 158 | -------------------------------------------------------------------------------- /program/src/instruction/deposit.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | /* 5 | This instruction pulls in a token deposit that was made by a user. In this 6 | instruction, tokens are moved from the deposit_ata to the vm omnibus and a 7 | virtual account owned by the depositor is updated. 8 | 9 | Users that wish to timelock/depsit tokens must first find their derived 10 | deposit PDA. This is exposed by the mobile app. From there, the user can 11 | send tokens to the associated token address of that deposit PDA using any 12 | SPL token wallet. 13 | 14 | Once they have done this, we can call this instruction to pull in the 15 | deposit and update the user's virtual account. 16 | 17 | Accounts expected by this instruction: 18 | 19 | | # | R/W | Type | PDA | Name | Description | 20 | |---|-----|--------------|-----|---------------|-----------------------------------------------| 21 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 22 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 23 | | 2 | mut | Memory | PDA | vm_memory | The memory account to pull from. | 24 | | 3 | | Address | | depositor | The owner of this deposit. | 25 | | 4 | | Address | PDA | deposit_pda | A derived account within the VM address space.| 26 | | 5 | mut | TokenAccount | PDA | deposit_ata | A derived token account owned by deposit_pda. | 27 | | 6 | mut | TokenAccount | PDA | omnibus | A derived token account owned by vm. | 28 | | 7 | | Program | | token_program | The SPL token program. | 29 | 30 | 31 | Derived account seeds: 32 | 33 | 1. vm: [ "code_vm", , , ] 34 | 2. vm_memory: [ "code_vm", "vm_memory_account", , ] 35 | 3. deposit_pda: [ "code_vm", "vm_deposit_pda", , ] 36 | 3. deposit_ata: 37 | 38 | Instruction data: 39 | 40 | 0. account_index: u16 - The index of the account in the VM's paged memory. 41 | 1. signature: [u8; 64] - A signature of the current account state signed by the VM authority. 42 | */ 43 | pub fn process_deposit(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 44 | let args = DepositIx::try_from_bytes(data)?.to_struct()?; 45 | let [ 46 | vm_authority_info, 47 | vm_info, 48 | vm_memory_info, 49 | depositor_info, 50 | deposit_pda_info, 51 | deposit_ata_info, 52 | omnibus_info, 53 | token_program_info, 54 | ] = accounts else { 55 | return Err(ProgramError::NotEnoughAccountKeys); 56 | }; 57 | 58 | check_signer(vm_authority_info)?; 59 | check_mut(vm_info)?; 60 | check_mut(vm_memory_info)?; 61 | check_mut(deposit_ata_info)?; 62 | check_mut(omnibus_info)?; 63 | check_program(token_program_info, &spl_token::id())?; 64 | 65 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 66 | 67 | check_omnibus(omnibus_info, vm_info)?; 68 | check_memory(vm_memory_info, vm_info)?; 69 | 70 | let va = try_read(&vm_memory_info, args.account_index)?; 71 | let mut vta = va.into_inner_timelock().unwrap(); 72 | 73 | check_condition( 74 | vta.owner.eq(depositor_info.key), 75 | "The depositor does not own this account", 76 | )?; 77 | 78 | transfer_signed( 79 | deposit_pda_info, 80 | deposit_ata_info, 81 | omnibus_info, 82 | token_program_info, 83 | args.amount, 84 | &[&[ 85 | CODE_VM, 86 | VM_DEPOSIT_PDA, 87 | &depositor_info.key.to_bytes(), 88 | &vm_info.key.to_bytes(), 89 | &[args.bump], 90 | ]], 91 | )?; 92 | 93 | vta.balance = vta 94 | .balance 95 | .checked_add(args.amount) 96 | .ok_or(ProgramError::ArithmeticOverflow)?; 97 | 98 | try_write( 99 | vm_memory_info, 100 | args.account_index, 101 | &VirtualAccount::Timelock(vta), 102 | )?; 103 | 104 | vm.advance_poh(CodeInstruction::DepositIx, accounts, data); 105 | 106 | Ok(()) 107 | } 108 | -------------------------------------------------------------------------------- /program/src/instruction/init_memory.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use solana_program::{system_program, sysvar}; 3 | use steel::*; 4 | 5 | /* 6 | This instruction initializes a new virtual account memory module for a VM 7 | instance. The VM is able to execute opcodes on virtual accounts that are 8 | stored in one of these. 9 | 10 | Accounts expected by this instruction: 11 | 12 | | # | R/W | Type | PDA | Name | Description | 13 | |---|-----|---------|-----|----------------|------------------------------------------| 14 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 15 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 16 | | 2 | mut | Memory | PDA | vm_memory | The memory account to create. | 17 | | 3 | | Program | | system_program | The system program. | 18 | | 4 | | Sysvar | | rent_sysvar | The rent sysvar. | 19 | 20 | 21 | Derived account seeds: 22 | 23 | 1. vm: [ "code_vm", , , ] 24 | 2. vm_memory: [ "code_vm", "vm_memory_account", , ] 25 | 26 | 27 | Instruction data: 28 | 29 | 0. name: [u8; 32] - The name of this memory module. 30 | 1. num_accounts: u32 - The number of accounts that can be stored in this memory module. 31 | 2. account_size: u16 - The size of each account in this memory module. 32 | 3. vm_memory_bump: u8 - The bump seed for the this memory account. 33 | */ 34 | pub fn process_init_memory(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 35 | let args = InitMemoryIx::try_from_bytes(data)?.to_struct()?; 36 | let [ 37 | vm_authority_info, 38 | vm_info, 39 | vm_memory_info, 40 | system_program_info, 41 | rent_sysvar_info 42 | ] = accounts else { 43 | return Err(ProgramError::NotEnoughAccountKeys); 44 | }; 45 | 46 | check_condition( 47 | args.account_size as usize >= MIN_ACCOUNT_SIZE && 48 | args.account_size as usize <= MAX_ACCOUNT_SIZE, 49 | "account_size must be between MIN_ACCOUNT_SIZE and MAX_ACCOUNT_SIZE", 50 | )?; 51 | 52 | check_condition( 53 | args.num_accounts as usize <= MAX_NUM_ACCOUNTS, 54 | "num_accounts must be less than MAX_NUM_ACCOUNTS", 55 | )?; 56 | 57 | check_signer(vm_authority_info)?; 58 | check_mut(vm_info)?; 59 | check_mut(vm_memory_info)?; 60 | check_program(system_program_info, &system_program::id())?; 61 | check_sysvar(rent_sysvar_info, &sysvar::rent::id())?; 62 | 63 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 64 | 65 | check_uninitialized_pda( 66 | vm_memory_info, 67 | &[ 68 | CODE_VM, 69 | VM_MEMORY_ACCOUNT, 70 | args.name.as_ref(), 71 | vm_info.key.as_ref(), 72 | ], 73 | args.vm_memory_bump, 74 | &code_vm_api::id(), 75 | )?; 76 | 77 | create_account::( 78 | vm_memory_info, 79 | &code_vm_api::ID, 80 | &[ 81 | CODE_VM, 82 | VM_MEMORY_ACCOUNT, 83 | args.name.as_ref(), 84 | vm_info.key.as_ref(), 85 | &[args.vm_memory_bump], 86 | ], 87 | system_program_info, 88 | vm_authority_info, 89 | )?; 90 | 91 | let memory = vm_memory_info.to_account_mut::(&code_vm_api::ID)?; 92 | 93 | memory.version = 1; 94 | memory.name = args.name; 95 | memory.vm = vm_info.key.clone(); 96 | memory.bump = args.vm_memory_bump; 97 | memory.set_account_size(args.account_size); 98 | memory.set_num_accounts(args.num_accounts); 99 | 100 | vm.advance_poh(CodeInstruction::InitMemoryIx, accounts, data); 101 | 102 | Ok(()) 103 | } 104 | -------------------------------------------------------------------------------- /program/src/instruction/init_nonce.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | /* 5 | This instruction initializes a new virtual durable nonce. The nonce is 6 | functionally similar to a real durable nonce within Solana, but is stored in 7 | the VM's memory. 8 | 9 | Accounts expected by this instruction: 10 | 11 | | # | R/W | Type | PDA | Name | Description | 12 | |---|-----|---------|-----|------------------------|------------------------------------------| 13 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 14 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 15 | | 2 | mut | Memory | PDA | vm_memory | Where to create the virtual account. | 16 | | 3 | | Address | | virtual_account_owner | The virtual account owner. | 17 | 18 | 19 | Derived account seeds: 20 | 21 | 1. vm: [ "code_vm", , , ] 22 | 2. vm_memory: [ "code_vm", "vm_memory_account", , ] 23 | 24 | 25 | Instruction data: 26 | 27 | 0. account_index: u16 - The location in the VM's paged memory to create the account. 28 | 0. nonce_bump: u8 - The bump seed for the nonce account address. 29 | */ 30 | pub fn process_init_nonce(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 31 | let args = InitNonceIx::try_from_bytes(data)?.to_struct()?; 32 | let [ 33 | vm_authority_info, 34 | vm_info, 35 | vm_memory_info, 36 | virtual_account_owner_info, 37 | ] = accounts else { 38 | return Err(ProgramError::NotEnoughAccountKeys); 39 | }; 40 | 41 | check_signer(vm_authority_info)?; 42 | check_mut(vm_info)?; 43 | check_mut(vm_memory_info)?; 44 | check_readonly(virtual_account_owner_info)?; 45 | 46 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 47 | 48 | check_memory(vm_memory_info, vm_info)?; 49 | check_is_empty(vm_memory_info, args.account_index)?; 50 | 51 | let seed = virtual_account_owner_info.key; 52 | let (nonce_address, _) = find_virtual_nonce_pda( 53 | &vm_info.key, seed, &vm.get_current_poh() 54 | ); 55 | 56 | let vdn = VirtualDurableNonce { 57 | address: nonce_address, 58 | value: vm.get_current_poh(), 59 | }; 60 | 61 | let va = VirtualAccount::Nonce(vdn); 62 | 63 | try_write(vm_memory_info, args.account_index, &va)?; 64 | 65 | vm.advance_poh(CodeInstruction::InitNonceIx, accounts, data); 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /program/src/instruction/init_relay.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use solana_program::{system_program, sysvar}; 3 | use steel::*; 4 | 5 | /* 6 | This instruction creates a new relay account and treasury. Relay accounts 7 | are used to facilitate private transfers using the Code privacy protocol. 8 | 9 | Accounts expected by this instruction: 10 | 11 | | # | R/W | Type | PDA | Name | Description | 12 | |---|-----|--------------|-----|----------------|------------------------------------------| 13 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 14 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 15 | | 2 | mut | Relay | PDA | vm_relay | The relay account to create. | 16 | | 3 | mut | TokenAccount | PDA | vm_relay_vault | The relay token account to create. | 17 | | 4 | | TokenMint | | mint | The mint to use for this relay. | 18 | | 5 | | Program | | token_program | The SPL token program. | 19 | | 6 | | Program | | system_program | The system program. | 20 | | 7 | | Sysvar | | rent_sysvar | The rent sysvar. | 21 | 22 | 23 | Derived account seeds: 24 | 25 | 1. vm: [ "code_vm", , , ] 26 | 2. relay: [ "code_vm", "vm_relay_account", , ] 27 | 2. relay_vault: [ "code_vm", "vm_relay_vault", ] 28 | 29 | 30 | Instruction data: 31 | 32 | 0. name: [u8; 32] - The name of this storage module. 33 | 1. relay_bump: u8 - The bump seed for the this relay account. 34 | 2. relay_vault_bump: u8 - The bump seed for the relay token account. 35 | */ 36 | pub fn process_init_relay(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 37 | let args = InitRelayIx::try_from_bytes(data)?; 38 | let [ 39 | vm_authority_info, 40 | vm_info, 41 | relay_info, 42 | relay_vault_info, 43 | mint_info, 44 | token_program_info, 45 | system_program_info, 46 | rent_sysvar_info 47 | ] = accounts else { 48 | return Err(ProgramError::NotEnoughAccountKeys); 49 | }; 50 | 51 | check_signer(vm_authority_info)?; 52 | check_mut(vm_info)?; 53 | check_mut(relay_info)?; 54 | check_mut(relay_vault_info)?; 55 | check_readonly(mint_info)?; 56 | check_program(token_program_info, &spl_token::id())?; 57 | check_program(system_program_info, &system_program::id())?; 58 | check_sysvar(rent_sysvar_info, &sysvar::rent::id())?; 59 | 60 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 61 | 62 | check_condition( 63 | mint_info.key == &vm.mint, 64 | "mint account does not match VM instance", 65 | )?; 66 | 67 | check_uninitialized_pda( 68 | relay_info, 69 | &[ 70 | CODE_VM, 71 | VM_RELAY_ACCOUNT, 72 | args.name.as_ref(), 73 | vm_info.key.as_ref(), 74 | ], 75 | args.relay_bump, 76 | &code_vm_api::id(), 77 | )?; 78 | check_uninitialized_pda( 79 | relay_vault_info, 80 | &[ 81 | CODE_VM, 82 | VM_RELAY_VAULT, 83 | relay_info.key.as_ref() 84 | ], 85 | args.relay_vault_bump, 86 | &code_vm_api::id(), 87 | )?; 88 | 89 | create_account::( 90 | relay_info, 91 | &code_vm_api::ID, 92 | &[ 93 | CODE_VM, 94 | VM_RELAY_ACCOUNT, 95 | args.name.as_ref(), 96 | vm_info.key.as_ref(), 97 | &[args.relay_bump], 98 | ], 99 | system_program_info, 100 | vm_authority_info, 101 | )?; 102 | 103 | create_token_account( 104 | mint_info, 105 | relay_vault_info, 106 | &[ 107 | CODE_VM, 108 | VM_RELAY_VAULT, 109 | relay_info.key.as_ref(), 110 | &[args.relay_vault_bump], 111 | ], 112 | vm_authority_info, 113 | system_program_info, 114 | rent_sysvar_info, 115 | )?; 116 | 117 | let relay = relay_info.to_account_mut::(&code_vm_api::ID)?; 118 | 119 | relay.vm = vm_info.key.clone(); 120 | relay.bump = args.relay_bump; 121 | relay.name = args.name; 122 | relay.num_levels = RELAY_STATE_DEPTH as u8; 123 | relay.num_history = RELAY_HISTORY_ITEMS as u8; 124 | 125 | relay.treasury.vault = relay_vault_info.key.clone(); 126 | relay.treasury.vault_bump = args.relay_vault_bump; 127 | 128 | relay 129 | .history 130 | .init(&[MERKLE_TREE_SEED, relay_info.key.as_ref()]); 131 | 132 | relay.recent_roots.push(relay.history.get_root().as_ref()); 133 | 134 | vm.advance_poh(CodeInstruction::InitRelayIx, accounts, data); 135 | 136 | Ok(()) 137 | } 138 | -------------------------------------------------------------------------------- /program/src/instruction/init_storage.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use solana_program::{ 3 | system_program, 4 | sysvar, 5 | }; 6 | use steel::*; 7 | 8 | /* 9 | This instruction initializes a new cold storage account for a VM instance. 10 | The VM can compress virtual accounts into one of these. This is useful for 11 | storing virtual accounts that are not frequently accessed or dormant. It 12 | reduces the rent cost to practically zero. 13 | 14 | Before an account can be compressed, it must be hashed and signed by the VM 15 | authority. This signature is used to prove that the account was witnessed by 16 | the VM authority as it currently exists on-chain. 17 | 18 | Before an account can be used by the VM again, it must be decompressed. The 19 | decompress process works in reverse to this instruction but also requires a 20 | merkle proof. 21 | 22 | Accounts expected by this instruction: 23 | 24 | | # | R/W | Type | PDA | Name | Description | 25 | |---|-----|---------|-----|----------------|------------------------------------------| 26 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 27 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 28 | | 2 | mut | Storage | PDA | vm_storage | The storage account to create. | 29 | | 3 | | Program | | system_program | The system program. | 30 | | 4 | | Sysvar | | rent_sysvar | The rent sysvar. | 31 | 32 | 33 | Derived account seeds: 34 | 35 | 1. vm: [ "code_vm", , , ] 36 | 2. vm_storage: [ "code_vm", "vm_storage_account", , ] 37 | 38 | 39 | Instruction data: 40 | 41 | 0. name: [u8; 32] - The name of this storage module. 42 | 1. vm_stroage_bump: u8 - The bump seed for the this memory account. 43 | */ 44 | pub fn process_init_storage(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 45 | 46 | let args = InitStorageIx::try_from_bytes(data)?; 47 | let [ 48 | vm_authority_info, 49 | vm_info, 50 | vm_storage_info, 51 | system_program_info, 52 | rent_sysvar_info 53 | ] = accounts else { 54 | return Err(ProgramError::NotEnoughAccountKeys); 55 | }; 56 | 57 | check_signer(vm_authority_info)?; 58 | check_mut(vm_info)?; 59 | check_mut(vm_storage_info)?; 60 | check_program(system_program_info, &system_program::id())?; 61 | check_sysvar(rent_sysvar_info, &sysvar::rent::id())?; 62 | 63 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 64 | 65 | check_uninitialized_pda( 66 | vm_storage_info, 67 | &[ 68 | CODE_VM, 69 | VM_STORAGE_ACCOUNT, 70 | args.name.as_ref(), 71 | vm_info.key.as_ref() 72 | ], 73 | args.vm_storage_bump, 74 | &code_vm_api::id() 75 | )?; 76 | 77 | create_account::( 78 | vm_storage_info, 79 | &code_vm_api::ID, 80 | &[ 81 | CODE_VM, 82 | VM_STORAGE_ACCOUNT, 83 | args.name.as_ref(), 84 | vm_info.key.as_ref(), 85 | &[args.vm_storage_bump] 86 | ], 87 | system_program_info, 88 | vm_authority_info, 89 | )?; 90 | 91 | let storage = vm_storage_info.to_account_mut::(&code_vm_api::ID)?; 92 | 93 | storage.vm = vm_info.key.clone(); 94 | storage.bump = args.vm_storage_bump; 95 | storage.name = args.name; 96 | storage.depth = COMPRESSED_STATE_DEPTH as u8; // not really needed but we have a few free bytes. 97 | 98 | storage.compressed_state.init(&[ 99 | MERKLE_TREE_SEED, 100 | &args.name.as_ref(), 101 | vm_info.key.as_ref() 102 | ]); 103 | 104 | vm.advance_poh(CodeInstruction::InitStorageIx, accounts, data); 105 | 106 | Ok(()) 107 | } 108 | 109 | -------------------------------------------------------------------------------- /program/src/instruction/init_timelock.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::{prelude::*, pdas}; 2 | use steel::*; 3 | 4 | /* 5 | This instruction initializes a virtual timelock account. A timelock account 6 | forms a state channel between the VM authority and the timelock account 7 | owner. This allows instant transfers of tokens. 8 | 9 | A timelock account can be non-custodially unlocked by the owner using the 10 | init_unlock and unlock instructions. 11 | 12 | Accounts expected by this instruction: 13 | 14 | | # | R/W | Type | PDA | Name | Description | 15 | |---|-----|---------|-----|------------------------|------------------------------------------| 16 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 17 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 18 | | 2 | mut | Memory | PDA | vm_memory | Where to create the virtual account. | 19 | | 3 | | Address | | virtual_account_owner | The virtual account owner. | 20 | 21 | 22 | Derived account seeds: 23 | 24 | 1. vm: [ "code_vm", , , ] 25 | 2. vm_memory: [ "code_vm", "vm_memory_account", , ] 26 | 27 | Instruction data: 28 | 29 | 0. account_index: u16 - The location in the VM's paged memory to create the account. 30 | 1. virtual_timelock_bump: u8 - The bump seed for the virtual timelock account. 31 | 2. virtual_vault_bump: u8 - The bump seed for the virtual token account. 32 | 3. unlock_pda_bump: u8 - The bump seed for the unlock PDA address. 33 | 34 | */ 35 | pub fn process_init_timelock(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 36 | 37 | let args = InitTimelockIx::try_from_bytes(data)?.to_struct()?; 38 | let [ 39 | vm_authority_info, 40 | vm_info, 41 | vm_memory_info, 42 | virtual_account_owner_info, 43 | ] = accounts else { 44 | return Err(ProgramError::NotEnoughAccountKeys); 45 | }; 46 | 47 | check_signer(vm_authority_info)?; 48 | check_mut(vm_info)?; 49 | check_mut(vm_memory_info)?; 50 | check_readonly(virtual_account_owner_info)?; 51 | 52 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 53 | 54 | check_memory(vm_memory_info, vm_info)?; 55 | check_is_empty(vm_memory_info, args.account_index)?; 56 | 57 | let owner = virtual_account_owner_info.key.clone(); 58 | let nonce = vm.get_current_poh(); 59 | 60 | let (timelock_address, timelock_bump) = pdas::find_virtual_timelock_address( 61 | &vm.get_mint(), 62 | &vm.get_authority(), 63 | &owner, 64 | vm.get_lock_duration(), 65 | ); 66 | 67 | if args.virtual_timelock_bump != timelock_bump { 68 | return Err(ProgramError::InvalidArgument); 69 | } 70 | 71 | let (unlock_address, unlock_bump) = pdas::find_unlock_address( 72 | &owner, 73 | &timelock_address, 74 | vm_info.key); 75 | 76 | if args.unlock_pda_bump != unlock_bump { 77 | return Err(ProgramError::InvalidArgument); 78 | } 79 | 80 | // We could technically require the user to provide the withdraw_bump, 81 | // however, that would make using this instruction more cumbersome since the 82 | // nonce value is determined above. 83 | let (_, withdraw_bump) = pdas::find_withdraw_receipt_address( // This call *can* be expensive 84 | &unlock_address, 85 | &nonce, 86 | vm_info.key); 87 | 88 | let vta = VirtualTimelockAccount { 89 | owner, 90 | instance: nonce, 91 | bump: args.virtual_timelock_bump, 92 | token_bump: args.virtual_vault_bump, 93 | unlock_bump: args.unlock_pda_bump, 94 | withdraw_bump, 95 | balance: 0, 96 | }; 97 | let va = VirtualAccount::Timelock(vta); 98 | 99 | try_write(vm_memory_info, args.account_index, &va)?; 100 | 101 | vm.advance_poh(CodeInstruction::InitTimelockIx, accounts, data); 102 | 103 | Ok(()) 104 | } 105 | 106 | -------------------------------------------------------------------------------- /program/src/instruction/init_unlock.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | use solana_program::msg; 4 | 5 | /* 6 | This instruction is used to begin the unlock process for a timelocked 7 | account. Once the VM lockduration has passed, the owner can finalize the 8 | unlock process and withdraw their funds non-custodially. 9 | 10 | Accounts expected by this instruction: 11 | 12 | | # | R/W | Type | PDA | Name | Description | 13 | |---|-----|-------------|-----|----------------|-----------------------------------| 14 | | 0 | mut | Signer | | account_owner | The virtual account owner. | 15 | | 1 | mut | Signer | | payer | The transaction fee payer. | 16 | | 2 | mut | Vm | PDA | vm | The VM instance state account. | 17 | | 3 | mut | UnlockState | PDA | unlock_pda | Account to create. | 18 | | 4 | | Program | | system_program | The system program. | 19 | | 5 | | Sysvar | | rent_sysvar | The rent sysvar. | 20 | 21 | 22 | Derived account seeds: 23 | 24 | 2. vm: [ "code_vm", , , ] 25 | 3. unlock_pda: [ "code_vm", "vm_unlock_pda_account", , , ] 26 | 27 | */ 28 | pub fn process_init_unlock(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 29 | 30 | let [ 31 | account_owner_info, 32 | payer_info, 33 | vm_info, 34 | unlock_pda_info, 35 | system_program_info, 36 | rent_sysvar_info, 37 | ] = accounts else { 38 | return Err(ProgramError::NotEnoughAccountKeys); 39 | }; 40 | 41 | check_signer(account_owner_info)?; 42 | check_signer(payer_info)?; 43 | check_program(system_program_info, &system_program::id())?; 44 | check_sysvar(rent_sysvar_info, &sysvar::rent::id())?; 45 | 46 | let vm = vm_info.to_account_mut::(&code_vm_api::ID)?; 47 | 48 | check_seeds( 49 | vm_info, 50 | &[ 51 | CODE_VM, 52 | vm.mint.as_ref(), 53 | vm.authority.as_ref(), 54 | vm.lock_duration.to_le_bytes().as_ref() 55 | ], 56 | vm.bump, 57 | &code_vm_api::ID 58 | )?; 59 | 60 | let (timelock_address, _) = find_virtual_timelock_address( 61 | &vm.get_mint(), 62 | &vm.get_authority(), 63 | account_owner_info.key, 64 | vm.get_lock_duration(), 65 | ); 66 | 67 | let (unlock_pda, bump) = find_unlock_address( 68 | account_owner_info.key, 69 | &timelock_address, 70 | vm_info.key, 71 | ); 72 | 73 | check_condition( 74 | unlock_pda.eq(&unlock_pda_info.key), 75 | "unlock PDA does not match the given owner", 76 | )?; 77 | 78 | check_uninitialized_pda( 79 | unlock_pda_info, 80 | &[ 81 | CODE_VM, 82 | VM_UNLOCK_ACCOUNT, 83 | account_owner_info.key.as_ref(), 84 | timelock_address.as_ref(), 85 | vm_info.key.as_ref(), 86 | ], 87 | bump, 88 | &code_vm_api::id() 89 | )?; 90 | 91 | create_account::( 92 | unlock_pda_info, 93 | &code_vm_api::ID, 94 | &[ 95 | CODE_VM, 96 | VM_UNLOCK_ACCOUNT, 97 | account_owner_info.key.as_ref(), 98 | timelock_address.as_ref(), 99 | vm_info.key.as_ref(), 100 | &[bump] 101 | ], 102 | system_program_info, 103 | payer_info, 104 | )?; 105 | 106 | let now = Clock::get()?.unix_timestamp; 107 | let second_per_day = 86400; // 60sec * 60min * 24hrs = 86400 108 | let mut unlock_at = now + (vm.lock_duration as i64 * second_per_day); 109 | if unlock_at % second_per_day > 0 { 110 | unlock_at = unlock_at + (second_per_day - (unlock_at % second_per_day)) 111 | } 112 | 113 | let unlock_pda = 114 | unlock_pda_info.to_account_mut::(&code_vm_api::id())?; 115 | 116 | unlock_pda.vm = vm_info.key.clone(); 117 | unlock_pda.bump = bump; 118 | unlock_pda.owner = account_owner_info.key.clone(); 119 | unlock_pda.address = timelock_address; 120 | unlock_pda.state = TimelockState::WaitingForTimeout as u8; 121 | unlock_pda.unlock_at = unlock_at; 122 | 123 | msg!("current time: {}", now); 124 | msg!("the timelock can be released after: {}", unlock_at); 125 | 126 | vm.advance_poh(CodeInstruction::InitUnlockIx, accounts, data); 127 | 128 | Ok(()) 129 | } 130 | 131 | -------------------------------------------------------------------------------- /program/src/instruction/init_vm.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use solana_program::{ 3 | system_program, 4 | sysvar, 5 | }; 6 | use steel::*; 7 | 8 | /* 9 | This instruction initializes a new VM instance owned by the given authority. 10 | 11 | Accounts expected by this instruction: 12 | 13 | | # | R/W | Type | PDA | Name | Description | 14 | |---|-----|------------- |-----|----------------|------------------------------------------| 15 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 16 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 17 | | 2 | mut | TokenAccount | PDA | omnibus | A derived token account owned by the VM. | 18 | | 3 | | TokenMint | | mint | The mint to use for this VM instance. | 19 | | 4 | | Program | | token_program | The SPL token program. | 20 | | 5 | | Program | | system_program | The system program. | 21 | | 6 | | Sysvar | | rent_sysvar | The rent sysvar. | 22 | 23 | 24 | Derived account seeds: 25 | 26 | 1. vm: [ "code_vm", , , ] 27 | 2. omnibus: [ "code_vm", "vm_omnibus", ] 28 | 29 | 30 | Instruction data: 31 | 32 | 0. lock_duration: u8 - The duration in days for timelocked accounts created by this VM. 33 | 1. vm_bump: u8 - The bump seed for the VM instance account. 34 | 2. vm_omnibus_bump: u8 - The bump seed for the VM's derived token account. 35 | */ 36 | pub fn process_init_vm(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 37 | 38 | let args = InitVmIx::try_from_bytes(data)?; 39 | let [ 40 | vm_authority_info, 41 | vm_info, 42 | omnibus_info, 43 | mint_info, 44 | token_program_info, 45 | system_program_info, 46 | rent_sysvar_info 47 | ] = accounts else { 48 | return Err(ProgramError::NotEnoughAccountKeys); 49 | }; 50 | 51 | check_condition( 52 | args.lock_duration > 0, 53 | "lock_duration must be greater than 0", 54 | )?; 55 | 56 | check_signer(vm_authority_info)?; 57 | check_mut(vm_info)?; 58 | check_mut(omnibus_info)?; 59 | check_readonly(mint_info)?; 60 | check_program(token_program_info, &spl_token::id())?; 61 | check_program(system_program_info, &system_program::id())?; 62 | check_sysvar(rent_sysvar_info, &sysvar::rent::id())?; 63 | 64 | check_uninitialized_pda( 65 | vm_info, 66 | &[ 67 | CODE_VM, 68 | mint_info.key.as_ref(), 69 | vm_authority_info.key.as_ref(), 70 | args.lock_duration.to_le_bytes().as_ref() 71 | ], 72 | args.vm_bump, 73 | &code_vm_api::id() 74 | )?; 75 | check_uninitialized_pda( 76 | omnibus_info, 77 | &[ 78 | CODE_VM, 79 | VM_OMNIBUS, 80 | vm_info.key.as_ref() 81 | ], 82 | args.vm_omnibus_bump, 83 | &code_vm_api::id() 84 | )?; 85 | 86 | // Create the VM instance account. 87 | create_account::( 88 | vm_info, 89 | &code_vm_api::ID, 90 | &[ 91 | CODE_VM, 92 | mint_info.key.as_ref(), 93 | vm_authority_info.key.as_ref(), 94 | args.lock_duration.to_le_bytes().as_ref(), 95 | &[args.vm_bump] 96 | ], 97 | system_program_info, 98 | vm_authority_info, 99 | )?; 100 | 101 | // Create the VM's derived token account. 102 | create_token_account( 103 | mint_info, 104 | omnibus_info, 105 | &[ 106 | CODE_VM, 107 | VM_OMNIBUS, 108 | vm_info.key.as_ref(), 109 | &[args.vm_omnibus_bump] 110 | ], 111 | vm_authority_info, 112 | system_program_info, 113 | rent_sysvar_info, 114 | )?; 115 | 116 | let vm = vm_info.to_account_mut::(&code_vm_api::ID)?; 117 | 118 | vm.authority = vm_authority_info.key.clone(); 119 | vm.mint = mint_info.key.clone(); 120 | vm.lock_duration = args.lock_duration; 121 | vm.bump = args.vm_bump; 122 | vm.omnibus.vault = omnibus_info.key.clone(); 123 | vm.omnibus.vault_bump = args.vm_omnibus_bump; 124 | 125 | vm.advance_poh(CodeInstruction::InitVmIx, accounts, data); 126 | 127 | Ok(()) 128 | } 129 | 130 | -------------------------------------------------------------------------------- /program/src/instruction/mod.rs: -------------------------------------------------------------------------------- 1 | mod compress; 2 | mod decompress; 3 | mod deposit; 4 | mod exec; 5 | mod init_memory; 6 | mod init_nonce; 7 | mod init_relay; 8 | mod init_storage; 9 | mod init_timelock; 10 | mod init_unlock; 11 | mod init_vm; 12 | mod resize; 13 | mod snapshot; 14 | mod unlock; 15 | mod withdraw; 16 | 17 | pub use compress::*; 18 | pub use decompress::*; 19 | pub use deposit::*; 20 | pub use exec::*; 21 | pub use init_memory::*; 22 | pub use init_nonce::*; 23 | pub use init_relay::*; 24 | pub use init_storage::*; 25 | pub use init_timelock::*; 26 | pub use init_unlock::*; 27 | pub use init_vm::*; 28 | pub use resize::*; 29 | pub use snapshot::*; 30 | pub use unlock::*; 31 | pub use withdraw::*; -------------------------------------------------------------------------------- /program/src/instruction/resize.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use solana_program::{ 3 | system_program, 4 | sysvar, 5 | }; 6 | use steel::*; 7 | 8 | /* 9 | This instruction resizes a memory account to the specified size. Only increasing 10 | the size of the account is allowed. 11 | 12 | This instruction works around the limitations the Solana runtime imposes on 13 | the maximum size of an account is allowed to be when it is created. Calling 14 | this instruction repeatedly will resize the memory account to the required 15 | size. 16 | 17 | Accounts expected by this instruction: 18 | 19 | | # | R/W | Type | PDA | Name | Description | 20 | |---|-----|---------|-----|----------------|------------------------------------------| 21 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 22 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 23 | | 2 | mut | Memory | PDA | vm_memory | The memory account to realloc. | 24 | | 3 | | Program | | system_program | The system program. | 25 | | 4 | | Sysvar | | rent_sysvar | The rent sysvar. | 26 | 27 | 28 | Derived account seeds: 29 | 30 | 1. vm: [ "code_vm", , , ] 31 | 2. vm_memory: [ "code_vm", "vm_memory_account", , ] 32 | 33 | 34 | Instruction data: 35 | 36 | 0. len: u32 - The new size of the vm_memory account. 37 | */ 38 | pub fn process_resize(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 39 | 40 | let args = ResizeMemoryIx::try_from_bytes(data)?.to_struct()?; 41 | let [ 42 | vm_authority_info, 43 | vm_info, 44 | vm_memory_info, 45 | system_program_info, 46 | rent_sysvar_info 47 | ] = accounts else { 48 | return Err(ProgramError::NotEnoughAccountKeys); 49 | }; 50 | 51 | check_condition( 52 | args.account_size as usize > MemoryAccount::get_size(), 53 | "account_size must be greater than the base size of a memory account", 54 | )?; 55 | 56 | check_condition( 57 | args.account_size as usize <= MAX_ACCOUNT_SIZE * MAX_NUM_ACCOUNTS, 58 | "account_size must be less than or equal to the maximum size for this type of memory account", 59 | )?; 60 | 61 | check_signer(vm_authority_info)?; 62 | check_mut(vm_info)?; 63 | check_mut(vm_memory_info)?; 64 | check_program(system_program_info, &system_program::id())?; 65 | check_sysvar(rent_sysvar_info, &sysvar::rent::id())?; 66 | 67 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 68 | let memory = load_memory(vm_memory_info, vm_info)?; 69 | 70 | let capacity = memory.get_capacity(); 71 | let account_size = memory.get_account_size(); 72 | 73 | let max_size = MemoryAccount::get_size_with_data(capacity, account_size); 74 | check_condition( 75 | args.account_size as usize <= max_size, 76 | "account_size must be less than or equal to the maximum size for this type of memory account", 77 | )?; 78 | 79 | check_condition( 80 | args.account_size as usize >= vm_memory_info.data_len(), 81 | "account_size must be greater than or equal to the current size of the memory account", 82 | )?; 83 | 84 | resize_account( 85 | vm_memory_info, 86 | vm_authority_info, 87 | args.account_size as usize, 88 | system_program_info, 89 | )?; 90 | 91 | vm.advance_poh(CodeInstruction::ResizeMemoryIx, accounts, data); 92 | 93 | Ok(()) 94 | } 95 | 96 | -------------------------------------------------------------------------------- /program/src/instruction/snapshot.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | /* 5 | This instruction saves the current root of the relay into a circular buffer 6 | in case it needs to be accessed later for a proof. 7 | 8 | Accounts expected by this instruction: 9 | 10 | | # | R/W | Type | PDA | Name | Description | 11 | |---|-----|---------|-----|----------------|------------------------------------------| 12 | | 0 | mut | Signer | | vm_authority | The authority of the VM. | 13 | | 1 | mut | Vm | PDA | vm | The VM instance state account. | 14 | | 2 | mut | Relay | PDA | relay | The relay to save a recent root on. | 15 | 16 | Derived account seeds: 17 | 18 | 1. vm: [ "code_vm", , , ] 19 | 2. relay: [ "code_vm", "vm_relay_account", , ] 20 | 21 | Instruction data: 22 | 23 | 24 | */ 25 | pub fn process_snapshot(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 26 | 27 | let [ 28 | vm_authority_info, 29 | vm_info, 30 | relay_info, 31 | ] = accounts else { 32 | return Err(ProgramError::NotEnoughAccountKeys); 33 | }; 34 | 35 | check_signer(vm_authority_info)?; 36 | check_mut(vm_info)?; 37 | check_mut(relay_info)?; 38 | check_relay(relay_info, vm_info)?; 39 | 40 | let relay = 41 | relay_info.to_account_mut::(&code_vm_api::ID)?; 42 | 43 | relay.save_recent_root(); 44 | 45 | let vm = load_vm_checked(vm_info, vm_authority_info)?; 46 | vm.advance_poh(CodeInstruction::SnapshotIx, accounts, data); 47 | 48 | Ok(()) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /program/src/instruction/unlock.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | use solana_program::msg; 4 | 5 | /* 6 | This instruction is used to finalize a timelock unlock. This allows the 7 | owner to issue a non-custodial withdraw instruction to claim the balance of 8 | any linked virtual account or deposit. 9 | 10 | Accounts expected by this instruction: 11 | 12 | | # | R/W | Type | PDA | Name | Description | 13 | |---|-----|-------------|-----|----------------|-----------------------------------| 14 | | 0 | mut | Signer | | account_owner | The virtual account owner. | 15 | | 1 | mut | Signer | | payer | The transaction fee payer. | 16 | | 2 | mut | Vm | PDA | vm | The VM instance state account. | 17 | | 3 | mut | UnlockState | PDA | unlock_pda | Account to create. | 18 | 19 | 20 | Derived account seeds: 21 | 22 | 2. vm: [ "code_vm", , , ] 23 | 3. unlock_pda: [ "code_vm", "vm_unlock_pda_account", , , ] 24 | 25 | */ 26 | pub fn process_unlock(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { 27 | 28 | let [ 29 | account_owner_info, 30 | payer_info, 31 | vm_info, 32 | unlock_pda_info, 33 | ] = accounts else { 34 | return Err(ProgramError::NotEnoughAccountKeys); 35 | }; 36 | 37 | check_signer(account_owner_info)?; 38 | check_signer(payer_info)?; 39 | 40 | let vm = vm_info.to_account_mut::(&code_vm_api::ID)?; 41 | 42 | check_seeds( 43 | vm_info, 44 | &[ 45 | CODE_VM, 46 | vm.mint.as_ref(), 47 | vm.authority.as_ref(), 48 | vm.lock_duration.to_le_bytes().as_ref() 49 | ], 50 | vm.bump, 51 | &code_vm_api::ID 52 | )?; 53 | 54 | let unlock_pda = unlock_pda_info.to_account_mut::(&code_vm_api::ID)?; 55 | 56 | check_seeds( 57 | unlock_pda_info, 58 | &[ 59 | CODE_VM, 60 | VM_UNLOCK_ACCOUNT, 61 | account_owner_info.key.as_ref(), 62 | unlock_pda.address.as_ref(), 63 | vm_info.key.as_ref(), 64 | ], 65 | unlock_pda.bump, 66 | &code_vm_api::id() 67 | )?; 68 | 69 | check_condition( 70 | unlock_pda.state == TimelockState::WaitingForTimeout as u8, 71 | "invalid unlock state" 72 | )?; 73 | 74 | let now = Clock::get()?.unix_timestamp; 75 | 76 | msg!("current time: {}", now); 77 | msg!("unlock time: {}", unlock_pda.unlock_at); 78 | 79 | check_condition( 80 | unlock_pda.unlock_at < now, 81 | "unlock time has not passed yet" 82 | )?; 83 | 84 | unlock_pda.state = TimelockState::Unlocked as u8; 85 | 86 | vm.advance_poh(CodeInstruction::UnlockIx, accounts, data); 87 | 88 | Ok(()) 89 | } 90 | 91 | -------------------------------------------------------------------------------- /program/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod opcode; 2 | mod instruction; 3 | mod security; 4 | 5 | use instruction::*; 6 | use code_vm_api::prelude::*; 7 | use steel::*; 8 | 9 | pub fn process_instruction( 10 | program_id: &Pubkey, 11 | accounts: &[AccountInfo], 12 | data: &[u8], 13 | ) -> ProgramResult { 14 | let (ix, data) = parse_instruction(&code_vm_api::ID, program_id, data)?; 15 | 16 | match ix { 17 | CodeInstruction::Unknown => return Err(ProgramError::InvalidInstructionData), 18 | 19 | CodeInstruction::InitVmIx => process_init_vm(accounts, data)?, 20 | CodeInstruction::InitMemoryIx => process_init_memory(accounts, data)?, 21 | CodeInstruction::InitStorageIx => process_init_storage(accounts, data)?, 22 | CodeInstruction::InitRelayIx => process_init_relay(accounts, data)?, 23 | CodeInstruction::InitNonceIx => process_init_nonce(accounts, data)?, 24 | CodeInstruction::InitTimelockIx => process_init_timelock(accounts, data)?, 25 | CodeInstruction::InitUnlockIx => process_init_unlock(accounts, data)?, 26 | 27 | CodeInstruction::ExecIx => process_exec(accounts, data)?, 28 | CodeInstruction::CompressIx => process_compress(accounts, data)?, 29 | CodeInstruction::DecompressIx => process_decompress(accounts, data)?, 30 | CodeInstruction::ResizeMemoryIx => process_resize(accounts, data)?, 31 | CodeInstruction::SnapshotIx => process_snapshot(accounts, data)?, 32 | 33 | CodeInstruction::DepositIx => process_deposit(accounts, data)?, 34 | CodeInstruction::WithdrawIx => process_withdraw(accounts, data)?, 35 | CodeInstruction::UnlockIx => process_unlock(accounts, data)?, 36 | } 37 | 38 | Ok(()) 39 | } 40 | 41 | entrypoint!(process_instruction); 42 | 43 | -------------------------------------------------------------------------------- /program/src/opcode/airdrop.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction is used to transfer tokens from *one* virtual account to a 8 | number of virtual accounts. The signature of the source account is required 9 | to authorize the transfer. 10 | 11 | Extra accounts required by this instruction: 12 | 13 | | # | R/W | Type | Req | PDA | Name | Description | 14 | |---|-----|------------- |-----|-----|--------|--------------| 15 | |...| The same as the vm_exec instruction. | 16 | |---|-----|------------- |-----|-----|--------|--------------| 17 | | 6 | | | | | | | 18 | | 7 | | | | | | | 19 | | 8 | | | | | | | 20 | | 9 | | | | | | | 21 | |10 | | | | | | | 22 | 23 | 24 | Instruction data: 25 | 26 | 0. signature: [u8;64] - The opcode to execute. 27 | 1. amount: [u64] - The account_indicies of the virtual accounts to use. 28 | 2. count: [u8] - The number of destinations. 29 | */ 30 | pub fn process_airdrop( 31 | ctx: &ExecContext, 32 | data: &ExecIxData, 33 | ) -> ProgramResult { 34 | 35 | let vm = load_vm(ctx.vm_info)?; 36 | let args = AirdropOp::try_from_bytes(&data.data)?.to_struct()?; 37 | 38 | let mem_indicies = &data.mem_indicies; 39 | let mem_banks = &data.mem_banks; 40 | let num_accounts = 2 + (args.count as usize); 41 | 42 | check_condition( 43 | mem_indicies.len() == num_accounts, 44 | "invalid number of memory indicies", 45 | )?; 46 | 47 | check_condition( 48 | mem_banks.len() == num_accounts, 49 | "invalid number of memory banks", 50 | )?; 51 | 52 | let nonce_index = mem_indicies[0]; 53 | let nonce_mem = mem_banks[0]; 54 | 55 | let src_index = mem_indicies[1]; 56 | let src_mem = mem_banks[1]; 57 | 58 | let vm_mem = ctx.get_banks(); 59 | 60 | check_condition( 61 | vm_mem[nonce_mem as usize].is_some(), 62 | "the nonce memory account must be provided", 63 | )?; 64 | 65 | check_condition( 66 | vm_mem[src_mem as usize].is_some(), 67 | "the source memory account must be provided", 68 | )?; 69 | 70 | let nonce_mem_info = vm_mem[nonce_mem as usize].unwrap(); 71 | let src_mem_info = vm_mem[src_mem as usize].unwrap(); 72 | 73 | let va = try_read(&nonce_mem_info, nonce_index)?; 74 | let mut vdn = va.into_inner_nonce().unwrap(); 75 | 76 | let va = try_read(&src_mem_info, src_index)?; 77 | let mut src_vta = va.into_inner_timelock().unwrap(); 78 | 79 | let total_amount = args.amount 80 | .checked_mul(args.count as u64) 81 | .ok_or(ProgramError::ArithmeticOverflow)?; 82 | 83 | if src_vta.balance < total_amount { 84 | return Err(ProgramError::InsufficientFunds); 85 | } 86 | 87 | src_vta.balance = src_vta.balance 88 | .checked_sub(total_amount) 89 | .ok_or(ProgramError::ArithmeticOverflow)?; 90 | 91 | let mut dst_pubkeys = Vec::new(); 92 | for i in 0..args.count as usize { 93 | let dst_index = mem_indicies[2 + i]; 94 | let dst_mem = mem_banks[2 + i]; 95 | 96 | check_condition( 97 | vm_mem[dst_mem as usize].is_some(), 98 | "a destination memory account must be provided", 99 | )?; 100 | 101 | let dst_mem_info = vm_mem[dst_mem as usize].unwrap(); 102 | 103 | let va = try_read(&dst_mem_info, dst_index)?; 104 | let mut dst_vta = va.into_inner_timelock().unwrap(); 105 | 106 | // Check if this destination is actually the source. 107 | let is_same_account = (src_mem == dst_mem) && (src_index == dst_index); 108 | if is_same_account { 109 | // If the source is also in the destinations list, it receives the airdrop as well. 110 | src_vta.balance = src_vta.balance 111 | .checked_add(args.amount) 112 | .ok_or(ProgramError::ArithmeticOverflow)?; 113 | 114 | } else { 115 | // Normal destination: add the airdrop to its balance 116 | dst_vta.balance = dst_vta.balance 117 | .checked_add(args.amount) 118 | .ok_or(ProgramError::ArithmeticOverflow)?; 119 | 120 | // Write the updated destination back 121 | try_write( 122 | dst_mem_info, 123 | dst_index, 124 | &VirtualAccount::Timelock(dst_vta) 125 | )?; 126 | } 127 | 128 | dst_pubkeys.push(dst_vta.owner); 129 | } 130 | 131 | let hash = create_airdrop_message( 132 | &vm, 133 | &src_vta, 134 | &dst_pubkeys, 135 | args.amount, 136 | &vdn, 137 | ); 138 | 139 | sig_verify( 140 | src_vta.owner.as_ref(), 141 | args.signature.as_ref(), 142 | hash.as_ref(), 143 | )?; 144 | 145 | vdn.value = vm.get_current_poh(); 146 | 147 | // Finally, write back the updated source (which now includes 148 | // any airdrop shares if the source was also in the destination list). 149 | try_write( 150 | src_mem_info, 151 | src_index, 152 | &VirtualAccount::Timelock(src_vta) 153 | )?; 154 | 155 | try_write( 156 | nonce_mem_info, 157 | nonce_index, 158 | &VirtualAccount::Nonce(vdn) 159 | )?; 160 | 161 | Ok(()) 162 | } -------------------------------------------------------------------------------- /program/src/opcode/conditional_transfer.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction is used to make a conditional transfer. The transfer can 8 | only happen if another transfer was done prior (either using RelayOp or 9 | ExternalRelayOp). The signature of the source account is required to 10 | authorize the transfer. 11 | 12 | The existance of the virtual relay account is proof that the action was done 13 | for a particular commitment value. 14 | 15 | Extra accounts required by this instruction: 16 | 17 | | # | R/W | Type | Req | PDA | Name | Description | 18 | |---|-----|------------- |-----|-----|------------------|----------------------------------------------| 19 | |...| The same as the vm_exec instruction. | 20 | |---|-----|------------- |-----|-----|------------------|----------------------------------------------| 21 | | 6 | mut | TokenAccount | Yes | PDA | vm_omnibus | A derived token account owned by the VM. | 22 | | 7 | | | | | | | 23 | | 8 | | | | | | | 24 | | 9 | mut | TokenAccount | | | external_address | Required when making external transfers. | 25 | | 10| | Program | Yes | | token_program | Required when making token transfers. | 26 | 27 | Instruction data: 28 | 29 | 0. signature: [u8;64] - The opcode to execute. 30 | 1. amount: [u64] - The account_indicies of the virtual accounts to use. 31 | */ 32 | pub fn process_conditional_transfer(ctx: &ExecContext, data: &ExecIxData) -> ProgramResult { 33 | let vm = load_vm(ctx.vm_info)?; 34 | let args = ConditionalTransferOp::try_from_bytes(&data.data)?.to_struct()?; 35 | 36 | check_condition( 37 | ctx.omnibus_info.is_some(), 38 | "the omnibus account must be provided", 39 | )?; 40 | 41 | check_condition( 42 | ctx.external_address_info.is_some(), 43 | "the external address account must be provided", 44 | )?; 45 | 46 | check_condition( 47 | ctx.token_program_info.is_some(), 48 | "the token program account must be provided", 49 | )?; 50 | 51 | let omnibus_info = ctx.omnibus_info.unwrap(); 52 | let external_address_info = ctx.external_address_info.unwrap(); 53 | let token_program_info = ctx.token_program_info.unwrap(); 54 | 55 | check_mut(omnibus_info)?; 56 | check_mut(external_address_info)?; 57 | check_program(token_program_info, &spl_token::id())?; 58 | check_omnibus(omnibus_info, ctx.vm_info)?; 59 | 60 | let mem_indicies = &data.mem_indicies; 61 | let mem_banks = &data.mem_banks; 62 | 63 | check_condition( 64 | mem_indicies.len() == 3, 65 | "the number of memory indicies must be 3", 66 | )?; 67 | 68 | check_condition( 69 | mem_banks.len() == 3, 70 | "the number of memory banks must be 3", 71 | )?; 72 | 73 | let nonce_index = mem_indicies[0]; 74 | let nonce_mem = mem_banks[0]; 75 | 76 | let src_index = mem_indicies[1]; 77 | let src_mem = mem_banks[1]; 78 | 79 | let vra_index = mem_indicies[2]; 80 | let vra_mem = mem_banks[2]; 81 | 82 | let vm_mem = ctx.get_banks(); 83 | 84 | check_condition( 85 | vm_mem[nonce_mem as usize].is_some(), 86 | "the nonce memory account must be provided", 87 | )?; 88 | 89 | check_condition( 90 | vm_mem[src_mem as usize].is_some(), 91 | "the source memory account must be provided", 92 | )?; 93 | 94 | check_condition( 95 | vm_mem[vra_mem as usize].is_some(), 96 | "the relay memory account must be provided", 97 | )?; 98 | 99 | let nonce_mem_info = vm_mem[nonce_mem as usize].unwrap(); 100 | let src_mem_info = vm_mem[src_mem as usize].unwrap(); 101 | let vra_mem_info = vm_mem[vra_mem as usize].unwrap(); 102 | 103 | let va = try_read(&nonce_mem_info, nonce_index)?; 104 | let mut vdn = va.into_inner_nonce().unwrap(); 105 | 106 | let va = try_read(&src_mem_info, src_index)?; 107 | let mut src_vta = va.into_inner_timelock().unwrap(); 108 | 109 | let va = try_read(&vra_mem_info, vra_index)?; 110 | let vra = va.into_inner_relay().unwrap(); 111 | 112 | check_condition( 113 | vra.destination.eq(external_address_info.key), 114 | "the virtual relay destination must match the external address", 115 | )?; 116 | 117 | let hash = create_transfer_message_to_external( 118 | &vm, 119 | &src_vta, 120 | &vra.target, 121 | &vdn, 122 | args.amount 123 | ); 124 | 125 | sig_verify( 126 | src_vta.owner.as_ref(), 127 | args.signature.as_ref(), 128 | hash.as_ref(), 129 | )?; 130 | 131 | transfer_signed( 132 | omnibus_info, 133 | omnibus_info, 134 | external_address_info, 135 | token_program_info, 136 | args.amount, 137 | &[&[ 138 | CODE_VM, 139 | VM_OMNIBUS, 140 | ctx.vm_info.key.as_ref(), 141 | &[vm.get_omnibus_bump()], 142 | ]], 143 | )?; 144 | 145 | src_vta.balance = src_vta 146 | .balance 147 | .checked_sub(args.amount) 148 | .ok_or(ProgramError::ArithmeticOverflow)?; 149 | 150 | vdn.value = vm.get_current_poh(); 151 | 152 | try_write( 153 | src_mem_info, 154 | src_index, 155 | &VirtualAccount::Timelock(src_vta) 156 | )?; 157 | 158 | try_write( 159 | nonce_mem_info, 160 | nonce_index, 161 | &VirtualAccount::Nonce(vdn) 162 | )?; 163 | 164 | Ok(()) 165 | } 166 | -------------------------------------------------------------------------------- /program/src/opcode/external_relay.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction makes a private payment from a relay to a real (external) 8 | token account. 9 | 10 | Extra accounts required by this instruction: 11 | 12 | | # | R/W | Type | Req | PDA | Name | Description | 13 | |---|-----|--------------|-----|-----|------------------|----------------------------------------------| 14 | |...| The same as the vm_exec instruction. | | 15 | |---|-----|--------------|-----|-----|------------------|----------------------------------------------| 16 | | 6 | | | | | | | 17 | | 7 | mut | Relay | | PDA | relay | A relay account to use for private transfers.| 18 | | 8 | mut | TokenAccount | | PDA | relay_vault | A derived token account owned by the relay. | 19 | | 9 | mut | TokenAccount | | | external_address | Required when making external transfers. | 20 | | 10| | Program | | | token_program | Required when making token transfers. | 21 | 22 | 23 | Instruction data: 24 | 25 | 0. amount: [u64] - The amount to transfer. 26 | 1. transcript: [u8;32] - The transcript to verify. 27 | 2. recent_root: [u8;32] - The recent root to use. 28 | 3. commitment: [u8;32] - The commitment to use. 29 | 30 | */ 31 | pub fn process_external_relay( 32 | ctx: &ExecContext, 33 | data: &ExecIxData, 34 | ) -> ProgramResult { 35 | 36 | let args = ExternalRelayOp::try_from_bytes(&data.data)?.to_struct()?; 37 | 38 | check_condition( 39 | ctx.external_address_info.is_some(), 40 | "the external_address_info account must be provided", 41 | )?; 42 | 43 | check_condition( 44 | ctx.relay_info.is_some(), 45 | "the relay account must be provided", 46 | )?; 47 | 48 | check_condition( 49 | ctx.relay_vault_info.is_some(), 50 | "the relay_vault account must be provided", 51 | )?; 52 | 53 | check_condition( 54 | ctx.token_program_info.is_some(), 55 | "the token program account must be provided", 56 | )?; 57 | 58 | let external_address_info = ctx.external_address_info.unwrap(); 59 | let relay_info = ctx.relay_info.unwrap(); 60 | let relay_vault_info = ctx.relay_vault_info.unwrap(); 61 | let token_program_info = ctx.token_program_info.unwrap(); 62 | 63 | check_mut(external_address_info)?; 64 | check_mut(relay_info)?; 65 | check_mut(relay_vault_info)?; 66 | check_program(token_program_info, &spl_token::id())?; 67 | check_relay(relay_info, ctx.vm_info)?; 68 | 69 | let mem_indicies = &data.mem_indicies; 70 | let mem_banks = &data.mem_banks; 71 | 72 | check_condition( 73 | mem_indicies.len() == 1, 74 | "the number of memory indicies must be 1", 75 | )?; 76 | 77 | check_condition( 78 | mem_banks.len() == 1, 79 | "the number of memory banks must be 1", 80 | )?; 81 | 82 | let vra_index = mem_indicies[0]; 83 | let vra_mem = mem_banks[0]; 84 | 85 | let vm_mem = ctx.get_banks(); 86 | 87 | check_condition( 88 | vm_mem[vra_mem as usize].is_some(), 89 | "the relay memory account must be provided", 90 | )?; 91 | 92 | // First, lets send the private payment from the relay_vault to the user 93 | // (thier virtual account) 94 | 95 | let relay = 96 | relay_info.to_account_mut::(&code_vm_api::ID)?; 97 | 98 | transfer_signed( 99 | relay_vault_info, 100 | relay_vault_info, 101 | external_address_info, 102 | token_program_info, 103 | args.amount, 104 | &[&[ 105 | CODE_VM, 106 | VM_RELAY_VAULT, 107 | relay_info.key.as_ref(), 108 | &[relay.treasury.vault_bump], 109 | ]] 110 | )?; 111 | 112 | let vra_mem_info = vm_mem[vra_mem as usize].unwrap(); 113 | 114 | check_is_empty(vra_mem_info, vra_index)?; 115 | check_condition( 116 | relay.recent_roots.contains(&args.recent_root.as_ref()), 117 | "the provided recent_root was not found in the relay recent_root list", 118 | )?; 119 | 120 | let destination_address = external_address_info.key; 121 | let (commitment, _) = find_relay_commitment_address( // <- expensive 122 | &relay_info.key, 123 | &args.recent_root, 124 | &args.transcript, // Contains the "source" but is hashed :) 125 | &destination_address, 126 | args.amount, 127 | ); 128 | 129 | check_condition( 130 | commitment.eq(&args.commitment), 131 | "the provided commitment does not match the calculated commitment", 132 | )?; 133 | 134 | // Add the commitment address to the merkle tree 135 | relay.add_commitment(&commitment)?; 136 | 137 | // Find the virtual relay address 138 | let (proof_address, _) = find_relay_proof_address( // <- expensive 139 | &relay_info.key, 140 | &args.recent_root, 141 | &args.commitment, 142 | ); 143 | 144 | let (vault_address, _) = find_relay_destination( // <- expensive 145 | &proof_address, 146 | ); 147 | 148 | let vra = VirtualRelayAccount { 149 | target: vault_address, 150 | destination: relay.treasury.vault, 151 | }; 152 | 153 | try_write( 154 | vra_mem_info, 155 | vra_index, 156 | &VirtualAccount::Relay(vra) 157 | )?; 158 | 159 | Ok(()) 160 | } 161 | -------------------------------------------------------------------------------- /program/src/opcode/external_transfer.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction is used to transfer tokens from a virtual account to a real 8 | (external) token account. The signature of the source account is required to 9 | authorize the transfer. 10 | 11 | Extra accounts required by this instruction: 12 | 13 | | # | R/W | Type | Req | PDA | Name | Description | 14 | |---|-----|------------- |-----|-----|------------------|----------------------------------------------| 15 | |...| The same as the vm_exec instruction. | 16 | |---|-----|------------- |-----|-----|------------------|----------------------------------------------| 17 | | 6 | mut | TokenAccount | Yes | PDA | vm_omnibus | A derived token account owned by the VM. | 18 | | 7 | | | | | | | 19 | | 8 | | | | | | | 20 | | 9 | mut | TokenAccount | Yes | | external_address | Required when making external transfers. | 21 | | 10| | Program | Yes | | token_program | Required when making token transfers. | 22 | 23 | 24 | Instruction data: 25 | 26 | 0. signature: [u8;64] - The opcode to execute. 27 | 1. amount: [u64] - The account_indicies of the virtual accounts to use. 28 | */ 29 | pub fn process_external_transfer( 30 | ctx: &ExecContext, 31 | data: &ExecIxData, 32 | ) -> ProgramResult { 33 | 34 | let vm = load_vm(ctx.vm_info)?; 35 | let args = ExternalTransferOp::try_from_bytes(&data.data)?.to_struct()?; 36 | 37 | check_condition( 38 | ctx.omnibus_info.is_some(), 39 | "the omnibus account must be provided", 40 | )?; 41 | 42 | check_condition( 43 | ctx.external_address_info.is_some(), 44 | "the external address account must be provided", 45 | )?; 46 | 47 | check_condition( 48 | ctx.token_program_info.is_some(), 49 | "the token program account must be provided", 50 | )?; 51 | 52 | let omnibus_info = ctx.omnibus_info.unwrap(); 53 | let external_address_info = ctx.external_address_info.unwrap(); 54 | let token_program_info = ctx.token_program_info.unwrap(); 55 | 56 | check_mut(omnibus_info)?; 57 | check_mut(external_address_info)?; 58 | check_program(token_program_info, &spl_token::id())?; 59 | 60 | let dst_pubkey = external_address_info.key; 61 | 62 | let mem_indicies = &data.mem_indicies; 63 | let mem_banks = &data.mem_banks; 64 | 65 | check_condition( 66 | mem_indicies.len() == 2, 67 | "the number of memory indicies must be 2", 68 | )?; 69 | 70 | check_condition( 71 | mem_banks.len() == 2, 72 | "the number of memory banks must be 2", 73 | )?; 74 | 75 | let nonce_index = mem_indicies[0]; 76 | let nonce_mem = mem_banks[0]; 77 | 78 | let src_index = mem_indicies[1]; 79 | let src_mem = mem_banks[1]; 80 | 81 | let vm_mem = ctx.get_banks(); 82 | 83 | check_condition( 84 | vm_mem[nonce_mem as usize].is_some(), 85 | "the nonce memory account must be provided", 86 | )?; 87 | 88 | check_condition( 89 | vm_mem[src_mem as usize].is_some(), 90 | "the source memory account must be provided", 91 | )?; 92 | 93 | let nonce_mem_info = vm_mem[nonce_mem as usize].unwrap(); 94 | let src_mem_info = vm_mem[src_mem as usize].unwrap(); 95 | 96 | let va = try_read(&nonce_mem_info, nonce_index)?; 97 | let mut vdn = va.into_inner_nonce().unwrap(); 98 | 99 | let va = try_read(&src_mem_info, src_index)?; 100 | let mut src_vta = va.into_inner_timelock().unwrap(); 101 | 102 | let hash = create_transfer_message_to_external( 103 | &vm, 104 | &src_vta, 105 | &dst_pubkey, 106 | &vdn, 107 | args.amount 108 | ); 109 | 110 | sig_verify( 111 | src_vta.owner.as_ref(), 112 | args.signature.as_ref(), 113 | hash.as_ref(), 114 | )?; 115 | 116 | transfer_signed( 117 | omnibus_info, 118 | omnibus_info, 119 | external_address_info, 120 | token_program_info, 121 | args.amount, 122 | &[&[ 123 | CODE_VM, 124 | VM_OMNIBUS, 125 | ctx.vm_info.key.as_ref(), 126 | &[vm.get_omnibus_bump()], 127 | ]] 128 | )?; 129 | 130 | src_vta.balance = src_vta.balance 131 | .checked_sub(args.amount) 132 | .ok_or(ProgramError::ArithmeticOverflow)?; 133 | 134 | vdn.value = vm.get_current_poh(); 135 | 136 | try_write( 137 | src_mem_info, 138 | src_index, 139 | &VirtualAccount::Timelock(src_vta) 140 | )?; 141 | 142 | try_write( 143 | nonce_mem_info, 144 | nonce_index, 145 | &VirtualAccount::Nonce(vdn) 146 | )?; 147 | 148 | Ok(()) 149 | } 150 | -------------------------------------------------------------------------------- /program/src/opcode/external_withdraw.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction is used to withdraw tokens from a virtual account to a real 8 | (external) token account. After the withdrawal, the account is deleted. The 9 | signature of the source account is required to authorize the withdraw. 10 | 11 | Extra accounts required by this instruction: 12 | 13 | | # | R/W | Type | Req | PDA | Name | Description | 14 | |---|-----|------------- |-----|-----|------------------|----------------------------------------------| 15 | |...| The same as the vm_exec instruction. | 16 | |---|-----|------------- |-----|-----|------------------|----------------------------------------------| 17 | | 6 | mut | TokenAccount | Yes | PDA | vm_omnibus | A derived token account owned by the VM. | 18 | | 7 | | | | | | | 19 | | 8 | | | | | | | 20 | | 9 | mut | TokenAccount | Yes | | external_address | Required when making external transfers. | 21 | | 10| | Program | Yes | | token_program | Required when making token transfers. | 22 | 23 | 24 | Instruction data: 25 | 26 | 0. signature: [u8;64] - The opcode to execute. 27 | 1. amount: [u64] - The account_indicies of the virtual accounts to use. 28 | */ 29 | pub fn process_external_withdraw( 30 | ctx: &ExecContext, 31 | data: &ExecIxData, 32 | ) -> ProgramResult { 33 | 34 | let vm = load_vm(ctx.vm_info)?; 35 | let args = ExternalWithdrawOp::try_from_bytes(&data.data)?; 36 | 37 | check_condition( 38 | ctx.omnibus_info.is_some(), 39 | "the omnibus account must be provided", 40 | )?; 41 | 42 | check_condition( 43 | ctx.external_address_info.is_some(), 44 | "the external address account must be provided", 45 | )?; 46 | 47 | check_condition( 48 | ctx.token_program_info.is_some(), 49 | "the token program account must be provided", 50 | )?; 51 | 52 | let omnibus_info = ctx.omnibus_info.unwrap(); 53 | let external_address_info = ctx.external_address_info.unwrap(); 54 | let token_program_info = ctx.token_program_info.unwrap(); 55 | 56 | check_mut(omnibus_info)?; 57 | check_mut(external_address_info)?; 58 | check_program(token_program_info, &spl_token::id())?; 59 | 60 | let dst_pubkey = external_address_info.key; 61 | 62 | let mem_indicies = &data.mem_indicies; 63 | let mem_banks = &data.mem_banks; 64 | 65 | check_condition( 66 | mem_indicies.len() == 2, 67 | "the number of memory indicies must be 2", 68 | )?; 69 | 70 | check_condition( 71 | mem_banks.len() == 2, 72 | "the number of memory banks must be 2", 73 | )?; 74 | 75 | let nonce_index = mem_indicies[0]; 76 | let nonce_mem = mem_banks[0]; 77 | 78 | let src_index = mem_indicies[1]; 79 | let src_mem = mem_banks[1]; 80 | 81 | let vm_mem = ctx.get_banks(); 82 | 83 | check_condition( 84 | vm_mem[nonce_mem as usize].is_some(), 85 | "the nonce memory account must be provided", 86 | )?; 87 | 88 | check_condition( 89 | vm_mem[src_mem as usize].is_some(), 90 | "the source memory account must be provided", 91 | )?; 92 | 93 | let nonce_mem_info = vm_mem[nonce_mem as usize].unwrap(); 94 | let src_mem_info = vm_mem[src_mem as usize].unwrap(); 95 | 96 | let va = try_read(&nonce_mem_info, nonce_index)?; 97 | let mut vdn = va.into_inner_nonce().unwrap(); 98 | 99 | let va = try_read(&src_mem_info, src_index)?; 100 | let mut src_vta = va.into_inner_timelock().unwrap(); 101 | 102 | let amount = src_vta.balance; 103 | 104 | let hash = create_withdraw_message_to_external( 105 | &vm, 106 | &src_vta, 107 | &dst_pubkey, 108 | &vdn, 109 | ); 110 | 111 | sig_verify( 112 | src_vta.owner.as_ref(), 113 | args.signature.as_ref(), 114 | hash.as_ref(), 115 | )?; 116 | 117 | transfer_signed( 118 | omnibus_info, 119 | omnibus_info, 120 | external_address_info, 121 | token_program_info, 122 | amount, 123 | &[&[ 124 | CODE_VM, 125 | VM_OMNIBUS, 126 | ctx.vm_info.key.as_ref(), 127 | &[vm.get_omnibus_bump()], 128 | ]] 129 | )?; 130 | 131 | src_vta.balance = src_vta.balance 132 | .checked_sub(amount) 133 | .ok_or(ProgramError::ArithmeticOverflow)?; 134 | 135 | vdn.value = vm.get_current_poh(); 136 | 137 | try_delete( 138 | src_mem_info, 139 | src_index, 140 | )?; 141 | 142 | try_write( 143 | nonce_mem_info, 144 | nonce_index, 145 | &VirtualAccount::Nonce(vdn) 146 | )?; 147 | 148 | Ok(()) 149 | } 150 | -------------------------------------------------------------------------------- /program/src/opcode/mod.rs: -------------------------------------------------------------------------------- 1 | mod airdrop; 2 | mod conditional_transfer; 3 | mod external_relay; 4 | mod external_transfer; 5 | mod external_withdraw; 6 | mod relay; 7 | mod transfer; 8 | mod withdraw; 9 | 10 | pub use airdrop::*; 11 | pub use conditional_transfer::*; 12 | pub use external_relay::*; 13 | pub use external_transfer::*; 14 | pub use external_withdraw::*; 15 | pub use relay::*; 16 | pub use transfer::*; 17 | pub use withdraw::*; -------------------------------------------------------------------------------- /program/src/opcode/relay.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction makes a private payment from a relay to a virtual account. 8 | Actual tokens move from relay to omnibus. 9 | 10 | Extra accounts required by this instruction: 11 | 12 | | # | R/W | Type | Req | PDA | Name | Description | 13 | |---|-----|--------------|-----|-----|------------------|----------------------------------------------| 14 | |...| The same as the vm_exec instruction. | | 15 | |---|-----|--------------|-----|-----|------------------|----------------------------------------------| 16 | | 6 | mut | TokenAccount | | PDA | vm_omnibus | A derived token account owned by the VM. | 17 | | 7 | mut | Relay | | PDA | relay | A relay account to use for private transfers.| 18 | | 8 | mut | TokenAccount | | PDA | relay_vault | A derived token account owned by the relay. | 19 | | 9 | | | | | | | 20 | | 10| | Program | | | token_program | Required when making token transfers. | 21 | 22 | 23 | Instruction data: 24 | 25 | 0. amount: [u64] - The amount to transfer. 26 | 1. transcript: [u8;32] - The transcript to verify. 27 | 2. recent_root: [u8;32] - The recent root to use. 28 | 3. commitment: [u8;32] - The commitment to use. 29 | 30 | */ 31 | pub fn process_relay( 32 | ctx: &ExecContext, 33 | data: &ExecIxData, 34 | ) -> ProgramResult { 35 | 36 | let vm = load_vm(ctx.vm_info)?; 37 | let args = RelayOp::try_from_bytes(&data.data)?.to_struct()?; 38 | 39 | check_condition( 40 | ctx.omnibus_info.is_some(), 41 | "the omnibus account must be provided", 42 | )?; 43 | 44 | check_condition( 45 | ctx.relay_info.is_some(), 46 | "the relay account must be provided", 47 | )?; 48 | 49 | check_condition( 50 | ctx.relay_vault_info.is_some(), 51 | "the relay_vault account must be provided", 52 | )?; 53 | 54 | check_condition( 55 | ctx.token_program_info.is_some(), 56 | "the token program account must be provided", 57 | )?; 58 | 59 | let omnibus_info = ctx.omnibus_info.unwrap(); 60 | let relay_info = ctx.relay_info.unwrap(); 61 | let relay_vault_info = ctx.relay_vault_info.unwrap(); 62 | let token_program_info = ctx.token_program_info.unwrap(); 63 | 64 | check_mut(omnibus_info)?; 65 | check_mut(relay_info)?; 66 | check_mut(relay_vault_info)?; 67 | check_program(token_program_info, &spl_token::id())?; 68 | check_omnibus(omnibus_info, ctx.vm_info)?; 69 | check_relay(relay_info, ctx.vm_info)?; 70 | 71 | let mem_indicies = &data.mem_indicies; 72 | let mem_banks = &data.mem_banks; 73 | 74 | check_condition( 75 | mem_indicies.len() == 2, 76 | "the number of memory indicies must be 2", 77 | )?; 78 | 79 | check_condition( 80 | mem_banks.len() == 2, 81 | "the number of memory banks must be 2", 82 | )?; 83 | 84 | let dst_index = mem_indicies[0]; 85 | let dst_mem = mem_banks[0]; 86 | 87 | let vra_index = mem_indicies[1]; 88 | let vra_mem = mem_banks[1]; 89 | 90 | let vm_mem = ctx.get_banks(); 91 | 92 | check_condition( 93 | vm_mem[dst_mem as usize].is_some(), 94 | "the destination memory account must be provided", 95 | )?; 96 | 97 | check_condition( 98 | vm_mem[vra_mem as usize].is_some(), 99 | "the relay memory account must be provided", 100 | )?; 101 | 102 | let dst_mem_info = vm_mem[dst_mem as usize].unwrap(); 103 | let vra_mem_info = vm_mem[vra_mem as usize].unwrap(); 104 | 105 | // First, lets send the private payment from the relay_vault to the user 106 | // (thier virtual account) 107 | 108 | let relay = 109 | relay_info.to_account_mut::(&code_vm_api::ID)?; 110 | 111 | transfer_signed( 112 | relay_vault_info, 113 | relay_vault_info, 114 | omnibus_info, 115 | token_program_info, 116 | args.amount, 117 | &[&[ 118 | CODE_VM, 119 | VM_RELAY_VAULT, 120 | relay_info.key.as_ref(), 121 | &[relay.treasury.vault_bump], 122 | ]] 123 | )?; 124 | 125 | let va = try_read(&dst_mem_info, dst_index)?; 126 | let mut vta = va.into_inner_timelock().unwrap(); 127 | 128 | vta.balance = vta.balance 129 | .checked_add(args.amount) 130 | .ok_or(ProgramError::ArithmeticOverflow)?; 131 | 132 | 133 | check_is_empty(vra_mem_info, vra_index)?; 134 | check_condition( 135 | relay.recent_roots.contains(&args.recent_root.as_ref()), 136 | "the provided recent_root was not found in the relay recent_root list", 137 | )?; 138 | 139 | let timelock_address = vta.get_timelock_address( 140 | &vm.get_mint(), 141 | &vm.get_authority(), 142 | vm.get_lock_duration(), 143 | ); 144 | let token_address = vta.get_token_address(&timelock_address); 145 | 146 | let destination_address = token_address; 147 | let (commitment, _) = find_relay_commitment_address( // <- expensive 148 | &relay_info.key, 149 | &args.recent_root, 150 | &args.transcript, // Contains the "source" but is hashed :) 151 | &destination_address, 152 | args.amount, 153 | ); 154 | 155 | check_condition( 156 | commitment.eq(&args.commitment), 157 | "the provided commitment does not match the calculated commitment", 158 | )?; 159 | 160 | // Add the commitment address to the merkle tree 161 | relay.add_commitment(&commitment)?; 162 | 163 | // Find the virtual relay address 164 | let (proof_address, _) = find_relay_proof_address( // <- expensive 165 | &relay_info.key, 166 | &args.recent_root, 167 | &args.commitment, 168 | ); 169 | 170 | let (vault_address, _) = find_relay_destination( // <- expensive 171 | &proof_address, 172 | ); 173 | 174 | let vra = VirtualRelayAccount { 175 | target: vault_address, 176 | destination: relay.treasury.vault, 177 | }; 178 | 179 | try_write( 180 | dst_mem_info, 181 | dst_index, 182 | &VirtualAccount::Timelock(vta) 183 | )?; 184 | 185 | try_write( 186 | vra_mem_info, 187 | vra_index, 188 | &VirtualAccount::Relay(vra) 189 | )?; 190 | 191 | Ok(()) 192 | } 193 | -------------------------------------------------------------------------------- /program/src/opcode/transfer.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction is used to transfer tokens from one virtual account to 8 | another virtual account. The signature of the source account is required 9 | to authorize the transfer. 10 | 11 | Extra accounts required by this instruction: 12 | 13 | | # | R/W | Type | Req | PDA | Name | Description | 14 | |---|-----|------------- |-----|-----|--------|--------------| 15 | |...| The same as the vm_exec instruction. | 16 | |---|-----|------------- |-----|-----|--------|--------------| 17 | | 6 | | | | | | | 18 | | 7 | | | | | | | 19 | | 8 | | | | | | | 20 | | 9 | | | | | | | 21 | |10 | | | | | | | 22 | 23 | 24 | Instruction data: 25 | 26 | 0. signature: [u8;64] - The opcode to execute. 27 | 1. amount: [u64] - The account_indicies of the virtual accounts to use. 28 | */ 29 | pub fn process_transfer( 30 | ctx: &ExecContext, 31 | data: &ExecIxData, 32 | ) -> ProgramResult { 33 | 34 | let vm = load_vm(ctx.vm_info)?; 35 | let args = TransferOp::try_from_bytes(&data.data)?.to_struct()?; 36 | 37 | let mem_indicies = &data.mem_indicies; 38 | let mem_banks = &data.mem_banks; 39 | 40 | check_condition( 41 | mem_indicies.len() == 3, 42 | "the number of memory indicies must be 3", 43 | )?; 44 | 45 | check_condition( 46 | mem_banks.len() == 3, 47 | "the number of memory banks must be 3", 48 | )?; 49 | 50 | let nonce_index = mem_indicies[0]; 51 | let nonce_mem = mem_banks[0]; 52 | 53 | let src_index = mem_indicies[1]; 54 | let src_mem = mem_banks[1]; 55 | 56 | let dst_index = mem_indicies[2]; 57 | let dst_mem = mem_banks[2]; 58 | 59 | let vm_mem = ctx.get_banks(); 60 | 61 | check_condition( 62 | vm_mem[nonce_mem as usize].is_some(), 63 | "the nonce memory account must be provided", 64 | )?; 65 | 66 | check_condition( 67 | vm_mem[src_mem as usize].is_some(), 68 | "the source memory account must be provided", 69 | )?; 70 | 71 | check_condition( 72 | vm_mem[dst_mem as usize].is_some(), 73 | "the destination memory account must be provided", 74 | )?; 75 | 76 | let nonce_mem_info = vm_mem[nonce_mem as usize].unwrap(); 77 | let src_mem_info = vm_mem[src_mem as usize].unwrap(); 78 | let dst_mem_info = vm_mem[dst_mem as usize].unwrap(); 79 | 80 | let va = try_read(&nonce_mem_info, nonce_index)?; 81 | let mut vdn = va.into_inner_nonce().unwrap(); 82 | 83 | let va = try_read(&src_mem_info, src_index)?; 84 | let mut src_vta = va.into_inner_timelock().unwrap(); 85 | 86 | let va = try_read(&dst_mem_info, dst_index)?; 87 | let mut dst_vta = va.into_inner_timelock().unwrap(); 88 | 89 | let hash = create_transfer_message( 90 | &vm, 91 | &src_vta, 92 | &dst_vta, 93 | &vdn, 94 | args.amount 95 | ); 96 | 97 | sig_verify( 98 | src_vta.owner.as_ref(), 99 | args.signature.as_ref(), 100 | hash.as_ref(), 101 | )?; 102 | 103 | if src_vta.balance < args.amount { 104 | return Err(ProgramError::InsufficientFunds); 105 | } 106 | 107 | // If the source and destination accounts are the same, then we don't need 108 | // to do anything. 109 | 110 | let is_same_account = src_mem == dst_mem && src_index == dst_index; 111 | if !is_same_account { 112 | src_vta.balance = src_vta.balance 113 | .checked_sub(args.amount) 114 | .ok_or(ProgramError::ArithmeticOverflow)?; 115 | 116 | dst_vta.balance = dst_vta.balance 117 | .checked_add(args.amount) 118 | .ok_or(ProgramError::ArithmeticOverflow)?; 119 | } 120 | 121 | vdn.value = vm.get_current_poh(); 122 | 123 | try_write( 124 | src_mem_info, 125 | src_index, 126 | &VirtualAccount::Timelock(src_vta) 127 | )?; 128 | 129 | try_write( 130 | dst_mem_info, 131 | dst_index, 132 | &VirtualAccount::Timelock(dst_vta) 133 | )?; 134 | 135 | try_write( 136 | nonce_mem_info, 137 | nonce_index, 138 | &VirtualAccount::Nonce(vdn) 139 | )?; 140 | 141 | Ok(()) 142 | } -------------------------------------------------------------------------------- /program/src/opcode/withdraw.rs: -------------------------------------------------------------------------------- 1 | use code_vm_api::prelude::*; 2 | use steel::*; 3 | 4 | use crate::ExecContext; 5 | 6 | /* 7 | This instruction is used to withdraw tokens from a virtual account and 8 | deposit them into another virtual account. After the withdrawal, the account 9 | is deleted. The signature of the source account is required to authorize the 10 | withdraw. 11 | 12 | Extra accounts required by this instruction: 13 | 14 | | # | R/W | Type | Req | PDA | Name | Description | 15 | |---|-----|------------- |-----|-----|--------|--------------| 16 | |...| The same as the vm_exec instruction. | 17 | |---|-----|------------- |-----|-----|--------|--------------| 18 | | 6 | | | | | | | 19 | | 7 | | | | | | | 20 | | 8 | | | | | | | 21 | | 9 | | | | | | | 22 | |10 | | | | | | | 23 | 24 | 25 | Instruction data: 26 | 27 | 0. signature: [u8;64] - The opcode to execute. 28 | */ 29 | pub fn process_withdraw( 30 | ctx: &ExecContext, 31 | data: &ExecIxData, 32 | ) -> ProgramResult { 33 | 34 | let vm = load_vm(ctx.vm_info)?; 35 | let args = WithdrawOp::try_from_bytes(&data.data)?; 36 | 37 | let mem_indicies = &data.mem_indicies; 38 | let mem_banks = &data.mem_banks; 39 | 40 | check_condition( 41 | mem_indicies.len() == 3, 42 | "the number of memory indicies must be 3", 43 | )?; 44 | 45 | check_condition( 46 | mem_banks.len() == 3, 47 | "the number of memory banks must be 3", 48 | )?; 49 | 50 | let nonce_index = mem_indicies[0]; 51 | let nonce_mem = mem_banks[0]; 52 | 53 | let src_index = mem_indicies[1]; 54 | let src_mem = mem_banks[1]; 55 | 56 | let dst_index = mem_indicies[2]; 57 | let dst_mem = mem_banks[2]; 58 | 59 | let vm_mem = ctx.get_banks(); 60 | 61 | check_condition( 62 | vm_mem[nonce_mem as usize].is_some(), 63 | "the nonce memory account must be provided", 64 | )?; 65 | 66 | check_condition( 67 | vm_mem[src_mem as usize].is_some(), 68 | "the source memory account must be provided", 69 | )?; 70 | 71 | check_condition( 72 | vm_mem[dst_mem as usize].is_some(), 73 | "the destination memory account must be provided", 74 | )?; 75 | 76 | let nonce_mem_info = vm_mem[nonce_mem as usize].unwrap(); 77 | let src_mem_info = vm_mem[src_mem as usize].unwrap(); 78 | let dst_mem_info = vm_mem[dst_mem as usize].unwrap(); 79 | 80 | let va = try_read(&nonce_mem_info, nonce_index)?; 81 | let mut vdn = va.into_inner_nonce().unwrap(); 82 | 83 | let va = try_read(&src_mem_info, src_index)?; 84 | let mut src_vta = va.into_inner_timelock().unwrap(); 85 | 86 | let va = try_read(&dst_mem_info, dst_index)?; 87 | let mut dst_vta = va.into_inner_timelock().unwrap(); 88 | 89 | let amount = src_vta.balance; 90 | 91 | let hash = create_withdraw_message( 92 | &vm, 93 | &src_vta, 94 | &dst_vta, 95 | &vdn, 96 | ); 97 | 98 | sig_verify( 99 | src_vta.owner.as_ref(), 100 | args.signature.as_ref(), 101 | hash.as_ref(), 102 | )?; 103 | 104 | if src_vta.balance < amount { 105 | return Err(ProgramError::InsufficientFunds); 106 | } 107 | 108 | // If the source and destination accounts are the same, then we don't need 109 | // to do anything. 110 | 111 | let is_same_account = src_mem == dst_mem && src_index == dst_index; 112 | if !is_same_account { 113 | src_vta.balance = src_vta.balance 114 | .checked_sub(amount) 115 | .ok_or(ProgramError::ArithmeticOverflow)?; 116 | 117 | dst_vta.balance = dst_vta.balance 118 | .checked_add(amount) 119 | .ok_or(ProgramError::ArithmeticOverflow)?; 120 | } 121 | 122 | vdn.value = vm.get_current_poh(); 123 | 124 | try_delete( 125 | src_mem_info, 126 | src_index 127 | )?; 128 | 129 | try_write( 130 | dst_mem_info, 131 | dst_index, 132 | &VirtualAccount::Timelock(dst_vta) 133 | )?; 134 | 135 | try_write( 136 | nonce_mem_info, 137 | nonce_index, 138 | &VirtualAccount::Nonce(vdn) 139 | )?; 140 | 141 | Ok(()) 142 | } 143 | -------------------------------------------------------------------------------- /program/src/security.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "no-entrypoint"))] 2 | use {solana_security_txt::security_txt}; 3 | 4 | #[cfg(not(feature = "no-entrypoint"))] 5 | security_txt! { 6 | name: "code-vm", 7 | project_url: "https://getcode.com", 8 | contacts: "email:security@getcode.com,email:contact@getcode.com", 9 | policy: "https://github.com/code-payments/code-vm/blob/main/SECURITY.md", 10 | preferred_languages: "en", 11 | source_code: "https://github.com/code-payments/code-vm", 12 | auditors: "OtterSec" 13 | } 14 | -------------------------------------------------------------------------------- /program/tests/relay_init.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::prelude::*; 6 | 7 | #[test] 8 | fn run_relay_init_test() { 9 | let (mut svm, payer, _mint_owner, mint_pk, vm_address) = 10 | setup_svm_with_payer_and_vm(21); 11 | 12 | let name = "test"; 13 | 14 | let (relay_address, relay_bump) = 15 | create_relay_account(&mut svm, &payer, &mint_pk, vm_address, name); 16 | let (relay_vault_address, relay_vault_bump) = 17 | find_vm_relay_vault_pda(&relay_address); 18 | 19 | let relay_account = svm.get_account(&relay_address).unwrap(); 20 | assert!(relay_account.data.len() == RelayAccount::get_size()); 21 | 22 | let relay = get_relay_account(&svm, relay_address); 23 | assert_eq!(relay.vm, vm_address); 24 | assert_eq!(relay.bump, relay_bump); 25 | assert_eq!(relay.name, create_name(name)); 26 | 27 | assert_eq!(relay.treasury.vault, relay_vault_address); 28 | assert_eq!(relay.treasury.vault_bump, relay_vault_bump); 29 | 30 | assert_eq!(relay.history.get_depth(), RELAY_STATE_DEPTH as u8); 31 | assert_eq!(relay.recent_roots.capacity(), RELAY_HISTORY_ITEMS); 32 | assert_eq!(relay.recent_roots.num_items, 1); 33 | assert_eq!(relay.recent_roots.first().unwrap(), relay.history.get_root().as_ref()); 34 | 35 | } -------------------------------------------------------------------------------- /program/tests/relay_save_root.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | //use code_vm_api::prelude::*; 6 | 7 | #[test] 8 | fn run_relay_save_root_test() { 9 | let (mut svm, payer, _mint_owner, mint_pk, vm_address) = 10 | setup_svm_with_payer_and_vm(21); 11 | 12 | let name = "test"; 13 | 14 | let (relay_address, _) = 15 | create_relay_account(&mut svm, &payer, &mint_pk, vm_address, name); 16 | 17 | assert!(tx_save_root(&mut svm, &payer, vm_address, relay_address).is_ok()); 18 | 19 | } -------------------------------------------------------------------------------- /program/tests/relay_transfer.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use solana_sdk::signature::Signer; 6 | use code_vm_api::prelude::*; 7 | 8 | #[test] 9 | fn run_relay_transfer() { 10 | // Initialize the test context 11 | let mut ctx = TestContext::new(21); 12 | 13 | // Setup a relay and treasury (with tokens) 14 | let relay_ctx = ctx.create_relay("relay_0", 10_00); 15 | 16 | // Create our virtual memory accounts 17 | let mem_a = ctx.create_memory(100, VirtualDurableNonce::LEN + 1, "mem_nonce_0"); 18 | let mem_b = ctx.create_memory(100, VirtualTimelockAccount::LEN + 1, "mem_timelock_0"); 19 | let mem_c = ctx.create_memory(100, VirtualRelayAccount::LEN + 1, "mem_relay_0"); 20 | 21 | // Create some virtual accounts 22 | let vta_a_index = 7; 23 | let vta_b_index = 15; 24 | let vdn_index = 8; 25 | let vra_index = 3; 26 | 27 | let vta_a_ctx = ctx.create_timelock_account(mem_b, vta_a_index); 28 | let vta_b_ctx = ctx.create_timelock_account(mem_b, vta_b_index); 29 | let vdn_ctx = ctx.create_durable_nonce_account(mem_a, vdn_index); 30 | 31 | // Deposit 100 tokens into vta_b 32 | ctx.deposit_tokens_to_timelock(mem_b, &vta_b_ctx, 100) 33 | .unwrap(); 34 | 35 | // We're going to do a relay transfer from vta_b to vta_a, and then a 36 | // conditional transfer from vta_b to the relay treasury. The net result is 37 | // that vta_a gets 42 tokens. 38 | 39 | // First, we need to calculate the commitment value 40 | let amount: u64 = 42; 41 | let recent_root = relay_ctx.relay.get_recent_root(); 42 | let transcript = hashv(&[b"transfer", &amount.to_le_bytes()]); 43 | 44 | let timelock_address = vta_a_ctx.account.get_timelock_address( 45 | &ctx.vm.get_mint(), 46 | &ctx.vm.get_authority(), 47 | ctx.vm.get_lock_duration(), 48 | ); 49 | let destination = vta_a_ctx.account.get_token_address(&timelock_address); 50 | 51 | let (commitment, _) = find_relay_commitment_address( 52 | &relay_ctx.relay_address, 53 | &recent_root, 54 | &transcript, 55 | &destination, 56 | amount, 57 | ); 58 | 59 | // Next, we're going to calculate the target address and sign a transaction 60 | let (proof_address, _) = find_relay_proof_address( 61 | &relay_ctx.relay_address, 62 | &recent_root, 63 | &commitment, 64 | ); 65 | let (target, _) = find_relay_destination(&proof_address); 66 | let conditional_payment = create_transfer_message_to_external( 67 | &ctx.vm, 68 | &vta_b_ctx.account, 69 | &target, 70 | &vdn_ctx.account, 71 | amount, 72 | ); 73 | let conditional_sig = vta_b_ctx 74 | .key 75 | .sign_message(conditional_payment.as_ref()) 76 | .as_ref() 77 | .try_into() 78 | .unwrap(); 79 | 80 | // Run the relay to vta_a transfer 81 | let mem_indices = vec![vta_a_index, vra_index]; // dst, vra 82 | let mem_banks = vec![1, 2]; // mem_b, mem_c 83 | let data = RelayOp::from_struct( 84 | ParsedRelayOp { 85 | amount, 86 | transcript, 87 | recent_root, 88 | commitment, 89 | }).to_bytes(); 90 | 91 | ctx.exec_relay_op( 92 | &relay_ctx, 93 | [None, Some(mem_b), Some(mem_c), None], 94 | mem_indices, 95 | mem_banks, 96 | data, 97 | ) 98 | .unwrap(); 99 | 100 | let vta = ctx.get_virtual_timelock(mem_b, vta_a_index); 101 | assert_eq!(vta.balance, amount); 102 | 103 | let vta = ctx.get_virtual_timelock(mem_b, vta_b_index); 104 | assert_eq!(vta.balance, 100); 105 | 106 | let vra = get_virtual_relay(&ctx.svm, mem_c, vra_index); 107 | assert_eq!(vra.target, target); 108 | assert_eq!(vra.destination, relay_ctx.relay.treasury.vault); 109 | 110 | // Now, we're going to run the conditional transfer from vta_b to the relay 111 | let mem_indices = vec![vdn_index, vta_b_index, vra_index]; 112 | let mem_banks = vec![0, 1, 2]; 113 | let data = ConditionalTransferOp::from_struct( 114 | ParsedConditionalTransferOp { 115 | amount, 116 | signature: conditional_sig, 117 | }).to_bytes(); 118 | 119 | ctx.exec_conditional_transfer( 120 | relay_ctx.relay.treasury.vault, 121 | [Some(mem_a), Some(mem_b), Some(mem_c), None], 122 | mem_indices, 123 | mem_banks, 124 | data, 125 | ) 126 | .unwrap(); 127 | 128 | // Let's confirm tokens left vta_b 129 | let vta = ctx.get_virtual_timelock(mem_b, vta_a_index); 130 | assert_eq!(vta.balance, 42); 131 | 132 | let vta = ctx.get_virtual_timelock(mem_b, vta_b_index); 133 | assert_eq!(vta.balance, 100 - 42); 134 | } 135 | -------------------------------------------------------------------------------- /program/tests/system_compress.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::{prelude::*, utils::hashv}; 6 | use solana_sdk::signer::Signer; 7 | 8 | #[test] 9 | fn run_system_account_compress() { 10 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 11 | setup_svm_with_payer_and_vm(21); 12 | 13 | let name = "test"; 14 | let capacity = 100; 15 | let account_size = VirtualDurableNonce::LEN+1; 16 | 17 | let (vm_mem_address, _) = 18 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 19 | 20 | let (vm_storage_address, _) = 21 | create_storage_account(&mut svm, &payer, vm_address, name); 22 | 23 | let virtual_account_owner = create_keypair().pubkey(); 24 | let account_index = 0; 25 | assert!(tx_create_virtual_nonce(&mut svm, &payer, vm_address, vm_mem_address, virtual_account_owner, account_index).is_ok()); 26 | 27 | let va = get_virtual_account(&svm, vm_mem_address, account_index); 28 | let va_hash = va.get_hash(); 29 | 30 | let sig = Signature::new(payer.sign_message(va_hash.as_ref()).as_ref()); 31 | let sig_hash = hashv(&[sig.as_ref(), va_hash.as_ref()]); 32 | 33 | assert!(tx_account_compress( 34 | &mut svm, 35 | &payer, 36 | vm_address, 37 | vm_mem_address, 38 | vm_storage_address, 39 | account_index, 40 | sig 41 | ).is_ok()); 42 | 43 | let data = get_virtual_account_data(&svm, vm_mem_address, account_index); 44 | assert!(data.is_none()); 45 | 46 | let compressed_mem = get_storage_account(&svm, vm_storage_address).compressed_state; 47 | let mut expected = MerkleTree::<{StorageAccount::MERKLE_TREE_DEPTH}>::new(&[ 48 | MERKLE_TREE_SEED, 49 | create_name(name).as_ref(), 50 | vm_address.as_ref() 51 | ]); 52 | assert!(expected.try_insert(sig_hash).is_ok()); 53 | let proof = expected.get_merkle_proof(&[sig_hash], 0); 54 | 55 | assert!(compressed_mem.contains(&proof, sig_hash)) 56 | 57 | } -------------------------------------------------------------------------------- /program/tests/system_decompress.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::{prelude::*, utils::hashv}; 6 | use solana_sdk::signer::Signer; 7 | 8 | #[test] 9 | fn run_system_account_decompress() { 10 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 11 | setup_svm_with_payer_and_vm(21); 12 | 13 | let name = "test"; 14 | let capacity = 100; 15 | let account_size = VirtualDurableNonce::LEN+1; 16 | 17 | let (vm_mem_address, _) = 18 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 19 | 20 | let (vm_storage_address, _) = 21 | create_storage_account(&mut svm, &payer, vm_address, name); 22 | 23 | let virtual_account_owner = create_keypair().pubkey(); 24 | let account_index = 0; 25 | assert!(tx_create_virtual_nonce(&mut svm, &payer, vm_address, vm_mem_address, virtual_account_owner, account_index).is_ok()); 26 | 27 | let va = get_virtual_account(&svm, vm_mem_address, account_index); 28 | let va_hash = va.get_hash(); 29 | 30 | let sig = Signature::new(payer.sign_message(va_hash.as_ref()).as_ref()); 31 | let sig_hash = hashv(&[sig.as_ref(), va_hash.as_ref()]); 32 | 33 | assert!(tx_account_compress( 34 | &mut svm, 35 | &payer, 36 | vm_address, 37 | vm_mem_address, 38 | vm_storage_address, 39 | account_index, 40 | sig 41 | ).is_ok()); 42 | 43 | let data = get_virtual_account_data(&svm, vm_mem_address, account_index); 44 | assert!(data.is_none()); 45 | 46 | let compressed_mem = get_storage_account(&svm, vm_storage_address).compressed_state; 47 | let mut expected = MerkleTree::<{StorageAccount::MERKLE_TREE_DEPTH}>::new(&[ 48 | MERKLE_TREE_SEED, 49 | create_name(name).as_ref(), 50 | vm_address.as_ref() 51 | ]); 52 | assert!(expected.try_insert(sig_hash).is_ok()); 53 | assert_eq!(expected.get_root(), compressed_mem.get_root()); 54 | 55 | let packed_va = va.pack(); 56 | let proof = expected.get_merkle_proof(&[sig_hash], 0); 57 | let account_index = 42; 58 | 59 | assert!(tx_account_decompress( 60 | &mut svm, 61 | &payer, 62 | vm_address, 63 | vm_mem_address, 64 | vm_storage_address, 65 | None, 66 | None, 67 | account_index, 68 | packed_va, 69 | proof.clone(), 70 | sig 71 | ).is_ok()); 72 | 73 | let compressed_mem = get_storage_account(&svm, vm_storage_address).compressed_state; 74 | 75 | assert!(expected.try_remove(&proof, sig_hash).is_ok()); 76 | assert_eq!(expected.get_root(), compressed_mem.get_root()); 77 | 78 | let old_index = get_virtual_account_data(&svm, vm_mem_address, 0); 79 | let new_index = get_virtual_account_data(&svm, vm_mem_address, account_index); 80 | 81 | assert!(old_index.is_none()); 82 | assert!(new_index.is_some()); 83 | 84 | let va = get_virtual_account(&svm, vm_mem_address, account_index); 85 | assert!(va.is_nonce()); 86 | } -------------------------------------------------------------------------------- /program/tests/system_nonce_init.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::prelude::*; 6 | use solana_sdk::signer::Signer; 7 | 8 | #[test] 9 | fn run_system_nonce_init() { 10 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 11 | setup_svm_with_payer_and_vm(21); 12 | 13 | let name = "test"; 14 | let capacity = 100; 15 | let account_size = VirtualDurableNonce::LEN+1; 16 | 17 | let (vm_mem_address, _) = 18 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 19 | 20 | let vm = get_vm_account(&svm, vm_address); 21 | 22 | let virtual_account_owner = create_keypair().pubkey(); 23 | let account_index = 0; 24 | assert!(tx_create_virtual_nonce(&mut svm, &payer, vm_address, vm_mem_address, virtual_account_owner, account_index).is_ok()); 25 | 26 | // Actual nonce values 27 | let vdn = get_virtual_nonce(&svm, vm_mem_address, account_index); 28 | 29 | // Expected nonce values 30 | let seed = virtual_account_owner; 31 | let (nonce_address, _) = find_virtual_nonce_pda( 32 | &vm_address, &seed, &vm.get_current_poh() 33 | ); 34 | 35 | assert_eq!(vdn.address, nonce_address); 36 | assert_eq!(vdn.value, vm.get_current_poh()); 37 | 38 | } -------------------------------------------------------------------------------- /program/tests/system_timelock_init.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::prelude::*; 6 | use solana_sdk::signer::Signer; 7 | 8 | #[test] 9 | fn run_system_timelock_init() { 10 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 11 | setup_svm_with_payer_and_vm(21); 12 | 13 | let name = "test"; 14 | let capacity = 100; 15 | let account_size = VirtualTimelockAccount::LEN+1; 16 | 17 | let (vm_mem_address, _) = 18 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 19 | 20 | let vm = get_vm_account(&svm, vm_address); 21 | 22 | let virtual_account_owner = create_keypair().pubkey(); 23 | let account_index = 0; 24 | let nonce = vm.get_current_poh(); 25 | 26 | let (timelock_address, virtual_timelock_bump) = find_virtual_timelock_address( 27 | &vm.get_mint(), 28 | &vm.get_authority(), 29 | &virtual_account_owner, 30 | vm.get_lock_duration() 31 | ); 32 | 33 | let (_, virtual_vault_bump) = find_virtual_timelock_vault_address( 34 | &timelock_address 35 | ); 36 | 37 | let (unlock_address, unlock_pda_bump) = find_unlock_address( 38 | &virtual_account_owner, 39 | &timelock_address, 40 | &vm_address, 41 | ); 42 | 43 | let (_, withdraw_bump) = find_withdraw_receipt_address( 44 | &unlock_address, 45 | &nonce, 46 | &vm_address 47 | ); 48 | 49 | assert!(tx_create_virtual_timelock( 50 | &mut svm, 51 | &payer, 52 | vm_address, 53 | vm_mem_address, 54 | virtual_account_owner, 55 | account_index, 56 | virtual_timelock_bump, 57 | virtual_vault_bump, 58 | unlock_pda_bump, 59 | ).is_ok()); 60 | 61 | // Actual values 62 | let actual = get_virtual_timelock(&svm, vm_mem_address, account_index); 63 | 64 | // Expected values 65 | let expected = VirtualTimelockAccount { 66 | owner: virtual_account_owner, 67 | instance: nonce, 68 | bump: virtual_timelock_bump, 69 | token_bump: virtual_vault_bump, 70 | unlock_bump: unlock_pda_bump, 71 | withdraw_bump, 72 | balance: 0, 73 | }; 74 | 75 | assert_eq!(expected, actual); 76 | 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /program/tests/timelock_deposit.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use solana_sdk::signer::Signer; 4 | use utils::*; 5 | 6 | use code_vm_api::prelude::*; 7 | 8 | #[test] 9 | fn run_deposit() { 10 | let (mut svm, payer, mint_owner, mint_pk, vm_address) = 11 | setup_svm_with_payer_and_vm(21); 12 | 13 | let name = "test"; 14 | let capacity = 100; 15 | let account_size = VirtualTimelockAccount::LEN+1; 16 | 17 | let (vm_memory, _) = 18 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 19 | 20 | let amount = 1000; 21 | let account_index = 7; 22 | 23 | let (_, vta_key) = 24 | create_timelock(&mut svm, &payer, vm_address, vm_memory, account_index); 25 | let depositor = vta_key.pubkey(); 26 | let (deposit_pda, bump) = find_timelock_deposit_pda(&vm_address, &depositor); 27 | let deposit_ata = create_ata(&mut svm, &payer, &mint_pk, &deposit_pda); 28 | 29 | mint_to(&mut svm, &payer, &mint_pk, &mint_owner, &deposit_ata, amount).unwrap(); 30 | 31 | let vm = get_vm_account(&svm, vm_address); 32 | 33 | assert!(tx_deposit( 34 | &mut svm, 35 | &payer, 36 | vm_address, 37 | vm_memory, 38 | depositor, 39 | deposit_pda, 40 | deposit_ata, 41 | vm.omnibus.vault, 42 | account_index, 43 | amount, 44 | bump 45 | ).is_ok()); 46 | 47 | let vta = get_virtual_timelock(&svm, vm_memory, account_index); 48 | 49 | assert_eq!(vta.balance, amount); 50 | } -------------------------------------------------------------------------------- /program/tests/timelock_unlock.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use steel::Clock; 4 | use utils::*; 5 | 6 | use code_vm_api::prelude::*; 7 | 8 | #[test] 9 | fn run_unlock() { 10 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 11 | setup_svm_with_payer_and_vm(21); 12 | 13 | let name = "test"; 14 | let capacity = 100; 15 | let account_size = VirtualTimelockAccount::LEN+1; 16 | 17 | let (vm_memory, _) = 18 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 19 | 20 | let (vta, vta_key) = 21 | create_timelock(&mut svm, &payer, vm_address, vm_memory, 0); 22 | 23 | let vm = get_vm_account(&svm, vm_address); 24 | 25 | let timelock_address = vta.get_timelock_address( 26 | &vm.get_mint(), 27 | &vm.get_authority(), 28 | vm.get_lock_duration() 29 | ); 30 | 31 | let unlock_address = vta.get_unlock_address(&timelock_address, &vm_address); 32 | 33 | assert!(tx_unlock_init( 34 | &mut svm, 35 | &payer, 36 | &vta_key, 37 | vm_address, 38 | unlock_address, 39 | ).is_ok()); 40 | 41 | let unlock = get_unlock_state(&svm, unlock_address); 42 | 43 | assert_eq!(unlock.owner, vta.owner); 44 | assert_eq!(unlock.address, timelock_address); 45 | assert_eq!(unlock.vm, vm_address); 46 | assert_eq!(unlock.state, TimelockState::WaitingForTimeout as u8); 47 | assert!(unlock.bump > 0); 48 | assert!(unlock.unlock_at > 0); 49 | 50 | let mut clock = svm.get_sysvar::(); 51 | clock.unix_timestamp = unlock.unlock_at + 1; 52 | svm.set_sysvar::(&clock); 53 | 54 | assert!(tx_unlock_finalize( 55 | &mut svm, 56 | &payer, 57 | &vta_key, 58 | vm_address, 59 | unlock_address, 60 | ).is_ok()); 61 | } -------------------------------------------------------------------------------- /program/tests/timelock_withdraw.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use steel::Clock; 4 | use solana_sdk::signer::Signer; 5 | use utils::*; 6 | 7 | use code_vm_api::prelude::*; 8 | 9 | 10 | 11 | #[test] 12 | fn run_withdraw_from_deposit_pda() { 13 | let (mut svm, payer, mint_owner, mint_pk, vm_address) = 14 | setup_svm_with_payer_and_vm(21); 15 | 16 | let name = "test"; 17 | let capacity = 100; 18 | let account_size = VirtualTimelockAccount::LEN+1; 19 | 20 | let (vm_memory, _) = 21 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 22 | 23 | let amount = 1000; 24 | let account_index = 7; 25 | 26 | let (vta, vta_key) = 27 | create_timelock(&mut svm, &payer, vm_address, vm_memory, account_index); 28 | 29 | let depositor = vta_key.pubkey(); 30 | let (deposit_pda, deposit_pda_bump) = find_timelock_deposit_pda(&vm_address, &depositor); 31 | let deposit_ata = create_ata(&mut svm, &payer, &mint_pk, &deposit_pda); 32 | 33 | let dest_key = create_keypair(); 34 | let destination = create_ata(&mut svm, &payer, &mint_pk, &dest_key.pubkey()); 35 | 36 | mint_to(&mut svm, &payer, &mint_pk, &mint_owner, &deposit_ata, amount).unwrap(); 37 | 38 | let vm = get_vm_account(&svm, vm_address); 39 | let timelock_address = vta.get_timelock_address( 40 | &vm.get_mint(), 41 | &vm.get_authority(), 42 | vm.get_lock_duration() 43 | ); 44 | 45 | let unlock_address = vta.get_unlock_address(&timelock_address, &vm_address); 46 | 47 | assert!(tx_unlock_init( 48 | &mut svm, 49 | &payer, 50 | &vta_key, 51 | vm_address, 52 | unlock_address, 53 | ).is_ok()); 54 | 55 | let unlock = get_unlock_state(&svm, unlock_address); 56 | let mut clock = svm.get_sysvar::(); 57 | clock.unix_timestamp = unlock.unlock_at + 1; 58 | svm.set_sysvar::(&clock); 59 | 60 | assert!(tx_unlock_finalize( 61 | &mut svm, 62 | &payer, 63 | &vta_key, 64 | vm_address, 65 | unlock_address, 66 | ).is_ok()); 67 | 68 | assert!(tx_withdraw_from_deposit( 69 | &mut svm, 70 | &payer, 71 | &vta_key, 72 | vm_address, 73 | deposit_pda, 74 | deposit_ata, 75 | unlock_address, 76 | destination, 77 | WithdrawIxData::FromDeposit { bump: deposit_pda_bump } 78 | ).is_ok()); 79 | } 80 | 81 | #[test] 82 | fn run_withdraw_from_memory() { 83 | let (mut svm, payer, _mint_owner, mint_pk, vm_address) = 84 | setup_svm_with_payer_and_vm(21); 85 | 86 | let name = "test"; 87 | let capacity = 100; 88 | let account_size = VirtualTimelockAccount::LEN+1; 89 | 90 | let (vm_memory, _) = 91 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 92 | 93 | let account_index = 7; 94 | 95 | let (vta, vta_key) = 96 | create_timelock(&mut svm, &payer, vm_address, vm_memory, account_index); 97 | 98 | let dest_key = create_keypair(); 99 | let destination = create_ata(&mut svm, &payer, &mint_pk, &dest_key.pubkey()); 100 | 101 | let vm = get_vm_account(&svm, vm_address); 102 | let timelock_address = vta.get_timelock_address( 103 | &vm.get_mint(), 104 | &vm.get_authority(), 105 | vm.get_lock_duration() 106 | ); 107 | 108 | let unlock_address = vta.get_unlock_address(&timelock_address, &vm_address); 109 | let receipt_address = vta.get_withdraw_receipt_address(&unlock_address, &vm_address); 110 | 111 | assert!(tx_unlock_init( 112 | &mut svm, 113 | &payer, 114 | &vta_key, 115 | vm_address, 116 | unlock_address, 117 | ).is_ok()); 118 | 119 | let unlock = get_unlock_state(&svm, unlock_address); 120 | let mut clock = svm.get_sysvar::(); 121 | clock.unix_timestamp = unlock.unlock_at + 1; 122 | svm.set_sysvar::(&clock); 123 | 124 | assert!(tx_unlock_finalize( 125 | &mut svm, 126 | &payer, 127 | &vta_key, 128 | vm_address, 129 | unlock_address, 130 | ).is_ok()); 131 | 132 | assert!(tx_withdraw_from_memory( 133 | &mut svm, 134 | &payer, 135 | &vta_key, 136 | vm_address, 137 | vm.omnibus.vault, 138 | vm_memory, 139 | unlock_address, 140 | receipt_address, 141 | destination, 142 | WithdrawIxData::FromMemory { account_index } 143 | ).is_ok()); 144 | } 145 | 146 | 147 | #[test] 148 | fn run_withdraw_from_storage() { 149 | let (mut svm, payer, _mint_owner, mint_pk, vm_address) = 150 | setup_svm_with_payer_and_vm(21); 151 | 152 | let name = "test"; 153 | let capacity = 100; 154 | let account_size = VirtualTimelockAccount::LEN+1; 155 | 156 | let (vm_memory, _) = 157 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 158 | 159 | let (vm_storage, _) = 160 | create_storage_account(&mut svm, &payer, vm_address, name); 161 | 162 | let account_index = 7; 163 | 164 | let (vta, vta_key) = 165 | create_timelock(&mut svm, &payer, vm_address, vm_memory, account_index); 166 | 167 | let dest_key = create_keypair(); 168 | let destination = create_ata(&mut svm, &payer, &mint_pk, &dest_key.pubkey()); 169 | 170 | let vm = get_vm_account(&svm, vm_address); 171 | let timelock_address = vta.get_timelock_address( 172 | &vm.get_mint(), 173 | &vm.get_authority(), 174 | vm.get_lock_duration() 175 | ); 176 | 177 | let va = VirtualAccount::Timelock(vta); 178 | let va_hash = va.get_hash(); 179 | let sig = Signature::new(payer.sign_message(va_hash.as_ref()).as_ref()); 180 | let sig_hash = hashv(&[sig.as_ref(), va_hash.as_ref()]); 181 | 182 | assert!(tx_account_compress( 183 | &mut svm, 184 | &payer, 185 | vm_address, 186 | vm_memory, 187 | vm_storage, 188 | account_index, 189 | sig 190 | ).is_ok()); 191 | 192 | let compressed_mem = get_storage_account(&svm, vm_storage).compressed_state; 193 | let proof = compressed_mem.get_merkle_proof(&[sig_hash], 0); 194 | 195 | let unlock_address = vta.get_unlock_address(&timelock_address, &vm_address); 196 | let receipt_address = vta.get_withdraw_receipt_address(&unlock_address, &vm_address); 197 | 198 | assert!(tx_unlock_init( 199 | &mut svm, 200 | &payer, 201 | &vta_key, 202 | vm_address, 203 | unlock_address, 204 | ).is_ok()); 205 | 206 | let unlock = get_unlock_state(&svm, unlock_address); 207 | let mut clock = svm.get_sysvar::(); 208 | clock.unix_timestamp = unlock.unlock_at + 1; 209 | svm.set_sysvar::(&clock); 210 | 211 | assert!(tx_unlock_finalize( 212 | &mut svm, 213 | &payer, 214 | &vta_key, 215 | vm_address, 216 | unlock_address, 217 | ).is_ok()); 218 | 219 | assert!(tx_withdraw_from_storage( 220 | &mut svm, 221 | &payer, 222 | &vta_key, 223 | vm_address, 224 | vm.omnibus.vault, 225 | vm_storage, 226 | unlock_address, 227 | receipt_address, 228 | destination, 229 | WithdrawIxData::FromStorage { 230 | packed_va: va.pack(), 231 | proof, 232 | signature: sig, 233 | } 234 | ).is_ok()); 235 | } -------------------------------------------------------------------------------- /program/tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod svm; 2 | mod state; 3 | mod context; 4 | 5 | pub use svm::*; 6 | pub use state::*; 7 | pub use context::*; 8 | -------------------------------------------------------------------------------- /program/tests/utils/svm.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use std::path::PathBuf; 3 | use code_vm_api::prelude::CodeInstruction; 4 | use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction}; 5 | use litesvm::{types::{FailedTransactionMetadata, TransactionMetadata, TransactionResult}, LiteSVM}; 6 | use litesvm_token::{CreateAssociatedTokenAccount, CreateMint, MintTo, spl_token::{state::Account}, get_spl_account}; 7 | use pretty_hex::*; 8 | 9 | pub fn program_bytes() -> Vec { 10 | let mut so_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 11 | so_path.push("../target/deploy/code_vm_program.so"); 12 | std::fs::read(so_path).unwrap() 13 | } 14 | 15 | pub fn setup_svm() -> LiteSVM { 16 | let mut svm = LiteSVM::new(); 17 | svm.add_program(code_vm_api::ID, &program_bytes()); 18 | svm 19 | } 20 | 21 | pub fn send_tx(svm: &mut LiteSVM, tx: Transaction) -> TransactionResult { 22 | let res = svm.send_transaction(tx.clone()); 23 | 24 | let meta = match res.as_ref() { 25 | Ok(v) => v.clone(), 26 | Err(v) => v.meta.clone() 27 | }; 28 | 29 | print_tx(meta, tx); 30 | 31 | if res.is_err() { 32 | println!("error:\t{:?}", res.as_ref().err().unwrap().err); 33 | } 34 | 35 | res 36 | } 37 | 38 | pub fn create_payer(svm: &mut LiteSVM) -> Keypair { 39 | let payer_kp = Keypair::new(); 40 | let payer_pk = payer_kp.pubkey(); 41 | svm.airdrop(&payer_pk, 64_000_000_000).unwrap(); 42 | payer_kp 43 | } 44 | 45 | pub fn create_keypair() -> Keypair { 46 | Keypair::new() 47 | } 48 | 49 | pub fn create_mint(svm: &mut LiteSVM, payer_kp: &Keypair, owner_pk: &Pubkey) -> Pubkey { 50 | CreateMint::new(svm, payer_kp) 51 | .authority(owner_pk) 52 | .send() 53 | .unwrap() 54 | } 55 | 56 | pub fn create_ata(svm: &mut LiteSVM, payer_kp: &Keypair, mint_pk: &Pubkey, owner_pk: &Pubkey) -> Pubkey { 57 | CreateAssociatedTokenAccount::new(svm, payer_kp, mint_pk) 58 | .owner(owner_pk) 59 | .send() 60 | .unwrap() 61 | } 62 | 63 | pub fn get_ata_balance(svm: &LiteSVM, ata: &Pubkey) -> u64 { 64 | let info:Account = get_spl_account(svm, &ata).unwrap(); 65 | info.amount 66 | } 67 | 68 | pub fn mint_to(svm: &mut LiteSVM, 69 | payer: &Keypair, 70 | mint: &Pubkey, 71 | mint_owner: &Keypair, 72 | destination: &Pubkey, 73 | amount: u64, 74 | ) -> Result<(), FailedTransactionMetadata> { 75 | MintTo::new(svm, payer, mint, destination, amount) 76 | .owner(mint_owner) 77 | .send() 78 | } 79 | 80 | pub fn print_tx(meta: TransactionMetadata, tx: Transaction) { 81 | let msg = tx.message().serialize(); 82 | 83 | println!("\n"); 84 | println!("--------------------------------------------------------------------------------"); 85 | println!("sig:\t{:?}", meta.signature); 86 | println!("len:\t{:?}", msg.len()); 87 | println!("\nbody:\n{}", base64::encode(&msg)); 88 | 89 | for i in 0..tx.message.instructions.len() { 90 | let ix = &tx.message.instructions[i]; 91 | let ix_type = CodeInstruction::try_from(ix.data[0] as u8).unwrap(); 92 | 93 | println!("\nix:\t{:?} ({})", ix_type, ix.data[0]); 94 | println!("accounts:"); 95 | 96 | for key in &ix.accounts { 97 | println!("\t{}: {:?}", key, tx.message.account_keys[*key as usize]); 98 | } 99 | 100 | println!("\ndata:\n\t{:?}", ix.data); 101 | println!("\n\n{}\n", pretty_hex(&ix.data)) 102 | } 103 | 104 | println!(""); 105 | println!("cu:\t{:?}", meta.compute_units_consumed); 106 | println!("logs:"); 107 | for log in &meta.logs { 108 | println!("\t{:?}", log); 109 | } 110 | println!(""); 111 | } 112 | -------------------------------------------------------------------------------- /program/tests/vm_init.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::prelude::*; 6 | use solana_sdk::signer::Signer; 7 | 8 | #[test] 9 | fn run_vm_init_test() { 10 | // Initialize the test context with a lock duration of 21 days 11 | let ctx = TestContext::new(21); 12 | 13 | // Find the expected VM and omnibus addresses and their bumps 14 | let (expected_vm_address, expected_vm_bump) = 15 | find_vm_pda(&ctx.mint_pk, &ctx.payer.pubkey(), 21); 16 | let (expected_omnibus_address, expected_omnibus_bump) = 17 | find_vm_omnibus_pda(&expected_vm_address); 18 | 19 | // Retrieve the VM account from the SVM 20 | let vm_account = ctx.svm.get_account(&ctx.vm_address).unwrap(); 21 | assert_eq!(vm_account.data.len(), CodeVmAccount::get_size()); 22 | 23 | // Use the VM account from the context 24 | let vm = &ctx.vm; 25 | 26 | // Perform assertions to verify the VM initialization 27 | assert_eq!(vm.lock_duration, 21); 28 | assert_eq!(vm.mint, ctx.mint_pk); 29 | assert_eq!(vm.authority, ctx.payer.pubkey()); 30 | assert_eq!(vm.bump, expected_vm_bump); 31 | assert_eq!(vm.omnibus.vault, expected_omnibus_address); 32 | assert_eq!(vm.omnibus.vault_bump, expected_omnibus_bump); 33 | assert_ne!(vm.poh, Hash::default()); 34 | assert_eq!(vm.slot, 1); 35 | } -------------------------------------------------------------------------------- /program/tests/vm_legacy_mem.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use steel::Discriminator; 4 | use utils::*; 5 | 6 | use solana_sdk::signature::Signer; 7 | use code_vm_api::prelude::*; 8 | 9 | #[test] 10 | fn run_transfer_on_legacy_memory() { 11 | // Initialize the test context 12 | let mut ctx = TestContext::new(21); 13 | 14 | // Create memory accounts 15 | let mem_a = ctx.create_memory(100, VirtualDurableNonce::LEN + 1, "mem_nonce_0"); 16 | let mem_b = ctx.create_memory(NUM_ACCOUNTS, VirtualTimelockAccount::LEN + 1, "mem_timelock_0"); 17 | 18 | // Change the memory account to legacy 19 | let mut info = ctx.svm.get_account(&mem_b).unwrap(); 20 | let mem_data = info.data; 21 | let mut mem = MemoryAccount::unpack(&mem_data); 22 | 23 | mem.version = MemoryVersion::Legacy as u8; 24 | mem.packed_info = [ 25 | 0, 0, 0, 0, 0, // _padding 26 | 1 // layout (1 = Timelock) 27 | ]; 28 | 29 | // Assemble the account back together 30 | let discriminator: &[u8; 8] = &[ 31 | MemoryAccount::discriminator(), 32 | 0, 0, 0, 0, 0, 0, 0, 33 | ]; 34 | info.data = [ 35 | discriminator, 36 | mem.to_bytes(), 37 | &mem_data[MemoryAccount::get_size()..], 38 | ].concat(); 39 | 40 | // Set the account directly 41 | ctx.svm 42 | .set_account(mem_b, info) 43 | .unwrap(); 44 | 45 | // Check that the memory account is now legacy 46 | let info = ctx.svm.get_account(&mem_b).unwrap(); 47 | let mem = MemoryAccount::unpack(&info.data); 48 | assert_eq!(mem.get_version(), MemoryVersion::Legacy); 49 | assert_eq!(mem.get_capacity(), NUM_ACCOUNTS); 50 | assert_eq!(mem.get_account_size(), VirtualTimelockAccount::LEN + 1); 51 | 52 | // Do a transfer to check that the memory account is still usable 53 | 54 | // Create timelock accounts 55 | let vta_a_ctx = ctx.create_timelock_account(mem_b, 0); 56 | let vta_b_ctx = ctx.create_timelock_account(mem_b, 1); 57 | 58 | // Create durable nonce account 59 | let vdn_ctx = ctx.create_durable_nonce_account(mem_a, 0); 60 | 61 | // Create the transfer message and signature 62 | let amount = 0; // Sending 0 tokens to keep the test simple 63 | let hash = create_transfer_message( 64 | &ctx.vm, 65 | &vta_a_ctx.account, 66 | &vta_b_ctx.account, 67 | &vdn_ctx.account, 68 | amount, 69 | ); 70 | let signature = vta_a_ctx 71 | .key 72 | .sign_message(hash.as_ref()) 73 | .as_ref() 74 | .try_into() 75 | .unwrap(); 76 | 77 | // Prepare the opcode data 78 | let mem_indices = vec![vdn_ctx.index, vta_a_ctx.index, vta_b_ctx.index]; 79 | let mem_banks = vec![0, 1, 1]; 80 | let data = TransferOp::from_struct( 81 | ParsedTransferOp { amount, signature } 82 | ).to_bytes(); 83 | 84 | // Execute the opcode 85 | ctx.exec_opcode( 86 | [Some(mem_a), Some(mem_b), None, None], 87 | None, // vm_omnibus 88 | None, // relay 89 | None, // relay_vault 90 | None, // external_address 91 | None, // token_program 92 | data, 93 | mem_indices, 94 | mem_banks, 95 | ) 96 | .unwrap(); 97 | } 98 | -------------------------------------------------------------------------------- /program/tests/vm_memory_init.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::prelude::*; 6 | 7 | #[test] 8 | fn run_mem_init_test() { 9 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 10 | setup_svm_with_payer_and_vm(21); 11 | 12 | let name = create_name("test"); 13 | let capacity = 100; 14 | let account_size = VirtualDurableNonce::LEN+1; 15 | 16 | let (vm_mem_address, vm_mem_bump) = find_vm_memory_pda(&vm_address, &name); 17 | 18 | assert!(tx_create_memory(&mut svm, &payer, vm_address, capacity, account_size, "test").is_ok()); 19 | 20 | let mem_account = svm.get_account(&vm_mem_address).unwrap(); 21 | assert!(mem_account.data.len() == MemoryAccount::get_size()); 22 | 23 | let memory = get_memory_account(&svm, vm_mem_address); 24 | assert!(memory.vm == vm_address); 25 | assert!(memory.bump == vm_mem_bump); 26 | assert!(memory.version == 1); 27 | assert!(memory.get_capacity() == capacity); 28 | assert!(memory.get_account_size() == account_size); 29 | assert!(memory.name == name); 30 | 31 | let vm = get_vm_account(&svm, vm_address); 32 | assert!(vm.slot == 2); 33 | assert!(vm.poh != Hash::default()); 34 | } -------------------------------------------------------------------------------- /program/tests/vm_memory_resize.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::prelude::*; 6 | 7 | #[test] 8 | fn run_mem_resize_test() { 9 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 10 | setup_svm_with_payer_and_vm(21); 11 | 12 | let name = "test"; 13 | let capacity = 1000; 14 | let account_size = VirtualDurableNonce::LEN+1; 15 | 16 | let (vm_mem_address, _) = 17 | create_and_resize_memory(&mut svm, &payer, vm_address, capacity, account_size, name); 18 | 19 | let required_size = MemoryAccount::get_size_with_data(capacity, account_size); 20 | let mem_account = svm.get_account(&vm_mem_address).unwrap(); 21 | assert!(mem_account.data.len() == required_size); 22 | } -------------------------------------------------------------------------------- /program/tests/vm_storage_init.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | pub mod utils; 3 | use utils::*; 4 | 5 | use code_vm_api::prelude::*; 6 | 7 | #[test] 8 | fn run_storage_init_test() { 9 | let (mut svm, payer, _mint_owner, _mint_pk, vm_address) = 10 | setup_svm_with_payer_and_vm(21); 11 | 12 | let name = "test"; 13 | 14 | let (vm_storage_address, vm_storage_bump) = 15 | create_storage_account(&mut svm, &payer, vm_address, name); 16 | 17 | let storage_account = svm.get_account(&vm_storage_address).unwrap(); 18 | assert!(storage_account.data.len() == StorageAccount::get_size()); 19 | 20 | let storage = get_storage_account(&svm, vm_storage_address); 21 | assert!(storage.vm == vm_address); 22 | assert!(storage.bump == vm_storage_bump); 23 | assert!(storage.name == create_name(name)); 24 | 25 | let actual = storage.compressed_state; 26 | 27 | assert_eq!(actual.get_depth(), StorageAccount::MERKLE_TREE_DEPTH as u8); 28 | assert_ne!(actual.get_root(), Hash::default()); 29 | assert_ne!(actual.get_empty_leaf(), Hash::default()); 30 | 31 | let expected = MerkleTree::<{StorageAccount::MERKLE_TREE_DEPTH}>::new(&[ 32 | MERKLE_TREE_SEED, 33 | create_name(name).as_ref(), 34 | vm_address.as_ref() 35 | ]); 36 | 37 | assert_eq!(actual.get_root(), expected.get_root()); 38 | 39 | let vm = get_vm_account(&svm, vm_address); 40 | assert!(vm.slot == 2); 41 | assert!(vm.poh != Hash::default()); 42 | } --------------------------------------------------------------------------------