Skip to content

Commit

Permalink
bundled positions (#89)
Browse files Browse the repository at this point in the history
## Anchor v0.26.0 upgrade (rebased)
Special thanks @dovahcrow !

* anchor 0.26 solana 1.14.12

use mpl-token-metadata 1.7 because rust 1.60 does not compile 1.8

* initialize_pool: ignore passed bump and use one anchor derived

- initialize_pool.test: update error codes, add a test case for ignoring bump

* update expected error messages

* add CHECK comments on UncheckedAccounts

* use create_metadata_accounts_v3 (v2 have been deprecated)

* update unit test cases (cargo test)

## Bundled Positions
* import bundled positions

* upgrade to Anchor v0.26.0 (position bundles related codes)

- ProgramResult to Result<()>
- add /// CHECK comments
- remove space attribute on Mint account
- change create_metadata_accounts_v2 to v3
- update testcases
  - Change in error code detected first
  - Change in account closing method
    coral-xyz/anchor#2169

* cargo fmt

* update seed of BundledPosition

* change temporary mint_authority to position_bundle

* bump to v0.2.0

doc fields are added on IDL

* fix: accidental failure test cases

Fixed test cases that did not take into account rewards accruing up to just before the close.
This test case is not related to bundled positions.
The test case happened to fail, so I fixed them to make them all successful.

---------

Co-authored-by: Weiyuan Wu <weiyuan@crows.land>
  • Loading branch information
yugure-orca and dovahcrow committed Apr 7, 2023
1 parent d18229e commit 44021b1
Show file tree
Hide file tree
Showing 87 changed files with 9,037 additions and 570 deletions.
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
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
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
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
2 changes: 1 addition & 1 deletion programs/whirlpool/src/instructions/decrease_liquidity.rs
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
@@ -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,
)
}
2 changes: 1 addition & 1 deletion programs/whirlpool/src/instructions/increase_liquidity.rs
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
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
2 changes: 1 addition & 1 deletion programs/whirlpool/src/instructions/initialize_fee_tier.rs
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
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
63 changes: 63 additions & 0 deletions programs/whirlpool/src/instructions/initialize_position_bundle.rs
@@ -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],
],
)
}

0 comments on commit 44021b1

Please sign in to comment.