Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
'npm run test:change-protocol-fee',
'npm run test:tickmap',
'npm run test:change-fee-receiver',
'npm run test:remove-pool',
'npm run test:whole-liquidity',
'npm run test:position-change',
'npm run test:protocol-fee',
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"scripts": {
"test:invariant-all": "npm run test:swap && npm run test:multiple-swap && npm run test:cross && npm run test:cross-both-side && npm run test:liquidity-gap && npm run test:reversed && npm run test:position && npm run test:math && npm run test:withdraw && npm run test:position-list && npm run test:claim && npm run test:protocol-fee && npm run test:target && npm run test:slippage && npm run test:position-slippage && npm run test:fee-tier && npm run test:simulate-swap && npm run test:simulate-claim-amount && npm run test:oracle && npm run test:limits && npm run test:big-swap && npm run test:compare && npm run test:tickmap && npm run test:change-fee-receiver && npm run test:random && npm run test:change-protocol-fee && npm run test:whole-liquidity && npm run test:cu && npm run test:referral-default && npm run test:referral-all && npm run test:referral-none && npm run test:referral-jupiter && npm run test:max-tick-cross",
"test:invariant-all": "npm run test:swap && npm run test:multiple-swap && npm run test:cross && npm run test:cross-both-side && npm run test:liquidity-gap && npm run test:reversed && npm run test:position && npm run test:math && npm run test:withdraw && npm run test:position-list && npm run test:claim && npm run test:protocol-fee && npm run test:target && npm run test:slippage && npm run test:position-slippage && npm run test:fee-tier && npm run test:simulate-swap && npm run test:simulate-claim-amount && npm run test:oracle && npm run test:limits && npm run test:big-swap && npm run test:compare && npm run test:tickmap && npm run test:change-fee-receiver && npm run test:remove-pool && npm run test:random && npm run test:change-protocol-fee && npm run test:whole-liquidity && npm run test:cu && npm run test:referral-default && npm run test:referral-all && npm run test:referral-none && npm run test:referral-jupiter && npm run test:max-tick-cross",
"test:staker-all": "npm run test:create && npm run test:stake && npm run test:withdraw-staker && npm run test:multicall && npm run test:position-change && npm run test:math-staker && npm run test:close-stake",
"test:all": "npm run test:invariant-all && npm run test:staker-all",
"test:swap": "anchor test --skip-build tests/swap.spec.ts",
Expand All @@ -10,6 +10,7 @@
"test:referral-none": "anchor test tests/referral-swap-none.spec.ts -- --features \"none\"",
"test:referral-jupiter": "anchor test tests/referral-swap-none.spec.ts -- --features \"jupiter\"",
"test:range": "anchor test --skip-build tests/liquidity-range.spec.ts",
"test:remove-pool": "anchor test --skip-build tests/remove-pool.spec.ts",
"test:cross-both-side": "anchor test --skip-build tests/cross-both-side.spec.ts",
"test:liquidity-gap": "anchor test --skip-build tests/liquidity-gap.spec.ts",
"test:simulate-swap": "anchor test --skip-build tests/simulate-swap.spec.ts",
Expand Down
2 changes: 1 addition & 1 deletion programs/invariant/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ all = []

[dependencies]
decimal = { path = "decimal" }
anchor-lang = "0.21.0"
anchor-lang = { version = "0.21.0", features = ['init-if-needed'] }
anchor-spl = "0.21.0"
integer-sqrt = "0.1.5"
uint = "0.9.1"
Expand Down
2 changes: 2 additions & 0 deletions programs/invariant/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod create_position_list;
pub mod create_state;
pub mod create_tick;
pub mod initialize_oracle;
pub mod remove_defunct_pool;
pub mod remove_position;
pub mod swap;
pub mod transfer_position_ownership;
Expand All @@ -24,6 +25,7 @@ pub use create_position_list::*;
pub use create_state::*;
pub use create_tick::*;
pub use initialize_oracle::*;
pub use remove_defunct_pool::*;
pub use remove_position::*;
pub use swap::*;
pub use transfer_position_ownership::*;
Expand Down
143 changes: 143 additions & 0 deletions programs/invariant/src/instructions/remove_defunct_pool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::get_signer;
use crate::interfaces;
use crate::interfaces::SendTokens;
use crate::structs::pool::Pool;
use crate::structs::tickmap::Tickmap;
use crate::structs::State;
use crate::ErrorCode::*;
use crate::SEED;
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token;
use anchor_spl::token::CloseAccount;
use anchor_spl::token::Token;
use anchor_spl::token::Transfer;
use anchor_spl::token::{Mint, TokenAccount};

#[derive(Accounts)]
pub struct RemoveDefunctPool<'info> {
#[account(seeds = [b"statev1".as_ref()], bump = state.load()?.bump)]
pub state: AccountLoader<'info, State>,
#[account(mut,
close = admin,
seeds = [b"poolv1", token_x.key().as_ref(), token_y.key().as_ref(), &pool.load()?.fee.v.to_le_bytes(), &pool.load()?.tick_spacing.to_le_bytes()],
bump = pool.load()?.bump
)]
pub pool: AccountLoader<'info, Pool>,
#[account(mut,
close = admin,
address = pool.load()?.tickmap @ InvalidTickmap
)]
pub tickmap: AccountLoader<'info, Tickmap>,
pub token_x: Account<'info, Mint>,
pub token_y: Account<'info, Mint>,
#[account(init_if_needed,
payer = admin,
associated_token::mint = token_x,
associated_token::authority = admin
)]
pub account_x: Box<Account<'info, TokenAccount>>,
#[account(init_if_needed,
payer = admin,
associated_token::mint = token_y,
associated_token::authority = admin
)]
pub account_y: Box<Account<'info, TokenAccount>>,
#[account(mut,
constraint = reserve_x.mint == token_x.key() @ InvalidMint,
constraint = &reserve_x.owner == program_authority.key @ InvalidOwner,
address = pool.load()?.token_x_reserve @ InvalidTokenAccount
)]
pub reserve_x: Box<Account<'info, TokenAccount>>,
#[account(mut,
constraint = reserve_y.mint == token_y.key() @ InvalidMint,
constraint = &reserve_y.owner == program_authority.key @ InvalidOwner,
address = pool.load()?.token_y_reserve @ InvalidTokenAccount
)]
pub reserve_y: Box<Account<'info, TokenAccount>>,
#[account(address = state.load()?.admin @ InvalidAdmin)]
pub admin: Signer<'info>,
#[account(constraint = &state.load()?.authority == program_authority.key @ InvalidAuthority)]
pub program_authority: AccountInfo<'info>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
}

impl<'info> interfaces::send_tokens::SendTokens<'info> for RemoveDefunctPool<'info> {
fn send_x(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
CpiContext::new(
self.token_program.to_account_info(),
Transfer {
from: self.reserve_x.to_account_info(),
to: self.account_x.to_account_info(),
authority: self.program_authority.to_account_info(),
},
)
}

fn send_y(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
CpiContext::new(
self.token_program.to_account_info(),
Transfer {
from: self.reserve_y.to_account_info(),
to: self.account_y.to_account_info(),
authority: self.program_authority.to_account_info(),
},
)
}
}

impl<'info> RemoveDefunctPool<'info> {
fn close_reserve_x(&self) -> CpiContext<'_, '_, '_, 'info, CloseAccount<'info>> {
return CpiContext::new(
self.token_program.to_account_info(),
CloseAccount {
account: self.reserve_x.to_account_info(),
destination: self.admin.to_account_info(),
authority: self.program_authority.to_account_info(),
},
);
}

fn close_reserve_y(&self) -> CpiContext<'_, '_, '_, 'info, CloseAccount<'info>> {
return CpiContext::new(
self.token_program.to_account_info(),
CloseAccount {
account: self.reserve_y.to_account_info(),
destination: self.admin.to_account_info(),
authority: self.program_authority.to_account_info(),
},
);
}

fn validate_tickmap(&self) -> ProgramResult {
let tickmap = self.tickmap.load()?;
for tick in tickmap.bitmap.iter() {
if *tick != 0 {
return Err(InvalidTickmap.into());
}
}
Ok(())
}

pub fn handler(&self) -> ProgramResult {
msg!("INVARIANT: REMOVE DEFUNCT POOL");

// contraint: all ticks are empty
self.validate_tickmap()?;

let signer: &[&[&[u8]]] = get_signer!(self.state.load()?.nonce);

let amount_x = self.reserve_x.amount;
let amount_y = self.reserve_y.amount;

token::transfer(self.send_x().with_signer(signer), amount_x)?;
token::transfer(self.send_y().with_signer(signer), amount_y)?;
token::close_account(self.close_reserve_x().with_signer(signer))?;
token::close_account(self.close_reserve_y().with_signer(signer))?;

Ok(())
}
}
4 changes: 2 additions & 2 deletions programs/invariant/src/interfaces/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod send_tokens;
pub mod take_tokens;
pub mod take_ref_tokens;
pub mod take_tokens;

pub use send_tokens::*;
pub use take_tokens::*;
pub use take_ref_tokens::*;
pub use take_tokens::*;
2 changes: 1 addition & 1 deletion programs/invariant/src/interfaces/take_ref_tokens.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Transfer;
pub trait TakeRefTokens<'info> {
fn take_ref_x(&self, to: AccountInfo<'info>) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>>;
fn take_ref_x(&self, to: AccountInfo<'info>) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>>;
fn take_ref_y(&self, to: AccountInfo<'info>) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>>;
}
5 changes: 5 additions & 0 deletions programs/invariant/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ pub mod invariant {
pub fn change_fee_receiver(ctx: Context<ChangeFeeReceiver>) -> ProgramResult {
ctx.accounts.handler()
}

#[access_control(admin(&ctx.accounts.state, &ctx.accounts.admin))]
pub fn remove_defunct_pool(ctx: Context<RemoveDefunctPool>) -> ProgramResult {
ctx.accounts.handler()
}
}

fn admin(state_loader: &AccountLoader<State>, signer: &AccountInfo) -> Result<()> {
Expand Down
53 changes: 53 additions & 0 deletions scripts/find-defunct-pools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Provider } from '@project-serum/anchor'
import { Network } from '@invariant-labs/sdk/src/network'
import { Market } from '@invariant-labs/sdk/src'
import { Decimal, PoolStructure } from '@invariant-labs/sdk/lib/market'
import { DECIMAL } from '@invariant-labs/sdk/lib/utils'
import { DENOMINATOR } from '@invariant-labs/sdk'

require('dotenv').config()
console.log(process.cwd())

const provider = Provider.local(
'https://mainnet.helius-rpc.com/?api-key=ef843b40-9876-4a02-a181-a1e6d3e61b4c'
)

const connection = provider.connection

const main = async () => {
const market = await Market.build(Network.MAIN, provider.wallet, connection)

const allPools = await market.getAllPools()
const allTickmaps = Object.fromEntries(
(await market.program.account.tickmap.all()).map(x => [x.publicKey, x.account.bitmap])
)

const emptyPools: PoolStructure[] = []

for (const poolState of allPools) {
const tickmap: number[] = allTickmaps[poolState.tickmap.toString()]

const empty = !tickmap.some((tick: number) => tick !== 0)
if (empty) {
emptyPools.push(poolState)
console.log(
poolState.tokenX.toString(),
poolState.tokenY.toString(),
formatFee(poolState.fee),
poolState.tickSpacing.toString()
)
}
}

console.log(emptyPools.length)
console.log(allPools.length)
}

main()

export const formatFee = (fee: Decimal) => {
const feeB = BigInt(fee.v.toString())
const feeDenominator = BigInt(DENOMINATOR)
let afterDot = (feeB % feeDenominator).toString()
return (feeB / feeDenominator).toString() + '.' + '0'.repeat(DECIMAL - afterDot.length) + afterDot
}
Loading