First commit
This commit is contained in:
commit
4cc17bc1c1
|
@ -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
|
|
@ -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!"
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
**/*.rs.bk
|
||||
node_modules
|
||||
|
||||
.idea
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -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"] }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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");
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pub use deposit_into_mango_account::*;
|
||||
pub use init_gateway::*;
|
||||
|
||||
pub mod deposit_into_mango_account;
|
||||
pub mod init_gateway;
|
|
@ -0,0 +1,2 @@
|
|||
pub mod instructions;
|
||||
pub mod state;
|
|
@ -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);
|
|
@ -0,0 +1,2 @@
|
|||
pub use gateway::*;
|
||||
pub mod gateway;
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
#[error]
|
||||
pub enum ErrorCode {
|
||||
#[msg("")]
|
||||
InvalidDepositMint,
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1,3 @@
|
|||
pub mod error;
|
||||
pub mod instructions;
|
||||
pub mod state;
|
|
@ -0,0 +1,5 @@
|
|||
pub use pool::*;
|
||||
pub use pool_account::*;
|
||||
|
||||
pub mod pool;
|
||||
pub mod pool_account;
|
|
@ -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);
|
|
@ -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);
|
Binary file not shown.
Binary file not shown.
|
@ -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);
|
||||
}
|
|
@ -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, "e_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, "e_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, "e_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,
|
||||
"e_mint_pk,
|
||||
"e_vault_pk,
|
||||
"e_node_bank_pk,
|
||||
"e_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
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue