https://github.com/laugharne/ssf_s3_exo
Build an asset manager’s vault, where customers can deposit SPL tokens of their choice. The vault manager should not be able to withdraw the vault’s funds.
https://github.com/laugharne/ssf_s3_exo
anchor cpi fellowship pda rust rust-lang solana summer test test-unit token typescript
Last synced: about 2 months ago
JSON representation
Build an asset manager’s vault, where customers can deposit SPL tokens of their choice. The vault manager should not be able to withdraw the vault’s funds.
- Host: GitHub
- URL: https://github.com/laugharne/ssf_s3_exo
- Owner: Laugharne
- Created: 2024-08-06T06:42:29.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2024-08-10T05:58:37.000Z (9 months ago)
- Last Synced: 2025-02-01T05:41:44.066Z (4 months ago)
- Topics: anchor, cpi, fellowship, pda, rust, rust-lang, solana, summer, test, test-unit, token, typescript
- Language: TypeScript
- Homepage:
- Size: 550 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Asset manager's vault
## Deployed program
**Program Id:** `8uot8k7km7RtdkxFfXGG2nrRd8CxAG4p2SNJ5sSpmQaz`
**On devnet : [Solana Explorer Link](https://solana.fm/address/8uot8k7km7RtdkxFfXGG2nrRd8CxAG4p2SNJ5sSpmQaz/transactions?cluster=devnet-solana)**
--------
## Overview

**Exercise:** Build an **asset manager’s vault**, where customers can deposit SPL tokens of their choice. The vault manager should not be able to withdraw the vault’s funds
Topics in this exercice :
- Rust
- Anchor
- PDA
- init_if_needed
- CPI
- ATA creation
- Token transfer
- Build & deploy on devnet### Components
- This Anchor program defines a vault that allows users to deposit SPL tokens into a secure account managed by a PDA.
- The PDA acts as the authority over the vault, ensuring that only authorized actions can be performed.
- The program is designed with error handling and careful account validation to prevent unauthorized access or insufficient funds errors.1. Program & instructions
- **Initialization**: `initialize()`, a function to set up initial state or accounts required by the program. In this case, it just logs "INITIALIZE" and does nothing else.
- **Depose tokens**: `deposit()`, the function to deposit SPL tokens into the vault. It performs checks and transfers tokens.2. Accounts
**Initialize Struct**
```rust
#[derive(Accounts)]
pub struct Initialize<'info> {
...
}
```
- **`#[derive(Accounts)]`**: This macro implements necessary traits for the struct to be used in a context (e.g., in `Context`).
- **`token_account_owner_pda: AccountInfo<'info>`**: An unchecked account that represents the PDA (Program Derived Address) which will own the token vault. It's marked as `CHECK`, meaning it requires extra care as it's not automatically validated by Anchor.
- **`signer: Signer<'info>`**: The account that signs the transaction and pays for the transaction fees.
- **`system_program: Program<'info, System>`**: The Solana System Program, which is necessary for account initialization.
- **`token_program: Program<'info, Token>`**: The SPL Token program, used for handling tokens.
- **`rent: Sysvar<'info, Rent>`**: A system variable that provides the current rent exemption status.**Deposit Struct**
```rust
#[derive(Accounts)]
pub struct Deposit<'info> {
...
}
```
- **`token_account_owner_pda: AccountInfo<'info>`**: Similar to the Initialize struct, this is the PDA that will own the vault.
- **`vault: Account<'info, TokenAccount>`**: The vault account where tokens will be stored. It is created if needed (`init_if_needed`), associated with the mint and owned by the PDA.
- **`signer: Signer<'info>`**: The user who is depositing tokens.
- **`mint_account: Account<'info, Mint>`**: The mint account for the tokens being deposited.
- **`sender_token_account: Account<'info, TokenAccount>`**: The token account from which tokens will be transferred.
- **`token_program: Program<'info, Token>`**: The SPL Token program.
- **`system_program: Program<'info, System>`**: The system program, required for account initialization.3. **Error Handling**
```rust
#[error_code]
pub enum VaultError {
#[msg("Insufficient Funds.")]
InsufficientFunds,
}
```- **`#[error_code]`**: This macro defines custom error codes for the program. It allows the program to return specific errors that can be handled by the client.
- **`VaultError`**: This enum defines possible errors, such as "Insufficient Funds", which is used when a user tries to deposit more tokens than they have.4. **CPI Context and Token Transfer**
```rust
let transfer_instruction: anchor_spl::token::Transfer = Transfer {
from: ctx.accounts.sender_token_account.to_account_info(),
to: ctx.accounts.vault.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};let cpi_ctx: CpiContext = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_instruction,
);anchor_spl::token::transfer(cpi_ctx, amount)?;
```- **Transfer**: A struct defining the details of the token transfer, specifying the source account, destination account, and authority.
- **CpiContext**: Context for a cross-program invocation (CPI). This wraps the transfer instruction and provides the necessary program information.
- **anchor_spl::token::transfer**: This function performs the token transfer.## Tree repository
```bash
.
├── app
├── migrations
│ └── deploy.ts
├── programs
│ └── vault
│ ├── src
│ │ └── lib.rs
│ ├── Cargo.toml
│ └── Xargo.toml
├── tests
│ └── vault.ts
├── 2024-08-06-08-27-27.png
├── Anchor.toml
├── Cargo.lock
├── Cargo.toml
├── package.json
├── package-lock.json
├── README.md
├── tsconfig.json
└── yarn.lock6 directories, 14 files
```## Deployment
**Changing the cluster environnement**
```bash
solana config set --url devnet
```**Create and set wallet if needed**
```bash
solana-keygen new --outfile ~/.config/solana/devnet.json
solana config set --keypair ~/.config/solana/devnet.json
solana airdrop 2
```**Update : Anchor.toml**
```toml
[programs.devnet]
my_program = "8uot8k7km7RtdkxFfXGG2nrRd8CxAG4p2SNJ5sSpmQaz"[provider]
cluster = "devnet"
wallet = "~/.config/solana/devnet.json"
# cluster = "Localnet"
# wallet = "~/.config/solana/id.json"
```**Build and deploy**
```bash
anchor build
anchor deploy
```>**Program Id:** 8uot8k7km7RtdkxFfXGG2nrRd8CxAG4p2SNJ5sSpmQaz
>
> Deploy success## Local tests
### Local validator
`solana-test-validator --reset`
⚠️ Beware it creates local files and directories at the current working directory.
### Real-time logs display
`solana logs`
### Local deploy and launch tests
`anchor test --skip-local-validator`
## Versions
```
rustc 1.79.0 (129f3b996 2024-06-10)
cargo 1.79.0 (ffa9cf99a 2024-06-03)
solana-cli 1.18.17 (src:b685182a; feat:4215500110, client:SolanaLabs)
anchor-cli 0.29.0
yarn 1.22.22
node v18.17.0
npm 9.6.7
````cargo build-sbf -V`
```
solana-cargo-build-sbf 1.18.17
platform-tools v1.41
rustc 1.75.0
```## Resources
- [Cross Program Invocation (CPI) | Solana](https://solana.com/docs/core/cpi)
- [Anchor : High-level Overview - Docs](https://www.anchor-lang.com/docs/high-level-overview)
- [Program Derived Addresses (PDAs) | Solana Cookbook](https://solanacookbook.com/core-concepts/pdas.html#facts)
- [Slides : Anchor](https://pitch.com/v/anchor-dudz4a)
- [CPI code for transferring tokens](https://github.com/solana-developers/program-examples/blob/main/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs)
- [Transferring SOL and building a payment splitter: "msg.value" in Solana](https://www.rareskills.io/post/anchor-transfer-sol)
- [Cross Program Invocation in Anchor](https://www.rareskills.io/post/cross-program-invocation)
- [PDA (Program Derived Address) vs Keypair Account in Solana](https://www.rareskills.io/post/solana-pda)
- [Solana Playground| Spl Token Vault](https://beta.solpg.io/tutorials/spl-token-vault)