Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bundled positions #89

Merged
merged 14 commits into from
Apr 7, 2023
Merged
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,294 changes: 1,042 additions & 252 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions programs/whirlpool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "whirlpool"
version = "0.1.0"
version = "0.2.0"
description = "Created with Anchor"
edition = "2018"

Expand All @@ -15,14 +15,14 @@ cpi = ["no-entrypoint"]
default = []

[dependencies]
anchor-lang = { git = "https://github.com/project-serum/anchor", tag = "v0.20.1", version = "0.20.1", package = "anchor-lang" }
anchor-spl = { git = "https://github.com/project-serum/anchor", tag = "v0.20.1", version = "0.20.1", package = "anchor-spl" }
spl-token = { version = "3.1.1", features = ["no-entrypoint"] }
solana-program = "1.8.12"
anchor-lang = "0.26"
anchor-spl = "0.26"
spl-token = {version = "3.3", features = ["no-entrypoint"]}
solana-program = "1.14.12"
thiserror = "1.0"
uint = { version = "0.9.1", default-features = false }
uint = {version = "0.9.1", default-features = false}
borsh = "0.9.1"
mpl-token-metadata = { version = "1.2.5", features = ["no-entrypoint"] }
mpl-token-metadata = { version = "1.7", features = ["no-entrypoint"] }

[dev-dependencies]
proptest = "1.0"
Expand Down
2 changes: 2 additions & 0 deletions programs/whirlpool/src/constants/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod nft;
pub mod test_constants;

pub use nft::*;
pub use test_constants::*;
18 changes: 18 additions & 0 deletions programs/whirlpool/src/constants/nft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use anchor_lang::prelude::*;

pub mod whirlpool_nft_update_auth {
use super::*;
declare_id!("3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr");
}

// METADATA_NAME : max 32 bytes
// METADATA_SYMBOL : max 10 bytes
// METADATA_URI : max 200 bytes
pub const WP_METADATA_NAME: &str = "Orca Whirlpool Position";
pub const WP_METADATA_SYMBOL: &str = "OWP";
pub const WP_METADATA_URI: &str = "https://arweave.net/E19ZNY2sqMqddm1Wx7mrXPUZ0ZZ5ISizhebb0UsVEws";

pub const WPB_METADATA_NAME_PREFIX: &str = "Orca Position Bundle";
pub const WPB_METADATA_SYMBOL: &str = "OPB";
pub const WPB_METADATA_URI: &str =
"https://arweave.net/A_Wo8dx2_3lSUwMIi7bdT_sqxi8soghRNAWXXiqXpgE";
13 changes: 11 additions & 2 deletions programs/whirlpool/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::num::TryFromIntError;

use anchor_lang::error;
use anchor_lang::prelude::*;

#[error]
#[error_code]
#[derive(PartialEq)]
pub enum ErrorCode {
#[msg("Enum value could not be converted")]
Expand Down Expand Up @@ -106,6 +106,15 @@ pub enum ErrorCode {
InvalidIntermediaryMint, //0x1799
#[msg("Duplicate two hop pool")]
DuplicateTwoHopPool, //0x179a

#[msg("Bundle index is out of bounds")]
InvalidBundleIndex, //0x179b
#[msg("Position has already been opened")]
BundledPositionAlreadyOpened, //0x179c
#[msg("Position has already been closed")]
BundledPositionAlreadyClosed, //0x179d
#[msg("Unable to delete PositionBundle with open positions")]
PositionBundleNotDeletable, //0x179e
}

impl From<TryFromIntError> for ErrorCode {
Expand Down
56 changes: 56 additions & 0 deletions programs/whirlpool/src/instructions/close_bundled_position.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use anchor_lang::prelude::*;
use anchor_spl::token::TokenAccount;

use crate::errors::ErrorCode;
use crate::{state::*, util::verify_position_bundle_authority};

#[derive(Accounts)]
#[instruction(bundle_index: u16)]
pub struct CloseBundledPosition<'info> {
#[account(mut,
close = receiver,
seeds = [
b"bundled_position".as_ref(),
position_bundle.position_bundle_mint.key().as_ref(),
bundle_index.to_string().as_bytes()
],
bump,
)]
pub bundled_position: Account<'info, Position>,

#[account(mut)]
pub position_bundle: Box<Account<'info, PositionBundle>>,

#[account(
constraint = position_bundle_token_account.mint == bundled_position.position_mint,
constraint = position_bundle_token_account.mint == position_bundle.position_bundle_mint,
constraint = position_bundle_token_account.amount == 1
)]
pub position_bundle_token_account: Box<Account<'info, TokenAccount>>,

pub position_bundle_authority: Signer<'info>,

/// CHECK: safe, for receiving rent only
#[account(mut)]
pub receiver: UncheckedAccount<'info>,
}

pub fn handler(ctx: Context<CloseBundledPosition>, bundle_index: u16) -> Result<()> {
let position_bundle = &mut ctx.accounts.position_bundle;

// Allow delegation
verify_position_bundle_authority(
&ctx.accounts.position_bundle_token_account,
&ctx.accounts.position_bundle_authority,
)?;

if !Position::is_position_empty(&ctx.accounts.bundled_position) {
return Err(ErrorCode::ClosePositionNotEmpty.into());
}

position_bundle.close_bundled_position(bundle_index)?;

// Anchor will close the Position account

Ok(())
}
9 changes: 7 additions & 2 deletions programs/whirlpool/src/instructions/close_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ use crate::util::{burn_and_close_user_position_token, verify_position_authority}
pub struct ClosePosition<'info> {
pub position_authority: Signer<'info>,

/// CHECK: safe, for receiving rent only
#[account(mut)]
pub receiver: UncheckedAccount<'info>,

#[account(mut, close = receiver)]
#[account(mut,
close = receiver,
seeds = [b"position".as_ref(), position_mint.key().as_ref()],
bump,
)]
pub position: Account<'info, Position>,

#[account(mut, address = position.position_mint)]
Expand All @@ -27,7 +32,7 @@ pub struct ClosePosition<'info> {
pub token_program: Program<'info, Token>,
}

pub fn handler(ctx: Context<ClosePosition>) -> ProgramResult {
pub fn handler(ctx: Context<ClosePosition>) -> Result<()> {
verify_position_authority(
&ctx.accounts.position_token_account,
&ctx.accounts.position_authority,
Expand Down
2 changes: 1 addition & 1 deletion programs/whirlpool/src/instructions/collect_fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub struct CollectFees<'info> {
pub token_program: Program<'info, Token>,
}

pub fn handler(ctx: Context<CollectFees>) -> ProgramResult {
pub fn handler(ctx: Context<CollectFees>) -> Result<()> {
verify_position_authority(
&ctx.accounts.position_token_account,
&ctx.accounts.position_authority,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub struct CollectProtocolFees<'info> {
pub token_program: Program<'info, Token>,
}

pub fn handler(ctx: Context<CollectProtocolFees>) -> ProgramResult {
pub fn handler(ctx: Context<CollectProtocolFees>) -> Result<()> {
let whirlpool = &ctx.accounts.whirlpool;

transfer_from_vault_to_owner(
Expand Down
2 changes: 1 addition & 1 deletion programs/whirlpool/src/instructions/collect_reward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub struct CollectReward<'info> {
/// - `Ok`: Reward tokens at the specified reward index have been successfully harvested
/// - `Err`: `RewardNotInitialized` if the specified reward has not been initialized
/// `InvalidRewardIndex` if the reward index is not 0, 1, or 2
pub fn handler(ctx: Context<CollectReward>, reward_index: u8) -> ProgramResult {
pub fn handler(ctx: Context<CollectReward>, reward_index: u8) -> Result<()> {
verify_position_authority(
&ctx.accounts.position_token_account,
&ctx.accounts.position_authority,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn handler(
liquidity_amount: u128,
token_min_a: u64,
token_min_b: u64,
) -> ProgramResult {
) -> Result<()> {
verify_position_authority(
&ctx.accounts.position_token_account,
&ctx.accounts.position_authority,
Expand Down
47 changes: 47 additions & 0 deletions programs/whirlpool/src/instructions/delete_position_bundle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Mint, Token, TokenAccount};

use crate::errors::ErrorCode;
use crate::state::*;
use crate::util::burn_and_close_position_bundle_token;

#[derive(Accounts)]
pub struct DeletePositionBundle<'info> {
#[account(mut, close = receiver)]
pub position_bundle: Account<'info, PositionBundle>,

#[account(mut, address = position_bundle.position_bundle_mint)]
pub position_bundle_mint: Account<'info, Mint>,

#[account(mut,
constraint = position_bundle_token_account.mint == position_bundle.position_bundle_mint,
constraint = position_bundle_token_account.owner == position_bundle_owner.key(),
constraint = position_bundle_token_account.amount == 1,
)]
pub position_bundle_token_account: Box<Account<'info, TokenAccount>>,

pub position_bundle_owner: Signer<'info>,

/// CHECK: safe, for receiving rent only
#[account(mut)]
pub receiver: UncheckedAccount<'info>,

#[account(address = token::ID)]
pub token_program: Program<'info, Token>,
}

pub fn handler(ctx: Context<DeletePositionBundle>) -> Result<()> {
let position_bundle = &ctx.accounts.position_bundle;

if !position_bundle.is_deletable() {
return Err(ErrorCode::PositionBundleNotDeletable.into());
}

burn_and_close_position_bundle_token(
&ctx.accounts.position_bundle_owner,
&ctx.accounts.receiver,
&ctx.accounts.position_bundle_mint,
&ctx.accounts.position_bundle_token_account,
&ctx.accounts.token_program,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub fn handler(
liquidity_amount: u128,
token_max_a: u64,
token_max_b: u64,
) -> ProgramResult {
) -> Result<()> {
verify_position_authority(
&ctx.accounts.position_token_account,
&ctx.accounts.position_authority,
Expand Down
2 changes: 1 addition & 1 deletion programs/whirlpool/src/instructions/initialize_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn handler(
collect_protocol_fees_authority: Pubkey,
reward_emissions_super_authority: Pubkey,
default_protocol_fee_rate: u16,
) -> ProgramResult {
) -> Result<()> {
let config = &mut ctx.accounts.config;

Ok(config.initialize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn handler(
ctx: Context<InitializeFeeTier>,
tick_spacing: u16,
default_fee_rate: u16,
) -> ProgramResult {
) -> Result<()> {
Ok(ctx
.accounts
.fee_tier
Expand Down
11 changes: 7 additions & 4 deletions programs/whirlpool/src/instructions/initialize_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct InitializePool<'info> {
token_mint_b.key().as_ref(),
tick_spacing.to_le_bytes().as_ref()
],
bump = bumps.whirlpool_bump,
bump,
payer = funder,
space = Whirlpool::LEN)]
pub whirlpool: Box<Account<'info, Whirlpool>>,
Expand Down Expand Up @@ -49,10 +49,10 @@ pub struct InitializePool<'info> {

pub fn handler(
ctx: Context<InitializePool>,
bumps: WhirlpoolBumps,
_bumps: WhirlpoolBumps,
tick_spacing: u16,
initial_sqrt_price: u128,
) -> ProgramResult {
) -> Result<()> {
let token_mint_a = ctx.accounts.token_mint_a.key();
let token_mint_b = ctx.accounts.token_mint_b.key();

Expand All @@ -61,9 +61,12 @@ pub fn handler(

let default_fee_rate = ctx.accounts.fee_tier.default_fee_rate;

// ignore the bump passed and use one Anchor derived
let bump = *ctx.bumps.get("whirlpool").unwrap();

Ok(whirlpool.initialize(
whirlpools_config,
bumps.whirlpool_bump,
bump,
tick_spacing,
initial_sqrt_price,
default_fee_rate,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::{self, Mint, Token, TokenAccount};

use crate::{state::*, util::mint_position_bundle_token_and_remove_authority};

#[derive(Accounts)]
pub struct InitializePositionBundle<'info> {
#[account(init,
payer = funder,
space = PositionBundle::LEN,
seeds = [b"position_bundle".as_ref(), position_bundle_mint.key().as_ref()],
bump,
)]
pub position_bundle: Box<Account<'info, PositionBundle>>,

#[account(init,
payer = funder,
mint::authority = position_bundle, // will be removed in the transaction
mint::decimals = 0,
)]
pub position_bundle_mint: Account<'info, Mint>,

#[account(init,
payer = funder,
associated_token::mint = position_bundle_mint,
associated_token::authority = position_bundle_owner,
)]
pub position_bundle_token_account: Box<Account<'info, TokenAccount>>,

/// CHECK: safe, the account that will be the owner of the position bundle can be arbitrary
pub position_bundle_owner: UncheckedAccount<'info>,

#[account(mut)]
pub funder: Signer<'info>,

#[account(address = token::ID)]
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
pub associated_token_program: Program<'info, AssociatedToken>,
}

pub fn handler(ctx: Context<InitializePositionBundle>) -> Result<()> {
let position_bundle_mint = &ctx.accounts.position_bundle_mint;
let position_bundle = &mut ctx.accounts.position_bundle;

position_bundle.initialize(position_bundle_mint.key())?;

let bump = *ctx.bumps.get("position_bundle").unwrap();

mint_position_bundle_token_and_remove_authority(
&ctx.accounts.position_bundle,
position_bundle_mint,
&ctx.accounts.position_bundle_token_account,
&ctx.accounts.token_program,
&[
b"position_bundle".as_ref(),
position_bundle_mint.key().as_ref(),
&[bump],
],
)
}
Loading