First commit

This commit is contained in:
microwavedcola1 2022-01-28 10:22:59 +01:00
commit 4cc17bc1c1
36 changed files with 8080 additions and 0 deletions

46
.github/workflows/ci-cargo-audit.yml vendored Normal file
View File

@ -0,0 +1,46 @@
# CI job for scanning Cargo dependencies for vulnerabilities and report/fail job based on criticality.
# Critically vulnerable dependencies with fix available will mark the run as failed (X)
name: Rust Cargo Audit
on:
push:
branches: master
pull_request:
branches: master
# Allowing manual runs with ability to choose branch
workflow_dispatch:
# Optimisation option by targeting direct paths to only scan when there are changes to dependencies in the push/PR
# push:
# paths:
# - 'Cargo.toml'
# - 'Cargo.lock'
# pull_request:
# paths:
# - 'Cargo.toml'
# - 'Cargo.lock'
# Example of running scheduled scans at 6AM UTC every Monday to regularly check for vulnerable dependencies
# schedule:
# - cron: '0 6 * * 1'
# Run the job
jobs:
Cargo-audit:
name: Cargo Vulnerability Scanner
runs-on: ubuntu-latest
steps:
# Check out GitHub repo
- uses: actions/checkout@v2
# Install cargo audit
- name: Install Cargo Audit
uses: actions-rs/install@v0.1
with:
crate: cargo-audit
version: latest
# Run cargo audit using args from .cargo/audit.toml (ignores, etc.)
- name: Run Cargo Audit
run: cargo audit -c always

99
.github/workflows/ci-lint-test.yml vendored Normal file
View File

@ -0,0 +1,99 @@
name: Lint and Test
on:
push:
branches: [ master, v*.* ]
pull_request:
branches: [ master, v*.* ]
env:
CARGO_TERM_COLOR: always
SOLANA_VERSION: "1.9.1"
RUST_TOOLCHAIN: nightly-2021-12-15
defaults:
run:
working-directory: ./
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust nightly
uses: actions-rs/toolchain@v1
with:
override: true
profile: minimal
toolchain: ${{ env.RUST_TOOLCHAIN }}
components: rustfmt, clippy
- name: Cache dependencies
uses: Swatinem/rust-cache@v1
- name: Run fmt
run: cargo fmt -- --check
# The style and complexity lints have not been processed yet.
- name: Run clippy
run: cargo clippy -- --deny=warnings --allow=clippy::style --allow=clippy::complexity
tests:
name: Test and Soteria
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Linux dependencies
run: sudo apt-get install -y pkg-config build-essential libudev-dev
- name: Install Rust nightly
uses: actions-rs/toolchain@v1
with:
override: true
profile: minimal
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Cache dependencies
uses: Swatinem/rust-cache@v1
# Install Solana
- name: Cache Solana binaries
uses: actions/cache@v2
with:
path: ~/.cache/solana
key: ${{ runner.os }}-${{ env.SOLANA_VERSION }}
- name: Install Solana
run: |
sh -c "$(curl -sSfL https://release.solana.com/v${{ env.SOLANA_VERSION }}/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH"
solana --version
echo "Generating keypair..."
solana-keygen new -o "$HOME/.config/solana/id.json" --no-passphrase --silent
- name: Run bpf tests
run: cargo test-bpf
# Create a cache for Soteria
- name: Cache Soteria
id: cache-soteria
uses: actions/cache@v2
with:
path: ~/.cache/soteria
key: ${{ runner.os }}-soteria
# Install Soteria
- name: Install Soteria
run: |
echo "Downloading Soteria..."
sh -c "$(curl -k https://supercompiler.xyz/install)"
export PATH=$PWD/soteria-linux-develop/bin/:$PATH
echo "$PWD/soteria-linux-develop/bin" >> $GITHUB_PATH
echo "Updating Rust..."
rustup update
echo "Soteria ready!"
# Run Soteria tests against Cargo.toml for this repo (root folder)
- name: Run Soteria
run: |
echo "Running Soteria..."
soteria -analyzeAll .
echo "Soteria finished!"

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
.idea

3874
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

4
Cargo.toml Normal file
View File

@ -0,0 +1,4 @@
[workspace]
members = [
"programs/*"
]

View File

@ -0,0 +1,41 @@
[package]
name = "dasheri"
version = "0.1.0"
description = "Created with Anchor"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "dasheri"
doctest = false
[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
test-bpf = []
[dependencies]
#anchor-lang = {version= "^0.20.1", features=["anchor-debug"]}
#anchor-spl = {version= "^0.20.1"}
anchor-lang = { path = "../../../anchor/lang", features=["anchor-debug"]}
anchor-spl = { path = "../../../anchor/spl"}
solana-program = "^1.8.5"
spl-token = { version = "3.1.1", features = ["no-entrypoint"] }
mango = { git = "https://github.com/blockworks-foundation/mango-v3.git", default-features = false, features = ["no-entrypoint"], branch = "ckamm/payer-for-pda" }
mango-common = { git = "https://github.com/blockworks-foundation/mango-v3.git", branch = "ckamm/payer-for-pda" }
static_assertions = "1.1"
[dev-dependencies]
solana-sdk = "^1.8.5"
solana-program-test = "^1.8.5"
solana-logger = "^1.8.5"
bytemuck = "^1.7.2"
fixed = { version = "=1.9.0", features = ["serde"] }
fixed-macro = "^1.1.1"
serum_dex = { version = "0.4.0", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features = false, features = ["no-entrypoint", "program"] }
bincode = "^1.3.1"
serde = "^1.0.118"
spl-associated-token-account = { version = "^1.0.3", features = ["no-entrypoint"] }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,8 @@
pub mod usdc_token {
use solana_program::declare_id;
#[cfg(feature = "devnet")]
declare_id!("8FRFC6MoGGkMFQwngccyu69VnYbzykGeez7ignHVAFSN");
#[cfg(not(feature = "devnet"))]
declare_id!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
}

View File

@ -0,0 +1,114 @@
use crate::iou::state::Gateway;
use crate::pool::state::Pool;
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::accessor::amount;
use anchor_spl::token::{self, Mint, MintTo, Token, TokenAccount};
use mango::instruction;
use solana_program::program::invoke;
#[derive(Accounts)]
pub struct IouDepositIntoMangoAccount<'info> {
pub mango_program: UncheckedAccount<'info>,
pub mango_group: UncheckedAccount<'info>,
pub mango_cache: UncheckedAccount<'info>,
pub root_bank: UncheckedAccount<'info>,
#[account(mut)]
pub node_bank: UncheckedAccount<'info>,
#[account(mut)]
pub node_bank_vault: UncheckedAccount<'info>,
#[account(mut)]
pub owner_token_account: UncheckedAccount<'info>,
#[account(mut)]
pub mango_account: UncheckedAccount<'info>,
#[account(
seeds = [b"gateway".as_ref(), gateway.admin.key().as_ref()],
bump = gateway.bump,
has_one = deposit_iou_mint,
has_one = token_mint
)]
pub gateway: Box<Account<'info, Gateway>>,
#[account(
mut,
seeds = [token_mint.key().as_ref()],
bump = gateway.deposit_iou_mint_bump,
)]
pub deposit_iou_mint: AccountInfo<'info>,
pub token_mint: Account<'info, Mint>,
#[account(
init_if_needed,
associated_token::authority = payer,
associated_token::mint = deposit_iou_mint,
payer = payer
)]
pub deposit_iou_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub rent: Sysvar<'info, Rent>,
}
impl<'info> IouDepositIntoMangoAccount<'info> {
fn iou_mint_context(&self) -> CpiContext<'_, '_, '_, 'info, MintTo<'info>> {
CpiContext::new(
self.token_program.to_account_info(),
MintTo {
to: self.deposit_iou_account.to_account_info(),
mint: self.deposit_iou_mint.to_account_info(),
authority: self.gateway.to_account_info(),
},
)
}
}
pub fn handler(ctx: Context<IouDepositIntoMangoAccount>, quantity: u64) -> ProgramResult {
let instruction = instruction::deposit(
&ctx.accounts.mango_program.key(),
&ctx.accounts.mango_group.key(),
&ctx.accounts.mango_account.key(),
&ctx.accounts.payer.key(),
&ctx.accounts.mango_cache.key(),
&ctx.accounts.root_bank.key(),
&ctx.accounts.node_bank.key(),
&ctx.accounts.node_bank_vault.key(),
&ctx.accounts.owner_token_account.key(),
quantity,
)
.unwrap();
invoke(
&instruction,
&[
ctx.accounts.mango_program.to_account_info().clone(),
ctx.accounts.mango_group.to_account_info().clone(),
ctx.accounts.mango_account.to_account_info().clone(),
ctx.accounts.payer.to_account_info().clone(),
ctx.accounts.mango_cache.to_account_info().clone(),
ctx.accounts.root_bank.to_account_info().clone(),
ctx.accounts.node_bank.to_account_info().clone(),
ctx.accounts.node_bank_vault.to_account_info().clone(),
ctx.accounts.owner_token_account.to_account_info().clone(),
ctx.accounts.system_program.to_account_info().clone(),
ctx.accounts.token_program.to_account_info().clone(),
],
)?;
token::mint_to(
ctx.accounts.iou_mint_context().with_signer(&[&[
"gateway".as_ref(),
ctx.accounts.gateway.admin.key().as_ref(),
&[ctx.accounts.gateway.bump],
]]),
quantity,
)?;
Ok(())
}

View File

@ -0,0 +1,80 @@
use crate::iou::state::Gateway;
use anchor_lang::prelude::*;
use anchor_spl::token::{self, InitializeMint, Mint, Token};
#[derive(Accounts)]
#[instruction(gateway_bump: u8, deposit_iou_mint_bump: u8)]
pub struct IouInitGateway<'info> {
#[account(
init,
seeds = [b"gateway".as_ref(), admin.key().as_ref()],
bump = gateway_bump,
payer = admin,
space = 8 + std::mem::size_of::<Gateway>(),
)]
pub gateway: Box<Account<'info, Gateway>>,
/// The mint for iou which will represent user deposits
#[account(
init,
seeds = [token_mint.key().as_ref()],
bump = deposit_iou_mint_bump,
payer = admin,
owner = token::ID,
space = Mint::LEN
)]
pub deposit_iou_mint: AccountInfo<'info>,
/// The mint for the token deposited via the gateway
pub token_mint: Account<'info, Mint>,
pub admin: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub rent: Sysvar<'info, Rent>,
}
impl<'info> IouInitGateway<'info> {
fn init_deposit_iou_mint_context(
&self,
) -> CpiContext<'_, '_, '_, 'info, InitializeMint<'info>> {
CpiContext::new(
self.token_program.to_account_info(),
InitializeMint {
mint: self.deposit_iou_mint.clone(),
rent: self.rent.to_account_info(),
},
)
}
fn init_mint(&self) -> ProgramResult {
token::initialize_mint(
self.init_deposit_iou_mint_context(),
self.token_mint.decimals,
&self.gateway.key(),
Some(&self.gateway.key()),
)?;
// todo: create mint for borrowing iou
Ok(())
}
}
pub fn handler(
ctx: Context<IouInitGateway>,
gateway_bump: u8,
deposit_iou_mint_bump: u8,
) -> ProgramResult {
ctx.accounts.init_mint();
let gateway = &mut ctx.accounts.gateway;
gateway.token_mint = ctx.accounts.token_mint.key();
gateway.bump = gateway_bump;
gateway.deposit_iou_mint_bump = deposit_iou_mint_bump;
gateway.deposit_iou_mint = ctx.accounts.deposit_iou_mint.key();
gateway.admin = ctx.accounts.admin.key();
Ok(())
}

View File

@ -0,0 +1,5 @@
pub use deposit_into_mango_account::*;
pub use init_gateway::*;
pub mod deposit_into_mango_account;
pub mod init_gateway;

View File

@ -0,0 +1,2 @@
pub mod instructions;
pub mod state;

View File

@ -0,0 +1,17 @@
use anchor_lang::prelude::*;
/// A gateway acts a middleware for depositing and borrowing. Depositing in mango
/// doesn't return iou tokens.
/// One can use a deposit mint to mint and return iou
/// tokens back to depositor.
#[account]
#[derive(Default)]
pub struct Gateway {
pub token_mint: Pubkey,
pub bump: u8,
pub deposit_iou_mint_bump: u8,
pub deposit_iou_mint: Pubkey,
pub admin: Pubkey,
pub padding: [u8; 31],
}
const_assert!(std::mem::size_of::<Gateway>() == 1 + 1 + 32 + 32 + 32 + 31);

View File

@ -0,0 +1,2 @@
pub use gateway::*;
pub mod gateway;

View File

@ -0,0 +1,65 @@
#[macro_use]
extern crate static_assertions;
use anchor_lang::prelude::*;
use iou::instructions::*;
use pool::instructions::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
mod ids;
mod iou;
mod pool;
#[program]
pub mod dasheri {
use super::*;
/// iou
pub fn iou_init_gateway(
ctx: Context<IouInitGateway>,
_gateway_bump: u8,
_deposit_iou_mint_bump: u8,
) -> ProgramResult {
iou::instructions::init_gateway::handler(ctx, _gateway_bump, _deposit_iou_mint_bump)
}
pub fn iou_deposit_into_mango_account(
ctx: Context<IouDepositIntoMangoAccount>,
quantity: u64,
) -> ProgramResult {
iou::instructions::deposit_into_mango_account::handler(ctx, quantity)
}
/// pool
pub fn pool_create_pool(ctx: Context<PoolCreatePool>, bump: u8) -> ProgramResult {
create_pool::handler(ctx, bump)
}
pub fn pool_create_pool_account(
ctx: Context<PoolCreatePoolAccount>,
bump: u8,
) -> ProgramResult {
create_pool_account::handler(ctx, bump)
}
pub fn pool_deposit_into_pool(ctx: Context<PoolDepositIntoPool>, amount: u64) -> ProgramResult {
deposit_into_pool::handler(ctx, amount)
}
pub fn pool_create_mango_account(
ctx: Context<PoolCreateMangoAccount>,
account_num: u64,
bump: u8,
) -> ProgramResult {
create_mango_account::handler(ctx, account_num, bump)
}
pub fn pool_deposit_into_mango_account(
ctx: Context<PoolDepositIntoMangoAccount>,
quantity: u64,
) -> ProgramResult {
pool::instructions::deposit_into_mango_account::handler(ctx, quantity)
}
}

View File

@ -0,0 +1,7 @@
use anchor_lang::prelude::*;
#[error]
pub enum ErrorCode {
#[msg("")]
InvalidDepositMint,
}

View File

@ -0,0 +1,60 @@
use crate::pool::state::Pool;
use anchor_lang::prelude::*;
use mango::instruction;
use solana_program::program::invoke_signed;
#[derive(Accounts)]
#[instruction(account_num: u64, bump: u8)]
pub struct PoolCreateMangoAccount<'info> {
pub mango_program: AccountInfo<'info>,
#[account(mut)]
pub mango_group: AccountInfo<'info>,
#[account(mut)]
pub mango_account: AccountInfo<'info>,
#[account(
mut,
seeds = [b"pool".as_ref(), pool.admin.key().as_ref()],
bump = pool.bump,
)]
pub pool: Box<Account<'info, Pool>>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn handler(ctx: Context<PoolCreateMangoAccount>, account_num: u64, bump: u8) -> ProgramResult {
let instruction = instruction::create_mango_account(
ctx.accounts.mango_program.key,
ctx.accounts.mango_group.to_account_info().key,
ctx.accounts.mango_account.to_account_info().key,
ctx.accounts.pool.to_account_info().key,
ctx.accounts.system_program.to_account_info().key,
ctx.accounts.payer.to_account_info().key,
account_num,
)
.unwrap();
let pool_admin_key = &ctx.accounts.pool.admin.key();
let seeds = &[
b"pool".as_ref(),
pool_admin_key.as_ref(),
&[ctx.accounts.pool.bump],
];
invoke_signed(
&instruction,
&[
ctx.accounts.mango_program.to_account_info().clone(),
ctx.accounts.mango_group.to_account_info().clone(),
ctx.accounts.mango_account.to_account_info().clone(),
ctx.accounts.pool.to_account_info().clone(),
ctx.accounts.system_program.to_account_info().clone(),
ctx.accounts.payer.to_account_info().clone(),
],
&[&seeds[..]],
)?;
Ok(())
}

View File

@ -0,0 +1,50 @@
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::{Mint, Token, TokenAccount};
use crate::pool::state::Pool;
#[derive(Accounts)]
#[instruction(bump: u8)]
pub struct PoolCreatePool<'info> {
#[account(
init,
seeds = [b"pool".as_ref(), admin.key().as_ref()],
bump = bump,
payer = admin,
space = 8 + std::mem::size_of::<Pool>(),
)]
pub pool: Box<Account<'info, Pool>>,
#[account(
init,
associated_token::authority = pool,
associated_token::mint = deposit_iou_mint,
payer = admin
)]
pub vault: Box<Account<'info, TokenAccount>>,
// todo: verify that the mint is a specific one you are expecting e.g. usdc
// #[account(
// constraint = deposit_iou_mint.key() == usdc_token::ID
// )]
pub deposit_iou_mint: Box<Account<'info, Mint>>,
#[account(mut)]
pub admin: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub rent: Sysvar<'info, Rent>,
}
pub fn handler(ctx: Context<PoolCreatePool>, bump: u8) -> ProgramResult {
let pool = &mut ctx.accounts.pool;
pool.bump = bump;
pool.deposit_iou_mint = ctx.accounts.deposit_iou_mint.key();
pool.vault = ctx.accounts.vault.key();
pool.admin = ctx.accounts.admin.key();
Ok(())
}

View File

@ -0,0 +1,36 @@
use anchor_lang::prelude::*;
use crate::pool::state::{Pool, PoolAccount};
#[derive(Accounts)]
#[instruction(bump: u8)]
pub struct PoolCreatePoolAccount<'info> {
#[account(
init,
seeds = [b"pool_account".as_ref(), pool.key().as_ref()],
bump = bump,
payer = user,
space = 8 + std::mem::size_of::<PoolAccount>(),
)]
pub pool_account: Box<Account<'info, PoolAccount>>,
#[account(
seeds = [b"pool".as_ref(), pool.admin.key().as_ref()],
bump = pool.bump,
)]
pub pool: Box<Account<'info, Pool>>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn handler(ctx: Context<PoolCreatePoolAccount>, bump: u8) -> ProgramResult {
let pool_account = &mut ctx.accounts.pool_account;
pool_account.bump = bump;
pool_account.pool = ctx.accounts.pool.key();
pool_account.owner = ctx.accounts.user.key();
Ok(())
}

View File

@ -0,0 +1,68 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
use mango::instruction;
use solana_program::program::invoke_signed;
use crate::pool::state::Pool;
#[derive(Accounts)]
pub struct PoolDepositIntoMangoAccount<'info> {
pub mango_program: UncheckedAccount<'info>,
pub mango_group: UncheckedAccount<'info>,
pub mango_cache: UncheckedAccount<'info>,
pub root_bank: UncheckedAccount<'info>,
#[account(mut)]
pub node_bank: UncheckedAccount<'info>,
#[account(mut)]
pub node_bank_vault: UncheckedAccount<'info>,
#[account(mut)]
pub owner_token_account: UncheckedAccount<'info>,
#[account(mut)]
pub mango_account: UncheckedAccount<'info>,
pub pool: Box<Account<'info, Pool>>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
pub fn handler(ctx: Context<PoolDepositIntoMangoAccount>, quantity: u64) -> ProgramResult {
let instruction = instruction::deposit(
&ctx.accounts.mango_program.key(),
&ctx.accounts.mango_group.key(),
&ctx.accounts.mango_account.key(),
&ctx.accounts.pool.key(),
&ctx.accounts.mango_cache.key(),
&ctx.accounts.root_bank.key(),
&ctx.accounts.node_bank.key(),
&ctx.accounts.node_bank_vault.key(),
&ctx.accounts.owner_token_account.key(),
quantity,
)
.unwrap();
let pool_admin_key = &ctx.accounts.pool.admin.key();
let seeds = &[
b"pool".as_ref(),
pool_admin_key.as_ref(),
&[ctx.accounts.pool.bump],
];
invoke_signed(
&instruction,
&[
ctx.accounts.mango_program.to_account_info().clone(),
ctx.accounts.mango_group.to_account_info().clone(),
ctx.accounts.mango_account.to_account_info().clone(),
ctx.accounts.pool.to_account_info().clone(),
ctx.accounts.mango_cache.to_account_info().clone(),
ctx.accounts.root_bank.to_account_info().clone(),
ctx.accounts.node_bank.to_account_info().clone(),
ctx.accounts.node_bank_vault.to_account_info().clone(),
ctx.accounts.owner_token_account.to_account_info().clone(),
ctx.accounts.system_program.to_account_info().clone(),
ctx.accounts.token_program.to_account_info().clone(),
],
&[&seeds[..]],
)?;
Ok(())
}

View File

@ -0,0 +1,61 @@
use crate::pool::state::{Pool, PoolAccount};
use anchor_lang::prelude::*;
use anchor_spl::token;
use anchor_spl::token::{Token, TokenAccount};
#[derive(Accounts)]
pub struct PoolDepositIntoPool<'info> {
#[account(
seeds = [b"pool".as_ref(), pool.admin.key().as_ref()],
bump = pool.bump,
)]
pub pool: Box<Account<'info, Pool>>,
#[account(
seeds = [b"pool_account".as_ref(), pool.key().as_ref()],
bump = pool_account.bump,
constraint = pool_account.owner == user.key()
)]
pub pool_account: Box<Account<'info, PoolAccount>>,
#[account(
mut,
associated_token::authority = pool,
associated_token::mint = pool.deposit_iou_mint,
)]
pub vault: Box<Account<'info, TokenAccount>>,
#[account(
mut,
constraint = deposit_token.owner == user.key(),
)]
pub deposit_token: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub user: Signer<'info>,
pub token_program: Program<'info, Token>,
}
impl<'info> PoolDepositIntoPool<'info> {
pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
let program = self.token_program.to_account_info();
let accounts = token::Transfer {
from: self.deposit_token.to_account_info(),
to: self.vault.to_account_info(),
authority: self.user.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
pub fn handler(ctx: Context<PoolDepositIntoPool>, amount: u64) -> ProgramResult {
token::transfer(ctx.accounts.transfer_ctx(), amount)?;
// todo: record how much amount user deposited on pool_account
// todo: record pool performance at the time of deposit, is important if you want
// to support users depositing at various points in time (after pool has already e.g. started
// trading on mango), so that you can distribute rewards fairly
Ok(())
}

View File

@ -0,0 +1,11 @@
pub use create_mango_account::*;
pub use create_pool::*;
pub use create_pool_account::*;
pub use deposit_into_mango_account::*;
pub use deposit_into_pool::*;
pub mod create_mango_account;
pub mod create_pool;
pub mod create_pool_account;
pub mod deposit_into_mango_account;
pub mod deposit_into_pool;

View File

@ -0,0 +1,3 @@
pub mod error;
pub mod instructions;
pub mod state;

View File

@ -0,0 +1,5 @@
pub use pool::*;
pub use pool_account::*;
pub mod pool;
pub mod pool_account;

View File

@ -0,0 +1,19 @@
use anchor_lang::prelude::*;
/// A pool represents a collective entity. Initialized by an admin,
/// users can create accounts within the pool, and deposit assets
/// of a said deposit mint. Pool would have a corresponding mango account
/// to carry out collective activities e.g.
/// 1) run liquidator using all the deposited
/// amounts
/// 2) run a fund which does trading and distributes the profits to all users etc.
#[account]
#[derive(Default)]
pub struct Pool {
pub bump: u8,
pub deposit_iou_mint: Pubkey,
pub vault: Pubkey,
pub admin: Pubkey,
pub padding: [u8; 31],
}
const_assert!(std::mem::size_of::<Pool>() == 1 + 32 + 32 + 32 + 31);

View File

@ -0,0 +1,14 @@
use anchor_lang::prelude::*;
/// A pool account is a user account attached to a pool, it will do bookkeeping
/// for owner, and additionally other metadata e.g. how much funds did the user
/// deposit in pool etc.
#[account]
#[derive(Default)]
pub struct PoolAccount {
pub bump: u8,
pub pool: Pubkey,
pub owner: Pubkey,
pub padding: [u8; 31],
}
const_assert!(std::mem::size_of::<PoolAccount>() == 1 + 32 + 32 + 31);

BIN
programs/dasheri/tests/fixtures/mango.so vendored Executable file

Binary file not shown.

BIN
programs/dasheri/tests/fixtures/serum_dex.so vendored Executable file

Binary file not shown.

View File

@ -0,0 +1,227 @@
use crate::*;
use fixed::types::I80F48;
use mango::state::*;
use solana_program::pubkey::Pubkey;
use std::collections::HashMap;
#[allow(dead_code)]
pub fn assert_deposits(
mango_group_cookie: &MangoGroupCookie,
expected_values: (usize, HashMap<usize, I80F48>),
) {
let (user_index, expected_value) = expected_values;
for (mint_index, expected_deposit) in expected_value.iter() {
let actual_deposit = &mango_group_cookie.mango_accounts[user_index]
.mango_account
.get_native_deposit(
&mango_group_cookie.mango_cache.root_bank_cache[*mint_index],
*mint_index,
)
.unwrap();
println!(
"==\nUser: {}, Mint: {}\nExpected deposit: {}, Actual deposit: {}\n==",
user_index,
mint_index,
expected_deposit.to_string(),
actual_deposit.to_string(),
);
assert!(expected_deposit == actual_deposit);
}
}
#[allow(dead_code)]
pub fn assert_open_spot_orders(
mango_group_cookie: &MangoGroupCookie,
user_spot_orders: &Vec<(usize, usize, serum_dex::matching::Side, f64, f64)>,
) {
for i in 0..user_spot_orders.len() {
let (user_index, mint_index, _, _, _) = user_spot_orders[i];
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
assert_ne!(mango_account.spot_open_orders[mint_index], Pubkey::default());
}
}
#[allow(dead_code)]
pub async fn assert_user_spot_orders(
test: &mut MangoProgramTest,
mango_group_cookie: &MangoGroupCookie,
expected_values: (usize, usize, HashMap<&str, I80F48>),
) {
let (mint_index, user_index, expected_value) = expected_values;
let (actual_quote_free, actual_quote_locked, actual_base_free, actual_base_locked) =
test.get_oo_info(&mango_group_cookie, user_index, mint_index).await;
println!("User index: {}", user_index);
if let Some(quote_free) = expected_value.get("quote_free") {
// println!(
// "==\nUser: {}, Mint: {}\nExpected quote_free: {}, Actual quote_free: {}\n==",
// user_index,
// mint_index,
// quote_free.to_string(),
// actual_quote_free.to_string(),
// );
assert!(*quote_free == actual_quote_free);
}
if let Some(quote_locked) = expected_value.get("quote_locked") {
// println!(
// "==\nUser: {}, Mint: {}\nExpected quote_locked: {}, Actual quote_locked: {}\n==",
// user_index,
// mint_index,
// quote_locked.to_string(),
// actual_quote_locked.to_string(),
// );
assert!(*quote_locked == actual_quote_locked);
}
if let Some(base_free) = expected_value.get("base_free") {
println!(
"==\nUser: {}, Mint: {}\nExpected base_free: {}, Actual base_free: {}\n==",
user_index,
mint_index,
base_free.to_string(),
actual_base_free.to_string(),
);
assert!(*base_free == actual_base_free);
}
if let Some(base_locked) = expected_value.get("base_locked") {
println!(
"==\nUser: {}, Mint: {}\nExpected base_locked: {}, Actual base_locked: {}\n==",
user_index,
mint_index,
base_locked.to_string(),
actual_base_locked.to_string(),
);
assert!(*base_locked == actual_base_locked);
}
}
// #[allow(dead_code)]
// pub fn assert_matched_spot_orders(
// mango_group_cookie: &MangoGroupCookie,
// user_spot_orders: &Vec<(usize, usize, serum_dex::matching::Side, f64, f64)>,
// ) {
// let mut balances_map: HashMap<String, (f64, f64)> = HashMap::new();
// for i in 0..user_spot_orders.len() {
// let (user_index, _, arranged_order_side, arranged_order_size, arranged_order_price) = user_spot_orders[i];
// let balances_map_key = format!("{}", user_index);
// let sign = match arranged_order_side {
// serum_dex::matching::Side::Bid => 1.0,
// serum_dex::matching::Side::Ask => -1.0,
// }
// if let Some((base_balance, quote_balance)) = balances_map.get_mut(&balances_map_key) {
// *base_balance += arranged_order_size * arranged_order_price * sign;
// *quote_balance += arranged_order_size * arranged_order_price * (sign * -1.0);
// } else {
// perp_orders_map.insert(perp_orders_map_key.clone(), 0);
// }
// }
// }
#[allow(dead_code)]
pub fn assert_open_perp_orders(
mango_group_cookie: &MangoGroupCookie,
user_perp_orders: &Vec<(usize, usize, mango::matching::Side, f64, f64)>,
starting_order_id: u64,
) {
let mut perp_orders_map: HashMap<String, usize> = HashMap::new();
for i in 0..user_perp_orders.len() {
let (user_index, _, arranged_order_side, _, _) = user_perp_orders[i];
let perp_orders_map_key = format!("{}", user_index);
if let Some(x) = perp_orders_map.get_mut(&perp_orders_map_key) {
*x += 1;
} else {
perp_orders_map.insert(perp_orders_map_key.clone(), 0);
}
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let client_order_id = mango_account.client_order_ids[perp_orders_map[&perp_orders_map_key]];
let order_side = mango_account.order_side[perp_orders_map[&perp_orders_map_key]];
assert_eq!(client_order_id, starting_order_id + i as u64,);
assert_eq!(order_side, arranged_order_side);
}
}
// #[allow(dead_code)]
// pub fn assert_matched_perp_orders(
// test: &mut MangoProgramTest,
// mango_group_cookie: &MangoGroupCookie,
// user_perp_orders: &Vec<(usize, usize, mango::matching::Side, f64, f64)>,
// ) {
// let mut matched_perp_orders_map: HashMap<String, I80F48> = HashMap::new();
// let (_, _, _, maker_side, _) = user_perp_orders[0];
// for i in 0..user_perp_orders.len() {
// let (user_index, mint_index, arranged_order_side, arranged_order_size, arranged_order_price) = user_perp_orders[i];
// let mango_group = mango_group_cookie.mango_group;
// let perp_market_info = mango_group.perp_markets[mint_index];
//
// let mint = test.with_mint(mint_index);
//
// let order_size = test.base_size_number_to_lots(&self.mint, arranged_order_size);
// let order_price = test.price_number_to_lots(&self.mint, arranged_order_price);
//
// let mut taker = None;
// let mut base_position: I80F48;
// let mut quote_position: I80F48;
//
// let fee = maker_side
//
// if arranged_order_side == mango::matching::Side::Bid {
// base_position = order_size;
// quote_position = -order_size * order_price - (order_size * order_price * perp_market_info.maker_fee);
// } else {
// base_position = -order_size;
// quote_position = order_size * order_price - (order_size * order_price * perp_market_info.taker_fee);
// }
//
// let perp_orders_map_key = format!("{}_{}", user_index, mint_index);
//
// if let Some(x) = perp_orders_map.get_mut(&perp_orders_map_key) {
//
// *x += 1;
// } else {
// perp_orders_map.insert(perp_orders_map_key.clone(), 0);
// }
// }
// }
fn get_net(mango_account: &MangoAccount, bank_cache: &RootBankCache, mint_index: usize) -> I80F48 {
if mango_account.deposits[mint_index].is_positive() {
mango_account.deposits[mint_index].checked_mul(bank_cache.deposit_index).unwrap()
} else if mango_account.borrows[mint_index].is_positive() {
-mango_account.borrows[mint_index].checked_mul(bank_cache.borrow_index).unwrap()
} else {
ZERO_I80F48
}
}
#[allow(dead_code)]
pub async fn assert_vault_net_deposit_diff(
test: &mut MangoProgramTest,
mango_group_cookie: &MangoGroupCookie,
mint_index: usize,
) {
let mango_cache = mango_group_cookie.mango_cache;
let root_bank_cache = mango_cache.root_bank_cache[mint_index];
let (_root_bank_pk, root_bank) =
test.with_root_bank(&mango_group_cookie.mango_group, mint_index).await;
let mut total_net = ZERO_I80F48;
for mango_account in &mango_group_cookie.mango_accounts {
total_net += get_net(&mango_account.mango_account, &root_bank_cache, mint_index);
}
total_net = total_net.checked_round().unwrap();
let mut vault_amount = ZERO_I80F48;
for node_bank_pk in root_bank.node_banks {
if node_bank_pk != Pubkey::default() {
let node_bank = test.load_account::<NodeBank>(node_bank_pk).await;
let balance = test.get_token_balance(node_bank.vault).await;
vault_amount += I80F48::from_num(balance);
}
}
println!("total_net: {}", total_net.to_string());
println!("vault_amount: {}", vault_amount.to_string());
assert!(total_net == vault_amount);
}

View File

@ -0,0 +1,834 @@
use fixed::types::I80F48;
use fixed::FixedI128;
use std::mem::size_of;
use std::num::NonZeroU64;
use solana_program::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::transport::TransportError;
use mango::{ids::*, matching::*, queue::*, state::*, utils::*};
use crate::*;
pub const STARTING_SPOT_ORDER_ID: u64 = 0;
pub const STARTING_PERP_ORDER_ID: u64 = 10_000;
pub const STARTING_ADVANCED_ORDER_ID: u64 = 20_000;
#[derive(Copy, Clone)]
pub struct MintCookie {
pub index: usize,
pub decimals: u8,
pub unit: f64,
pub base_lot: f64,
pub quote_lot: f64,
pub pubkey: Option<Pubkey>,
}
pub struct MangoGroupCookie {
pub address: Pubkey,
pub mango_group: MangoGroup,
pub mango_cache: MangoCache,
// oracles are available from mango_group
pub mango_accounts: Vec<MangoAccountCookie>,
pub spot_markets: Vec<SpotMarketCookie>,
pub perp_markets: Vec<PerpMarketCookie>,
pub current_spot_order_id: u64,
pub current_perp_order_id: u64,
pub current_advanced_order_id: u64,
pub users_with_spot_event: Vec<Vec<usize>>,
pub users_with_perp_event: Vec<Vec<usize>>,
}
impl MangoGroupCookie {
#[allow(dead_code)]
pub async fn default(test: &mut MangoProgramTest) -> Self {
let mango_program_id = test.mango_program_id;
let serum_program_id = test.serum_program_id;
let mango_group_pk = test.create_account(size_of::<MangoGroup>(), &mango_program_id).await;
let mango_cache_pk = test.create_account(size_of::<MangoCache>(), &mango_program_id).await;
let (signer_pk, signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let admin_pk = test.get_payer_pk();
let quote_mint_pk = test.mints[test.quote_index].pubkey.unwrap();
let quote_vault_pk = test.create_token_account(&signer_pk, &quote_mint_pk).await;
let quote_node_bank_pk =
test.create_account(size_of::<NodeBank>(), &mango_program_id).await;
let quote_root_bank_pk =
test.create_account(size_of::<RootBank>(), &mango_program_id).await;
let dao_vault_pk = test.create_token_account(&signer_pk, &quote_mint_pk).await;
let msrm_vault_pk = test.create_token_account(&signer_pk, &msrm_token::ID).await;
let fees_vault_pk = test.create_token_account(&signer_pk, &quote_mint_pk).await;
let quote_optimal_util = I80F48::from_num(0.7);
let quote_optimal_rate = I80F48::from_num(0.06);
let quote_max_rate = I80F48::from_num(1.5);
let instructions = [mango::instruction::init_mango_group(
&mango_program_id,
&mango_group_pk,
&signer_pk,
&admin_pk,
&quote_mint_pk,
&quote_vault_pk,
&quote_node_bank_pk,
&quote_root_bank_pk,
&dao_vault_pk,
&msrm_vault_pk,
&fees_vault_pk,
&mango_cache_pk,
&serum_program_id,
signer_nonce,
5,
quote_optimal_util,
quote_optimal_rate,
quote_max_rate,
)
.unwrap()];
test.process_transaction(&instructions, None).await.unwrap();
let mango_group = test.load_account::<MangoGroup>(mango_group_pk).await;
let mango_cache = test.load_account::<MangoCache>(mango_group.mango_cache).await;
MangoGroupCookie {
address: mango_group_pk,
mango_group: mango_group,
mango_cache: mango_cache,
mango_accounts: vec![],
spot_markets: vec![],
perp_markets: vec![],
current_spot_order_id: STARTING_SPOT_ORDER_ID,
current_perp_order_id: STARTING_PERP_ORDER_ID,
current_advanced_order_id: STARTING_ADVANCED_ORDER_ID,
users_with_spot_event: vec![Vec::new(); test.num_mints - 1],
users_with_perp_event: vec![Vec::new(); test.num_mints - 1],
}
}
#[allow(dead_code)]
pub async fn full_setup(
&mut self,
test: &mut MangoProgramTest,
num_users: usize,
num_markets: usize,
) {
let oracle_pks = test.add_oracles_to_mango_group(&self.address).await;
self.mango_accounts = self.add_mango_accounts(test, num_users).await;
self.spot_markets = self.add_spot_markets(test, num_markets, &oracle_pks).await;
self.perp_markets = self.add_perp_markets(test, num_markets, &oracle_pks).await;
self.mango_group = test.load_account::<MangoGroup>(self.address).await;
}
#[allow(dead_code)]
pub async fn add_mango_accounts(
&mut self,
test: &mut MangoProgramTest,
num_users: usize,
) -> Vec<MangoAccountCookie> {
let mut mango_accounts = Vec::new();
for i in 0..num_users {
mango_accounts.push(MangoAccountCookie::init(test, self, i).await);
}
mango_accounts
}
#[allow(dead_code)]
pub async fn add_perp_markets(
&mut self,
test: &mut MangoProgramTest,
num_markets: usize,
oracle_pks: &Vec<Pubkey>,
) -> Vec<PerpMarketCookie> {
let mut perp_markets = Vec::new();
for i in 0..num_markets {
perp_markets.push(PerpMarketCookie::init(test, self, i, oracle_pks).await);
}
perp_markets
}
#[allow(dead_code)]
pub async fn add_spot_markets(
&mut self,
test: &mut MangoProgramTest,
num_markets: usize,
oracle_pks: &Vec<Pubkey>,
) -> Vec<SpotMarketCookie> {
let mut spot_markets = Vec::new();
for i in 0..num_markets {
spot_markets.push(SpotMarketCookie::init(test, self, i, oracle_pks).await);
}
spot_markets
}
#[allow(dead_code)]
pub async fn set_oracle(
&mut self,
test: &mut MangoProgramTest,
oracle_index: usize,
price: f64,
) {
let mint = test.with_mint(oracle_index);
let oracle_price = test.with_oracle_price(&mint, price);
let mango_program_id = test.mango_program_id;
let admin_pk = test.get_payer_pk();
let oracle_pk = self.mango_group.oracles[oracle_index];
let instructions = [mango::instruction::set_oracle(
&mango_program_id,
&self.address,
&oracle_pk,
&admin_pk,
oracle_price,
)
.unwrap()];
test.process_transaction(&instructions, None).await.unwrap();
}
#[allow(dead_code)]
pub async fn run_keeper(&mut self, test: &mut MangoProgramTest) {
let mango_group = self.mango_group;
let mango_group_pk = self.address;
let oracle_pks = mango_group
.oracles
.iter()
.filter(|x| **x != Pubkey::default())
.map(|x| *x)
.collect::<Vec<Pubkey>>();
let perp_market_pks = self.perp_markets.iter().map(|x| x.address).collect::<Vec<Pubkey>>();
test.advance_clock().await;
test.cache_all_prices(&mango_group, &mango_group_pk, &oracle_pks[..]).await;
test.update_all_root_banks(&mango_group, &mango_group_pk).await;
for perp_market_index in 0..self.perp_markets.len() {
let perp_market = &self.perp_markets[perp_market_index];
test.update_funding(self, perp_market).await;
}
test.cache_all_root_banks(&mango_group, &mango_group_pk).await;
test.cache_all_perp_markets(&mango_group, &mango_group_pk, &perp_market_pks).await;
self.mango_cache = test.load_account::<MangoCache>(mango_group.mango_cache).await;
for user_index in 0..self.mango_accounts.len() {
self.mango_accounts[user_index].mango_account =
test.load_account::<MangoAccount>(self.mango_accounts[user_index].address).await;
}
}
#[allow(dead_code)]
pub async fn consume_spot_events(&mut self, test: &mut MangoProgramTest) {
for spot_market_index in 0..self.users_with_spot_event.len() {
let users_with_spot_event = &self.users_with_spot_event[spot_market_index];
if users_with_spot_event.len() > 0 {
let spot_market_cookie = self.spot_markets[spot_market_index];
let mut open_orders = Vec::new();
for user_index in users_with_spot_event {
open_orders.push(
&self.mango_accounts[*user_index].mango_account.spot_open_orders
[spot_market_index],
);
}
test.consume_spot_events(
&spot_market_cookie,
open_orders,
0, // TODO: Change coin_fee_receivable_account, pc_fee_receivable_account to owner of test
)
.await;
}
self.users_with_spot_event[spot_market_index] = Vec::new();
}
self.run_keeper(test).await;
}
#[allow(dead_code)]
pub async fn settle_spot_funds(
&mut self,
test: &mut MangoProgramTest,
spot_orders: &Vec<(usize, usize, serum_dex::matching::Side, f64, f64)>,
) {
for spot_order in spot_orders {
let (user_index, market_index, _, _, _) = *spot_order;
let spot_market_cookie = self.spot_markets[market_index];
test.settle_spot_funds(self, &spot_market_cookie, user_index).await;
}
}
#[allow(dead_code)]
pub async fn consume_perp_events(&mut self, test: &mut MangoProgramTest) {
for perp_market_index in 0..self.users_with_perp_event.len() {
let users_with_perp_event = &self.users_with_perp_event[perp_market_index];
if users_with_perp_event.len() > 0 {
let perp_market_cookie = self.perp_markets[perp_market_index];
let mut mango_account_pks = Vec::new();
for user_index in users_with_perp_event {
mango_account_pks.push(self.mango_accounts[*user_index].address);
}
test.consume_perp_events(&self, &perp_market_cookie, &mut mango_account_pks).await;
}
self.users_with_perp_event[perp_market_index] = Vec::new();
}
self.run_keeper(test).await;
}
// NOTE: This function assumes an array of perp orders for the same market (coming from match_perp_order_scenario)
#[allow(dead_code)]
pub async fn settle_perp_funds(
&mut self,
test: &mut MangoProgramTest,
perp_orders: &Vec<(usize, usize, mango::matching::Side, f64, f64)>,
) {
if perp_orders.len() > 0 {
let mut bidders = Vec::new();
let mut askers = Vec::new();
let (_, market_index, _, _, _) = perp_orders[0];
let perp_market_cookie = self.perp_markets[market_index];
for perp_order in perp_orders {
let (user_index, _, order_side, _, _) = *perp_order;
if order_side == mango::matching::Side::Bid {
bidders.push(user_index);
} else {
askers.push(user_index);
}
}
for user_a_index in &bidders {
for user_b_index in &askers {
test.settle_perp_funds(self, &perp_market_cookie, *user_a_index, *user_b_index)
.await;
self.run_keeper(test).await;
}
}
}
}
}
pub struct MangoAccountCookie {
pub address: Pubkey,
pub user: Keypair,
pub mango_account: MangoAccount,
}
impl MangoAccountCookie {
#[allow(dead_code)]
pub async fn init(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
user_index: usize,
) -> Self {
let mango_program_id = test.mango_program_id;
let mango_account_pk =
test.create_account(size_of::<MangoAccount>(), &mango_program_id).await;
let user = Keypair::from_base58_string(&test.users[user_index].to_base58_string());
let user_pk = user.pubkey();
let instructions = [mango::instruction::init_mango_account(
&mango_program_id,
&mango_group_cookie.address,
&mango_account_pk,
&user_pk,
)
.unwrap()];
test.process_transaction(&instructions, Some(&[&user])).await.unwrap();
let mango_account = test.load_account::<MangoAccount>(mango_account_pk).await;
MangoAccountCookie { address: mango_account_pk, user, mango_account }
}
}
pub struct AdvancedOrdersCookie {
pub address: Pubkey,
pub advanced_orders: AdvancedOrders,
}
impl AdvancedOrdersCookie {
#[allow(dead_code)]
pub async fn init(
test: &mut MangoProgramTest,
mango_account_cookie: &MangoAccountCookie,
) -> Self {
let mango_program_id = test.mango_program_id;
let mango_account = &mango_account_cookie.mango_account;
let user = &mango_account_cookie.user;
let user_pk = user.pubkey();
assert!(user_pk == mango_account.owner);
let (advanced_orders_pk, _bump_seed) = Pubkey::find_program_address(
&[&mango_account_cookie.address.to_bytes()],
&mango_program_id,
);
let instructions = [mango::instruction::init_advanced_orders(
&mango_program_id,
&mango_account.mango_group,
&mango_account_cookie.address,
&user_pk,
&advanced_orders_pk,
&solana_sdk::system_program::id(),
)
.unwrap()];
test.process_transaction(&instructions, Some(&[user])).await.unwrap();
let advanced_orders = test.load_account::<AdvancedOrders>(advanced_orders_pk).await;
AdvancedOrdersCookie { address: advanced_orders_pk, advanced_orders }
}
#[allow(dead_code)]
pub async fn remove_advanced_order(
&mut self,
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
user_index: usize,
order_index: u8,
) -> Result<(), TransportError> {
let mango_program_id = test.mango_program_id;
let mango_account_cookie = &mango_group_cookie.mango_accounts[user_index];
let mango_account_pk = mango_account_cookie.address;
let user = &mango_account_cookie.user;
let user_pk = user.pubkey();
let mango_group_pk = mango_group_cookie.address;
let instructions = [mango::instruction::remove_advanced_order(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user_pk,
&self.address,
&solana_sdk::system_program::id(),
order_index,
)
.unwrap()];
let result = test.process_transaction(&instructions, Some(&[&user])).await;
if result.is_ok() {
self.advanced_orders = test.load_account::<AdvancedOrders>(self.address).await;
}
result
}
}
#[derive(Copy, Clone)]
pub struct SpotMarketCookie {
pub market: Pubkey,
pub req_q: Pubkey,
pub event_q: Pubkey,
pub bids: Pubkey,
pub asks: Pubkey,
pub coin_vault: Pubkey,
pub pc_vault: Pubkey,
pub vault_signer_key: Pubkey,
pub mint: MintCookie,
}
impl SpotMarketCookie {
#[allow(dead_code)]
pub async fn init(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
mint_index: usize,
oracle_pks: &Vec<Pubkey>,
) -> Self {
let mango_program_id = test.mango_program_id;
let serum_program_id = test.serum_program_id;
let mango_group_pk = mango_group_cookie.address;
let mut spot_market_cookie = test.list_spot_market(mint_index).await;
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let vault_pk =
test.create_token_account(&signer_pk, &test.mints[mint_index].pubkey.unwrap()).await;
let node_bank_pk = test.create_account(size_of::<NodeBank>(), &mango_program_id).await;
let root_bank_pk = test.create_account(size_of::<RootBank>(), &mango_program_id).await;
let init_leverage = I80F48::from_num(10);
let liquidation_fee = I80F48::from_num(0.025);
let maint_leverage = init_leverage * 2;
let optimal_util = I80F48::from_num(0.7);
let optimal_rate = I80F48::from_num(0.06);
let max_rate = I80F48::from_num(1.5);
let admin_pk = test.get_payer_pk();
let instructions = [mango::instruction::add_spot_market(
&mango_program_id,
&mango_group_pk,
&oracle_pks[mint_index],
&spot_market_cookie.market,
&serum_program_id,
&test.mints[mint_index].pubkey.unwrap(),
&node_bank_pk,
&vault_pk,
&root_bank_pk,
&admin_pk,
maint_leverage,
init_leverage,
liquidation_fee,
optimal_util,
optimal_rate,
max_rate,
)
.unwrap()];
test.process_transaction(&instructions, None).await.unwrap();
spot_market_cookie.mint = test.with_mint(mint_index);
spot_market_cookie
}
#[allow(dead_code)]
pub async fn change_params(
&mut self,
test: &mut MangoProgramTest,
mango_group_pk: &Pubkey,
root_bank_pk: &Pubkey,
mint_index: usize,
init_leverage: Option<I80F48>,
maint_leverage: Option<I80F48>,
liquidation_fee: Option<I80F48>,
optimal_util: Option<I80F48>,
optimal_rate: Option<I80F48>,
max_rate: Option<I80F48>,
version: Option<u8>,
) {
let mango_program_id = test.mango_program_id;
let mango_group_pk = mango_group_pk;
let spot_market_pk = self.market;
let root_bank_pk = root_bank_pk;
let admin_pk = test.get_payer_pk();
let instructions = [mango::instruction::change_spot_market_params(
&mango_program_id,
&mango_group_pk,
&spot_market_pk,
&root_bank_pk,
&admin_pk,
maint_leverage,
init_leverage,
liquidation_fee,
optimal_util,
optimal_rate,
max_rate,
version,
)
.unwrap()];
test.process_transaction(&instructions, None).await.unwrap();
}
#[allow(dead_code)]
pub async fn place_order(
&mut self,
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
user_index: usize,
side: serum_dex::matching::Side,
size: f64,
price: f64,
) {
let limit_price = test.price_number_to_lots(&self.mint, price);
let max_coin_qty = test.base_size_number_to_lots(&self.mint, size);
let max_native_pc_qty_including_fees = match side {
serum_dex::matching::Side::Bid => {
self.mint.quote_lot as u64 * limit_price * max_coin_qty
}
serum_dex::matching::Side::Ask => std::u64::MAX,
};
let order = serum_dex::instruction::NewOrderInstructionV3 {
side: side, //serum_dex::matching::Side::Bid,
limit_price: NonZeroU64::new(limit_price).unwrap(),
max_coin_qty: NonZeroU64::new(max_coin_qty).unwrap(),
max_native_pc_qty_including_fees: NonZeroU64::new(max_native_pc_qty_including_fees)
.unwrap(),
self_trade_behavior: serum_dex::instruction::SelfTradeBehavior::DecrementTake,
order_type: serum_dex::matching::OrderType::Limit,
client_order_id: mango_group_cookie.current_spot_order_id,
limit: u16::MAX,
};
test.place_spot_order(&mango_group_cookie, self, user_index, order).await;
mango_group_cookie.mango_accounts[user_index].mango_account = test
.load_account::<MangoAccount>(mango_group_cookie.mango_accounts[user_index].address)
.await;
mango_group_cookie.current_spot_order_id += 1;
}
#[allow(dead_code)]
pub async fn place_order_with_delegate(
&mut self,
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
user_index: usize,
delegate_user_index: usize,
side: serum_dex::matching::Side,
size: f64,
price: f64,
) -> Result<(), TransportError> {
let limit_price = test.price_number_to_lots(&self.mint, price);
let max_coin_qty = test.base_size_number_to_lots(&self.mint, size);
let max_native_pc_qty_including_fees = match side {
serum_dex::matching::Side::Bid => {
self.mint.quote_lot as u64 * limit_price * max_coin_qty
}
serum_dex::matching::Side::Ask => std::u64::MAX,
};
let order = serum_dex::instruction::NewOrderInstructionV3 {
side: side,
limit_price: NonZeroU64::new(limit_price).unwrap(),
max_coin_qty: NonZeroU64::new(max_coin_qty).unwrap(),
max_native_pc_qty_including_fees: NonZeroU64::new(max_native_pc_qty_including_fees)
.unwrap(),
self_trade_behavior: serum_dex::instruction::SelfTradeBehavior::DecrementTake,
order_type: serum_dex::matching::OrderType::Limit,
client_order_id: mango_group_cookie.current_spot_order_id,
limit: u16::MAX,
};
test.place_spot_order_with_delegate(
&mango_group_cookie,
self,
user_index,
delegate_user_index,
order,
)
.await
}
}
#[derive(Copy, Clone)]
pub struct PerpMarketCookie {
pub address: Pubkey,
pub perp_market: PerpMarket,
pub mint: MintCookie,
}
impl PerpMarketCookie {
#[allow(dead_code)]
pub async fn init(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
mint_index: usize,
oracle_pks: &Vec<Pubkey>,
) -> Self {
let mango_program_id = test.mango_program_id;
let mango_group_pk = mango_group_cookie.address;
let perp_market_pk = test.create_account(size_of::<PerpMarket>(), &mango_program_id).await;
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let max_num_events = 32;
let event_queue_pk = test
.create_account(
size_of::<EventQueue>() + size_of::<AnyEvent>() * max_num_events,
&mango_program_id,
)
.await;
let bids_pk = test.create_account(size_of::<BookSide>(), &mango_program_id).await;
let asks_pk = test.create_account(size_of::<BookSide>(), &mango_program_id).await;
let mngo_vault_pk = test.create_token_account(&signer_pk, &mngo_token::ID).await;
let admin_pk = test.get_payer_pk();
let init_leverage = I80F48::from_num(10);
let maint_leverage = init_leverage * 2;
let liquidation_fee = I80F48::from_num(0.025);
let maker_fee = I80F48::from_num(0.01);
let taker_fee = I80F48::from_num(0.01);
let rate = I80F48::from_num(1);
let max_depth_bps = I80F48::from_num(200);
let target_period_length = 3600;
let mngo_per_period = 11400;
let instructions = [mango::instruction::add_perp_market(
&mango_program_id,
&mango_group_pk,
&oracle_pks[mint_index],
&perp_market_pk,
&event_queue_pk,
&bids_pk,
&asks_pk,
&mngo_vault_pk,
&admin_pk,
maint_leverage,
init_leverage,
liquidation_fee,
maker_fee,
taker_fee,
test.mints[mint_index].base_lot as i64,
test.mints[mint_index].quote_lot as i64,
rate,
max_depth_bps,
target_period_length,
mngo_per_period,
)
.unwrap()];
test.process_transaction(&instructions, None).await.unwrap();
let perp_market = test.load_account::<PerpMarket>(perp_market_pk).await;
PerpMarketCookie {
address: perp_market_pk,
perp_market: perp_market,
mint: test.with_mint(mint_index),
}
}
#[allow(dead_code)]
pub async fn place_order(
&mut self,
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
user_index: usize,
side: mango::matching::Side,
size: f64,
price: f64,
) {
let order_size = test.base_size_number_to_lots(&self.mint, size);
let order_price = test.price_number_to_lots(&self.mint, price);
test.place_perp_order(
&mango_group_cookie,
self,
user_index,
side,
order_size,
order_price,
mango_group_cookie.current_perp_order_id,
mango::matching::OrderType::Limit,
false,
)
.await;
mango_group_cookie.mango_accounts[user_index].mango_account = test
.load_account::<MangoAccount>(mango_group_cookie.mango_accounts[user_index].address)
.await;
mango_group_cookie.current_perp_order_id += 1;
}
#[allow(dead_code)]
pub async fn add_trigger_order(
&mut self,
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
advanced_orders_cookie: &mut AdvancedOrdersCookie,
user_index: usize,
order_type: mango::matching::OrderType,
side: mango::matching::Side,
trigger_condition: TriggerCondition,
price: f64,
quantity: f64,
trigger_price: I80F48,
) {
let mango_program_id = test.mango_program_id;
let mango_account_cookie = &mango_group_cookie.mango_accounts[user_index];
let mango_account_pk = mango_account_cookie.address;
let user = &mango_account_cookie.user;
let user_pk = user.pubkey();
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let perp_market_pk = self.address;
let order_quantity = test.base_size_number_to_lots(&self.mint, quantity);
let order_price = test.price_number_to_lots(&self.mint, price);
let order_id = mango_group_cookie.current_advanced_order_id;
let instructions = [mango::instruction::add_perp_trigger_order(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user_pk,
&advanced_orders_cookie.address,
&mango_group.mango_cache,
&perp_market_pk,
&solana_sdk::system_program::id(),
order_type,
side,
trigger_condition,
false,
order_id,
order_price as i64,
order_quantity as i64,
trigger_price,
)
.unwrap()];
test.process_transaction(&instructions, Some(&[&user])).await.unwrap();
mango_group_cookie.mango_accounts[user_index].mango_account =
test.load_account::<MangoAccount>(mango_account_pk).await;
mango_group_cookie.current_advanced_order_id += 1;
advanced_orders_cookie.advanced_orders =
test.load_account::<AdvancedOrders>(advanced_orders_cookie.address).await;
}
#[allow(dead_code)]
pub async fn execute_trigger_order(
&mut self,
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
advanced_orders_cookie: &mut AdvancedOrdersCookie,
user_index: usize,
agent_user_index: usize,
order_index: u8,
) -> Result<(), TransportError> {
let mango_program_id = test.mango_program_id;
let mango_account_cookie = &mango_group_cookie.mango_accounts[user_index];
let mango_account_pk = mango_account_cookie.address;
let agent_user =
Keypair::from_base58_string(&test.users[agent_user_index].to_base58_string());
let agent_user_pk = agent_user.pubkey();
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let perp_market = self.perp_market;
let perp_market_pk = self.address;
let instructions = [mango::instruction::execute_perp_trigger_order(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&advanced_orders_cookie.address,
&agent_user_pk,
&mango_group.mango_cache,
&perp_market_pk,
&perp_market.bids,
&perp_market.asks,
&perp_market.event_queue,
order_index,
)
.unwrap()];
let result = test.process_transaction(&instructions, Some(&[&agent_user])).await;
if result.is_ok() {
mango_group_cookie.mango_accounts[user_index].mango_account =
test.load_account::<MangoAccount>(mango_account_pk).await;
advanced_orders_cookie.advanced_orders =
test.load_account::<AdvancedOrders>(advanced_orders_cookie.address).await;
}
result
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,189 @@
use crate::*;
use solana_sdk::transport::TransportError;
#[allow(dead_code)]
pub fn arrange_deposit_all_scenario(
test: &mut MangoProgramTest,
user_index: usize,
mint_amount: f64,
quote_amount: f64,
) -> Vec<(usize, usize, f64)> {
let mut user_deposits = Vec::new();
for mint_index in 0..test.num_mints - 1 {
user_deposits.push((user_index, mint_index, mint_amount));
}
user_deposits.push((user_index, test.quote_index, quote_amount));
return user_deposits;
}
#[allow(dead_code)]
pub async fn deposit_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
deposits: &Vec<(usize, usize, f64)>,
) {
mango_group_cookie.run_keeper(test).await;
for deposit in deposits {
let (user_index, mint_index, amount) = deposit;
let mint = test.with_mint(*mint_index);
let deposit_amount = (*amount * mint.unit) as u64;
test.perform_deposit(&mango_group_cookie, *user_index, *mint_index, deposit_amount).await;
}
}
#[allow(dead_code)]
pub async fn withdraw_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
withdraws: &Vec<(usize, usize, f64, bool)>,
) {
mango_group_cookie.run_keeper(test).await;
for withdraw in withdraws {
let (user_index, mint_index, amount, allow_borrow) = withdraw;
let mint = test.with_mint(*mint_index);
let withdraw_amount = (*amount * mint.unit) as u64;
test.perform_withdraw(
&mango_group_cookie,
*user_index,
*mint_index,
withdraw_amount,
*allow_borrow,
)
.await;
}
}
#[allow(dead_code)]
pub async fn withdraw_scenario_with_delegate(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
withdraw: &(usize, usize, usize, f64, bool),
) -> Result<(), TransportError> {
mango_group_cookie.run_keeper(test).await;
let (user_index, delegate_user_index, mint_index, amount, allow_borrow) = withdraw;
let mint = test.with_mint(*mint_index);
let withdraw_amount = (*amount * mint.unit) as u64;
test.perform_withdraw_with_delegate(
&mango_group_cookie,
*user_index,
*delegate_user_index,
*mint_index,
withdraw_amount,
*allow_borrow,
)
.await
}
#[allow(dead_code)]
pub async fn delegate_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
user_index: usize,
delegate_user_index: usize,
) {
test.perform_set_delegate(&mango_group_cookie, user_index, delegate_user_index).await;
}
#[allow(dead_code)]
pub async fn reset_delegate_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
user_index: usize,
delegate_user_index: usize,
) {
test.perform_reset_delegate(&mango_group_cookie, user_index, delegate_user_index).await;
}
#[allow(dead_code)]
pub async fn place_spot_order_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
spot_orders: &Vec<(usize, usize, serum_dex::matching::Side, f64, f64)>,
) {
mango_group_cookie.run_keeper(test).await;
for spot_order in spot_orders {
let (user_index, market_index, order_side, order_size, order_price) = *spot_order;
let mut spot_market_cookie = mango_group_cookie.spot_markets[market_index];
spot_market_cookie
.place_order(test, mango_group_cookie, user_index, order_side, order_size, order_price)
.await;
mango_group_cookie.users_with_spot_event[market_index].push(user_index);
}
}
#[allow(dead_code)]
pub async fn place_spot_order_scenario_with_delegate(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
spot_order: &(usize, usize, usize, serum_dex::matching::Side, f64, f64),
) -> Result<(), TransportError> {
mango_group_cookie.run_keeper(test).await;
let (user_index, delegate_user_index, market_index, order_side, order_size, order_price) =
*spot_order;
let mut spot_market_cookie = mango_group_cookie.spot_markets[market_index];
mango_group_cookie.users_with_spot_event[market_index].push(user_index);
spot_market_cookie
.place_order_with_delegate(
test,
mango_group_cookie,
user_index,
delegate_user_index,
order_side,
order_size,
order_price,
)
.await
}
#[allow(dead_code)]
pub async fn place_perp_order_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
perp_orders: &Vec<(usize, usize, mango::matching::Side, f64, f64)>,
) {
mango_group_cookie.run_keeper(test).await;
for perp_order in perp_orders {
let (user_index, market_index, order_side, order_size, order_price) = *perp_order;
let mut perp_market_cookie = mango_group_cookie.perp_markets[market_index];
perp_market_cookie
.place_order(test, mango_group_cookie, user_index, order_side, order_size, order_price) // TODO - pass in reduce only param
.await;
mango_group_cookie.users_with_perp_event[market_index].push(user_index);
}
}
#[allow(dead_code)]
pub async fn match_spot_order_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
matched_spot_orders: &Vec<Vec<(usize, usize, serum_dex::matching::Side, f64, f64)>>,
) {
for matched_spot_order in matched_spot_orders {
place_spot_order_scenario(test, mango_group_cookie, matched_spot_order).await;
mango_group_cookie.run_keeper(test).await;
mango_group_cookie.consume_spot_events(test).await;
mango_group_cookie.run_keeper(test).await;
}
}
#[allow(dead_code)]
pub async fn match_perp_order_scenario(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
matched_perp_orders: &Vec<Vec<(usize, usize, mango::matching::Side, f64, f64)>>,
) {
for matched_perp_order in matched_perp_orders {
place_perp_order_scenario(test, mango_group_cookie, matched_perp_order).await;
mango_group_cookie.run_keeper(test).await;
mango_group_cookie.consume_perp_events(test).await;
mango_group_cookie.run_keeper(test).await;
}
}

View File

@ -0,0 +1,121 @@
use anchor_lang::Key;
use mango::state::{MangoGroup, NodeBank, RootBank, QUOTE_INDEX};
use solana_program::instruction::Instruction;
use solana_program::pubkey::Pubkey;
use solana_program_test::*;
use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer;
use crate::cookies::MangoGroupCookie;
use program_test::*;
mod program_test;
#[allow(unaligned_references)]
#[tokio::test]
async fn test_iou() {
// Setup
let config = MangoProgramTestConfig::default();
let mut test = MangoProgramTest::start_new(&config).await;
let mut mango_group_cookie = MangoGroupCookie::default(&mut test).await;
MangoGroupCookie::full_setup(
&mut mango_group_cookie,
&mut test,
config.num_users,
config.num_mints - 1,
)
.await;
// Update prices
mango_group_cookie.run_keeper(&mut test).await;
// Init gateway
let (gateway, _gateway_bump) = Pubkey::find_program_address(
&[b"gateway".as_ref(), test.context.payer.pubkey().as_ref()],
&test.dasheri_program_id,
);
let (deposit_iou_mint, _deposit_iou_mint_bump) = Pubkey::find_program_address(
&[test.mints[test.mints.len() - 1].pubkey.unwrap().as_ref()],
&test.dasheri_program_id,
);
let instructions = vec![Instruction {
program_id: test.dasheri_program_id,
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&dasheri::accounts::IouInitGateway {
gateway,
deposit_iou_mint,
token_mint: test.mints[test.mints.len() - 1].pubkey.unwrap(),
admin: test.context.payer.pubkey(),
system_program: solana_sdk::system_program::id(),
token_program: spl_token::id(),
rent: solana_program::sysvar::rent::id(),
},
None,
),
data: anchor_lang::InstructionData::data(&dasheri::instruction::IouInitGateway {
_gateway_bump,
_deposit_iou_mint_bump,
}),
}];
&mut test
.process_transaction(
&instructions,
Some(&[&Keypair::from_base58_string(
&test.context.payer.to_base58_string(),
)]),
)
.await
.unwrap();
// Deposit
let mango_group = test
.load_account::<MangoGroup>(mango_group_cookie.address)
.await;
let root_bank_pk = mango_group.tokens[QUOTE_INDEX].root_bank;
let root_bank = &mut test.load_account::<RootBank>(root_bank_pk).await;
let node_bank_pk = root_bank.node_banks[0];
let node_bank = test.load_account::<NodeBank>(node_bank_pk).await;
let deposit_iou_account = spl_associated_token_account::get_associated_token_address(
&test.users[0].pubkey(),
&deposit_iou_mint,
);
let instructions = vec![Instruction {
program_id: test.dasheri_program_id,
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&dasheri::accounts::IouDepositIntoMangoAccount {
mango_program: test.mango_program_id,
mango_group: mango_group_cookie.address,
mango_cache: mango_group.mango_cache.key(),
root_bank: root_bank_pk,
node_bank: node_bank_pk,
node_bank_vault: node_bank.vault.key(),
owner_token_account: test.token_accounts[15].key(),
mango_account: mango_group_cookie.mango_accounts[0].address,
deposit_iou_account,
deposit_iou_mint,
token_mint: test.mints[test.mints.len() - 1].pubkey.unwrap(),
payer: test.users[0].pubkey(),
gateway,
system_program: solana_sdk::system_program::id(),
token_program: spl_token::id(),
associated_token_program: spl_associated_token_account::id(),
rent: solana_program::sysvar::rent::id(),
},
None,
),
data: anchor_lang::InstructionData::data(
&dasheri::instruction::IouDepositIntoMangoAccount { quantity: 100 },
),
}];
&mut test
.process_transaction(
&instructions,
Some(&[&Keypair::from_base58_string(
&test.users[0].to_base58_string(),
)]),
)
.await
.unwrap();
}

View File

@ -0,0 +1,246 @@
use anchor_lang::Key;
use mango::state::{MangoGroup, NodeBank, RootBank, QUOTE_INDEX};
use solana_program::instruction::Instruction;
use solana_program::pubkey::Pubkey;
use solana_program_test::*;
use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer;
use crate::cookies::MangoGroupCookie;
use program_test::*;
mod program_test;
#[allow(unaligned_references)]
#[tokio::test]
async fn test_pool() {
// Setup
let config = MangoProgramTestConfig::default();
let mut test = MangoProgramTest::start_new(&config).await;
let mut mango_group_cookie = MangoGroupCookie::default(&mut test).await;
MangoGroupCookie::full_setup(
&mut mango_group_cookie,
&mut test,
config.num_users,
config.num_mints - 1,
)
.await;
// Create pool
let (pool, bump) = Pubkey::find_program_address(
&[b"pool".as_ref(), test.context.payer.pubkey().as_ref()],
&test.dasheri_program_id,
);
let vault = spl_associated_token_account::get_associated_token_address(
&pool,
&test.mints[test.mints.len() - 1].pubkey.unwrap(),
);
create_pool(&mut test, &pool, bump, &vault).await;
// Create pool account
let (pool_account, bump) = Pubkey::find_program_address(
&[b"pool_account".as_ref(), pool.as_ref()],
&test.dasheri_program_id,
);
create_pool_account(&mut test, &pool, &pool_account, bump).await;
// Deposit into pool
deposit_into_pool(&mut test, &pool, &vault, &pool_account).await;
// Create mango account
const ACCOUNT_NUM: u64 = 0_u64;
let (mango_account, bump) = Pubkey::find_program_address(
&[
&mango_group_cookie.address.as_ref(),
&pool.as_ref(),
&ACCOUNT_NUM.to_le_bytes(),
],
&test.mango_program_id,
);
create_mango_account(
&mut test,
&mut mango_group_cookie,
&pool,
&mango_account,
bump,
ACCOUNT_NUM,
)
.await;
// Update cache
mango_group_cookie.run_keeper(&mut test).await;
// Deposit
let mango_group = test
.load_account::<MangoGroup>(mango_group_cookie.address)
.await;
deposit_into_mango_account(
&mut test,
&mut mango_group_cookie,
&pool,
&vault,
&mango_account,
&mango_group,
)
.await;
}
async fn create_pool(test: &mut MangoProgramTest, pool: &Pubkey, bump: u8, vault: &Pubkey) {
let instructions = vec![Instruction {
program_id: test.dasheri_program_id,
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&dasheri::accounts::PoolCreatePool {
pool: *pool,
vault: *vault,
deposit_iou_mint: test.mints[test.mints.len() - 1].pubkey.unwrap(),
admin: test.context.payer.pubkey(),
system_program: solana_sdk::system_program::id(),
token_program: spl_token::id(),
associated_token_program: spl_associated_token_account::id(),
rent: solana_sdk::sysvar::rent::id(),
},
None,
),
data: anchor_lang::InstructionData::data(&dasheri::instruction::PoolCreatePool { bump }),
}];
test.process_transaction(&instructions, Some(&[]))
.await
.unwrap();
}
async fn create_pool_account(
test: &mut MangoProgramTest,
pool: &Pubkey,
pool_account: &Pubkey,
bump: u8,
) {
let instructions = vec![Instruction {
program_id: test.dasheri_program_id,
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&dasheri::accounts::PoolCreatePoolAccount {
pool_account: *pool_account,
pool: *pool,
user: test.users[0].pubkey(),
system_program: solana_sdk::system_program::id(),
},
None,
),
data: anchor_lang::InstructionData::data(&dasheri::instruction::PoolCreatePoolAccount {
bump,
}),
}];
test.process_transaction(
&instructions,
Some(&[&Keypair::from_base58_string(
&test.users[0].to_base58_string(),
)]),
)
.await
.unwrap();
}
async fn deposit_into_pool(
test: &mut MangoProgramTest,
pool: &Pubkey,
vault: &Pubkey,
pool_account: &Pubkey,
) {
let instructions = vec![Instruction {
program_id: test.dasheri_program_id,
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&dasheri::accounts::PoolDepositIntoPool {
pool: *pool,
vault: *vault,
pool_account: *pool_account,
deposit_token: test.token_accounts[15].key(),
user: test.users[0].pubkey(),
token_program: spl_token::id(),
},
None,
),
data: anchor_lang::InstructionData::data(&dasheri::instruction::PoolDepositIntoPool {
amount: 100_000_000,
}),
}];
test.process_transaction(
&instructions,
Some(&[&Keypair::from_base58_string(
&test.users[0].to_base58_string(),
)]),
)
.await
.unwrap();
}
async fn create_mango_account(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
pool: &Pubkey,
mango_account: &Pubkey,
bump: u8,
ACCOUNT_NUM: u64,
) {
let instructions = vec![Instruction {
program_id: test.dasheri_program_id,
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&dasheri::accounts::PoolCreateMangoAccount {
mango_program: test.mango_program_id,
mango_group: mango_group_cookie.address,
mango_account: *mango_account,
pool: *pool,
payer: test.context.payer.pubkey(),
system_program: solana_sdk::system_program::id(),
},
None,
),
data: anchor_lang::InstructionData::data(&dasheri::instruction::PoolCreateMangoAccount {
account_num: ACCOUNT_NUM,
bump,
}),
}];
test.process_transaction(&instructions, Some(&[]))
.await
.unwrap();
}
async fn deposit_into_mango_account(
test: &mut MangoProgramTest,
mango_group_cookie: &mut MangoGroupCookie,
pool: &Pubkey,
vault: &Pubkey,
mango_account: &Pubkey,
mango_group: &MangoGroup,
) {
let root_bank_pk = mango_group.tokens[QUOTE_INDEX].root_bank;
let root_bank = test.load_account::<RootBank>(root_bank_pk).await;
let node_bank_pk = root_bank.node_banks[0];
let node_bank = test.load_account::<NodeBank>(node_bank_pk).await;
let instructions = vec![Instruction {
program_id: test.dasheri_program_id,
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&dasheri::accounts::PoolDepositIntoMangoAccount {
mango_program: test.mango_program_id,
mango_group: mango_group_cookie.address,
mango_cache: mango_group.mango_cache.key(),
root_bank: root_bank_pk,
node_bank: node_bank_pk,
node_bank_vault: node_bank.vault.key(),
owner_token_account: *vault,
mango_account: *mango_account,
pool: *pool,
system_program: solana_sdk::system_program::id(),
token_program: spl_token::id(),
},
None,
),
data: anchor_lang::InstructionData::data(
&dasheri::instruction::PoolDepositIntoMangoAccount { quantity: 100 },
),
}];
test.process_transaction(&instructions, Some(&[]))
.await
.unwrap();
}

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
(cd ../mango-v3; cargo build-bpf; cp -v ./target/deploy/mango.so \
../dasheri/programs/dasheri/tests/fixtures);
cargo test-bpf

4
todo.md Normal file
View File

@ -0,0 +1,4 @@
* review all existing ix's
* add constraints everywhere possible
* add readme
* add todos to flesh out pool and ious further