Refactor SDK and add formal documentation (#2)
- Add Typedoc integration to SDK - Reorganize and finalize SDK structure - Add documentation and categorize SDK public items (not all) - Port quotes over from orca-sdk and provide sync options - Port all utils over from orca-sdk and reorganize them into cleaner buckets - Update test structure for integration tests and SDK tests - Update README.md - Add WhirlpoolClient to help construct the more complicated transactions - Remove WhirlpoolIx Instruction's dependency on Transaction
This commit is contained in:
parent
951f75d543
commit
1d7b482cbd
|
@ -7,3 +7,4 @@ node_modules
|
|||
test-ledger/
|
||||
sdk/dist/
|
||||
sdk/node_modules
|
||||
.vscode/
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"files.insertFinalNewline": true
|
||||
}
|
|
@ -14,4 +14,4 @@ wallet = "~/.config/solana/id.json"
|
|||
# program = "../metaplex-program-library/token-metadata/target/deploy/mpl_token_metadata.so"
|
||||
|
||||
[scripts]
|
||||
test = "ts-mocha -p sdk/tests/tsconfig.json -t 1000000 sdk/tests/**/*.ts"
|
||||
test = "ts-mocha -p sdk/tests/tsconfig.json -t 1000000 sdk/tests/**/*.test.ts"
|
||||
|
|
15
README.md
15
README.md
|
@ -6,6 +6,7 @@ This repository contains the Rust smart contract as well as the Typescript SDK (
|
|||
The contract has been audited by Kudelski and Neodyme.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Anchor 0.20.1
|
||||
- Solana 1.9.3
|
||||
- Rust 1.59.0
|
||||
|
@ -18,12 +19,14 @@ Set up a valid Solana keypair at the path specified in the `wallet` in `Anchor.t
|
|||
|
||||
`$NODE_PATH` must be set to the `node_modules` directory of your global installs.
|
||||
For example, using Node 16.10.0 installed through `nvm`, the $NODE_PATH is the following:
|
||||
|
||||
```
|
||||
$ echo $NODE_PATH
|
||||
/Users/<home_dir>/.nvm/versions/node/v16.10.0/lib/node_modules
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Instructions on how to interact with the Whirlpools contract is documented in the Orca Developer Portal.
|
||||
|
||||
## Tests
|
||||
|
@ -33,7 +36,6 @@ Instructions on how to interact with the Whirlpools contract is documented in th
|
|||
|
||||
---
|
||||
|
||||
|
||||
# Whirlpool SDK
|
||||
|
||||
Use the SDK to interact with a deployed Whirlpools program via Typescript.
|
||||
|
@ -41,14 +43,25 @@ Use the SDK to interact with a deployed Whirlpools program via Typescript.
|
|||
## Installation
|
||||
|
||||
In your package, run:
|
||||
|
||||
```
|
||||
yarn add `@orca-so/whirlpool-sdk`
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Read instructions on how to use the SDK on the Orca Developer Portal.
|
||||
|
||||
## Run Typescript tests via local validator
|
||||
|
||||
In the whirlpools/sdk folder, run:
|
||||
|
||||
```
|
||||
anchor test
|
||||
```
|
||||
|
||||
## Generate TypeDoc
|
||||
|
||||
In the `sdk` folder, run `yarn run docs`
|
||||
|
||||
---
|
||||
|
|
|
@ -29,7 +29,7 @@ pub mod whirlpool {
|
|||
/// Initializes a WhirlpoolsConfig account that hosts info & authorities
|
||||
/// required to govern a set of Whirlpools.
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `fee_authority` - Authority authorized to initialize fee-tiers and set customs fees.
|
||||
/// - `collect_protocol_fees_authority` - Authority authorized to collect protocol fees.
|
||||
/// - `reward_emissions_super_authority` - Authority authorized to set reward authorities in pools.
|
||||
|
@ -52,12 +52,12 @@ pub mod whirlpool {
|
|||
/// Initializes a Whirlpool account.
|
||||
/// Fee rate is set to the default values on the config and supplied fee_tier.
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `bumps` - The bump value when deriving the PDA of the Whirlpool address.
|
||||
/// - `tick_spacing` - The desired tick spacing for this pool.
|
||||
/// - `initial_sqrt_price` - The desired initial sqrt-price for this pool
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// `InvalidTokenMintOrder` - The order of mints have to be ordered by
|
||||
/// `SqrtPriceOutOfBounds` - provided initial_sqrt_price is not between 2^-64 to 2^64
|
||||
///
|
||||
|
@ -77,11 +77,11 @@ pub mod whirlpool {
|
|||
|
||||
/// Initializes a tick_array account to represent a tick-range in a Whirlpool.
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `start_tick_index` - The starting tick index for this tick-array.
|
||||
/// Has to be a multiple of TickArray size & the tick spacing of this pool.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `InvalidStartTick` - if the provided start tick is out of bounds or is not a multiple of
|
||||
/// TICK_ARRAY_SIZE * tick spacing.
|
||||
pub fn initialize_tick_array(
|
||||
|
@ -93,15 +93,15 @@ pub mod whirlpool {
|
|||
|
||||
/// Initializes a fee_tier account usable by Whirlpools in a WhirlpoolConfig space.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "fee_authority" - Set authority in the WhirlpoolConfig
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `tick_spacing` - The tick-spacing that this fee-tier suggests the default_fee_rate for.
|
||||
/// - `default_fee_rate` - The default fee rate that a pool will use if the pool uses this
|
||||
/// fee tier during initialization.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE.
|
||||
pub fn initialize_fee_tier(
|
||||
ctx: Context<InitializeFeeTier>,
|
||||
|
@ -113,14 +113,14 @@ pub mod whirlpool {
|
|||
|
||||
/// Initialize reward for a Whirlpool. A pool can only support up to a set number of rewards.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "reward_authority" - assigned authority by the reward_super_authority for the specified
|
||||
/// reward-index in this Whirlpool
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `reward_index` - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS)
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized
|
||||
/// index in this pool, or exceeds NUM_REWARDS, or
|
||||
/// all reward slots for this pool has been initialized.
|
||||
|
@ -130,15 +130,15 @@ pub mod whirlpool {
|
|||
|
||||
/// Set the reward emissions for a reward in a Whirlpool.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "reward_authority" - assigned authority by the reward_super_authority for the specified
|
||||
/// reward-index in this Whirlpool
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `reward_index` - The reward index (0 <= index <= NUM_REWARDS) that we'd like to modify.
|
||||
/// - `emissions_per_second_x64` - The amount of rewards emitted in this pool.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `RewardVaultAmountInsufficient` - The amount of rewards in the reward vault cannot emit
|
||||
/// more than a day of desired emissions.
|
||||
/// - `InvalidTimestamp` - Provided timestamp is not in order with the previous timestamp.
|
||||
|
@ -160,11 +160,11 @@ pub mod whirlpool {
|
|||
/// Open a position in a Whirlpool. A unique token will be minted to represent the position
|
||||
/// in the users wallet. The position will start off with 0 liquidity.
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `tick_lower_index` - The tick specifying the lower end of the position range.
|
||||
/// - `tick_upper_index` - The tick specifying the upper end of the position range.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of
|
||||
/// the tick-spacing in this pool.
|
||||
pub fn open_position(
|
||||
|
@ -185,11 +185,11 @@ pub mod whirlpool {
|
|||
/// in the users wallet. Additional Metaplex metadata is appended to identify the token.
|
||||
/// The position will start off with 0 liquidity.
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `tick_lower_index` - The tick specifying the lower end of the position range.
|
||||
/// - `tick_upper_index` - The tick specifying the upper end of the position range.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of
|
||||
/// the tick-spacing in this pool.
|
||||
pub fn open_position_with_metadata(
|
||||
|
@ -206,17 +206,17 @@ pub mod whirlpool {
|
|||
);
|
||||
}
|
||||
|
||||
/// Add liquidity to a position in the Whirlpool.
|
||||
/// Add liquidity to a position in the Whirlpool. This call also updates the position's accrued fees and rewards.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - `position_authority` - authority that owns the token corresponding to this desired position.
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `liquidity_amount` - The total amount of Liquidity the user is willing to deposit.
|
||||
/// - `token_max_a` - The maximum amount of tokenA the user is willing to deposit.
|
||||
/// - `token_max_b` - The maximum amount of tokenB the user is willing to deposit.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `LiquidityZero` - Provided liquidity amount is zero.
|
||||
/// - `LiquidityTooHigh` - Provided liquidity exceeds u128::max.
|
||||
/// - `TokenMaxExceeded` - The required token to perform this operation exceeds the user defined amount.
|
||||
|
@ -234,17 +234,17 @@ pub mod whirlpool {
|
|||
);
|
||||
}
|
||||
|
||||
/// Withdraw liquidity from a position in the Whirlpool.
|
||||
/// Withdraw liquidity from a position in the Whirlpool. This call also updates the position's accrued fees and rewards.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - `position_authority` - authority that owns the token corresponding to this desired position.
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `liquidity_amount` - The total amount of Liquidity the user desires to withdraw.
|
||||
/// - `token_min_a` - The minimum amount of tokenA the user is willing to withdraw.
|
||||
/// - `token_min_b` - The minimum amount of tokenB the user is willing to withdraw.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `LiquidityZero` - Provided liquidity amount is zero.
|
||||
/// - `LiquidityTooHigh` - Provided liquidity exceeds u128::max.
|
||||
/// - `TokenMinSubceeded` - The required token to perform this operation subceeds the user defined amount.
|
||||
|
@ -264,7 +264,7 @@ pub mod whirlpool {
|
|||
|
||||
/// Update the accrued fees and rewards for a position.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `TickNotFound` - Provided tick array account does not contain the tick for this position.
|
||||
/// - `LiquidityZero` - Position has zero liquidity and therefore already has the most updated fees and reward values.
|
||||
pub fn update_fees_and_rewards(ctx: Context<UpdateFeesAndRewards>) -> ProgramResult {
|
||||
|
@ -273,7 +273,7 @@ pub mod whirlpool {
|
|||
|
||||
/// Collect fees accrued for this position.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - `position_authority` - authority that owns the token corresponding to this desired position.
|
||||
pub fn collect_fees(ctx: Context<CollectFees>) -> ProgramResult {
|
||||
return instructions::collect_fees::handler(ctx);
|
||||
|
@ -281,7 +281,7 @@ pub mod whirlpool {
|
|||
|
||||
/// Collect rewards accrued for this position.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - `position_authority` - authority that owns the token corresponding to this desired position.
|
||||
pub fn collect_reward(ctx: Context<CollectReward>, reward_index: u8) -> ProgramResult {
|
||||
return instructions::collect_reward::handler(ctx, reward_index);
|
||||
|
@ -289,7 +289,7 @@ pub mod whirlpool {
|
|||
|
||||
/// Collect the protocol fees accrued in this Whirlpool
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - `collect_protocol_fees_authority` - assigned authority in the WhirlpoolConfig that can collect protocol fees
|
||||
pub fn collect_protocol_fees(ctx: Context<CollectProtocolFees>) -> ProgramResult {
|
||||
return instructions::collect_protocol_fees::handler(ctx);
|
||||
|
@ -297,12 +297,25 @@ pub mod whirlpool {
|
|||
|
||||
/// Perform a swap in this Whirlpool
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `amount`
|
||||
/// - `other_amount_threshold`
|
||||
/// - `sqrt_price_limit`
|
||||
/// - `exact_input`
|
||||
/// - `a_to_b`
|
||||
/// ### Authority
|
||||
/// - "token_authority" - The authority to withdraw tokens from the input token account.
|
||||
///
|
||||
/// ### Parameters
|
||||
/// - `amount` - The amount of input or output token to swap from (depending on exact_input).
|
||||
/// - `other_amount_threshold` - The maximum/minimum of input/output token to swap into (depending on exact_input).
|
||||
/// - `sqrt_price_limit` - The maximum/minimum price the swap will swap to.
|
||||
/// - `exact_input` - Specifies the token the parameter `amount`represents. If true, the amount represents the input token of the swap.
|
||||
/// - `a_to_b` - The direction of the swap. True if swapping from A to B. False if swapping from B to A.
|
||||
///
|
||||
/// #### Special Errors
|
||||
/// - `ZeroTradableAmount` - User provided parameter `amount` is 0.
|
||||
/// - `InvalidSqrtPriceLimitDirection` - User provided parameter `sqrt_price_limit` does not match the direction of the trade.
|
||||
/// - `SqrtPriceOutOfBounds` - User provided parameter `sqrt_price_limit` is over Whirlppool's max/min bounds for sqrt-price.
|
||||
/// - `InvalidTickArraySequence` - User provided tick-arrays are not in sequential order required to proceed in this trade direction.
|
||||
/// - `TickArraySequenceInvalidIndex` - The swap loop attempted to access an invalid array index during the query of the next initialized tick.
|
||||
/// - `TickArrayIndexOutofBounds` - The swap loop attempted to access an invalid array index during tick crossing.
|
||||
/// - `LiquidityOverflow` - Liquidity value overflowed 128bits during tick crossing.
|
||||
/// - `InvalidTickSpacing` - The swap pool was initialized with tick-spacing of 0.
|
||||
pub fn swap(
|
||||
ctx: Context<Swap>,
|
||||
amount: u64,
|
||||
|
@ -321,6 +334,13 @@ pub mod whirlpool {
|
|||
);
|
||||
}
|
||||
|
||||
/// Close a position in a Whirlpool. Burns the position token in the owner's wallet.
|
||||
///
|
||||
/// ### Authority
|
||||
/// - "position_authority" - The authority that owns the position token.
|
||||
///
|
||||
/// #### Special Errors
|
||||
/// - `ClosePositionNotEmpty` - The provided position account is not empty.
|
||||
pub fn close_position(ctx: Context<ClosePosition>) -> ProgramResult {
|
||||
return instructions::close_position::handler(ctx);
|
||||
}
|
||||
|
@ -328,14 +348,14 @@ pub mod whirlpool {
|
|||
/// Set the default_fee_rate for a FeeTier
|
||||
/// Only the current fee authority has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "fee_authority" - Set authority in the WhirlpoolConfig
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `default_fee_rate` - The default fee rate that a pool will use if the pool uses this
|
||||
/// fee tier during initialization.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE.
|
||||
pub fn set_default_fee_rate(
|
||||
ctx: Context<SetDefaultFeeRate>,
|
||||
|
@ -348,13 +368,13 @@ pub mod whirlpool {
|
|||
/// Protocol fee rate is represented as a basis point.
|
||||
/// Only the current fee authority has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `default_protocol_fee_rate` - Rate that is referenced during the initialization of a Whirlpool using this config.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE.
|
||||
pub fn set_default_protocol_fee_rate(
|
||||
ctx: Context<SetDefaultProtocolFeeRate>,
|
||||
|
@ -370,14 +390,14 @@ pub mod whirlpool {
|
|||
/// Fee rate is represented as hundredths of a basis point.
|
||||
/// Only the current fee authority has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `fee_rate` - The rate that the pool will use to calculate fees going onwards.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE.
|
||||
/// #### Special Errors
|
||||
/// - `FeeRateMaxExceeded` - If the provided fee_rate exceeds MAX_FEE_RATE.
|
||||
pub fn set_fee_rate(ctx: Context<SetFeeRate>, fee_rate: u16) -> ProgramResult {
|
||||
return instructions::set_fee_rate::handler(ctx, fee_rate);
|
||||
}
|
||||
|
@ -386,13 +406,13 @@ pub mod whirlpool {
|
|||
/// Protocol fee rate is represented as a basis point.
|
||||
/// Only the current fee authority has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig
|
||||
///
|
||||
/// # Parameters
|
||||
/// ### Parameters
|
||||
/// - `protocol_fee_rate` - The rate that the pool will use to calculate protocol fees going onwards.
|
||||
///
|
||||
/// # Special Errors
|
||||
/// #### Special Errors
|
||||
/// - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE.
|
||||
pub fn set_protocol_fee_rate(
|
||||
ctx: Context<SetProtocolFeeRate>,
|
||||
|
@ -404,9 +424,9 @@ pub mod whirlpool {
|
|||
/// Sets the fee authority for a WhirlpoolConfig.
|
||||
/// The fee authority can set the fee & protocol fee rate for individual pools or
|
||||
/// set the default fee rate for newly minted pools.
|
||||
/// Only the current collect fee authority has permission to invoke this instruction.
|
||||
/// Only the current fee authority has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig
|
||||
pub fn set_fee_authority(ctx: Context<SetFeeAuthority>) -> ProgramResult {
|
||||
return instructions::set_fee_authority::handler(ctx);
|
||||
|
@ -415,7 +435,7 @@ pub mod whirlpool {
|
|||
/// Sets the fee authority to collect protocol fees for a WhirlpoolConfig.
|
||||
/// Only the current collect protocol fee authority has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "fee_authority" - Set authority that can collect protocol fees in the WhirlpoolConfig
|
||||
pub fn set_collect_protocol_fees_authority(
|
||||
ctx: Context<SetCollectProtocolFeesAuthority>,
|
||||
|
@ -426,8 +446,13 @@ pub mod whirlpool {
|
|||
/// Set the whirlpool reward authority at the provided `reward_index`.
|
||||
/// Only the current reward authority for this reward index has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "reward_authority" - Set authority that can control reward emission for this particular reward.
|
||||
///
|
||||
/// #### Special Errors
|
||||
/// - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized
|
||||
/// index in this pool, or exceeds NUM_REWARDS, or
|
||||
/// all reward slots for this pool has been initialized.
|
||||
pub fn set_reward_authority(
|
||||
ctx: Context<SetRewardAuthority>,
|
||||
reward_index: u8,
|
||||
|
@ -438,8 +463,13 @@ pub mod whirlpool {
|
|||
/// Set the whirlpool reward authority at the provided `reward_index`.
|
||||
/// Only the current reward super authority has permission to invoke this instruction.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "reward_authority" - Set authority that can control reward emission for this particular reward.
|
||||
///
|
||||
/// #### Special Errors
|
||||
/// - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized
|
||||
/// index in this pool, or exceeds NUM_REWARDS, or
|
||||
/// all reward slots for this pool has been initialized.
|
||||
pub fn set_reward_authority_by_super_authority(
|
||||
ctx: Context<SetRewardAuthorityBySuperAuthority>,
|
||||
reward_index: u8,
|
||||
|
@ -451,7 +481,7 @@ pub mod whirlpool {
|
|||
/// Only the current reward super authority has permission to invoke this instruction.
|
||||
/// This instruction will not change the authority on any `WhirlpoolRewardInfo` whirlpool rewards.
|
||||
///
|
||||
/// # Authority
|
||||
/// ### Authority
|
||||
/// - "reward_emissions_super_authority" - Set authority that can control reward authorities for all pools in this config space.
|
||||
pub fn set_reward_emissions_super_authority(
|
||||
ctx: Context<SetRewardEmissionsSuperAuthority>,
|
||||
|
|
|
@ -99,9 +99,9 @@ impl<'info> SwapTickSequence<'info> {
|
|||
///
|
||||
/// # Returns
|
||||
/// - `(usize, i32, &mut Tick)`: The array_index which the next initialized index was found, the next initialized tick-index & a mutable reference to that tick
|
||||
///
|
||||
/// - `InvalidTickArraySequence`: - Unable to find the next initialized index with the provided tick-array sequence.
|
||||
/// - Provided tick-arrays are not in sequential order to the trade direction.
|
||||
/// - `TickArraySequenceInvalidIndex` - The swap loop provided an invalid array index to query the next tick in.
|
||||
/// - `InvalidTickArraySequence`: - User provided tick-arrays are not in sequential order required to proceed in this trade direction.
|
||||
|
||||
pub fn get_next_initialized_tick_index(
|
||||
&self,
|
||||
tick_index: i32,
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
"roots": [
|
||||
"<rootDir>/src",
|
||||
"<rootDir>/tests/sdk"
|
||||
],
|
||||
"testMatch": [
|
||||
"**/__tests__/**/*.+(ts|tsx|js)",
|
||||
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.(ts|tsx)$": "ts-jest"
|
||||
},
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsconfig: "./tests/tsconfig.json"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,29 @@
|
|||
{
|
||||
"name": "@orca-so/whirlpool-client-sdk",
|
||||
"version": "0.0.8",
|
||||
"name": "@orca-so/whirlpools-sdk",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides functions to generate instructions needed for Orca's Whirlpool contracts",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"dependencies": {
|
||||
"@metaplex-foundation/mpl-token-metadata": "1.2.5",
|
||||
"@orca-so/common-sdk": "^0.0.4",
|
||||
"@project-serum/anchor": "^0.20.1",
|
||||
"@solana/spl-token": "^0.1.8",
|
||||
"decimal.js": "^10.3.1"
|
||||
"decimal.js": "^10.3.1",
|
||||
"tiny-invariant": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/decimal.js": "^7.4.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||
"@typescript-eslint/parser": "^4.26.0",
|
||||
"chai": "^4.3.4",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"mocha": "^9.0.3",
|
||||
"jest": "^27.0.6",
|
||||
"ts-jest": "^27.0.3",
|
||||
"prettier": "^2.3.2",
|
||||
"typescript": "^4.5.5"
|
||||
},
|
||||
|
@ -26,7 +31,9 @@
|
|||
"build": "tsc -p src",
|
||||
"watch": "tsc -w -p src",
|
||||
"prepublishOnly": "yarn build",
|
||||
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write"
|
||||
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
||||
"test": "jest",
|
||||
"docs": "npx typedoc --excludePrivate --categorizeByGroup false --tsconfig src/tsconfig.json"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,md}": "yarn run prettier-format"
|
||||
|
|
|
@ -1,379 +0,0 @@
|
|||
import { WhirlpoolContext } from "./context";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { WhirlpoolConfigAccount } from "./types/public/account-types";
|
||||
import { TransactionBuilder } from "./utils/transactions/transactions-builder";
|
||||
import { buildInitializeConfigIx } from "./instructions/initialize-config-ix";
|
||||
import {
|
||||
ClosePositionParams,
|
||||
CollectFeesParams,
|
||||
CollectProtocolFeesParams,
|
||||
CollectRewardParams,
|
||||
InitConfigParams,
|
||||
InitializeRewardParams,
|
||||
InitPoolParams,
|
||||
InitTickArrayParams,
|
||||
OpenPositionParams,
|
||||
SetCollectProtocolFeesAuthorityParams,
|
||||
SetFeeAuthorityParams,
|
||||
SetRewardAuthorityBySuperAuthorityParams,
|
||||
parsePosition,
|
||||
parseTickArray,
|
||||
parseWhirlpool,
|
||||
parseWhirlpoolsConfig,
|
||||
SetRewardAuthorityParams,
|
||||
SetRewardEmissionsParams,
|
||||
SetRewardEmissionsSuperAuthorityParams,
|
||||
SwapParams,
|
||||
UpdateFeesAndRewardsParams,
|
||||
SetFeeRateParams,
|
||||
SetDefaultProtocolFeeRateParams,
|
||||
SetProtocolFeeRateParams,
|
||||
SetDefaultFeeRateParams,
|
||||
DecreaseLiquidityParams,
|
||||
IncreaseLiquidityParams,
|
||||
InitFeeTierParams,
|
||||
} from ".";
|
||||
import { buildInitPoolIx } from "./instructions/initialize-pool-ix";
|
||||
import {
|
||||
FeeTierData,
|
||||
PositionData,
|
||||
TickArrayData,
|
||||
WhirlpoolData,
|
||||
} from "./types/public/anchor-types";
|
||||
import {
|
||||
buildOpenPositionIx,
|
||||
buildOpenPositionWithMetadataIx,
|
||||
} from "./instructions/open-position-ix";
|
||||
import { buildInitTickArrayIx } from "./instructions/initialize-tick-array-ix";
|
||||
import { buildIncreaseLiquidityIx } from "./instructions/increase-liquidity-ix";
|
||||
import { buildCollectFeesIx } from "./instructions/collect-fees-ix";
|
||||
import { buildCollectRewardIx } from "./instructions/collect-reward-ix";
|
||||
import { buildSwapIx } from "./instructions/swap-ix";
|
||||
import { buildInitializeRewardIx } from "./instructions/initialize-reward-ix";
|
||||
import { buildSetRewardEmissionsSuperAuthorityIx } from "./instructions/set-reward-emissions-super-authority-ix";
|
||||
import { buildSetRewardAuthorityIx } from "./instructions/set-reward-authority-ix";
|
||||
import { buildSetRewardEmissionsIx } from "./instructions/set-reward-emissions-ix";
|
||||
import { buildClosePositionIx } from "./instructions/close-position-ix";
|
||||
import { buildSetRewardAuthorityBySuperAuthorityIx } from "./instructions/set-reward-authority-by-super-authority-ix";
|
||||
import { buildSetFeeAuthorityIx } from "./instructions/set-fee-authority-ix";
|
||||
import { buildSetCollectProtocolFeesAuthorityIx } from "./instructions/set-collect-protocol-fees-authority-ix";
|
||||
import { buildUpdateFeesAndRewardsIx } from "./instructions/update-fees-and-rewards-ix";
|
||||
import { buildCollectProtocolFeesIx } from "./instructions/collect-protocol-fees-ix";
|
||||
import { buildDecreaseLiquidityIx } from "./instructions/decrease-liquidity-ix";
|
||||
import { buildSetFeeRateIx } from "./instructions/set-fee-rate-ix";
|
||||
import { buildSetDefaultProtocolFeeRateIx } from "./instructions/set-default-protocol-fee-rate-ix";
|
||||
import { buildSetDefaultFeeRateIx } from "./instructions/set-default-fee-rate-ix";
|
||||
import { buildSetProtocolFeeRateIx } from "./instructions/set-protocol-fee-rate-ix";
|
||||
import { buildInitializeFeeTier } from "./instructions/initialize-fee-tier";
|
||||
import { Decimal } from "decimal.js";
|
||||
|
||||
// Global rules for Decimals
|
||||
// - 40 digits of precision for the largest number
|
||||
// - 20 digits of precision for the smallest number
|
||||
// - Always round towards 0 to mirror smart contract rules
|
||||
Decimal.set({ precision: 40, toExpPos: 40, toExpNeg: -20, rounding: 1 });
|
||||
|
||||
/**
|
||||
* WhirlpoolClient provides a portal to perform admin-type tasks on the Whirlpool protocol.
|
||||
*/
|
||||
export class WhirlpoolClient {
|
||||
readonly context: WhirlpoolContext;
|
||||
|
||||
public constructor(context: WhirlpoolContext) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to initialize a WhirlpoolConfig account with the provided parameters.
|
||||
* @param params Parameters to configure the initialized WhirlpoolConfig account
|
||||
* @returns A TransactionBuilder to initialize a WhirlpoolConfig account with the provided parameters.
|
||||
*/
|
||||
public initConfigTx(params: InitConfigParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildInitializeConfigIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses a WhirlpoolConfig account.
|
||||
* @param poolPubKey A public key of a WhirlpoolConfig account
|
||||
* @returns A WhirlpoolConfig type containing the parameters stored on the account
|
||||
*/
|
||||
public async getConfig(configPubKey: PublicKey): Promise<WhirlpoolConfigAccount> {
|
||||
const program = this.context.program;
|
||||
const account = await program.account.whirlpoolsConfig.fetch(configPubKey);
|
||||
// TODO: If we feel nice we can build a builder or something instead of casting
|
||||
return account as WhirlpoolConfigAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a WhirlpoolConfig account.
|
||||
* @param data A buffer containing data fetched from an account
|
||||
* @returns A WhirlpoolConfig type containing the parameters stored on the account
|
||||
*/
|
||||
public parseConfig(data: Buffer): WhirlpoolConfigAccount | null {
|
||||
return parseWhirlpoolsConfig(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to initialize a FeeTier account with the provided parameters.
|
||||
* @param params Parameters to configure the initialized FeeTier account
|
||||
* @returns A TransactionBuilder to initialize a FeeTier account with the provided parameters.
|
||||
*/
|
||||
public initFeeTierTx(params: InitFeeTierParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildInitializeFeeTier(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses a FeeTier account.
|
||||
* @param feeTierKey A public key of a FeeTier account
|
||||
* @returns A FeeTier type containing the parameters stored on the account
|
||||
*/
|
||||
public async getFeeTier(feeTierKey: PublicKey): Promise<FeeTierData> {
|
||||
const program = this.context.program;
|
||||
|
||||
const feeTierAccount = await program.account.feeTier.fetch(feeTierKey);
|
||||
return feeTierAccount as unknown as FeeTierData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to initialize a Whirlpool account with the provided parameters.
|
||||
* @param params Parameters to configure the initialized Whirlpool account
|
||||
* @returns A TransactionBuilder to initialize a Whirlpool account with the provided parameters.
|
||||
*/
|
||||
public initPoolTx(params: InitPoolParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildInitPoolIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses a Whirlpool account.
|
||||
* @param poolPubKey A public key of a Whirlpool account
|
||||
* @returns A Whirlpool type containing the parameters stored on the account
|
||||
*/
|
||||
public async getPool(poolKey: PublicKey): Promise<WhirlpoolData> {
|
||||
const program = this.context.program;
|
||||
|
||||
const whirlpoolAccount = await program.account.whirlpool.fetch(poolKey);
|
||||
return whirlpoolAccount as unknown as WhirlpoolData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Whirlpool account.
|
||||
* @param data A buffer containing data fetched from an account
|
||||
* @returns A Whirlpool type containing the parameters stored on the account
|
||||
*/
|
||||
public parsePool(data: Buffer): WhirlpoolData | null {
|
||||
return parseWhirlpool(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to open a Position account.
|
||||
* @param params Parameters to configure the initialized Position account.
|
||||
* @returns A TransactionBuilder to initialize a Position account with the provided parameters.
|
||||
*/
|
||||
public openPositionTx(params: OpenPositionParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildOpenPositionIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to open a Position account with metadata.
|
||||
* @param params Parameters to configure the initialized Position account.
|
||||
* @returns A TransactionBuilder to initialize a Position account with the provided parameters.
|
||||
*/
|
||||
public openPositionWithMetadataTx(params: Required<OpenPositionParams>): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildOpenPositionWithMetadataIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public closePositionTx(params: ClosePositionParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildClosePositionIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Position account.
|
||||
* @param positionKey The public key of the Position account
|
||||
* @returns A Position type containing the parameters stored on the account
|
||||
*/
|
||||
public async getPosition(positionKey: PublicKey): Promise<PositionData> {
|
||||
const positionAccount = await this.context.program.account.position.fetch(positionKey);
|
||||
return positionAccount as unknown as PositionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Position account.
|
||||
* @param data A buffer containing data fetched from an account
|
||||
* @returns A Position type containing the parameters stored on the account
|
||||
*/
|
||||
public parsePosition(data: Buffer): PositionData | null {
|
||||
return parsePosition(data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a TransactionBuilder to initialize a TickArray account with the provided parameters.
|
||||
* @param params Parameters to configure the initialized TickArray account
|
||||
* @returns A TransactionBuilder to initialize a TickArray account with the provided parameters.
|
||||
*/
|
||||
public initTickArrayTx(params: InitTickArrayParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildInitTickArrayIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses a TickArray account. Account is used to store Ticks for a Whirlpool.
|
||||
* @param arrayPubKey A public key of a TickArray account
|
||||
* @returns A TickArrayData type containing the parameters stored on the account
|
||||
*/
|
||||
public async getTickArray(arrayPubKey: PublicKey): Promise<TickArrayData> {
|
||||
const program = this.context.program;
|
||||
const tickArrayAccount = await program.account.tickArray.fetch(arrayPubKey);
|
||||
return tickArrayAccount as unknown as TickArrayData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a TickArray account.
|
||||
* @param data A buffer containing data fetched from an account
|
||||
* @returns A Position type containing the parameters stored on the account
|
||||
*/
|
||||
public parseTickArray(data: Buffer): TickArrayData | null {
|
||||
return parseTickArray(data);
|
||||
}
|
||||
|
||||
public initializeRewardTx(params: InitializeRewardParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildInitializeRewardIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setRewardEmissionsTx(params: SetRewardEmissionsParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetRewardEmissionsIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to increase the liquidity of a Position.
|
||||
* @param params Parameters to configure the increase liquidity instruction
|
||||
* @returns A TransactionBuilder containing one increase liquidity instruction
|
||||
*/
|
||||
public increaseLiquidityTx(params: IncreaseLiquidityParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildIncreaseLiquidityIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public decreaseLiquidityTx(params: DecreaseLiquidityParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildDecreaseLiquidityIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public updateFeesAndRewards(params: UpdateFeesAndRewardsParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildUpdateFeesAndRewardsIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to collect the fees for a Position.
|
||||
* @param params Parameters to configure the collect fees instruction
|
||||
* @returns A TransactionBuilder containing one collect fees instruction
|
||||
*/
|
||||
public collectFeesTx(params: CollectFeesParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildCollectFeesIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransactionBuilder to collect a reward at the specified index for a Position.
|
||||
* @param params Parameters to configure the collect reward instruction
|
||||
* @returns A TransactionBuilder containing one collect reward instruction
|
||||
*/
|
||||
public collectRewardTx(params: CollectRewardParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildCollectRewardIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public collectProtocolFeesTx(params: CollectProtocolFeesParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildCollectProtocolFeesIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public swapTx(params: SwapParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSwapIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setRewardEmissionsSuperAuthorityTx(
|
||||
params: SetRewardEmissionsSuperAuthorityParams
|
||||
): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetRewardEmissionsSuperAuthorityIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setRewardAuthorityTx(params: SetRewardAuthorityParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetRewardAuthorityIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setRewardAuthorityBySuperAuthorityTx(
|
||||
params: SetRewardAuthorityBySuperAuthorityParams
|
||||
): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetRewardAuthorityBySuperAuthorityIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setFeeAuthorityTx(params: SetFeeAuthorityParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetFeeAuthorityIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setCollectProtocolFeesAuthorityTx(
|
||||
params: SetCollectProtocolFeesAuthorityParams
|
||||
): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetCollectProtocolFeesAuthorityIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setFeeRateIx(params: SetFeeRateParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetFeeRateIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setProtocolFeeRateIx(params: SetProtocolFeeRateParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetProtocolFeeRateIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setDefaultFeeRateIx(params: SetDefaultFeeRateParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetDefaultFeeRateIx(this.context, params)
|
||||
);
|
||||
}
|
||||
|
||||
public setDefaultProtocolFeeRateIx(params: SetDefaultProtocolFeeRateParams): TransactionBuilder {
|
||||
return new TransactionBuilder(this.context.provider).addInstruction(
|
||||
buildSetDefaultProtocolFeeRateIx(this.context, params)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,9 @@ import WhirlpoolIDL from "./artifacts/whirlpool.json";
|
|||
import { Whirlpool } from "./artifacts/whirlpool";
|
||||
import { Wallet } from "@project-serum/anchor/dist/cjs/provider";
|
||||
|
||||
/**
|
||||
* @category Core
|
||||
*/
|
||||
export class WhirlpoolContext {
|
||||
readonly connection: Connection;
|
||||
readonly wallet: Wallet;
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
import { AddressUtil, deriveATA, TransactionBuilder } from "@orca-so/common-sdk";
|
||||
import { Address } from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import {
|
||||
IncreaseLiquidityInput,
|
||||
DecreaseLiquidityInput,
|
||||
increaseLiquidityIx,
|
||||
decreaseLiquidityIx,
|
||||
} from "../instructions";
|
||||
import { PositionData } from "../types/public";
|
||||
import { Position } from "../whirlpool-client";
|
||||
import { PublicKey, Signer } from "@solana/web3.js";
|
||||
import { AccountFetcher } from "../network/public";
|
||||
import { PDAUtil, TickUtil } from "../utils/public";
|
||||
import { toTx } from "../utils/instructions-util";
|
||||
|
||||
export class PositionImpl implements Position {
|
||||
private data: PositionData;
|
||||
constructor(
|
||||
readonly ctx: WhirlpoolContext,
|
||||
readonly fetcher: AccountFetcher,
|
||||
readonly address: PublicKey,
|
||||
data: PositionData
|
||||
) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
getData(): PositionData {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async refreshData() {
|
||||
await this.refresh();
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async increaseLiquidity(
|
||||
liquidityInput: IncreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address
|
||||
) {
|
||||
const sourceWalletKey = sourceWallet
|
||||
? AddressUtil.toPubKey(sourceWallet)
|
||||
: this.ctx.wallet.publicKey;
|
||||
const positionWalletKey = positionWallet
|
||||
? AddressUtil.toPubKey(positionWallet)
|
||||
: this.ctx.wallet.publicKey;
|
||||
|
||||
const whirlpool = await this.fetcher.getPool(this.data.whirlpool, true);
|
||||
if (!whirlpool) {
|
||||
throw new Error("Unable to fetch whirlpool for this position.");
|
||||
}
|
||||
|
||||
return toTx(
|
||||
this.ctx,
|
||||
increaseLiquidityIx(this.ctx.program, {
|
||||
...liquidityInput,
|
||||
whirlpool: this.data.whirlpool,
|
||||
position: this.address,
|
||||
positionTokenAccount: await deriveATA(positionWalletKey, this.data.positionMint),
|
||||
tokenOwnerAccountA: await deriveATA(sourceWalletKey, whirlpool.tokenMintA),
|
||||
tokenOwnerAccountB: await deriveATA(sourceWalletKey, whirlpool.tokenMintB),
|
||||
tokenVaultA: whirlpool.tokenVaultA,
|
||||
tokenVaultB: whirlpool.tokenVaultB,
|
||||
tickArrayLower: PDAUtil.getTickArray(
|
||||
this.ctx.program.programId,
|
||||
this.data.whirlpool,
|
||||
TickUtil.getStartTickIndex(this.data.tickLowerIndex, whirlpool.tickSpacing)
|
||||
).publicKey,
|
||||
tickArrayUpper: PDAUtil.getTickArray(
|
||||
this.ctx.program.programId,
|
||||
this.data.whirlpool,
|
||||
TickUtil.getStartTickIndex(this.data.tickUpperIndex, whirlpool.tickSpacing)
|
||||
).publicKey,
|
||||
positionAuthority: positionWalletKey,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async decreaseLiquidity(
|
||||
liquidityInput: DecreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address
|
||||
) {
|
||||
const sourceWalletKey = sourceWallet
|
||||
? AddressUtil.toPubKey(sourceWallet)
|
||||
: this.ctx.wallet.publicKey;
|
||||
const positionWalletKey = positionWallet
|
||||
? AddressUtil.toPubKey(positionWallet)
|
||||
: this.ctx.wallet.publicKey;
|
||||
const whirlpool = await this.fetcher.getPool(this.data.whirlpool, true);
|
||||
|
||||
if (!whirlpool) {
|
||||
throw new Error("Unable to fetch whirlpool for this position.");
|
||||
}
|
||||
|
||||
return toTx(
|
||||
this.ctx,
|
||||
decreaseLiquidityIx(this.ctx.program, {
|
||||
...liquidityInput,
|
||||
whirlpool: this.data.whirlpool,
|
||||
position: this.address,
|
||||
positionTokenAccount: await deriveATA(positionWalletKey, this.data.positionMint),
|
||||
tokenOwnerAccountA: await deriveATA(sourceWalletKey, whirlpool.tokenMintA),
|
||||
tokenOwnerAccountB: await deriveATA(sourceWalletKey, whirlpool.tokenMintB),
|
||||
tokenVaultA: whirlpool.tokenVaultA,
|
||||
tokenVaultB: whirlpool.tokenVaultB,
|
||||
tickArrayLower: PDAUtil.getTickArray(
|
||||
this.ctx.program.programId,
|
||||
this.data.whirlpool,
|
||||
TickUtil.getStartTickIndex(this.data.tickLowerIndex, whirlpool.tickSpacing)
|
||||
).publicKey,
|
||||
tickArrayUpper: PDAUtil.getTickArray(
|
||||
this.ctx.program.programId,
|
||||
this.data.whirlpool,
|
||||
TickUtil.getStartTickIndex(this.data.tickUpperIndex, whirlpool.tickSpacing)
|
||||
).publicKey,
|
||||
positionAuthority: positionWalletKey,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async refresh() {
|
||||
const account = await this.fetcher.getPosition(this.address, true);
|
||||
if (!!account) {
|
||||
this.data = account;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { AddressUtil } from "@orca-so/common-sdk";
|
||||
import { Address } from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { AccountFetcher } from "../network/public";
|
||||
import { WhirlpoolData, TokenInfo } from "../types/public";
|
||||
import { WhirlpoolClient, Whirlpool, Position } from "../whirlpool-client";
|
||||
import { PositionImpl } from "./position-impl";
|
||||
import { WhirlpoolImpl } from "./whirlpool-impl";
|
||||
|
||||
export class WhirlpoolClientImpl implements WhirlpoolClient {
|
||||
constructor(readonly ctx: WhirlpoolContext, readonly fetcher: AccountFetcher) {}
|
||||
|
||||
public async getPool(poolAddress: Address): Promise<Whirlpool> {
|
||||
const account = await this.fetcher.getPool(poolAddress, true);
|
||||
if (!account) {
|
||||
throw new Error(`Unable to fetch Whirlpool at address at ${poolAddress}`);
|
||||
}
|
||||
const tokenInfos = await getTokenInfos(this.fetcher, account);
|
||||
return new WhirlpoolImpl(
|
||||
this.ctx,
|
||||
this.fetcher,
|
||||
AddressUtil.toPubKey(poolAddress),
|
||||
tokenInfos[0],
|
||||
tokenInfos[1],
|
||||
account
|
||||
);
|
||||
}
|
||||
|
||||
public async getPosition(positionAddress: Address): Promise<Position> {
|
||||
const account = await this.fetcher.getPosition(positionAddress, true);
|
||||
if (!account) {
|
||||
throw new Error(`Unable to fetch Position at address at ${positionAddress}`);
|
||||
}
|
||||
return new PositionImpl(this.ctx, this.fetcher, AddressUtil.toPubKey(positionAddress), account);
|
||||
}
|
||||
}
|
||||
|
||||
async function getTokenInfos(fetcher: AccountFetcher, data: WhirlpoolData): Promise<TokenInfo[]> {
|
||||
const mintA = data.tokenMintA;
|
||||
const infoA = await fetcher.getMintInfo(mintA);
|
||||
if (!infoA) {
|
||||
throw new Error(`Unable to fetch MintInfo for mint - ${mintA}`);
|
||||
}
|
||||
const mintB = data.tokenMintB;
|
||||
const infoB = await fetcher.getMintInfo(mintA);
|
||||
if (!infoB) {
|
||||
throw new Error(`Unable to fetch MintInfo for mint - ${mintB}`);
|
||||
}
|
||||
return [
|
||||
{ mint: mintA, ...infoA },
|
||||
{ mint: mintB, ...infoB },
|
||||
];
|
||||
}
|
|
@ -0,0 +1,517 @@
|
|||
import {
|
||||
AddressUtil,
|
||||
deriveATA,
|
||||
Percentage,
|
||||
resolveOrCreateATA,
|
||||
TransactionBuilder,
|
||||
ZERO,
|
||||
} from "@orca-so/common-sdk";
|
||||
import { Address, BN, translateAddress } from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import {
|
||||
IncreaseLiquidityInput,
|
||||
openPositionIx,
|
||||
openPositionWithMetadataIx,
|
||||
initTickArrayIx,
|
||||
increaseLiquidityIx,
|
||||
decreaseLiquidityIx,
|
||||
closePositionIx,
|
||||
swapIx,
|
||||
} from "../instructions";
|
||||
import { MAX_SQRT_PRICE, MIN_SQRT_PRICE, TokenInfo, WhirlpoolData } from "../types/public";
|
||||
import { Whirlpool } from "../whirlpool-client";
|
||||
import { PublicKey, Keypair } from "@solana/web3.js";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { AccountFetcher } from "../network/public";
|
||||
import invariant from "tiny-invariant";
|
||||
import { PDAUtil, PriceMath, TickUtil } from "../utils/public";
|
||||
import { decreaseLiquidityQuoteByLiquidityWithParams, SwapQuote } from "../quotes/public";
|
||||
|
||||
export class WhirlpoolImpl implements Whirlpool {
|
||||
private data: WhirlpoolData;
|
||||
constructor(
|
||||
readonly ctx: WhirlpoolContext,
|
||||
readonly fetcher: AccountFetcher,
|
||||
readonly address: PublicKey,
|
||||
readonly tokenAInfo: TokenInfo,
|
||||
readonly tokenBInfo: TokenInfo,
|
||||
data: WhirlpoolData
|
||||
) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
getData(): WhirlpoolData {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
getTokenAInfo(): TokenInfo {
|
||||
return this.tokenAInfo;
|
||||
}
|
||||
|
||||
getTokenBInfo(): TokenInfo {
|
||||
return this.tokenBInfo;
|
||||
}
|
||||
|
||||
async refreshData() {
|
||||
await this.refresh();
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async openPosition(
|
||||
tickLower: number,
|
||||
tickUpper: number,
|
||||
liquidityInput: IncreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address,
|
||||
funder?: Address
|
||||
) {
|
||||
await this.refresh();
|
||||
return this.getOpenPositionWithOptMetadataTx(
|
||||
tickLower,
|
||||
tickUpper,
|
||||
liquidityInput,
|
||||
!!sourceWallet ? AddressUtil.toPubKey(sourceWallet) : this.ctx.wallet.publicKey,
|
||||
!!positionWallet ? AddressUtil.toPubKey(positionWallet) : this.ctx.wallet.publicKey,
|
||||
!!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey
|
||||
);
|
||||
}
|
||||
|
||||
async openPositionWithMetadata(
|
||||
tickLower: number,
|
||||
tickUpper: number,
|
||||
liquidityInput: IncreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address,
|
||||
funder?: Address
|
||||
) {
|
||||
await this.refresh();
|
||||
return this.getOpenPositionWithOptMetadataTx(
|
||||
tickLower,
|
||||
tickUpper,
|
||||
liquidityInput,
|
||||
!!sourceWallet ? AddressUtil.toPubKey(sourceWallet) : this.ctx.wallet.publicKey,
|
||||
!!positionWallet ? AddressUtil.toPubKey(positionWallet) : this.ctx.wallet.publicKey,
|
||||
!!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
initTickArrayForTicks(ticks: number[], funder?: Address) {
|
||||
const startTicks = ticks.map((tick) => TickUtil.getStartTickIndex(tick, this.data.tickSpacing));
|
||||
const tx = new TransactionBuilder(this.ctx.provider);
|
||||
const initializedArrayTicks: number[] = [];
|
||||
|
||||
startTicks.forEach((startTick) => {
|
||||
if (initializedArrayTicks.includes(startTick)) {
|
||||
return;
|
||||
}
|
||||
initializedArrayTicks.push(startTick);
|
||||
|
||||
const tickArrayPda = PDAUtil.getTickArray(
|
||||
this.ctx.program.programId,
|
||||
this.address,
|
||||
startTick
|
||||
);
|
||||
|
||||
tx.addInstruction(
|
||||
initTickArrayIx(this.ctx.program, {
|
||||
startTick,
|
||||
tickArrayPda,
|
||||
whirlpool: this.address,
|
||||
funder: !!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey,
|
||||
})
|
||||
);
|
||||
});
|
||||
return tx;
|
||||
}
|
||||
|
||||
async closePosition(
|
||||
positionAddress: Address,
|
||||
slippageTolerance: Percentage,
|
||||
destinationWallet?: Address,
|
||||
positionWallet?: Address
|
||||
) {
|
||||
await this.refresh();
|
||||
const positionWalletKey = positionWallet
|
||||
? AddressUtil.toPubKey(positionWallet)
|
||||
: this.ctx.wallet.publicKey;
|
||||
const destinationWalletKey = destinationWallet
|
||||
? AddressUtil.toPubKey(destinationWallet)
|
||||
: this.ctx.wallet.publicKey;
|
||||
return this.getClosePositionIx(
|
||||
AddressUtil.toPubKey(positionAddress),
|
||||
slippageTolerance,
|
||||
destinationWalletKey,
|
||||
positionWalletKey
|
||||
);
|
||||
}
|
||||
|
||||
async swap(quote: SwapQuote, sourceWallet?: Address) {
|
||||
const sourceWalletKey = sourceWallet
|
||||
? AddressUtil.toPubKey(sourceWallet)
|
||||
: this.ctx.wallet.publicKey;
|
||||
return this.getSwapTx(quote, sourceWalletKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a transaction for opening an new position with optional metadata
|
||||
*/
|
||||
async getOpenPositionWithOptMetadataTx(
|
||||
tickLower: number,
|
||||
tickUpper: number,
|
||||
liquidityInput: IncreaseLiquidityInput,
|
||||
sourceWallet: PublicKey,
|
||||
positionWallet: PublicKey,
|
||||
funder: PublicKey,
|
||||
withMetadata: boolean = false
|
||||
): Promise<{ positionMint: PublicKey; tx: TransactionBuilder }> {
|
||||
invariant(TickUtil.checkTickInBounds(tickLower), "tickLower is out of bounds.");
|
||||
invariant(TickUtil.checkTickInBounds(tickUpper), "tickUpper is out of bounds.");
|
||||
|
||||
const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput;
|
||||
|
||||
invariant(liquidity.gt(new u64(0)), "liquidity must be greater than zero");
|
||||
|
||||
const whirlpool = await this.fetcher.getPool(this.address, false);
|
||||
if (!whirlpool) {
|
||||
throw new Error(`Whirlpool not found: ${translateAddress(this.address).toBase58()}`);
|
||||
}
|
||||
|
||||
invariant(
|
||||
TickUtil.isTickInitializable(tickLower, whirlpool.tickSpacing),
|
||||
`lower tick ${tickLower} is not an initializable tick for tick-spacing ${whirlpool.tickSpacing}`
|
||||
);
|
||||
invariant(
|
||||
TickUtil.isTickInitializable(tickUpper, whirlpool.tickSpacing),
|
||||
`upper tick ${tickUpper} is not an initializable tick for tick-spacing ${whirlpool.tickSpacing}`
|
||||
);
|
||||
|
||||
const positionMintKeypair = Keypair.generate();
|
||||
const positionPda = PDAUtil.getPosition(
|
||||
this.ctx.program.programId,
|
||||
positionMintKeypair.publicKey
|
||||
);
|
||||
const metadataPda = PDAUtil.getPositionMetadata(positionMintKeypair.publicKey);
|
||||
const positionTokenAccountAddress = await deriveATA(
|
||||
positionWallet,
|
||||
positionMintKeypair.publicKey
|
||||
);
|
||||
|
||||
const txBuilder = new TransactionBuilder(this.ctx.provider);
|
||||
|
||||
const positionIx = (withMetadata ? openPositionWithMetadataIx : openPositionIx)(
|
||||
this.ctx.program,
|
||||
{
|
||||
funder,
|
||||
owner: positionWallet,
|
||||
positionPda,
|
||||
metadataPda,
|
||||
positionMintAddress: positionMintKeypair.publicKey,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: this.address,
|
||||
tickLowerIndex: tickLower,
|
||||
tickUpperIndex: tickUpper,
|
||||
}
|
||||
);
|
||||
txBuilder.addInstruction(positionIx).addSigner(positionMintKeypair);
|
||||
|
||||
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA(
|
||||
this.ctx.connection,
|
||||
sourceWallet,
|
||||
whirlpool.tokenMintA,
|
||||
() => this.fetcher.getAccountRentExempt(),
|
||||
tokenMaxA
|
||||
);
|
||||
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA(
|
||||
this.ctx.connection,
|
||||
sourceWallet,
|
||||
whirlpool.tokenMintB,
|
||||
() => this.fetcher.getAccountRentExempt(),
|
||||
tokenMaxB
|
||||
);
|
||||
txBuilder.addInstruction(tokenOwnerAccountAIx);
|
||||
txBuilder.addInstruction(tokenOwnerAccountBIx);
|
||||
|
||||
const tickArrayLowerPda = PDAUtil.getTickArrayFromTickIndex(
|
||||
tickLower,
|
||||
this.data.tickSpacing,
|
||||
this.address,
|
||||
this.ctx.program.programId
|
||||
);
|
||||
const tickArrayUpperPda = PDAUtil.getTickArrayFromTickIndex(
|
||||
tickUpper,
|
||||
this.data.tickSpacing,
|
||||
this.address,
|
||||
this.ctx.program.programId
|
||||
);
|
||||
|
||||
const [tickArrayLower, tickArrayUpper] = await this.fetcher.listTickArrays(
|
||||
[tickArrayLowerPda.publicKey, tickArrayUpperPda.publicKey],
|
||||
true
|
||||
);
|
||||
|
||||
invariant(!!tickArrayLower, "tickArray for the tickLower has not been initialized");
|
||||
invariant(!!tickArrayUpper, "tickArray for the tickUpper has not been initialized");
|
||||
|
||||
const liquidityIx = increaseLiquidityIx(this.ctx.program, {
|
||||
liquidityAmount: liquidity,
|
||||
tokenMaxA,
|
||||
tokenMaxB,
|
||||
whirlpool: this.address,
|
||||
positionAuthority: positionWallet,
|
||||
position: positionPda.publicKey,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
tokenOwnerAccountA,
|
||||
tokenOwnerAccountB,
|
||||
tokenVaultA: whirlpool.tokenVaultA,
|
||||
tokenVaultB: whirlpool.tokenVaultB,
|
||||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
});
|
||||
txBuilder.addInstruction(liquidityIx);
|
||||
|
||||
return {
|
||||
positionMint: positionMintKeypair.publicKey,
|
||||
tx: txBuilder,
|
||||
};
|
||||
}
|
||||
|
||||
async getClosePositionIx(
|
||||
positionAddress: PublicKey,
|
||||
slippageTolerance: Percentage,
|
||||
destinationWallet: PublicKey,
|
||||
positionWallet: PublicKey
|
||||
): Promise<TransactionBuilder> {
|
||||
const position = await this.fetcher.getPosition(positionAddress, true);
|
||||
if (!position) {
|
||||
throw new Error(`Position not found: ${positionAddress.toBase58()}`);
|
||||
}
|
||||
const whirlpool = this.data;
|
||||
|
||||
invariant(
|
||||
position.whirlpool.equals(this.address),
|
||||
`Position ${positionAddress.toBase58()} is not a position for Whirlpool ${this.address.toBase58()}`
|
||||
);
|
||||
|
||||
const tickArrayLower = PDAUtil.getTickArrayFromTickIndex(
|
||||
position.tickLowerIndex,
|
||||
whirlpool.tickSpacing,
|
||||
position.whirlpool,
|
||||
this.ctx.program.programId
|
||||
).publicKey;
|
||||
const tickArrayUpper = PDAUtil.getTickArrayFromTickIndex(
|
||||
position.tickUpperIndex,
|
||||
whirlpool.tickSpacing,
|
||||
position.whirlpool,
|
||||
this.ctx.program.programId
|
||||
).publicKey;
|
||||
|
||||
const positionTokenAccount = await deriveATA(positionWallet, position.positionMint);
|
||||
|
||||
const txBuilder = new TransactionBuilder(this.ctx.provider);
|
||||
|
||||
const resolvedAssociatedTokenAddresses: Record<string, PublicKey> = {};
|
||||
const { address: tokenOwnerAccountA, ...createTokenOwnerAccountAIx } = await resolveOrCreateATA(
|
||||
this.ctx.connection,
|
||||
destinationWallet,
|
||||
whirlpool.tokenMintA,
|
||||
() => this.fetcher.getAccountRentExempt()
|
||||
);
|
||||
const { address: tokenOwnerAccountB, ...createTokenOwnerAccountBIx } = await resolveOrCreateATA(
|
||||
this.ctx.connection,
|
||||
destinationWallet,
|
||||
whirlpool.tokenMintB,
|
||||
() => this.fetcher.getAccountRentExempt()
|
||||
);
|
||||
txBuilder.addInstruction(createTokenOwnerAccountAIx).addInstruction(createTokenOwnerAccountBIx);
|
||||
resolvedAssociatedTokenAddresses[whirlpool.tokenMintA.toBase58()] = tokenOwnerAccountA;
|
||||
resolvedAssociatedTokenAddresses[whirlpool.tokenMintB.toBase58()] = tokenOwnerAccountB;
|
||||
|
||||
// TODO: Collect all Fees and rewards for the position.
|
||||
// TODO: Optimize - no need to call updateFee if we call decreaseLiquidity first.
|
||||
// const collectTx = await buildCollectFeesAndRewardsTx(this.dal, {
|
||||
// provider,
|
||||
// positionAddress: positionAddress,
|
||||
// resolvedAssociatedTokenAddresses,
|
||||
// });
|
||||
// txBuilder.addInstruction(collectTx.compressIx(false));
|
||||
|
||||
/* Remove all liquidity remaining in the position */
|
||||
if (position.liquidity.gt(new u64(0))) {
|
||||
const decreaseLiqQuote = decreaseLiquidityQuoteByLiquidityWithParams({
|
||||
liquidity: position.liquidity,
|
||||
slippageTolerance,
|
||||
sqrtPrice: whirlpool.sqrtPrice,
|
||||
tickCurrentIndex: whirlpool.tickCurrentIndex,
|
||||
tickLowerIndex: position.tickLowerIndex,
|
||||
tickUpperIndex: position.tickUpperIndex,
|
||||
});
|
||||
|
||||
const liquidityIx = decreaseLiquidityIx(this.ctx.program, {
|
||||
liquidityAmount: decreaseLiqQuote.liquidityAmount,
|
||||
tokenMinA: decreaseLiqQuote.tokenMinA,
|
||||
tokenMinB: decreaseLiqQuote.tokenMinB,
|
||||
whirlpool: position.whirlpool,
|
||||
positionAuthority: positionWallet,
|
||||
position: positionAddress,
|
||||
positionTokenAccount,
|
||||
tokenOwnerAccountA,
|
||||
tokenOwnerAccountB,
|
||||
tokenVaultA: whirlpool.tokenVaultA,
|
||||
tokenVaultB: whirlpool.tokenVaultB,
|
||||
tickArrayLower,
|
||||
tickArrayUpper,
|
||||
});
|
||||
txBuilder.addInstruction(liquidityIx);
|
||||
}
|
||||
|
||||
/* Close position */
|
||||
const positionIx = closePositionIx(this.ctx.program, {
|
||||
positionAuthority: this.ctx.wallet.publicKey,
|
||||
receiver: this.ctx.wallet.publicKey,
|
||||
positionTokenAccount,
|
||||
position: positionAddress,
|
||||
positionMint: position.positionMint,
|
||||
});
|
||||
txBuilder.addInstruction(positionIx);
|
||||
|
||||
return txBuilder;
|
||||
}
|
||||
|
||||
private async getSwapTx(quote: SwapQuote, wallet: PublicKey): Promise<TransactionBuilder> {
|
||||
const {
|
||||
sqrtPriceLimit,
|
||||
otherAmountThreshold,
|
||||
estimatedAmountIn,
|
||||
estimatedAmountOut,
|
||||
aToB,
|
||||
amountSpecifiedIsInput,
|
||||
} = quote;
|
||||
const whirlpool = this.data;
|
||||
const txBuilder = new TransactionBuilder(this.ctx.provider);
|
||||
|
||||
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA(
|
||||
this.ctx.connection,
|
||||
wallet,
|
||||
whirlpool.tokenMintA,
|
||||
() => this.fetcher.getAccountRentExempt(),
|
||||
aToB ? estimatedAmountIn : ZERO
|
||||
);
|
||||
txBuilder.addInstruction(tokenOwnerAccountAIx);
|
||||
|
||||
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA(
|
||||
this.ctx.connection,
|
||||
wallet,
|
||||
whirlpool.tokenMintB,
|
||||
() => this.fetcher.getAccountRentExempt(),
|
||||
!aToB ? estimatedAmountIn : ZERO
|
||||
);
|
||||
txBuilder.addInstruction(tokenOwnerAccountBIx);
|
||||
|
||||
const targetSqrtPriceLimitX64 = sqrtPriceLimit || this.getDefaultSqrtPriceLimit(aToB);
|
||||
|
||||
const tickArrayAddresses = await this.getTickArrayPublicKeysForSwap(
|
||||
whirlpool.tickCurrentIndex,
|
||||
targetSqrtPriceLimitX64,
|
||||
whirlpool.tickSpacing,
|
||||
this.address,
|
||||
this.ctx.program.programId,
|
||||
aToB
|
||||
);
|
||||
|
||||
const oraclePda = PDAUtil.getOracle(this.ctx.program.programId, this.address);
|
||||
|
||||
txBuilder.addInstruction(
|
||||
swapIx(this.ctx.program, {
|
||||
amount: amountSpecifiedIsInput ? estimatedAmountIn : estimatedAmountOut,
|
||||
otherAmountThreshold,
|
||||
sqrtPriceLimit: targetSqrtPriceLimitX64,
|
||||
amountSpecifiedIsInput,
|
||||
aToB,
|
||||
whirlpool: this.address,
|
||||
tokenAuthority: wallet,
|
||||
tokenOwnerAccountA,
|
||||
tokenVaultA: whirlpool.tokenVaultA,
|
||||
tokenOwnerAccountB,
|
||||
tokenVaultB: whirlpool.tokenVaultB,
|
||||
tickArray0: tickArrayAddresses[0],
|
||||
tickArray1: tickArrayAddresses[1],
|
||||
tickArray2: tickArrayAddresses[2],
|
||||
oracle: oraclePda.publicKey,
|
||||
})
|
||||
);
|
||||
|
||||
return txBuilder;
|
||||
}
|
||||
|
||||
private async getTickArrayPublicKeysForSwap(
|
||||
tickCurrentIndex: number,
|
||||
targetSqrtPriceX64: BN,
|
||||
tickSpacing: number,
|
||||
poolAddress: PublicKey,
|
||||
programId: PublicKey,
|
||||
aToB: boolean
|
||||
): Promise<[PublicKey, PublicKey, PublicKey]> {
|
||||
// TODO: fix directionality
|
||||
const nextInitializableTickIndex = (
|
||||
aToB ? TickUtil.getPrevInitializableTickIndex : TickUtil.getNextInitializableTickIndex
|
||||
)(tickCurrentIndex, tickSpacing);
|
||||
const targetTickIndex = PriceMath.sqrtPriceX64ToTickIndex(targetSqrtPriceX64);
|
||||
|
||||
let currentStartTickIndex = TickUtil.getStartTickIndex(nextInitializableTickIndex, tickSpacing);
|
||||
const targetStartTickIndex = TickUtil.getStartTickIndex(targetTickIndex, tickSpacing);
|
||||
|
||||
const offset = nextInitializableTickIndex < targetTickIndex ? 1 : -1;
|
||||
|
||||
let count = 1;
|
||||
const tickArrayAddresses: [PublicKey, PublicKey, PublicKey] = [
|
||||
PDAUtil.getTickArray(programId, poolAddress, currentStartTickIndex).publicKey,
|
||||
PublicKey.default,
|
||||
PublicKey.default,
|
||||
];
|
||||
|
||||
while (currentStartTickIndex !== targetStartTickIndex && count < 3) {
|
||||
const nextStartTickIndex = TickUtil.getStartTickIndex(
|
||||
nextInitializableTickIndex,
|
||||
tickSpacing,
|
||||
offset * count
|
||||
);
|
||||
const nextTickArrayAddress = PDAUtil.getTickArray(
|
||||
programId,
|
||||
poolAddress,
|
||||
nextStartTickIndex
|
||||
).publicKey;
|
||||
|
||||
const nextTickArray = await this.fetcher.getTickArray(nextTickArrayAddress, false);
|
||||
if (!nextTickArray) {
|
||||
break;
|
||||
}
|
||||
|
||||
tickArrayAddresses[count] = nextTickArrayAddress;
|
||||
count++;
|
||||
currentStartTickIndex = nextStartTickIndex;
|
||||
}
|
||||
|
||||
while (count < 3) {
|
||||
tickArrayAddresses[count] = PDAUtil.getTickArray(
|
||||
programId,
|
||||
poolAddress,
|
||||
currentStartTickIndex
|
||||
).publicKey;
|
||||
count++;
|
||||
}
|
||||
|
||||
return tickArrayAddresses;
|
||||
}
|
||||
|
||||
private getDefaultSqrtPriceLimit(aToB: boolean): BN {
|
||||
return new BN(aToB ? MIN_SQRT_PRICE : MAX_SQRT_PRICE);
|
||||
}
|
||||
|
||||
private async refresh() {
|
||||
const account = await this.fetcher.getPool(this.address, true);
|
||||
if (!!account) {
|
||||
this.data = account;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,17 @@
|
|||
export * from "./client";
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
export * from "./context";
|
||||
export * from "./types/public";
|
||||
export * from "./utils/public";
|
||||
export * from "./network/public";
|
||||
export * from "./quotes/public";
|
||||
export * from "./ix";
|
||||
export * from "./whirlpool-client";
|
||||
|
||||
export * from "./types/public/anchor-types";
|
||||
export * from "./utils/transactions/transactions-builder";
|
||||
|
||||
// Global rules for Decimals
|
||||
// - 40 digits of precision for the largest number
|
||||
// - 20 digits of precision for the smallest number
|
||||
// - Always round towards 0 to mirror smart contract rules
|
||||
Decimal.set({ precision: 40, toExpPos: 40, toExpNeg: -20, rounding: 1 });
|
||||
|
|
|
@ -1,15 +1,48 @@
|
|||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { ClosePositionParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
|
||||
export function buildClosePositionIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to close a position in a Whirlpool.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param receiver - PublicKey for the wallet that will receive the rented lamports.
|
||||
* @param position - PublicKey for the position.
|
||||
* @param positionMint - PublicKey for the mint token for the Position token.
|
||||
* @param positionTokenAccount - The associated token address for the position token in the owners wallet.
|
||||
* @param positionAuthority - Authority that owns the position token.
|
||||
*/
|
||||
export type ClosePositionParams = {
|
||||
receiver: PublicKey;
|
||||
position: PublicKey;
|
||||
positionMint: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Close a position in a Whirlpool. Burns the position token in the owner's wallet.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - ClosePositionParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function closePositionIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ClosePositionParams
|
||||
): Instruction {
|
||||
const { positionAuthority, receiver, position, positionMint, positionTokenAccount } = params;
|
||||
const {
|
||||
positionAuthority,
|
||||
receiver: receiver,
|
||||
position: position,
|
||||
positionMint: positionMint,
|
||||
positionTokenAccount,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.closePosition({
|
||||
const ix = program.instruction.closePosition({
|
||||
accounts: {
|
||||
positionAuthority,
|
||||
receiver,
|
||||
|
|
|
@ -1,12 +1,44 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { CollectFeesParams } from "..";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildCollectFeesIx(
|
||||
context: WhirlpoolContext,
|
||||
params: CollectFeesParams
|
||||
): Instruction {
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Parameters to collect fees from a position.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param position - PublicKey for the position will be opened for.
|
||||
* @param positionTokenAccount - PublicKey for the position token's associated token address.
|
||||
* @param tokenOwnerAccountA - PublicKey for the token A account that will be withdrawed from.
|
||||
* @param tokenOwnerAccountB - PublicKey for the token B account that will be withdrawed from.
|
||||
* @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool.
|
||||
* @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool.
|
||||
* @param positionAuthority - authority that owns the token corresponding to this desired position.
|
||||
*/
|
||||
export type CollectFeesParams = {
|
||||
whirlpool: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect fees accrued for this position.
|
||||
* Call updateFeesAndRewards before this to update the position to the newest accrued values.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - CollectFeesParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function collectFeesIx(program: Program<Whirlpool>, params: CollectFeesParams): Instruction {
|
||||
const {
|
||||
whirlpool,
|
||||
positionAuthority,
|
||||
|
@ -18,7 +50,7 @@ export function buildCollectFeesIx(
|
|||
tokenVaultB,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.collectFees({
|
||||
const ix = program.instruction.collectFees({
|
||||
accounts: {
|
||||
whirlpool,
|
||||
positionAuthority,
|
||||
|
|
|
@ -1,10 +1,41 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { CollectProtocolFeesParams } from "..";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildCollectProtocolFeesIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to collect protocol fees for a Whirlpool
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool.
|
||||
* @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool.
|
||||
* @param tokenOwnerAccountA - PublicKey for the associated token account for tokenA in the collection wallet
|
||||
* @param tokenOwnerAccountB - PublicKey for the associated token account for tokenA in the collection wallet
|
||||
* @param collectProtocolFeesAuthority - assigned authority in the WhirlpoolsConfig that can collect protocol fees
|
||||
*/
|
||||
export type CollectProtocolFeesParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
whirlpool: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect protocol fees accrued in this Whirlpool.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - CollectProtocolFeesParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function collectProtocolFeesIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: CollectProtocolFeesParams
|
||||
): Instruction {
|
||||
const {
|
||||
|
@ -13,11 +44,11 @@ export function buildCollectProtocolFeesIx(
|
|||
collectProtocolFeesAuthority,
|
||||
tokenVaultA,
|
||||
tokenVaultB,
|
||||
tokenDestinationA,
|
||||
tokenDestinationB,
|
||||
tokenOwnerAccountA: tokenDestinationA,
|
||||
tokenOwnerAccountB: tokenDestinationB,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.collectProtocolFees({
|
||||
const ix = program.instruction.collectProtocolFees({
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
whirlpool,
|
||||
|
|
|
@ -1,10 +1,42 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { CollectRewardParams } from "..";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildCollectRewardIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to collect rewards from a reward index in a position.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param position - PublicKey for the position will be opened for.
|
||||
* @param positionTokenAccount - PublicKey for the position token's associated token address.
|
||||
* @param rewardIndex - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS).
|
||||
* @param rewardOwnerAccount - PublicKey for the reward token account that the reward will deposit into.
|
||||
* @param rewardVault - PublicKey of the vault account that reward will be withdrawn from.
|
||||
* @param positionAuthority - authority that owns the token corresponding to this desired position.
|
||||
*/
|
||||
export type CollectRewardParams = {
|
||||
whirlpool: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
rewardIndex: number;
|
||||
rewardOwnerAccount: PublicKey;
|
||||
rewardVault: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect rewards accrued for this reward index in a position.
|
||||
* Call updateFeesAndRewards before this to update the position to the newest accrued values.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - CollectRewardParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function collectRewardIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: CollectRewardParams
|
||||
): Instruction {
|
||||
const {
|
||||
|
@ -17,7 +49,7 @@ export function buildCollectRewardIx(
|
|||
rewardIndex,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.collectReward(rewardIndex, {
|
||||
const ix = program.instruction.collectReward(rewardIndex, {
|
||||
accounts: {
|
||||
whirlpool,
|
||||
positionAuthority,
|
||||
|
|
|
@ -1,10 +1,65 @@
|
|||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { DecreaseLiquidityParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
|
||||
export function buildDecreaseLiquidityIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to remove liquidity from a position.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param liquidityAmount - The total amount of Liquidity the user is withdrawing
|
||||
* @param tokenMinA - The minimum amount of token A to remove from the position.
|
||||
* @param tokenMinB - The minimum amount of token B to remove from the position.
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param position - PublicKey for the position will be opened for.
|
||||
* @param positionTokenAccount - PublicKey for the position token's associated token address.
|
||||
* @param tokenOwnerAccountA - PublicKey for the token A account that will be withdrawed from.
|
||||
* @param tokenOwnerAccountB - PublicKey for the token B account that will be withdrawed from.
|
||||
* @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool.
|
||||
* @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool.
|
||||
* @param tickArrayLower - PublicKey for the tick-array account that hosts the tick at the lower tick index.
|
||||
* @param tickArrayUpper - PublicKey for the tick-array account that hosts the tick at the upper tick index.
|
||||
* @param positionAuthority - authority that owns the token corresponding to this desired position.
|
||||
*/
|
||||
export type DecreaseLiquidityParams = {
|
||||
whirlpool: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
tickArrayLower: PublicKey;
|
||||
tickArrayUpper: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
} & DecreaseLiquidityInput;
|
||||
|
||||
/**
|
||||
* @category Instruction Types
|
||||
*/
|
||||
export type DecreaseLiquidityInput = {
|
||||
tokenMinA: BN;
|
||||
tokenMinB: BN;
|
||||
liquidityAmount: BN;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove liquidity to a position in the Whirlpool.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `LiquidityZero` - Provided liquidity amount is zero.
|
||||
* - `LiquidityTooHigh` - Provided liquidity exceeds u128::max.
|
||||
* - `TokenMinSubceeded` - The required token to perform this operation subceeds the user defined amount.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - DecreaseLiquidityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function decreaseLiquidityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: DecreaseLiquidityParams
|
||||
): Instruction {
|
||||
const {
|
||||
|
@ -23,7 +78,7 @@ export function buildDecreaseLiquidityIx(
|
|||
tickArrayUpper,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.decreaseLiquidity(liquidityAmount, tokenMinA, tokenMinB, {
|
||||
const ix = program.instruction.decreaseLiquidity(liquidityAmount, tokenMinA, tokenMinB, {
|
||||
accounts: {
|
||||
whirlpool,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
|
|
|
@ -1,10 +1,73 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { IncreaseLiquidityParams } from "..";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildIncreaseLiquidityIx(
|
||||
context: WhirlpoolContext,
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Parameters to increase liquidity for a position.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param liquidityAmount - The total amount of Liquidity the user is willing to deposit.
|
||||
* @param tokenMaxA - The maximum amount of token A to add to the position.
|
||||
* @param tokenMaxB - The maximum amount of token B to add to the position.
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param position - PublicKey for the position will be opened for.
|
||||
* @param positionTokenAccount - PublicKey for the position token's associated token address.
|
||||
* @param tokenOwnerAccountA - PublicKey for the token A account that will be withdrawed from.
|
||||
* @param tokenOwnerAccountB - PublicKey for the token B account that will be withdrawed from.
|
||||
* @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool.
|
||||
* @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool.
|
||||
* @param tickArrayLower - PublicKey for the tick-array account that hosts the tick at the lower tick index.
|
||||
* @param tickArrayUpper - PublicKey for the tick-array account that hosts the tick at the upper tick index.
|
||||
* @param positionAuthority - authority that owns the token corresponding to this desired position.
|
||||
*/
|
||||
export type IncreaseLiquidityParams = {
|
||||
whirlpool: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
tickArrayLower: PublicKey;
|
||||
tickArrayUpper: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
} & IncreaseLiquidityInput;
|
||||
|
||||
/**
|
||||
* Input parameters to deposit liquidity into a position.
|
||||
*
|
||||
* This type is usually generated by a quote class to estimate the amount of tokens required to
|
||||
* deposit a certain amount of liquidity into a position.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param tokenMaxA - the maximum amount of tokenA allowed to withdraw from the source wallet.
|
||||
* @param tokenMaxB - the maximum amount of tokenB allowed to withdraw from the source wallet.
|
||||
* @param liquidityAmount - the desired amount of liquidity to deposit into the position/
|
||||
*/
|
||||
export type IncreaseLiquidityInput = {
|
||||
tokenMaxA: u64;
|
||||
tokenMaxB: u64;
|
||||
liquidityAmount: u64;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add liquidity to a position in the Whirlpool.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `LiquidityZero` - Provided liquidity amount is zero.
|
||||
* `LiquidityTooHigh` - Provided liquidity exceeds u128::max.
|
||||
* `TokenMaxExceeded` - The required token to perform this operation exceeds the user defined amount.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - IncreaseLiquidityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function increaseLiquidityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: IncreaseLiquidityParams
|
||||
): Instruction {
|
||||
const {
|
||||
|
@ -23,7 +86,7 @@ export function buildIncreaseLiquidityIx(
|
|||
tickArrayUpper,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.increaseLiquidity(liquidityAmount, tokenMaxA, tokenMaxB, {
|
||||
const ix = program.instruction.increaseLiquidity(liquidityAmount, tokenMaxA, tokenMaxB, {
|
||||
accounts: {
|
||||
whirlpool,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
export * from "./close-position-ix";
|
||||
export * from "./collect-fees-ix";
|
||||
export * from "./collect-protocol-fees-ix";
|
||||
export * from "./collect-reward-ix";
|
||||
export * from "./decrease-liquidity-ix";
|
||||
export * from "./increase-liquidity-ix";
|
||||
export * from "./initialize-config-ix";
|
||||
export * from "./initialize-fee-tier-ix";
|
||||
export * from "./initialize-pool-ix";
|
||||
export * from "./initialize-reward-ix";
|
||||
export * from "./initialize-tick-array-ix";
|
||||
export * from "./open-position-ix";
|
||||
export * from "./set-collect-protocol-fees-authority-ix";
|
||||
export * from "./set-default-fee-rate-ix";
|
||||
export * from "./set-default-protocol-fee-rate-ix";
|
||||
export * from "./set-fee-authority-ix";
|
||||
export * from "./set-fee-rate-ix";
|
||||
export * from "./set-protocol-fee-rate-ix";
|
||||
export * from "./set-reward-authority-by-super-authority-ix";
|
||||
export * from "./set-reward-authority-ix";
|
||||
export * from "./set-reward-emissions-ix";
|
||||
export * from "./set-reward-emissions-super-authority-ix";
|
||||
export * from "./swap-ix";
|
||||
export * from "./update-fees-and-rewards-ix";
|
|
@ -1,10 +1,40 @@
|
|||
import { SystemProgram } from "@solana/web3.js";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { InitConfigParams } from "../types/public/ix-types";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { SystemProgram, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
|
||||
export function buildInitializeConfigIx(
|
||||
context: WhirlpoolContext,
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Parameters to initialize a WhirlpoolsConfig account.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfigKeypair - Generated keypair for the WhirlpoolsConfig.
|
||||
* @param feeAuthority - Authority authorized to initialize fee-tiers and set customs fees.
|
||||
* @param collect_protocol_fees_authority - Authority authorized to collect protocol fees.
|
||||
* @param rewardEmissionsSuperAuthority - Authority authorized to set reward authorities in pools.
|
||||
* @param defaultProtocolFeeRate - The default protocol fee rate. Stored as a basis point of the total fees collected by feeRate.
|
||||
* @param funder - The account that would fund the creation of this account
|
||||
*/
|
||||
export type InitConfigParams = {
|
||||
whirlpoolsConfigKeypair: Keypair;
|
||||
feeAuthority: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
defaultProtocolFeeRate: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a WhirlpoolsConfig account that hosts info & authorities
|
||||
* required to govern a set of Whirlpools.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - InitConfigParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function initializeConfigIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: InitConfigParams
|
||||
): Instruction {
|
||||
const {
|
||||
|
@ -15,14 +45,14 @@ export function buildInitializeConfigIx(
|
|||
funder,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.initializeConfig(
|
||||
const ix = program.instruction.initializeConfig(
|
||||
feeAuthority,
|
||||
collectProtocolFeesAuthority,
|
||||
rewardEmissionsSuperAuthority,
|
||||
defaultProtocolFeeRate,
|
||||
{
|
||||
accounts: {
|
||||
config: params.whirlpoolConfigKeypair.publicKey,
|
||||
config: params.whirlpoolsConfigKeypair.publicKey,
|
||||
funder,
|
||||
systemProgram: SystemProgram.programId,
|
||||
},
|
||||
|
@ -32,6 +62,6 @@ export function buildInitializeConfigIx(
|
|||
return {
|
||||
instructions: [ix],
|
||||
cleanupInstructions: [],
|
||||
signers: [params.whirlpoolConfigKeypair],
|
||||
signers: [params.whirlpoolsConfigKeypair],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import { SystemProgram, PublicKey } from "@solana/web3.js";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { PDA } from "@orca-so/common-sdk";
|
||||
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Parameters to initialize a FeeTier account.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfig - PublicKey for the whirlpool config space that the fee-tier will be initialized for.
|
||||
* @param feeTierPda - PDA for the fee-tier account that will be initialized
|
||||
* @param tickSpacing - The tick spacing this fee tier recommends its default fee rate for.
|
||||
* @param defaultFeeRate - The default fee rate for this fee-tier. Stored as a hundredths of a basis point.
|
||||
* @param feeAuthority - Authority authorized to initialize fee-tiers and set customs fees.
|
||||
* @param funder - The account that would fund the creation of this account
|
||||
*/
|
||||
export type InitFeeTierParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeTierPda: PDA;
|
||||
tickSpacing: number;
|
||||
defaultFeeRate: number;
|
||||
feeAuthority: PublicKey;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a fee tier account usable by Whirlpools in this WhirlpoolsConfig space.
|
||||
*
|
||||
* Special Errors
|
||||
* `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - InitFeeTierParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function initializeFeeTierIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: InitFeeTierParams
|
||||
): Instruction {
|
||||
const { feeTierPda, whirlpoolsConfig, tickSpacing, feeAuthority, defaultFeeRate, funder } =
|
||||
params;
|
||||
|
||||
const ix = program.instruction.initializeFeeTier(tickSpacing, defaultFeeRate, {
|
||||
accounts: {
|
||||
config: whirlpoolsConfig,
|
||||
feeTier: feeTierPda.publicKey,
|
||||
feeAuthority,
|
||||
funder,
|
||||
systemProgram: SystemProgram.programId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
instructions: [ix],
|
||||
cleanupInstructions: [],
|
||||
signers: [],
|
||||
};
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import { SystemProgram } from "@solana/web3.js";
|
||||
import { getFeeTierPda } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { InitFeeTierParams } from "../types/public/ix-types";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
|
||||
export function buildInitializeFeeTier(
|
||||
context: WhirlpoolContext,
|
||||
params: InitFeeTierParams
|
||||
): Instruction {
|
||||
const { feeTierPda, whirlpoolConfigKey, tickSpacing, feeAuthority, defaultFeeRate, funder } =
|
||||
params;
|
||||
|
||||
const ix = context.program.instruction.initializeFeeTier(tickSpacing, defaultFeeRate, {
|
||||
accounts: {
|
||||
config: whirlpoolConfigKey,
|
||||
feeTier: feeTierPda.publicKey,
|
||||
feeAuthority,
|
||||
funder,
|
||||
systemProgram: SystemProgram.programId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
instructions: [ix],
|
||||
cleanupInstructions: [],
|
||||
signers: [],
|
||||
};
|
||||
}
|
|
@ -1,18 +1,58 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { InitPoolParams } from "..";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { SystemProgram, SYSVAR_RENT_PUBKEY, PublicKey, Keypair } from "@solana/web3.js";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { WhirlpoolBumpsData } from "../types/public/anchor-types";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { PDA } from "@orca-so/common-sdk";
|
||||
|
||||
export function buildInitPoolIx(context: WhirlpoolContext, params: InitPoolParams): Instruction {
|
||||
const program = context.program;
|
||||
/**
|
||||
* Parameters to initialize a Whirlpool account.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param initSqrtPrice - The desired initial sqrt-price for this pool
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param whirlpoolPda - PDA for the whirlpool account that would be initialized
|
||||
* @param tokenMintA - Mint public key for token A
|
||||
* @param tokenMintB - Mint public key for token B
|
||||
* @param tokenVaultAKeypair - Keypair of the token A vault for this pool
|
||||
* @param tokenVaultBKeypair - Keypair of the token B vault for this pool
|
||||
* @param feeTierKey - PublicKey of the fee-tier account that this pool would use for the fee-rate
|
||||
* @param tickSpacing - The desired tick spacing for this pool.
|
||||
* @param funder - The account that would fund the creation of this account
|
||||
*/
|
||||
export type InitPoolParams = {
|
||||
initSqrtPrice: BN;
|
||||
whirlpoolsConfig: PublicKey;
|
||||
whirlpoolPda: PDA;
|
||||
tokenMintA: PublicKey;
|
||||
tokenMintB: PublicKey;
|
||||
tokenVaultAKeypair: Keypair;
|
||||
tokenVaultBKeypair: Keypair;
|
||||
feeTierKey: PublicKey;
|
||||
tickSpacing: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a tick_array account to represent a tick-range in a Whirlpool.
|
||||
*
|
||||
* Special Errors
|
||||
* `InvalidTokenMintOrder` - The order of mints have to be ordered by
|
||||
* `SqrtPriceOutOfBounds` - provided initial_sqrt_price is not between 2^-64 to 2^64
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - InitPoolParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function initializePoolIx(program: Program<Whirlpool>, params: InitPoolParams): Instruction {
|
||||
const {
|
||||
initSqrtPrice,
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
whirlpoolConfigKey,
|
||||
whirlpoolsConfig,
|
||||
whirlpoolPda,
|
||||
feeTierKey,
|
||||
tokenVaultAKeypair,
|
||||
|
@ -27,9 +67,9 @@ export function buildInitPoolIx(context: WhirlpoolContext, params: InitPoolParam
|
|||
|
||||
const ix = program.instruction.initializePool(whirlpoolBumps, tickSpacing, initSqrtPrice, {
|
||||
accounts: {
|
||||
whirlpoolsConfig: whirlpoolConfigKey,
|
||||
tokenMintA: tokenMintA,
|
||||
tokenMintB: tokenMintB,
|
||||
whirlpoolsConfig,
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
funder,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
|
|
|
@ -1,18 +1,52 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { SystemProgram } from "@solana/web3.js";
|
||||
import { InitializeRewardParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { SystemProgram, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
|
||||
export function buildInitializeRewardIx(
|
||||
context: WhirlpoolContext,
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Parameters to initialize a rewards for a Whirlpool
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool config space that the fee-tier will be initialized for.
|
||||
* @param rewardIndex - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS).
|
||||
* @param rewardMint - PublicKey for the reward mint that we'd use for the reward index.
|
||||
* @param rewardVaultKeypair - Keypair of the vault for this reward index.
|
||||
* @param rewardAuthority - Assigned authority by the reward_super_authority for the specified reward-index in this Whirlpool
|
||||
* @param funder - The account that would fund the creation of this account
|
||||
*/
|
||||
export type InitializeRewardParams = {
|
||||
whirlpool: PublicKey;
|
||||
rewardIndex: number;
|
||||
rewardMint: PublicKey;
|
||||
rewardVaultKeypair: Keypair;
|
||||
rewardAuthority: PublicKey;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize reward for a Whirlpool. A pool can only support up to a set number of rewards.
|
||||
* The initial emissionsPerSecond is set to 0.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS, or all reward slots for this pool has been initialized.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - InitializeRewardParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function initializeRewardIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: InitializeRewardParams
|
||||
): Instruction {
|
||||
const { rewardAuthority, funder, whirlpool, rewardMint, rewardVaultKeypair, rewardIndex } =
|
||||
params;
|
||||
|
||||
const ix = context.program.instruction.initializeReward(rewardIndex, {
|
||||
const ix = program.instruction.initializeReward(rewardIndex, {
|
||||
accounts: {
|
||||
rewardAuthority,
|
||||
funder,
|
||||
|
|
|
@ -1,14 +1,41 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { InitTickArrayParams } from "..";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { PDA } from "@orca-so/common-sdk";
|
||||
|
||||
export function buildInitTickArrayIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to initialize a TickArray account.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool that the initialized tick-array will host ticks for.
|
||||
* @param tickArrayPda - PDA for the tick array account that will be initialized
|
||||
* @param startTick - The starting tick index for this tick-array. Has to be a multiple of TickArray size & the tick spacing of this pool.
|
||||
* @param funder - The account that would fund the creation of this account
|
||||
*/
|
||||
export type InitTickArrayParams = {
|
||||
whirlpool: PublicKey;
|
||||
tickArrayPda: PDA;
|
||||
startTick: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a TickArray account.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `InvalidStartTick` - if the provided start tick is out of bounds or is not a multiple of TICK_ARRAY_SIZE * tick spacing.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - InitTickArrayParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function initTickArrayIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: InitTickArrayParams
|
||||
): Instruction {
|
||||
const program = context.program;
|
||||
|
||||
const { whirlpool, funder, tickArrayPda } = params;
|
||||
|
||||
const ix = program.instruction.initializeTickArray(params.startTick, {
|
||||
|
|
|
@ -1,17 +1,52 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { PublicKey, SystemProgram } from "@solana/web3.js";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { OpenPositionParams, PDA } from "..";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { PDA, Instruction } from "@orca-so/common-sdk";
|
||||
import { METADATA_PROGRAM_ADDRESS } from "..";
|
||||
import {
|
||||
OpenPositionBumpsData,
|
||||
OpenPositionWithMetadataBumpsData,
|
||||
} from "../types/public/anchor-types";
|
||||
import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { METADATA_PROGRAM_ADDRESS } from "../utils/public";
|
||||
import { openPositionAccounts } from "../utils/instructions-util";
|
||||
|
||||
export function buildOpenPositionIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to open a position in a Whirlpool.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param ownerKey - PublicKey for the wallet that will host the minted position token.
|
||||
* @param positionPda - PDA for the derived position address.
|
||||
* @param positionMintAddress - PublicKey for the mint token for the Position token.
|
||||
* @param positionTokenAccount - The associated token address for the position token in the owners wallet.
|
||||
* @param tickLowerIndex - The tick specifying the lower end of the position range.
|
||||
* @param tickUpperIndex - The tick specifying the upper end of the position range.
|
||||
* @param funder - The account that would fund the creation of this account
|
||||
*/
|
||||
export type OpenPositionParams = {
|
||||
whirlpool: PublicKey;
|
||||
owner: PublicKey;
|
||||
positionPda: PDA;
|
||||
positionMintAddress: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
tickLowerIndex: number;
|
||||
tickUpperIndex: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a position in a Whirlpool. A unique token will be minted to represent the position in the users wallet.
|
||||
* The position will start off with 0 liquidity.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - OpenPositionParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function openPositionIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: OpenPositionParams
|
||||
): Instruction {
|
||||
const { positionPda, tickLowerIndex, tickUpperIndex } = params;
|
||||
|
@ -20,10 +55,11 @@ export function buildOpenPositionIx(
|
|||
positionBump: positionPda.bump,
|
||||
};
|
||||
|
||||
const ix = context.program.instruction.openPosition(bumps, tickLowerIndex, tickUpperIndex, {
|
||||
const ix = program.instruction.openPosition(bumps, tickLowerIndex, tickUpperIndex, {
|
||||
accounts: openPositionAccounts(params),
|
||||
});
|
||||
|
||||
// TODO: Require Keypair and auto sign this ix
|
||||
return {
|
||||
instructions: [ix],
|
||||
cleanupInstructions: [],
|
||||
|
@ -31,8 +67,21 @@ export function buildOpenPositionIx(
|
|||
};
|
||||
}
|
||||
|
||||
export function buildOpenPositionWithMetadataIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Open a position in a Whirlpool. A unique token will be minted to represent the position
|
||||
* in the users wallet. Additional Metaplex metadata is appended to identify the token.
|
||||
* The position will start off with 0 liquidity.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - OpenPositionParams object and a derived PDA that hosts the position's metadata.
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function openPositionWithMetadataIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: OpenPositionParams & { metadataPda: PDA }
|
||||
): Instruction {
|
||||
const { positionPda, metadataPda, tickLowerIndex, tickUpperIndex } = params;
|
||||
|
@ -42,46 +91,19 @@ export function buildOpenPositionWithMetadataIx(
|
|||
metadataBump: metadataPda.bump,
|
||||
};
|
||||
|
||||
const ix = context.program.instruction.openPositionWithMetadata(
|
||||
bumps,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
{
|
||||
accounts: {
|
||||
...openPositionAccounts(params),
|
||||
positionMetadataAccount: metadataPda.publicKey,
|
||||
metadataProgram: METADATA_PROGRAM_ADDRESS,
|
||||
metadataUpdateAuth: new PublicKey("3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr"),
|
||||
},
|
||||
}
|
||||
);
|
||||
const ix = program.instruction.openPositionWithMetadata(bumps, tickLowerIndex, tickUpperIndex, {
|
||||
accounts: {
|
||||
...openPositionAccounts(params),
|
||||
positionMetadataAccount: metadataPda.publicKey,
|
||||
metadataProgram: METADATA_PROGRAM_ADDRESS,
|
||||
metadataUpdateAuth: new PublicKey("3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr"),
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: Require Keypair and auto sign this ix
|
||||
return {
|
||||
instructions: [ix],
|
||||
cleanupInstructions: [],
|
||||
signers: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function openPositionAccounts(params: OpenPositionParams) {
|
||||
const {
|
||||
funder,
|
||||
ownerKey,
|
||||
positionPda,
|
||||
positionMintAddress,
|
||||
positionTokenAccountAddress,
|
||||
whirlpoolKey,
|
||||
} = params;
|
||||
return {
|
||||
funder: funder,
|
||||
owner: ownerKey,
|
||||
position: positionPda.publicKey,
|
||||
positionMint: positionMintAddress,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolKey,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,15 +1,39 @@
|
|||
import { SetCollectProtocolFeesAuthorityParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetCollectProtocolFeesAuthorityIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to set the collect fee authority in a WhirlpoolsConfig
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param collectProtocolFeesAuthority - The current collectProtocolFeesAuthority in the WhirlpoolsConfig
|
||||
* @param newCollectProtocolFeesAuthority - The new collectProtocolFeesAuthority in the WhirlpoolsConfig
|
||||
*/
|
||||
export type SetCollectProtocolFeesAuthorityParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
newCollectProtocolFeesAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the fee authority to collect protocol fees for a WhirlpoolsConfig.
|
||||
* Only the current collect protocol fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetCollectProtocolFeesAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setCollectProtocolFeesAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetCollectProtocolFeesAuthorityParams
|
||||
): Instruction {
|
||||
const { whirlpoolsConfig, collectProtocolFeesAuthority, newCollectProtocolFeesAuthority } =
|
||||
params;
|
||||
|
||||
const ix = context.program.instruction.setCollectProtocolFeesAuthority({
|
||||
const ix = program.instruction.setCollectProtocolFeesAuthority({
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
collectProtocolFeesAuthority,
|
||||
|
|
|
@ -1,16 +1,47 @@
|
|||
import { getFeeTierPda, SetDefaultFeeRateParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetDefaultFeeRateIx(
|
||||
context: WhirlpoolContext,
|
||||
import { PDAUtil } from "../utils/public";
|
||||
|
||||
/**
|
||||
* Parameters to set the default fee rate for a FeeTier.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this fee-tier is initialized in
|
||||
* @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates.
|
||||
* @param tickSpacing - The tick spacing of the fee-tier that we would like to update.
|
||||
* @param defaultFeeRate - The new default fee rate for this fee-tier. Stored as a hundredths of a basis point.
|
||||
*/
|
||||
export type SetDefaultFeeRateParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
tickSpacing: number;
|
||||
defaultFeeRate: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a fee tier account with a new default fee rate. The new rate will not retroactively update
|
||||
* initialized pools.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetDefaultFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setDefaultFeeRateIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetDefaultFeeRateParams
|
||||
): Instruction {
|
||||
const { whirlpoolsConfig, feeAuthority, tickSpacing, defaultFeeRate } = params;
|
||||
|
||||
const feeTierPda = getFeeTierPda(context.program.programId, whirlpoolsConfig, tickSpacing);
|
||||
const feeTierPda = PDAUtil.getFeeTier(program.programId, whirlpoolsConfig, tickSpacing);
|
||||
|
||||
const ix = context.program.instruction.setDefaultFeeRate(defaultFeeRate, {
|
||||
const ix = program.instruction.setDefaultFeeRate(defaultFeeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
feeTier: feeTierPda.publicKey,
|
||||
|
|
|
@ -1,14 +1,41 @@
|
|||
import { SetDefaultProtocolFeeRateParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetDefaultProtocolFeeRateIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to set the default fee rate for a FeeTier.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates.
|
||||
* @param defaultProtocolFeeRate - The new default protocol fee rate for this config. Stored as a basis point of the total fees collected by feeRate.
|
||||
*/
|
||||
export type SetDefaultProtocolFeeRateParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
defaultProtocolFeeRate: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a WhirlpoolsConfig with a new default protocol fee rate. The new rate will not retroactively update
|
||||
* initialized pools.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetDefaultFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setDefaultProtocolFeeRateIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetDefaultProtocolFeeRateParams
|
||||
): Instruction {
|
||||
const { whirlpoolsConfig, feeAuthority, defaultProtocolFeeRate } = params;
|
||||
|
||||
const ix = context.program.instruction.setDefaultProtocolFeeRate(defaultProtocolFeeRate, {
|
||||
const ix = program.instruction.setDefaultProtocolFeeRate(defaultProtocolFeeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
feeAuthority,
|
||||
|
|
|
@ -1,14 +1,39 @@
|
|||
import { SetFeeAuthorityParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetFeeAuthorityIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to set the fee authority in a WhirlpoolsConfig
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param feeAuthority - The current feeAuthority in the WhirlpoolsConfig
|
||||
* @param newFeeAuthority - The new feeAuthority in the WhirlpoolsConfig
|
||||
*/
|
||||
export type SetFeeAuthorityParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
newFeeAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the fee authority for a WhirlpoolsConfig.
|
||||
* The fee authority can set the fee & protocol fee rate for individual pools or set the default fee rate for newly minted pools.
|
||||
* Only the current fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetFeeAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setFeeAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetFeeAuthorityParams
|
||||
): Instruction {
|
||||
const { whirlpoolsConfig, feeAuthority, newFeeAuthority } = params;
|
||||
|
||||
const ix = context.program.instruction.setFeeAuthority({
|
||||
const ix = program.instruction.setFeeAuthority({
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
feeAuthority,
|
||||
|
|
|
@ -1,14 +1,40 @@
|
|||
import { SetFeeRateParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetFeeRateIx(
|
||||
context: WhirlpoolContext,
|
||||
params: SetFeeRateParams
|
||||
): Instruction {
|
||||
/**
|
||||
* Parameters to set fee rate for a Whirlpool.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool to update. This whirlpool has to be part of the provided WhirlpoolsConfig space.
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates.
|
||||
* @param feeRate - The new fee rate for this fee-tier. Stored as a hundredths of a basis point.
|
||||
*/
|
||||
export type SetFeeRateParams = {
|
||||
whirlpool: PublicKey;
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
feeRate: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the fee rate for a Whirlpool.
|
||||
* Only the current fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `FeeRateMaxExceeded` - If the provided fee_rate exceeds MAX_FEE_RATE.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setFeeRateIx(program: Program<Whirlpool>, params: SetFeeRateParams): Instruction {
|
||||
const { whirlpoolsConfig, whirlpool, feeAuthority, feeRate } = params;
|
||||
|
||||
const ix = context.program.instruction.setFeeRate(feeRate, {
|
||||
const ix = program.instruction.setFeeRate(feeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
whirlpool,
|
||||
|
|
|
@ -1,14 +1,43 @@
|
|||
import { SetProtocolFeeRateParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetProtocolFeeRateIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to set fee rate for a Whirlpool.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool to update. This whirlpool has to be part of the provided WhirlpoolsConfig space.
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates.
|
||||
* @param protocolFeeRate - The new default protocol fee rate for this pool. Stored as a basis point of the total fees collected by feeRate.
|
||||
*/
|
||||
export type SetProtocolFeeRateParams = {
|
||||
whirlpool: PublicKey;
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
protocolFeeRate: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the protocol fee rate for a Whirlpool.
|
||||
* Only the current fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setProtocolFeeRateIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetProtocolFeeRateParams
|
||||
): Instruction {
|
||||
const { whirlpoolsConfig, whirlpool, feeAuthority, protocolFeeRate } = params;
|
||||
|
||||
const ix = context.program.instruction.setProtocolFeeRate(protocolFeeRate, {
|
||||
const ix = program.instruction.setProtocolFeeRate(protocolFeeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
whirlpool,
|
||||
|
|
|
@ -1,9 +1,41 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { SetRewardAuthorityBySuperAuthorityParams } from "..";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetRewardAuthorityBySuperAuthorityIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to update the reward authority at a particular rewardIndex on a Whirlpool.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool to update. This whirlpool has to be part of the provided WhirlpoolsConfig space.
|
||||
* @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in
|
||||
* @param rewardIndex - The reward index that we'd like to update. (0 <= index <= NUM_REWARDS).
|
||||
* @param rewardEmissionsSuperAuthority - The current rewardEmissionsSuperAuthority in the WhirlpoolsConfig
|
||||
* @param newRewardAuthority - The new rewardAuthority in the Whirlpool at the rewardIndex
|
||||
*/
|
||||
export type SetRewardAuthorityBySuperAuthorityParams = {
|
||||
whirlpool: PublicKey;
|
||||
whirlpoolsConfig: PublicKey;
|
||||
rewardIndex: number;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
newRewardAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the whirlpool reward authority at the provided `reward_index`.
|
||||
* Only the current reward super authority has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetRewardAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setRewardAuthorityBySuperAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetRewardAuthorityBySuperAuthorityParams
|
||||
): Instruction {
|
||||
const {
|
||||
|
@ -14,7 +46,7 @@ export function buildSetRewardAuthorityBySuperAuthorityIx(
|
|||
rewardIndex,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.setRewardAuthorityBySuperAuthority(rewardIndex, {
|
||||
const ix = program.instruction.setRewardAuthorityBySuperAuthority(rewardIndex, {
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
whirlpool,
|
||||
|
|
|
@ -1,13 +1,43 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { SetRewardAuthorityParams } from "..";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetRewardAuthorityIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to update the reward authority at a particular rewardIndex on a Whirlpool.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool to update.
|
||||
* @param rewardIndex - The reward index that we'd like to update. (0 <= index <= NUM_REWARDS).
|
||||
* @param rewardAuthority - The current rewardAuthority in the Whirlpool at the rewardIndex
|
||||
* @param newRewardAuthority - The new rewardAuthority in the Whirlpool at the rewardIndex
|
||||
*/
|
||||
export type SetRewardAuthorityParams = {
|
||||
whirlpool: PublicKey;
|
||||
rewardIndex: number;
|
||||
rewardAuthority: PublicKey;
|
||||
newRewardAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the whirlpool reward authority at the provided `reward_index`.
|
||||
* Only the current reward authority for this reward index has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetRewardAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setRewardAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetRewardAuthorityParams
|
||||
): Instruction {
|
||||
const { whirlpool, rewardAuthority, newRewardAuthority, rewardIndex } = params;
|
||||
const ix = context.program.instruction.setRewardAuthority(rewardIndex, {
|
||||
const ix = program.instruction.setRewardAuthority(rewardIndex, {
|
||||
accounts: {
|
||||
whirlpool,
|
||||
rewardAuthority,
|
||||
|
|
|
@ -1,14 +1,54 @@
|
|||
import { SetRewardEmissionsParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
|
||||
export function buildSetRewardEmissionsIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to set rewards emissions for a reward in a Whirlpool
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool which the reward resides in.
|
||||
* @param rewardIndex - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS).
|
||||
* @param rewardVaultKey - PublicKey of the vault for this reward index.
|
||||
* @param rewardAuthority - Assigned authority by the reward_super_authority for the specified reward-index in this Whirlpool
|
||||
* @param emissionsPerSecondX64 - The new emissions per second to set for this reward.
|
||||
*/
|
||||
export type SetRewardEmissionsParams = {
|
||||
whirlpool: PublicKey;
|
||||
rewardIndex: number;
|
||||
rewardVaultKey: PublicKey;
|
||||
rewardAuthority: PublicKey;
|
||||
emissionsPerSecondX64: BN;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the reward emissions for a reward in a Whirlpool.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `RewardVaultAmountInsufficient` - The amount of rewards in the reward vault cannot emit more than a day of desired emissions.
|
||||
* - `InvalidTimestamp` - Provided timestamp is not in order with the previous timestamp.
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetRewardEmissionsParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setRewardEmissionsIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetRewardEmissionsParams
|
||||
): Instruction {
|
||||
const { rewardAuthority, whirlpool, rewardIndex, rewardVault, emissionsPerSecondX64 } = params;
|
||||
const {
|
||||
rewardAuthority,
|
||||
whirlpool,
|
||||
rewardIndex,
|
||||
rewardVaultKey: rewardVault,
|
||||
emissionsPerSecondX64,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.setRewardEmissions(rewardIndex, emissionsPerSecondX64, {
|
||||
const ix = program.instruction.setRewardEmissions(rewardIndex, emissionsPerSecondX64, {
|
||||
accounts: {
|
||||
rewardAuthority,
|
||||
whirlpool,
|
||||
|
|
|
@ -1,15 +1,40 @@
|
|||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { SetRewardEmissionsSuperAuthorityParams } from "..";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildSetRewardEmissionsSuperAuthorityIx(
|
||||
context: WhirlpoolContext,
|
||||
/**
|
||||
* Parameters to set rewards emissions for a reward in a Whirlpool
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpoolsConfig - PublicKey for the WhirlpoolsConfig that we want to update.
|
||||
* @param rewardEmissionsSuperAuthority - Current reward emission super authority in this WhirlpoolsConfig
|
||||
* @param newRewardEmissionsSuperAuthority - New reward emission super authority for this WhirlpoolsConfig
|
||||
*/
|
||||
export type SetRewardEmissionsSuperAuthorityParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
newRewardEmissionsSuperAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the whirlpool reward super authority for a WhirlpoolsConfig
|
||||
* Only the current reward super authority has permission to invoke this instruction.
|
||||
* This instruction will not change the authority on any `WhirlpoolRewardInfo` whirlpool rewards.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SetRewardEmissionsSuperAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function setRewardEmissionsSuperAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: SetRewardEmissionsSuperAuthorityParams
|
||||
): Instruction {
|
||||
const { whirlpoolsConfig, rewardEmissionsSuperAuthority, newRewardEmissionsSuperAuthority } =
|
||||
params;
|
||||
|
||||
const ix = context.program.instruction.setRewardEmissionsSuperAuthority({
|
||||
const ix = program.instruction.setRewardEmissionsSuperAuthority({
|
||||
accounts: {
|
||||
whirlpoolsConfig,
|
||||
rewardEmissionsSuperAuthority: rewardEmissionsSuperAuthority,
|
||||
|
|
|
@ -1,9 +1,86 @@
|
|||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { SwapParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
|
||||
export function buildSwapIx(context: WhirlpoolContext, params: SwapParams): Instruction {
|
||||
/**
|
||||
* Parameters and accounts to swap on a Whirlpool
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A.
|
||||
* @param amountSpecifiedIsInput - Specifies the token the parameter `amount`represents. If true, the amount represents
|
||||
* the input token of the swap.
|
||||
* @param amount - The amount of input or output token to swap from (depending on amountSpecifiedIsInput).
|
||||
* @param otherAmountThreshold - The maximum/minimum of input/output token to swap into (depending on amountSpecifiedIsInput).
|
||||
* @param sqrtPriceLimit - The maximum/minimum price the swap will swap to.
|
||||
* @param tickArray0 - PublicKey of the tick-array where the Whirlpool's currentTickIndex resides in
|
||||
* @param tickArray1 - The next tick-array in the swap direction. If the swap will not reach the next tick-aray, input the same array as tickArray0.
|
||||
* @param tickArray2 - The next tick-array in the swap direction after tickArray2. If the swap will not reach the next tick-aray, input the same array as tickArray1.
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param tokenOwnerAccountA - PublicKey for the associated token account for tokenA in the collection wallet
|
||||
* @param tokenOwnerAccountB - PublicKey for the associated token account for tokenB in the collection wallet
|
||||
* @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool.
|
||||
* @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool.
|
||||
* @param oracle - PublicKey for the oracle account for this Whirlpool.
|
||||
* @param tokenAuthority - authority to withdraw tokens from the input token account
|
||||
*/
|
||||
export type SwapParams = SwapInput & {
|
||||
whirlpool: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
oracle: PublicKey;
|
||||
tokenAuthority: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parameters to swap on a Whirlpool
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A.
|
||||
* @param amountSpecifiedIsInput - Specifies the token the parameter `amount`represents. If true, the amount represents
|
||||
* the input token of the swap.
|
||||
* @param amount - The amount of input or output token to swap from (depending on amountSpecifiedIsInput).
|
||||
* @param otherAmountThreshold - The maximum/minimum of input/output token to swap into (depending on amountSpecifiedIsInput).
|
||||
* @param sqrtPriceLimit - The maximum/minimum price the swap will swap to.
|
||||
* @param tickArray0 - PublicKey of the tick-array where the Whirlpool's currentTickIndex resides in
|
||||
* @param tickArray1 - The next tick-array in the swap direction. If the swap will not reach the next tick-aray, input the same array as tickArray0.
|
||||
* @param tickArray2 - The next tick-array in the swap direction after tickArray2. If the swap will not reach the next tick-aray, input the same array as tickArray1.
|
||||
*/
|
||||
export type SwapInput = {
|
||||
amount: u64;
|
||||
otherAmountThreshold: u64;
|
||||
sqrtPriceLimit: BN;
|
||||
amountSpecifiedIsInput: boolean;
|
||||
aToB: boolean;
|
||||
tickArray0: PublicKey;
|
||||
tickArray1: PublicKey;
|
||||
tickArray2: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform a swap in this Whirlpool
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `ZeroTradableAmount` - User provided parameter `amount` is 0.
|
||||
* - `InvalidSqrtPriceLimitDirection` - User provided parameter `sqrt_price_limit` does not match the direction of the trade.
|
||||
* - `SqrtPriceOutOfBounds` - User provided parameter `sqrt_price_limit` is over Whirlppool's max/min bounds for sqrt-price.
|
||||
* - `InvalidTickArraySequence` - User provided tick-arrays are not in sequential order required to proceed in this trade direction.
|
||||
* - `TickArraySequenceInvalidIndex` - The swap loop attempted to access an invalid array index during the query of the next initialized tick.
|
||||
* - `TickArrayIndexOutofBounds` - The swap loop attempted to access an invalid array index during tick crossing.
|
||||
* - `LiquidityOverflow` - Liquidity value overflowed 128bits during tick crossing.
|
||||
* - `InvalidTickSpacing` - The swap pool was initialized with tick-spacing of 0.
|
||||
*
|
||||
* ### Parameters
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - SwapParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function swapIx(program: Program<Whirlpool>, params: SwapParams): Instruction {
|
||||
const {
|
||||
amount,
|
||||
otherAmountThreshold,
|
||||
|
@ -22,7 +99,7 @@ export function buildSwapIx(context: WhirlpoolContext, params: SwapParams): Inst
|
|||
oracle,
|
||||
} = params;
|
||||
|
||||
const ix = context.program.instruction.swap(
|
||||
const ix = program.instruction.swap(
|
||||
amount,
|
||||
otherAmountThreshold,
|
||||
sqrtPriceLimit,
|
||||
|
|
|
@ -1,14 +1,44 @@
|
|||
import { UpdateFeesAndRewardsParams } from "..";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction } from "../utils/transactions/transactions-builder";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "../artifacts/whirlpool";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export function buildUpdateFeesAndRewardsIx(
|
||||
context: WhirlpoolContext,
|
||||
import { Instruction } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Parameters to update fees and reward values for a position.
|
||||
*
|
||||
* @category Instruction Types
|
||||
* @param whirlpool - PublicKey for the whirlpool that the position will be opened for.
|
||||
* @param position - PublicKey for the position will be opened for.
|
||||
* @param tickArrayLower - PublicKey for the tick-array account that hosts the tick at the lower tick index.
|
||||
* @param tickArrayUpper - PublicKey for the tick-array account that hosts the tick at the upper tick index.
|
||||
*/
|
||||
export type UpdateFeesAndRewardsParams = {
|
||||
whirlpool: PublicKey;
|
||||
position: PublicKey;
|
||||
tickArrayLower: PublicKey;
|
||||
tickArrayUpper: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the accrued fees and rewards for a position.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `TickNotFound` - Provided tick array account does not contain the tick for this position.
|
||||
* `LiquidityZero` - Position has zero liquidity and therefore already has the most updated fees and reward values.
|
||||
*
|
||||
* @category Instructions
|
||||
* @param context - Context object containing services required to generate the instruction
|
||||
* @param params - UpdateFeesAndRewardsParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
export function updateFeesAndRewardsIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: UpdateFeesAndRewardsParams
|
||||
): Instruction {
|
||||
const { whirlpool, position, tickArrayLower, tickArrayUpper } = params;
|
||||
|
||||
const ix = context.program.instruction.updateFeesAndRewards({
|
||||
const ix = program.instruction.updateFeesAndRewards({
|
||||
accounts: {
|
||||
whirlpool,
|
||||
position,
|
||||
|
|
|
@ -0,0 +1,418 @@
|
|||
import { PDA } from "@orca-so/common-sdk";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Whirlpool } from "./artifacts/whirlpool";
|
||||
import * as ix from "./instructions";
|
||||
|
||||
/**
|
||||
* Instruction set for the Whirlpools program.
|
||||
*
|
||||
* @category Core
|
||||
*/
|
||||
export class WhirlpoolIx {
|
||||
/**
|
||||
* Initializes a WhirlpoolsConfig account that hosts info & authorities
|
||||
* required to govern a set of Whirlpools.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - InitConfigParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static initializeConfigIx(program: Program<Whirlpool>, params: ix.InitConfigParams) {
|
||||
return ix.initializeConfigIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a fee tier account usable by Whirlpools in this WhirlpoolsConfig space.
|
||||
*
|
||||
* Special Errors
|
||||
* `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - InitFeeTierParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static initializeFeeTierIx(program: Program<Whirlpool>, params: ix.InitFeeTierParams) {
|
||||
return ix.initializeFeeTierIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a tick_array account to represent a tick-range in a Whirlpool.
|
||||
*
|
||||
* Special Errors
|
||||
* `InvalidTokenMintOrder` - The order of mints have to be ordered by
|
||||
* `SqrtPriceOutOfBounds` - provided initial_sqrt_price is not between 2^-64 to 2^64
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - InitPoolParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static initializePoolIx(program: Program<Whirlpool>, params: ix.InitPoolParams) {
|
||||
return ix.initializePoolIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize reward for a Whirlpool. A pool can only support up to a set number of rewards.
|
||||
* The initial emissionsPerSecond is set to 0.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS, or all reward slots for this pool has been initialized.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - InitializeRewardParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static initializeRewardIx(program: Program<Whirlpool>, params: ix.InitializeRewardParams) {
|
||||
return ix.initializeRewardIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a TickArray account.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `InvalidStartTick` - if the provided start tick is out of bounds or is not a multiple of TICK_ARRAY_SIZE * tick spacing.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - InitTickArrayParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static initTickArrayIx(program: Program<Whirlpool>, params: ix.InitTickArrayParams) {
|
||||
return ix.initTickArrayIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a position in a Whirlpool. A unique token will be minted to represent the position in the users wallet.
|
||||
* The position will start off with 0 liquidity.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool.
|
||||
*
|
||||
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - OpenPositionParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static openPositionIx(program: Program<Whirlpool>, params: ix.OpenPositionParams) {
|
||||
return ix.openPositionIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a position in a Whirlpool. A unique token will be minted to represent the position
|
||||
* in the users wallet. Additional Metaplex metadata is appended to identify the token.
|
||||
* The position will start off with 0 liquidity.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool.
|
||||
*
|
||||
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - OpenPositionParams object and a derived PDA that hosts the position's metadata.
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static openPositionWithMetadataIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.OpenPositionParams & { metadataPda: PDA }
|
||||
) {
|
||||
return ix.openPositionWithMetadataIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add liquidity to a position in the Whirlpool. This call also updates the position's accrued fees and rewards.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `LiquidityZero` - Provided liquidity amount is zero.
|
||||
* `LiquidityTooHigh` - Provided liquidity exceeds u128::max.
|
||||
* `TokenMaxExceeded` - The required token to perform this operation exceeds the user defined amount.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - IncreaseLiquidityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static increaseLiquidityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.IncreaseLiquidityParams
|
||||
) {
|
||||
return ix.increaseLiquidityIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove liquidity to a position in the Whirlpool. This call also updates the position's accrued fees and rewards.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `LiquidityZero` - Provided liquidity amount is zero.
|
||||
* - `LiquidityTooHigh` - Provided liquidity exceeds u128::max.
|
||||
* - `TokenMinSubceeded` - The required token to perform this operation subceeds the user defined amount.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - DecreaseLiquidityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static decreaseLiquidityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.DecreaseLiquidityParams
|
||||
) {
|
||||
return ix.decreaseLiquidityIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a position in a Whirlpool. Burns the position token in the owner's wallet.
|
||||
*
|
||||
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - ClosePositionParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static closePositionIx(program: Program<Whirlpool>, params: ix.ClosePositionParams) {
|
||||
return ix.closePositionIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a swap in this Whirlpool
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `ZeroTradableAmount` - User provided parameter `amount` is 0.
|
||||
* - `InvalidSqrtPriceLimitDirection` - User provided parameter `sqrt_price_limit` does not match the direction of the trade.
|
||||
* - `SqrtPriceOutOfBounds` - User provided parameter `sqrt_price_limit` is over Whirlppool's max/min bounds for sqrt-price.
|
||||
* - `InvalidTickArraySequence` - User provided tick-arrays are not in sequential order required to proceed in this trade direction.
|
||||
* - `TickArraySequenceInvalidIndex` - The swap loop attempted to access an invalid array index during the query of the next initialized tick.
|
||||
* - `TickArrayIndexOutofBounds` - The swap loop attempted to access an invalid array index during tick crossing.
|
||||
* - `LiquidityOverflow` - Liquidity value overflowed 128bits during tick crossing.
|
||||
* - `InvalidTickSpacing` - The swap pool was initialized with tick-spacing of 0.
|
||||
*
|
||||
* ### Parameters
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SwapParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static swapIx(program: Program<Whirlpool>, params: ix.SwapParams) {
|
||||
return ix.swapIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the accrued fees and rewards for a position.
|
||||
*
|
||||
* #### Special Errors
|
||||
* `TickNotFound` - Provided tick array account does not contain the tick for this position.
|
||||
* `LiquidityZero` - Position has zero liquidity and therefore already has the most updated fees and reward values.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - UpdateFeesAndRewardsParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static updateFeesAndRewardsIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.UpdateFeesAndRewardsParams
|
||||
) {
|
||||
return ix.updateFeesAndRewardsIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect fees accrued for this position.
|
||||
* Call updateFeesAndRewards before this to update the position to the newest accrued values.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - CollectFeesParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static collectFeesIx(program: Program<Whirlpool>, params: ix.CollectFeesParams) {
|
||||
return ix.collectFeesIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect protocol fees accrued in this Whirlpool.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - CollectProtocolFeesParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static collectProtocolFeesIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.CollectProtocolFeesParams
|
||||
) {
|
||||
return ix.collectProtocolFeesIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect rewards accrued for this reward index in a position.
|
||||
* Call updateFeesAndRewards before this to update the position to the newest accrued values.
|
||||
*
|
||||
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - CollectRewardParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static collectRewardIx(program: Program<Whirlpool>, params: ix.CollectRewardParams) {
|
||||
return ix.collectRewardIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fee authority to collect protocol fees for a WhirlpoolsConfig.
|
||||
* Only the current collect protocol fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetCollectProtocolFeesAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setCollectProtocolFeesAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetCollectProtocolFeesAuthorityParams
|
||||
) {
|
||||
return ix.setCollectProtocolFeesAuthorityIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a fee tier account with a new default fee rate. The new rate will not retroactively update
|
||||
* initialized pools.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetDefaultFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setDefaultFeeRateIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetDefaultFeeRateParams
|
||||
) {
|
||||
return ix.setDefaultFeeRateIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a WhirlpoolsConfig with a new default protocol fee rate. The new rate will not retroactively update
|
||||
* initialized pools.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetDefaultFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setDefaultProtocolFeeRateIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetDefaultProtocolFeeRateParams
|
||||
) {
|
||||
return ix.setDefaultProtocolFeeRateIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fee authority for a WhirlpoolsConfig.
|
||||
* The fee authority can set the fee & protocol fee rate for individual pools or set the default fee rate for newly minted pools.
|
||||
* Only the current fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetFeeAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setFeeAuthorityIx(program: Program<Whirlpool>, params: ix.SetFeeAuthorityParams) {
|
||||
return ix.setFeeAuthorityIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fee rate for a Whirlpool.
|
||||
* Only the current fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `FeeRateMaxExceeded` - If the provided fee_rate exceeds MAX_FEE_RATE.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setFeeRateIx(program: Program<Whirlpool>, params: ix.SetFeeRateParams) {
|
||||
return ix.setFeeRateIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the protocol fee rate for a Whirlpool.
|
||||
* Only the current fee authority has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetFeeRateParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setProtocolFeeRateIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetProtocolFeeRateParams
|
||||
) {
|
||||
return ix.setProtocolFeeRateIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whirlpool reward authority at the provided `reward_index`.
|
||||
* Only the current reward super authority has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetRewardAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setRewardAuthorityBySuperAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetRewardAuthorityBySuperAuthorityParams
|
||||
) {
|
||||
return ix.setRewardAuthorityBySuperAuthorityIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whirlpool reward authority at the provided `reward_index`.
|
||||
* Only the current reward authority for this reward index has permission to invoke this instruction.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetRewardAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setRewardAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetRewardAuthorityParams
|
||||
) {
|
||||
return ix.setRewardAuthorityIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reward emissions for a reward in a Whirlpool.
|
||||
*
|
||||
* #### Special Errors
|
||||
* - `RewardVaultAmountInsufficient` - The amount of rewards in the reward vault cannot emit more than a day of desired emissions.
|
||||
* - `InvalidTimestamp` - Provided timestamp is not in order with the previous timestamp.
|
||||
* - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool,
|
||||
* or exceeds NUM_REWARDS.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetRewardEmissionsParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setRewardEmissionsIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetRewardEmissionsParams
|
||||
) {
|
||||
return ix.setRewardEmissionsIx(program, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whirlpool reward super authority for a WhirlpoolsConfig
|
||||
* Only the current reward super authority has permission to invoke this instruction.
|
||||
* This instruction will not change the authority on any `WhirlpoolRewardInfo` whirlpool rewards.
|
||||
*
|
||||
* @param program - program object containing services required to generate the instruction
|
||||
* @param params - SetRewardEmissionsSuperAuthorityParams object
|
||||
* @returns - Instruction to perform the action.
|
||||
*/
|
||||
public static setRewardEmissionsSuperAuthorityIx(
|
||||
program: Program<Whirlpool>,
|
||||
params: ix.SetRewardEmissionsSuperAuthorityParams
|
||||
) {
|
||||
return ix.setRewardEmissionsSuperAuthorityIx(program, params);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import invariant from "tiny-invariant";
|
||||
import { AccountInfo, AccountLayout, MintInfo } from "@solana/spl-token";
|
||||
import {
|
||||
ParsableEntity,
|
||||
ParsableFeeTier,
|
||||
ParsableMintInfo,
|
||||
ParsablePosition,
|
||||
ParsableTickArray,
|
||||
ParsableTokenInfo,
|
||||
ParsableWhirlpool,
|
||||
ParsableWhirlpoolsConfig,
|
||||
} from "./parsing";
|
||||
import { Address } from "@project-serum/anchor";
|
||||
import { PositionData, TickArrayData, WhirlpoolsConfigData, WhirlpoolData } from "../..";
|
||||
import { FeeTierData } from "../../types/public";
|
||||
import { AddressUtil } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Supported accounts
|
||||
*/
|
||||
type CachedValue =
|
||||
| WhirlpoolsConfigData
|
||||
| WhirlpoolData
|
||||
| PositionData
|
||||
| TickArrayData
|
||||
| FeeTierData
|
||||
| AccountInfo
|
||||
| MintInfo;
|
||||
|
||||
/**
|
||||
* Include both the entity (i.e. type) of the stored value, and the value itself
|
||||
*/
|
||||
interface CachedContent<T extends CachedValue> {
|
||||
entity: ParsableEntity<T>;
|
||||
value: CachedValue | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for rpc batch request response
|
||||
*/
|
||||
type GetMultipleAccountsResponse = {
|
||||
error?: string;
|
||||
result?: {
|
||||
value?: ({ data: [string, string] } | null)[];
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Data access layer to access Whirlpool related accounts
|
||||
* Includes internal cache that can be refreshed by the client.
|
||||
*
|
||||
* @category Core
|
||||
*/
|
||||
export class AccountFetcher {
|
||||
private readonly connection: Connection;
|
||||
private readonly _cache: Record<string, CachedContent<CachedValue>> = {};
|
||||
private _accountRentExempt: number | undefined;
|
||||
|
||||
constructor(connection: Connection, cache?: Record<string, CachedContent<CachedValue>>) {
|
||||
this.connection = connection;
|
||||
this._cache = cache ?? {};
|
||||
}
|
||||
|
||||
/*** Public Methods ***/
|
||||
|
||||
/**
|
||||
* Retrieve minimum balance for rent exemption of a Token Account;
|
||||
*
|
||||
* @param refresh force refresh of account rent exemption
|
||||
* @returns minimum balance for rent exemption
|
||||
*/
|
||||
public async getAccountRentExempt(refresh: boolean = false) {
|
||||
// This value should be relatively static or at least not break according to spec
|
||||
// https://docs.solana.com/developing/programming-model/accounts#rent-exemption
|
||||
if (!this._accountRentExempt || refresh) {
|
||||
this._accountRentExempt = await this.connection.getMinimumBalanceForRentExemption(
|
||||
AccountLayout.span
|
||||
);
|
||||
}
|
||||
return this._accountRentExempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached whirlpool account. Fetch from rpc on cache miss.
|
||||
*
|
||||
* @param address whirlpool address
|
||||
* @param refresh force cache refresh
|
||||
* @returns whirlpool account
|
||||
*/
|
||||
public async getPool(address: Address, refresh = false): Promise<WhirlpoolData | null> {
|
||||
return this.get(AddressUtil.toPubKey(address), ParsableWhirlpool, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached position account. Fetch from rpc on cache miss.
|
||||
*
|
||||
* @param address position address
|
||||
* @param refresh force cache refresh
|
||||
* @returns position account
|
||||
*/
|
||||
public async getPosition(address: Address, refresh = false): Promise<PositionData | null> {
|
||||
return this.get(AddressUtil.toPubKey(address), ParsablePosition, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached tick array account. Fetch from rpc on cache miss.
|
||||
*
|
||||
* @param address tick array address
|
||||
* @param refresh force cache refresh
|
||||
* @returns tick array account
|
||||
*/
|
||||
public async getTickArray(address: Address, refresh = false): Promise<TickArrayData | null> {
|
||||
return this.get(AddressUtil.toPubKey(address), ParsableTickArray, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached fee tier account. Fetch from rpc on cache miss.
|
||||
*
|
||||
* @param address fee tier address
|
||||
* @param refresh force cache refresh
|
||||
* @returns fee tier account
|
||||
*/
|
||||
public async getFeeTier(address: Address, refresh = false): Promise<FeeTierData | null> {
|
||||
return this.get(AddressUtil.toPubKey(address), ParsableFeeTier, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached token info account. Fetch from rpc on cache miss.
|
||||
*
|
||||
* @param address token info address
|
||||
* @param refresh force cache refresh
|
||||
* @returns token info account
|
||||
*/
|
||||
public async getTokenInfo(address: Address, refresh = false): Promise<AccountInfo | null> {
|
||||
return this.get(AddressUtil.toPubKey(address), ParsableTokenInfo, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached mint info account. Fetch from rpc on cache miss.
|
||||
*
|
||||
* @param address mint info address
|
||||
* @param refresh force cache refresh
|
||||
* @returns mint info account
|
||||
*/
|
||||
public async getMintInfo(address: Address, refresh = false): Promise<MintInfo | null> {
|
||||
return this.get(AddressUtil.toPubKey(address), ParsableMintInfo, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached whirlpool config account. Fetch from rpc on cache miss.
|
||||
*
|
||||
* @param address whirlpool config address
|
||||
* @param refresh force cache refresh
|
||||
* @returns whirlpool config account
|
||||
*/
|
||||
public async getConfig(address: Address, refresh = false): Promise<WhirlpoolsConfigData | null> {
|
||||
return this.get(AddressUtil.toPubKey(address), ParsableWhirlpoolsConfig, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of cached whirlpool accounts. Fetch from rpc for cache misses.
|
||||
*
|
||||
* @param addresses whirlpool addresses
|
||||
* @param refresh force cache refresh
|
||||
* @returns whirlpool accounts
|
||||
*/
|
||||
public async listPools(
|
||||
addresses: Address[],
|
||||
refresh: boolean
|
||||
): Promise<(WhirlpoolData | null)[]> {
|
||||
return this.list(AddressUtil.toPubKeys(addresses), ParsableWhirlpool, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of cached position accounts. Fetch from rpc for cache misses.
|
||||
*
|
||||
* @param addresses position addresses
|
||||
* @param refresh force cache refresh
|
||||
* @returns position accounts
|
||||
*/
|
||||
public async listPositions(
|
||||
addresses: Address[],
|
||||
refresh: boolean
|
||||
): Promise<(PositionData | null)[]> {
|
||||
return this.list(AddressUtil.toPubKeys(addresses), ParsablePosition, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of cached tick array accounts. Fetch from rpc for cache misses.
|
||||
*
|
||||
* @param addresses tick array addresses
|
||||
* @param refresh force cache refresh
|
||||
* @returns tick array accounts
|
||||
*/
|
||||
public async listTickArrays(
|
||||
addresses: Address[],
|
||||
refresh: boolean
|
||||
): Promise<(TickArrayData | null)[]> {
|
||||
return this.list(AddressUtil.toPubKeys(addresses), ParsableTickArray, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of cached token info accounts. Fetch from rpc for cache misses.
|
||||
*
|
||||
* @param addresses token info addresses
|
||||
* @param refresh force cache refresh
|
||||
* @returns token info accounts
|
||||
*/
|
||||
public async listTokenInfos(
|
||||
addresses: Address[],
|
||||
refresh: boolean
|
||||
): Promise<(AccountInfo | null)[]> {
|
||||
return this.list(AddressUtil.toPubKeys(addresses), ParsableTokenInfo, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of cached mint info accounts. Fetch from rpc for cache misses.
|
||||
*
|
||||
* @param addresses mint info addresses
|
||||
* @param refresh force cache refresh
|
||||
* @returns mint info accounts
|
||||
*/
|
||||
public async listMintInfos(addresses: Address[], refresh: boolean): Promise<(MintInfo | null)[]> {
|
||||
return this.list(AddressUtil.toPubKeys(addresses), ParsableMintInfo, refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cached value of all entities currently in the cache.
|
||||
* Uses batched rpc request for network efficient fetch.
|
||||
*/
|
||||
public async refreshAll(): Promise<void> {
|
||||
const addresses: string[] = Object.keys(this._cache);
|
||||
const data = await this.bulkRequest(addresses);
|
||||
|
||||
for (const [idx, [key, cachedContent]] of Object.entries(this._cache).entries()) {
|
||||
const entity = cachedContent.entity;
|
||||
const value = entity.parse(data[idx]);
|
||||
|
||||
this._cache[key] = { entity, value };
|
||||
}
|
||||
}
|
||||
|
||||
/*** Private Methods ***/
|
||||
|
||||
/**
|
||||
* Retrieve from cache or fetch from rpc, an account
|
||||
*/
|
||||
private async get<T extends CachedValue>(
|
||||
address: PublicKey,
|
||||
entity: ParsableEntity<T>,
|
||||
refresh: boolean
|
||||
): Promise<T | null> {
|
||||
const key = address.toBase58();
|
||||
const cachedValue: CachedValue | null | undefined = this._cache[key]?.value;
|
||||
|
||||
if (cachedValue !== undefined && !refresh) {
|
||||
return cachedValue as T | null;
|
||||
}
|
||||
|
||||
const accountInfo = await this.connection.getAccountInfo(address);
|
||||
const accountData = accountInfo?.data;
|
||||
const value = entity.parse(accountData);
|
||||
this._cache[key] = { entity, value };
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve from cache or fetch from rpc, a list of accounts
|
||||
*/
|
||||
private async list<T extends CachedValue>(
|
||||
addresses: PublicKey[],
|
||||
entity: ParsableEntity<T>,
|
||||
refresh: boolean
|
||||
): Promise<(T | null)[]> {
|
||||
const keys = addresses.map((address) => address.toBase58());
|
||||
const cachedValues: [string, CachedValue | null | undefined][] = keys.map((key) => [
|
||||
key,
|
||||
refresh ? undefined : this._cache[key]?.value,
|
||||
]);
|
||||
|
||||
/* Look for accounts not found in cache */
|
||||
const undefinedAccounts: { cacheIndex: number; key: string }[] = [];
|
||||
cachedValues.forEach(([key, value], cacheIndex) => {
|
||||
if (value === undefined) {
|
||||
undefinedAccounts.push({ cacheIndex, key });
|
||||
}
|
||||
});
|
||||
|
||||
/* Fetch accounts not found in cache */
|
||||
if (undefinedAccounts.length > 0) {
|
||||
const data = await this.bulkRequest(undefinedAccounts.map((account) => account.key));
|
||||
undefinedAccounts.forEach(({ cacheIndex, key }, dataIndex) => {
|
||||
const value = entity.parse(data[dataIndex]);
|
||||
invariant(cachedValues[cacheIndex]?.[1] === undefined, "unexpected non-undefined value");
|
||||
cachedValues[cacheIndex] = [key, value];
|
||||
this._cache[key] = { entity, value };
|
||||
});
|
||||
}
|
||||
|
||||
const result = cachedValues
|
||||
.map(([_, value]) => value)
|
||||
.filter((value): value is T | null => value !== undefined);
|
||||
invariant(result.length === addresses.length, "not enough results fetched");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make batch rpc request
|
||||
*/
|
||||
private async bulkRequest(addresses: string[]): Promise<(Buffer | null)[]> {
|
||||
const responses: Promise<GetMultipleAccountsResponse>[] = [];
|
||||
const chunk = 100; // getMultipleAccounts has limitation of 100 accounts per request
|
||||
|
||||
for (let i = 0; i < addresses.length; i += chunk) {
|
||||
const addressesSubset = addresses.slice(i, i + chunk);
|
||||
const res = (this.connection as any)._rpcRequest("getMultipleAccounts", [
|
||||
addressesSubset,
|
||||
{ commitment: this.connection.commitment },
|
||||
]);
|
||||
responses.push(res);
|
||||
}
|
||||
|
||||
const combinedResult: (Buffer | null)[] = [];
|
||||
|
||||
(await Promise.all(responses)).forEach((res) => {
|
||||
invariant(!res.error, `bulkRequest result error: ${res.error}`);
|
||||
invariant(!!res.result?.value, "bulkRequest no value");
|
||||
|
||||
res.result.value.forEach((account) => {
|
||||
if (!account || account.data[1] !== "base64") {
|
||||
combinedResult.push(null);
|
||||
} else {
|
||||
combinedResult.push(Buffer.from(account.data[0], account.data[1]));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
invariant(combinedResult.length === addresses.length, "bulkRequest not enough results");
|
||||
return combinedResult;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./fetcher";
|
||||
export * from "./parsing";
|
|
@ -0,0 +1,212 @@
|
|||
import { AccountInfo, MintInfo, MintLayout, u64 } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
WhirlpoolsConfigData,
|
||||
WhirlpoolData,
|
||||
PositionData,
|
||||
TickArrayData,
|
||||
AccountName,
|
||||
FeeTierData,
|
||||
} from "../../types/public";
|
||||
import { AccountsCoder, Coder, Idl } from "@project-serum/anchor";
|
||||
import * as WhirlpoolIDL from "../../artifacts/whirlpool.json";
|
||||
import { TokenUtil } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* Static abstract class definition to parse entities.
|
||||
* @category Parsables
|
||||
*/
|
||||
export interface ParsableEntity<T> {
|
||||
/**
|
||||
* Parse account data
|
||||
*
|
||||
* @param accountData Buffer data for the entity
|
||||
* @returns Parsed entity
|
||||
*/
|
||||
parse: (accountData: Buffer | undefined | null) => T | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Parsables
|
||||
*/
|
||||
@staticImplements<ParsableEntity<WhirlpoolsConfigData>>()
|
||||
export class ParsableWhirlpoolsConfig {
|
||||
private constructor() {}
|
||||
|
||||
public static parse(data: Buffer | undefined | null): WhirlpoolsConfigData | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return parseAnchorAccount(AccountName.WhirlpoolsConfig, data);
|
||||
} catch (e) {
|
||||
console.error(`error while parsing WhirlpoolsConfig: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Parsables
|
||||
*/
|
||||
@staticImplements<ParsableEntity<WhirlpoolData>>()
|
||||
export class ParsableWhirlpool {
|
||||
private constructor() {}
|
||||
|
||||
public static parse(data: Buffer | undefined | null): WhirlpoolData | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return parseAnchorAccount(AccountName.Whirlpool, data);
|
||||
} catch (e) {
|
||||
console.error(`error while parsing Whirlpool: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Parsables
|
||||
*/
|
||||
@staticImplements<ParsableEntity<PositionData>>()
|
||||
export class ParsablePosition {
|
||||
private constructor() {}
|
||||
|
||||
public static parse(data: Buffer | undefined | null): PositionData | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return parseAnchorAccount(AccountName.Position, data);
|
||||
} catch (e) {
|
||||
console.error(`error while parsing Position: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Parsables
|
||||
*/
|
||||
@staticImplements<ParsableEntity<TickArrayData>>()
|
||||
export class ParsableTickArray {
|
||||
private constructor() {}
|
||||
|
||||
public static parse(data: Buffer | undefined | null): TickArrayData | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return parseAnchorAccount(AccountName.TickArray, data);
|
||||
} catch (e) {
|
||||
console.error(`error while parsing TickArray: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Parsables
|
||||
*/
|
||||
@staticImplements<ParsableEntity<FeeTierData>>()
|
||||
export class ParsableFeeTier {
|
||||
private constructor() {}
|
||||
|
||||
public static parse(data: Buffer | undefined | null): FeeTierData | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return parseAnchorAccount(AccountName.FeeTier, data);
|
||||
} catch (e) {
|
||||
console.error(`error while parsing FeeTier: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Parsables
|
||||
*/
|
||||
@staticImplements<ParsableEntity<AccountInfo>>()
|
||||
export class ParsableTokenInfo {
|
||||
private constructor() {}
|
||||
|
||||
public static parse(data: Buffer | undefined | null): AccountInfo | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return TokenUtil.deserializeTokenAccount(data);
|
||||
} catch (e) {
|
||||
console.error(`error while parsing TokenAccount: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Parsables
|
||||
*/
|
||||
@staticImplements<ParsableEntity<MintInfo>>()
|
||||
export class ParsableMintInfo {
|
||||
private constructor() {}
|
||||
|
||||
public static parse(data: Buffer | undefined | null): MintInfo | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = MintLayout.decode(data);
|
||||
const mintInfo: MintInfo = {
|
||||
mintAuthority:
|
||||
buffer.mintAuthorityOption === 0 ? null : new PublicKey(buffer.mintAuthority),
|
||||
supply: u64.fromBuffer(buffer.supply),
|
||||
decimals: buffer.decimals,
|
||||
isInitialized: buffer.isInitialized !== 0,
|
||||
freezeAuthority:
|
||||
buffer.freezeAuthority === 0 ? null : new PublicKey(buffer.freezeAuthority),
|
||||
};
|
||||
|
||||
return mintInfo;
|
||||
} catch (e) {
|
||||
console.error(`error while parsing MintInfo: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class decorator to define an interface with static methods
|
||||
* Reference: https://github.com/Microsoft/TypeScript/issues/13462#issuecomment-295685298
|
||||
*/
|
||||
function staticImplements<T>() {
|
||||
return <U extends T>(constructor: U) => {
|
||||
constructor;
|
||||
};
|
||||
}
|
||||
|
||||
const WhirlpoolCoder = new Coder(WhirlpoolIDL as Idl);
|
||||
|
||||
function parseAnchorAccount(accountName: AccountName, data: Buffer) {
|
||||
const discriminator = AccountsCoder.accountDiscriminator(accountName);
|
||||
if (discriminator.compare(data.slice(0, 8))) {
|
||||
console.error("incorrect account name during parsing");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return WhirlpoolCoder.accounts.decode(accountName, data);
|
||||
} catch (_e) {
|
||||
console.error("unknown account name during parsing");
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { PositionData, TickData, WhirlpoolData } from "../../types/public";
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
*/
|
||||
export type CollectFeesQuoteParam = {
|
||||
whirlpool: WhirlpoolData;
|
||||
position: PositionData;
|
||||
tickLower: TickData;
|
||||
tickUpper: TickData;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
*/
|
||||
export type CollectFeesQuote = {
|
||||
feeOwedA: BN;
|
||||
feeOwedB: BN;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a quote on the outstanding fees owed to a position.
|
||||
*
|
||||
* @category Quotes
|
||||
* @param param A collection of fetched Whirlpool accounts to faciliate the quote.
|
||||
* @returns A quote object containing the fees owed for each token in the pool.
|
||||
*/
|
||||
export function collectFeesQuote(param: CollectFeesQuoteParam): CollectFeesQuote {
|
||||
const { whirlpool, position, tickLower, tickUpper } = param;
|
||||
|
||||
const {
|
||||
tickCurrentIndex,
|
||||
feeGrowthGlobalA: feeGrowthGlobalAX64,
|
||||
feeGrowthGlobalB: feeGrowthGlobalBX64,
|
||||
} = whirlpool;
|
||||
const {
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
liquidity,
|
||||
feeOwedA,
|
||||
feeOwedB,
|
||||
feeGrowthCheckpointA: feeGrowthCheckpointAX64,
|
||||
feeGrowthCheckpointB: feeGrowthCheckpointBX64,
|
||||
} = position;
|
||||
const {
|
||||
feeGrowthOutsideA: tickLowerFeeGrowthOutsideAX64,
|
||||
feeGrowthOutsideB: tickLowerFeeGrowthOutsideBX64,
|
||||
} = tickLower;
|
||||
const {
|
||||
feeGrowthOutsideA: tickUpperFeeGrowthOutsideAX64,
|
||||
feeGrowthOutsideB: tickUpperFeeGrowthOutsideBX64,
|
||||
} = tickUpper;
|
||||
|
||||
// Calculate the fee growths inside the position
|
||||
|
||||
let feeGrowthBelowAX64: BN | null = null;
|
||||
let feeGrowthBelowBX64: BN | null = null;
|
||||
|
||||
if (tickCurrentIndex < tickLowerIndex) {
|
||||
feeGrowthBelowAX64 = MathUtil.subUnderflowU128(
|
||||
feeGrowthGlobalAX64,
|
||||
tickLowerFeeGrowthOutsideAX64
|
||||
);
|
||||
feeGrowthBelowBX64 = MathUtil.subUnderflowU128(
|
||||
feeGrowthGlobalBX64,
|
||||
tickLowerFeeGrowthOutsideBX64
|
||||
);
|
||||
} else {
|
||||
feeGrowthBelowAX64 = tickLowerFeeGrowthOutsideAX64;
|
||||
feeGrowthBelowBX64 = tickLowerFeeGrowthOutsideBX64;
|
||||
}
|
||||
|
||||
let feeGrowthAboveAX64: BN | null = null;
|
||||
let feeGrowthAboveBX64: BN | null = null;
|
||||
|
||||
if (tickCurrentIndex < tickUpperIndex) {
|
||||
feeGrowthAboveAX64 = tickUpperFeeGrowthOutsideAX64;
|
||||
feeGrowthAboveBX64 = tickUpperFeeGrowthOutsideBX64;
|
||||
} else {
|
||||
feeGrowthAboveAX64 = MathUtil.subUnderflowU128(
|
||||
feeGrowthGlobalAX64,
|
||||
tickUpperFeeGrowthOutsideAX64
|
||||
);
|
||||
feeGrowthAboveBX64 = MathUtil.subUnderflowU128(
|
||||
feeGrowthGlobalBX64,
|
||||
tickUpperFeeGrowthOutsideBX64
|
||||
);
|
||||
}
|
||||
|
||||
const feeGrowthInsideAX64 = MathUtil.subUnderflowU128(
|
||||
MathUtil.subUnderflowU128(feeGrowthGlobalAX64, feeGrowthBelowAX64),
|
||||
feeGrowthAboveAX64
|
||||
);
|
||||
const feeGrowthInsideBX64 = MathUtil.subUnderflowU128(
|
||||
MathUtil.subUnderflowU128(feeGrowthGlobalBX64, feeGrowthBelowBX64),
|
||||
feeGrowthAboveBX64
|
||||
);
|
||||
|
||||
// Calculate the updated fees owed
|
||||
const feeOwedADelta = MathUtil.subUnderflowU128(feeGrowthInsideAX64, feeGrowthCheckpointAX64)
|
||||
.mul(liquidity)
|
||||
.shrn(64);
|
||||
const feeOwedBDelta = MathUtil.subUnderflowU128(feeGrowthInsideBX64, feeGrowthCheckpointBX64)
|
||||
.mul(liquidity)
|
||||
.shrn(64);
|
||||
|
||||
const updatedFeeOwedA = feeOwedA.add(feeOwedADelta);
|
||||
const updatedFeeOwedB = feeOwedB.add(feeOwedBDelta);
|
||||
|
||||
return {
|
||||
feeOwedA: updatedFeeOwedA,
|
||||
feeOwedB: updatedFeeOwedB,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import invariant from "tiny-invariant";
|
||||
import { NUM_REWARDS, PositionData, TickData, WhirlpoolData } from "../../types/public";
|
||||
import { PoolUtil } from "../../utils/public/pool-utils";
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
*/
|
||||
export type CollectRewardsQuoteParam = {
|
||||
whirlpool: WhirlpoolData;
|
||||
position: PositionData;
|
||||
tickLower: TickData;
|
||||
tickUpper: TickData;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
*/
|
||||
export type CollectRewardsQuote = [BN | undefined, BN | undefined, BN | undefined];
|
||||
|
||||
/**
|
||||
* Get a quote on the outstanding rewards owed to a position.
|
||||
*
|
||||
* @category Quotes
|
||||
* @param param A collection of fetched Whirlpool accounts to faciliate the quote.
|
||||
* @returns A quote object containing the rewards owed for each reward in the pool.
|
||||
*/
|
||||
export function collectRewardsQuote(param: CollectRewardsQuoteParam): CollectRewardsQuote {
|
||||
const { whirlpool, position, tickLower, tickUpper } = param;
|
||||
|
||||
const { tickCurrentIndex, rewardInfos: whirlpoolRewardsInfos } = whirlpool;
|
||||
const { tickLowerIndex, tickUpperIndex, liquidity, rewardInfos } = position;
|
||||
|
||||
// Calculate the reward growths inside the position
|
||||
|
||||
const range = [...Array(NUM_REWARDS).keys()];
|
||||
const rewardGrowthsBelowX64: BN[] = range.map(() => new BN(0));
|
||||
const rewardGrowthsAboveX64: BN[] = range.map(() => new BN(0));
|
||||
|
||||
for (const i of range) {
|
||||
const rewardInfo = whirlpoolRewardsInfos[i];
|
||||
invariant(!!rewardInfo, "whirlpoolRewardsInfos cannot be undefined");
|
||||
|
||||
const growthGlobalX64 = rewardInfo.growthGlobalX64;
|
||||
const lowerRewardGrowthsOutside = tickLower.rewardGrowthsOutside[i];
|
||||
const upperRewardGrowthsOutside = tickUpper.rewardGrowthsOutside[i];
|
||||
invariant(!!lowerRewardGrowthsOutside, "lowerRewardGrowthsOutside cannot be undefined");
|
||||
invariant(!!upperRewardGrowthsOutside, "upperRewardGrowthsOutside cannot be undefined");
|
||||
|
||||
if (tickCurrentIndex < tickLowerIndex) {
|
||||
rewardGrowthsBelowX64[i] = MathUtil.subUnderflowU128(
|
||||
growthGlobalX64,
|
||||
lowerRewardGrowthsOutside
|
||||
);
|
||||
} else {
|
||||
rewardGrowthsBelowX64[i] = lowerRewardGrowthsOutside;
|
||||
}
|
||||
|
||||
if (tickCurrentIndex < tickUpperIndex) {
|
||||
rewardGrowthsAboveX64[i] = upperRewardGrowthsOutside;
|
||||
} else {
|
||||
rewardGrowthsAboveX64[i] = MathUtil.subUnderflowU128(
|
||||
growthGlobalX64,
|
||||
upperRewardGrowthsOutside
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const rewardGrowthsInsideX64: [BN, boolean][] = range.map(() => [new BN(0), false]);
|
||||
|
||||
for (const i of range) {
|
||||
const rewardInfo = whirlpoolRewardsInfos[i];
|
||||
invariant(!!rewardInfo, "whirlpoolRewardsInfos cannot be undefined");
|
||||
|
||||
const isRewardInitialized = PoolUtil.isRewardInitialized(rewardInfo);
|
||||
|
||||
if (isRewardInitialized) {
|
||||
const growthBelowX64 = rewardGrowthsBelowX64[i];
|
||||
const growthAboveX64 = rewardGrowthsAboveX64[i];
|
||||
invariant(!!growthBelowX64, "growthBelowX64 cannot be undefined");
|
||||
invariant(!!growthAboveX64, "growthAboveX64 cannot be undefined");
|
||||
|
||||
const growthInsde = MathUtil.subUnderflowU128(
|
||||
MathUtil.subUnderflowU128(rewardInfo.growthGlobalX64, growthBelowX64),
|
||||
growthAboveX64
|
||||
);
|
||||
rewardGrowthsInsideX64[i] = [growthInsde, true];
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the updated rewards owed
|
||||
|
||||
const updatedRewardInfosX64: BN[] = range.map(() => new BN(0));
|
||||
|
||||
for (const i of range) {
|
||||
const growthInsideX64 = rewardGrowthsInsideX64[i];
|
||||
invariant(!!growthInsideX64, "growthInsideX64 cannot be undefined");
|
||||
|
||||
const [rewardGrowthInsideX64, isRewardInitialized] = growthInsideX64;
|
||||
|
||||
if (isRewardInitialized) {
|
||||
const rewardInfo = rewardInfos[i];
|
||||
invariant(!!rewardInfo, "rewardInfo cannot be undefined");
|
||||
|
||||
const amountOwedX64 = rewardInfo.amountOwed.shln(64);
|
||||
const growthInsideCheckpointX64 = rewardInfo.growthInsideCheckpoint;
|
||||
updatedRewardInfosX64[i] = amountOwedX64.add(
|
||||
MathUtil.subUnderflowU128(rewardGrowthInsideX64, growthInsideCheckpointX64).mul(liquidity)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
invariant(rewardGrowthsInsideX64.length >= 3, "rewards length is less than 3");
|
||||
|
||||
const rewardExistsA = rewardGrowthsInsideX64[0]?.[1];
|
||||
const rewardExistsB = rewardGrowthsInsideX64[1]?.[1];
|
||||
const rewardExistsC = rewardGrowthsInsideX64[2]?.[1];
|
||||
|
||||
const rewardOwedA = rewardExistsA ? updatedRewardInfosX64[0]?.shrn(64) : undefined;
|
||||
const rewardOwedB = rewardExistsB ? updatedRewardInfosX64[1]?.shrn(64) : undefined;
|
||||
const rewardOwedC = rewardExistsC ? updatedRewardInfosX64[2]?.shrn(64) : undefined;
|
||||
|
||||
return [rewardOwedA, rewardOwedB, rewardOwedC];
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
import { Percentage } from "@orca-so/common-sdk";
|
||||
import { ZERO } from "@orca-so/sdk";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import invariant from "tiny-invariant";
|
||||
import { DecreaseLiquidityInput } from "../../instructions";
|
||||
import {
|
||||
PositionUtil,
|
||||
PositionStatus,
|
||||
adjustForSlippage,
|
||||
getTokenAFromLiquidity,
|
||||
getTokenBFromLiquidity,
|
||||
} from "../../utils/position-util";
|
||||
import { PriceMath, TickUtil } from "../../utils/public";
|
||||
import { Position, Whirlpool } from "../../whirlpool-client";
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
* @param liquidity - The desired liquidity to withdraw from the Whirlpool
|
||||
* @param tickCurrentIndex - The Whirlpool's current tickIndex
|
||||
* @param sqrtPrice - The Whirlpool's current sqrtPrice
|
||||
* @param tickLowerIndex - The lower index of the position that we are withdrawing from.
|
||||
* @param tickUpperIndex - The upper index of the position that we are withdrawing from.
|
||||
* @param slippageTolerance - The maximum slippage allowed when calculating the minimum tokens received.
|
||||
*/
|
||||
export type DecreaseLiquidityQuoteParam = {
|
||||
liquidity: u64;
|
||||
tickCurrentIndex: number;
|
||||
sqrtPrice: BN;
|
||||
tickLowerIndex: number;
|
||||
tickUpperIndex: number;
|
||||
slippageTolerance: Percentage;
|
||||
};
|
||||
|
||||
export async function decreaseLiquidityQuoteByLiquidity(
|
||||
liquidity: u64,
|
||||
slippageTolerance: Percentage,
|
||||
position: Position,
|
||||
whirlpool: Whirlpool
|
||||
) {
|
||||
const positionData = position.getData();
|
||||
const whirlpoolData = whirlpool.getData();
|
||||
|
||||
invariant(
|
||||
liquidity.lte(positionData.liquidity),
|
||||
"Quote liquidity is more than the position liquidity."
|
||||
);
|
||||
|
||||
return decreaseLiquidityQuoteByLiquidityWithParams({
|
||||
liquidity,
|
||||
slippageTolerance,
|
||||
tickLowerIndex: positionData.tickLowerIndex,
|
||||
tickUpperIndex: positionData.tickUpperIndex,
|
||||
sqrtPrice: whirlpoolData.sqrtPrice,
|
||||
tickCurrentIndex: whirlpoolData.tickCurrentIndex,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an estimated quote on the minimum tokens receivable based on the desired withdraw liquidity value.
|
||||
*
|
||||
* @category Quotes
|
||||
* @param param DecreaseLiquidityQuoteParam
|
||||
* @returns An DecreaseLiquidityInput object detailing the tokenMin & liquidity values to use when calling decrease-liquidity-ix.
|
||||
*/
|
||||
export function decreaseLiquidityQuoteByLiquidityWithParams(
|
||||
param: DecreaseLiquidityQuoteParam
|
||||
): DecreaseLiquidityInput {
|
||||
invariant(TickUtil.checkTickInBounds(param.tickLowerIndex), "tickLowerIndex is out of bounds.");
|
||||
invariant(TickUtil.checkTickInBounds(param.tickUpperIndex), "tickUpperIndex is out of bounds.");
|
||||
invariant(
|
||||
TickUtil.checkTickInBounds(param.tickCurrentIndex),
|
||||
"tickCurrentIndex is out of bounds."
|
||||
);
|
||||
|
||||
const positionStatus = PositionUtil.getPositionStatus(
|
||||
param.tickCurrentIndex,
|
||||
param.tickLowerIndex,
|
||||
param.tickUpperIndex
|
||||
);
|
||||
|
||||
switch (positionStatus) {
|
||||
case PositionStatus.BelowRange:
|
||||
return quotePositionBelowRange(param);
|
||||
case PositionStatus.InRange:
|
||||
return quotePositionInRange(param);
|
||||
case PositionStatus.AboveRange:
|
||||
return quotePositionAboveRange(param);
|
||||
default:
|
||||
throw new Error(`type ${positionStatus} is an unknown PositionStatus`);
|
||||
}
|
||||
}
|
||||
|
||||
function quotePositionBelowRange(param: DecreaseLiquidityQuoteParam): DecreaseLiquidityInput {
|
||||
const { tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance } = param;
|
||||
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
const minTokenA = adjustForSlippage(
|
||||
getTokenAFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false),
|
||||
slippageTolerance,
|
||||
false
|
||||
);
|
||||
|
||||
return {
|
||||
tokenMinA: minTokenA,
|
||||
tokenMinB: ZERO,
|
||||
liquidityAmount: liquidity,
|
||||
};
|
||||
}
|
||||
|
||||
function quotePositionInRange(param: DecreaseLiquidityQuoteParam): DecreaseLiquidityInput {
|
||||
const { sqrtPrice, tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance } = param;
|
||||
|
||||
const sqrtPriceX64 = sqrtPrice;
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
const minTokenA = adjustForSlippage(
|
||||
getTokenAFromLiquidity(liquidity, sqrtPriceX64, sqrtPriceUpperX64, false),
|
||||
slippageTolerance,
|
||||
false
|
||||
);
|
||||
const minTokenB = adjustForSlippage(
|
||||
getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceX64, false),
|
||||
slippageTolerance,
|
||||
false
|
||||
);
|
||||
|
||||
return {
|
||||
tokenMinA: minTokenA,
|
||||
tokenMinB: minTokenB,
|
||||
liquidityAmount: liquidity,
|
||||
};
|
||||
}
|
||||
|
||||
function quotePositionAboveRange(param: DecreaseLiquidityQuoteParam): DecreaseLiquidityInput {
|
||||
const { tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance: slippageTolerance } = param;
|
||||
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
const minTokenB = adjustForSlippage(
|
||||
getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false),
|
||||
slippageTolerance,
|
||||
false
|
||||
);
|
||||
|
||||
return {
|
||||
tokenMinA: ZERO,
|
||||
tokenMinB: minTokenB,
|
||||
liquidityAmount: liquidity,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
import { DecimalUtil, Percentage, ZERO } from "@orca-so/common-sdk";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import Decimal from "decimal.js";
|
||||
import invariant from "tiny-invariant";
|
||||
import { IncreaseLiquidityInput } from "../../instructions";
|
||||
import { TokenInfo, WhirlpoolData } from "../../types/public";
|
||||
import {
|
||||
PositionUtil,
|
||||
PositionStatus,
|
||||
getLiquidityFromTokenA,
|
||||
adjustForSlippage,
|
||||
getTokenAFromLiquidity,
|
||||
getTokenBFromLiquidity,
|
||||
getLiquidityFromTokenB,
|
||||
} from "../../utils/position-util";
|
||||
import { PriceMath, TickUtil } from "../../utils/public";
|
||||
import { Whirlpool } from "../../whirlpool-client";
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
* @param inputTokenAmount - The amount of input tokens to deposit.
|
||||
* @param inputTokenMint - The mint of the input token the user would like to deposit.
|
||||
* @param tokenMintA - The mint of tokenA in the Whirlpool the user is depositing into.
|
||||
* @param tokenMintB -The mint of tokenB in the Whirlpool the user is depositing into.
|
||||
* @param tickCurrentIndex - The Whirlpool's current tickIndex
|
||||
* @param sqrtPrice - The Whirlpool's current sqrtPrice
|
||||
* @param tickLowerIndex - The lower index of the position that we are withdrawing from.
|
||||
* @param tickUpperIndex - The upper index of the position that we are withdrawing from.
|
||||
* @param slippageTolerance - The maximum slippage allowed when calculating the minimum tokens received.
|
||||
*/
|
||||
export type IncreaseLiquidityQuoteParam = {
|
||||
inputTokenAmount: u64;
|
||||
inputTokenMint: PublicKey;
|
||||
tokenMintA: PublicKey;
|
||||
tokenMintB: PublicKey;
|
||||
tickCurrentIndex: number;
|
||||
sqrtPrice: BN;
|
||||
tickLowerIndex: number;
|
||||
tickUpperIndex: number;
|
||||
slippageTolerance: Percentage;
|
||||
};
|
||||
|
||||
export function increaseLiquidityQuoteByInputToken(
|
||||
inputTokenMint: PublicKey,
|
||||
inputTokenAmount: Decimal,
|
||||
tickLower: number,
|
||||
tickUpper: number,
|
||||
slippageTolerance: Percentage,
|
||||
whirlpool: Whirlpool
|
||||
) {
|
||||
const data = whirlpool.getData();
|
||||
const tokenAInfo = whirlpool.getTokenAInfo();
|
||||
const tokenBInfo = whirlpool.getTokenBInfo();
|
||||
|
||||
const inputTokenInfo = inputTokenMint.equals(tokenAInfo.mint) ? tokenAInfo : tokenBInfo;
|
||||
|
||||
return increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenMint: inputTokenMint,
|
||||
inputTokenAmount: DecimalUtil.toU64(inputTokenAmount, inputTokenInfo.decimals),
|
||||
tickLowerIndex: TickUtil.getInitializableTickIndex(tickLower, data.tickSpacing),
|
||||
tickUpperIndex: TickUtil.getInitializableTickIndex(tickUpper, data.tickSpacing),
|
||||
slippageTolerance,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an estimated quote on the maximum tokens required to deposit based on a specified input token amount.
|
||||
*
|
||||
* @category Quotes
|
||||
* @param param IncreaseLiquidityQuoteParam
|
||||
* @returns An IncreaseLiquidityInput object detailing the required token amounts & liquidity values to use when calling increase-liquidity-ix.
|
||||
*/
|
||||
export function increaseLiquidityQuoteByInputTokenWithParams(
|
||||
param: IncreaseLiquidityQuoteParam
|
||||
): IncreaseLiquidityInput {
|
||||
invariant(TickUtil.checkTickInBounds(param.tickLowerIndex), "tickLowerIndex is out of bounds.");
|
||||
invariant(TickUtil.checkTickInBounds(param.tickUpperIndex), "tickUpperIndex is out of bounds.");
|
||||
invariant(
|
||||
param.inputTokenMint.equals(param.tokenMintA) || param.inputTokenMint.equals(param.tokenMintB),
|
||||
`input token mint ${param.inputTokenMint.toBase58()} does not match any tokens in the provided pool.`
|
||||
);
|
||||
|
||||
const positionStatus = PositionUtil.getPositionStatus(
|
||||
param.tickCurrentIndex,
|
||||
param.tickLowerIndex,
|
||||
param.tickUpperIndex
|
||||
);
|
||||
|
||||
switch (positionStatus) {
|
||||
case PositionStatus.BelowRange:
|
||||
return quotePositionBelowRange(param);
|
||||
case PositionStatus.InRange:
|
||||
return quotePositionInRange(param);
|
||||
case PositionStatus.AboveRange:
|
||||
return quotePositionAboveRange(param);
|
||||
default:
|
||||
throw new Error(`type ${positionStatus} is an unknown PositionStatus`);
|
||||
}
|
||||
}
|
||||
|
||||
/*** Private ***/
|
||||
|
||||
function quotePositionBelowRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityInput {
|
||||
const {
|
||||
tokenMintA,
|
||||
inputTokenMint,
|
||||
inputTokenAmount,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
slippageTolerance,
|
||||
} = param;
|
||||
|
||||
if (!tokenMintA.equals(inputTokenMint)) {
|
||||
return {
|
||||
tokenMaxA: ZERO,
|
||||
tokenMaxB: ZERO,
|
||||
liquidityAmount: ZERO,
|
||||
};
|
||||
}
|
||||
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
const liquidityAmount = getLiquidityFromTokenA(
|
||||
inputTokenAmount,
|
||||
sqrtPriceLowerX64,
|
||||
sqrtPriceUpperX64,
|
||||
false
|
||||
);
|
||||
|
||||
const maxTokenA = adjustForSlippage(
|
||||
getTokenAFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, true),
|
||||
slippageTolerance,
|
||||
true
|
||||
);
|
||||
const maxTokenB = ZERO;
|
||||
|
||||
return {
|
||||
tokenMaxA: maxTokenA,
|
||||
tokenMaxB: maxTokenB,
|
||||
liquidityAmount,
|
||||
};
|
||||
}
|
||||
|
||||
function quotePositionInRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityInput {
|
||||
const {
|
||||
tokenMintA,
|
||||
sqrtPrice,
|
||||
inputTokenMint,
|
||||
inputTokenAmount,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
slippageTolerance,
|
||||
} = param;
|
||||
|
||||
const sqrtPriceX64 = sqrtPrice;
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
let [tokenAmountA, tokenAmountB] = tokenMintA.equals(inputTokenMint)
|
||||
? [inputTokenAmount, undefined]
|
||||
: [undefined, inputTokenAmount];
|
||||
|
||||
let liquidityAmount: BN;
|
||||
|
||||
if (tokenAmountA) {
|
||||
liquidityAmount = getLiquidityFromTokenA(tokenAmountA, sqrtPriceX64, sqrtPriceUpperX64, false);
|
||||
tokenAmountA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true);
|
||||
tokenAmountB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true);
|
||||
} else if (tokenAmountB) {
|
||||
liquidityAmount = getLiquidityFromTokenB(tokenAmountB, sqrtPriceLowerX64, sqrtPriceX64, false);
|
||||
tokenAmountA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true);
|
||||
tokenAmountB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true);
|
||||
} else {
|
||||
throw new Error("invariant violation");
|
||||
}
|
||||
|
||||
const maxTokenA = adjustForSlippage(tokenAmountA, slippageTolerance, true);
|
||||
const maxTokenB = adjustForSlippage(tokenAmountB, slippageTolerance, true);
|
||||
|
||||
return {
|
||||
tokenMaxA: maxTokenA,
|
||||
tokenMaxB: maxTokenB,
|
||||
liquidityAmount,
|
||||
};
|
||||
}
|
||||
|
||||
function quotePositionAboveRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityInput {
|
||||
const {
|
||||
tokenMintB,
|
||||
inputTokenMint,
|
||||
inputTokenAmount,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
slippageTolerance,
|
||||
} = param;
|
||||
|
||||
if (!tokenMintB.equals(inputTokenMint)) {
|
||||
return {
|
||||
tokenMaxA: ZERO,
|
||||
tokenMaxB: ZERO,
|
||||
liquidityAmount: ZERO,
|
||||
};
|
||||
}
|
||||
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
const liquidityAmount = getLiquidityFromTokenB(
|
||||
inputTokenAmount,
|
||||
sqrtPriceLowerX64,
|
||||
sqrtPriceUpperX64,
|
||||
false
|
||||
);
|
||||
|
||||
const maxTokenA = ZERO;
|
||||
const maxTokenB = adjustForSlippage(
|
||||
getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, true),
|
||||
slippageTolerance,
|
||||
true
|
||||
);
|
||||
|
||||
return {
|
||||
tokenMaxA: maxTokenA,
|
||||
tokenMaxB: maxTokenB,
|
||||
liquidityAmount,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export * from "./increase-liquidity-quote";
|
||||
export * from "./decrease-liquidity-quote";
|
||||
export * from "./collect-fees-quote";
|
||||
export * from "./collect-rewards-quote";
|
||||
export * from "./swap-quote";
|
|
@ -0,0 +1,439 @@
|
|||
import { Address, BN } from "@project-serum/anchor";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import invariant from "tiny-invariant";
|
||||
import { PoolUtil } from "../../utils/public/pool-utils";
|
||||
import {
|
||||
SwapDirection,
|
||||
AmountSpecified,
|
||||
adjustAmountForSlippage,
|
||||
getAmountFixedDelta,
|
||||
getNextSqrtPrice,
|
||||
getAmountUnfixedDelta,
|
||||
} from "../../utils/position-util";
|
||||
import { SwapInput } from "../../instructions";
|
||||
import {
|
||||
WhirlpoolData,
|
||||
TickArrayData,
|
||||
MAX_TICK_ARRAY_CROSSINGS,
|
||||
MIN_SQRT_PRICE,
|
||||
MAX_SQRT_PRICE,
|
||||
TICK_ARRAY_SIZE,
|
||||
} from "../../types/public";
|
||||
import { AddressUtil, MathUtil, Percentage, ZERO } from "@orca-so/common-sdk";
|
||||
import { PriceMath, TickArrayUtil, TickUtil } from "../../utils/public";
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
*/
|
||||
export type SwapQuoteParam = {
|
||||
whirlpoolAddress: Address;
|
||||
swapTokenMint: Address;
|
||||
whirlpoolData: WhirlpoolData;
|
||||
tokenAmount: u64;
|
||||
amountSpecifiedIsInput: boolean;
|
||||
slippageTolerance: Percentage;
|
||||
tickArrayAddresses: PublicKey[];
|
||||
tickArrays: (TickArrayData | null)[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
*/
|
||||
export type SwapQuote = {
|
||||
estimatedAmountIn: u64;
|
||||
estimatedAmountOut: u64;
|
||||
} & SwapInput;
|
||||
|
||||
/**
|
||||
* TODO: Bug - The quote swap loop will ignore the first initialized tick of the next array on array traversal
|
||||
* if the tick is on offset 0.
|
||||
* TODO: Build out a comprhensive integ test-suite for all tick traversal. The test suite would confirm
|
||||
* edge cases for both smart-contract and quote (they must equal). The original test-cases in SDK can be ported
|
||||
* over in this effort.
|
||||
* TODO: Think about other types of quote that we want to write (ex. limit by price to get trade amount etc)
|
||||
*
|
||||
* Get an estimated quote of a swap
|
||||
*
|
||||
* @category Quotes
|
||||
* @param param a SwapQuoteParam object detailing parameters of the swap
|
||||
* @return a SwapQuote on the estimated amountIn & amountOut of the swap and a SwapInput to use on the swap instruction.
|
||||
*/
|
||||
export function swapQuoteByInputToken(param: SwapQuoteParam): SwapQuote {
|
||||
const {
|
||||
whirlpoolAddress,
|
||||
swapTokenMint,
|
||||
tokenAmount,
|
||||
whirlpoolData,
|
||||
amountSpecifiedIsInput,
|
||||
slippageTolerance,
|
||||
tickArrays,
|
||||
tickArrayAddresses,
|
||||
} = param;
|
||||
|
||||
const swapDirection =
|
||||
AddressUtil.toPubKey(swapTokenMint).equals(whirlpoolData.tokenMintA) === amountSpecifiedIsInput
|
||||
? SwapDirection.AtoB
|
||||
: SwapDirection.BtoA;
|
||||
const amountSpecified = amountSpecifiedIsInput ? AmountSpecified.Input : AmountSpecified.Output;
|
||||
|
||||
const { amountIn, amountOut, sqrtPriceLimitX64, tickArraysCrossed } = simulateSwap(
|
||||
{
|
||||
whirlpoolAddress,
|
||||
whirlpoolData,
|
||||
amountSpecified,
|
||||
swapDirection,
|
||||
tickArrays,
|
||||
},
|
||||
{
|
||||
amount: tokenAmount,
|
||||
currentSqrtPriceX64: whirlpoolData.sqrtPrice,
|
||||
currentTickIndex: whirlpoolData.tickCurrentIndex,
|
||||
currentLiquidity: whirlpoolData.liquidity,
|
||||
}
|
||||
);
|
||||
|
||||
const otherAmountThreshold = adjustAmountForSlippage(
|
||||
amountIn,
|
||||
amountOut,
|
||||
slippageTolerance,
|
||||
amountSpecified
|
||||
);
|
||||
|
||||
// Compute the traversed set of tick-arrays. Set the remaining slots to
|
||||
//the last traversed array.
|
||||
const traversedTickArrays = tickArrayAddresses.map((addr, index) => {
|
||||
if (index < tickArraysCrossed) {
|
||||
return addr;
|
||||
}
|
||||
return tickArrayAddresses[Math.max(tickArraysCrossed - 1, 0)];
|
||||
});
|
||||
|
||||
return {
|
||||
amount: tokenAmount,
|
||||
otherAmountThreshold,
|
||||
sqrtPriceLimit: sqrtPriceLimitX64,
|
||||
estimatedAmountIn: amountIn,
|
||||
estimatedAmountOut: amountOut,
|
||||
aToB: swapDirection === SwapDirection.AtoB,
|
||||
amountSpecifiedIsInput,
|
||||
tickArray0: traversedTickArrays[0],
|
||||
tickArray1: traversedTickArrays[1],
|
||||
tickArray2: traversedTickArrays[2],
|
||||
};
|
||||
}
|
||||
|
||||
type SwapSimulationBaseInput = {
|
||||
whirlpoolAddress: Address;
|
||||
whirlpoolData: WhirlpoolData;
|
||||
amountSpecified: AmountSpecified;
|
||||
swapDirection: SwapDirection;
|
||||
tickArrays: (TickArrayData | null)[];
|
||||
};
|
||||
|
||||
type SwapSimulationInput = {
|
||||
amount: BN;
|
||||
currentSqrtPriceX64: BN;
|
||||
currentTickIndex: number;
|
||||
currentLiquidity: BN;
|
||||
};
|
||||
|
||||
type SwapSimulationOutput = {
|
||||
amountIn: BN;
|
||||
amountOut: BN;
|
||||
sqrtPriceLimitX64: BN;
|
||||
tickArraysCrossed: number;
|
||||
};
|
||||
|
||||
type SwapStepSimulationInput = {
|
||||
sqrtPriceX64: BN;
|
||||
tickIndex: number;
|
||||
liquidity: BN;
|
||||
amountRemaining: u64;
|
||||
tickArraysCrossed: number;
|
||||
};
|
||||
|
||||
type SwapStepSimulationOutput = {
|
||||
nextSqrtPriceX64: BN;
|
||||
nextTickIndex: number;
|
||||
input: BN;
|
||||
output: BN;
|
||||
tickArraysCrossed: number;
|
||||
hasReachedNextTick: boolean;
|
||||
};
|
||||
|
||||
function simulateSwap(
|
||||
baseInput: SwapSimulationBaseInput,
|
||||
input: SwapSimulationInput
|
||||
): SwapSimulationOutput {
|
||||
const { amountSpecified, swapDirection } = baseInput;
|
||||
|
||||
let {
|
||||
currentTickIndex,
|
||||
currentLiquidity,
|
||||
amount: specifiedAmountLeft,
|
||||
currentSqrtPriceX64,
|
||||
} = input;
|
||||
|
||||
invariant(!specifiedAmountLeft.eq(ZERO), "amount must be nonzero");
|
||||
|
||||
let otherAmountCalculated = ZERO;
|
||||
|
||||
let tickArraysCrossed = 0;
|
||||
let sqrtPriceLimitX64;
|
||||
|
||||
while (specifiedAmountLeft.gt(ZERO)) {
|
||||
if (tickArraysCrossed > MAX_TICK_ARRAY_CROSSINGS) {
|
||||
throw Error("Crossed the maximum number of tick arrays");
|
||||
}
|
||||
|
||||
const swapStepSimulationOutput: SwapStepSimulationOutput = simulateSwapStep(baseInput, {
|
||||
sqrtPriceX64: currentSqrtPriceX64,
|
||||
amountRemaining: specifiedAmountLeft,
|
||||
tickIndex: currentTickIndex,
|
||||
liquidity: currentLiquidity,
|
||||
tickArraysCrossed,
|
||||
});
|
||||
|
||||
const { input, output, nextSqrtPriceX64, nextTickIndex, hasReachedNextTick } =
|
||||
swapStepSimulationOutput;
|
||||
|
||||
const [specifiedAmountUsed, otherAmount] = resolveTokenAmounts(input, output, amountSpecified);
|
||||
|
||||
specifiedAmountLeft = specifiedAmountLeft.sub(specifiedAmountUsed);
|
||||
otherAmountCalculated = otherAmountCalculated.add(otherAmount);
|
||||
|
||||
if (hasReachedNextTick) {
|
||||
const nextTick = fetchTick(baseInput, nextTickIndex);
|
||||
|
||||
currentLiquidity = calculateNewLiquidity(
|
||||
currentLiquidity,
|
||||
nextTick.liquidityNet,
|
||||
swapDirection
|
||||
);
|
||||
|
||||
currentTickIndex = swapDirection == SwapDirection.AtoB ? nextTickIndex - 1 : nextTickIndex;
|
||||
}
|
||||
|
||||
currentSqrtPriceX64 = nextSqrtPriceX64;
|
||||
tickArraysCrossed = swapStepSimulationOutput.tickArraysCrossed;
|
||||
|
||||
if (tickArraysCrossed > MAX_TICK_ARRAY_CROSSINGS) {
|
||||
sqrtPriceLimitX64 = PriceMath.tickIndexToSqrtPriceX64(nextTickIndex);
|
||||
}
|
||||
}
|
||||
|
||||
const [inputAmount, outputAmount] = resolveTokenAmounts(
|
||||
input.amount.sub(specifiedAmountLeft),
|
||||
otherAmountCalculated,
|
||||
amountSpecified
|
||||
);
|
||||
|
||||
if (!sqrtPriceLimitX64) {
|
||||
if (swapDirection === SwapDirection.AtoB) {
|
||||
sqrtPriceLimitX64 = new BN(MIN_SQRT_PRICE);
|
||||
} else {
|
||||
sqrtPriceLimitX64 = new BN(MAX_SQRT_PRICE);
|
||||
}
|
||||
}
|
||||
|
||||
// Return sqrtPriceLimit if 3 tick arrays crossed
|
||||
return {
|
||||
amountIn: inputAmount,
|
||||
amountOut: outputAmount,
|
||||
sqrtPriceLimitX64,
|
||||
tickArraysCrossed,
|
||||
};
|
||||
}
|
||||
|
||||
function simulateSwapStep(
|
||||
baseInput: SwapSimulationBaseInput,
|
||||
input: SwapStepSimulationInput
|
||||
): SwapStepSimulationOutput {
|
||||
const { whirlpoolData, amountSpecified, swapDirection } = baseInput;
|
||||
|
||||
const { feeRate } = whirlpoolData;
|
||||
|
||||
const feeRatePercentage = PoolUtil.getFeeRate(feeRate);
|
||||
|
||||
const { amountRemaining, liquidity, sqrtPriceX64, tickIndex, tickArraysCrossed } = input;
|
||||
|
||||
const { tickIndex: nextTickIndex, tickArraysCrossed: tickArraysCrossedUpdate } =
|
||||
// Return last tick in tick array if max tick arrays crossed
|
||||
// Error out of this gets called for another iteration
|
||||
getNextInitializedTickIndex(baseInput, tickIndex, tickArraysCrossed);
|
||||
|
||||
const targetSqrtPriceX64 = PriceMath.tickIndexToSqrtPriceX64(nextTickIndex);
|
||||
|
||||
let fixedDelta = getAmountFixedDelta(
|
||||
sqrtPriceX64,
|
||||
targetSqrtPriceX64,
|
||||
liquidity,
|
||||
amountSpecified,
|
||||
swapDirection
|
||||
);
|
||||
|
||||
let amountCalculated = amountRemaining;
|
||||
if (amountSpecified == AmountSpecified.Input) {
|
||||
amountCalculated = calculateAmountAfterFees(amountRemaining, feeRatePercentage);
|
||||
}
|
||||
|
||||
const nextSqrtPriceX64 = amountCalculated.gte(fixedDelta)
|
||||
? targetSqrtPriceX64 // Fully utilize liquidity till upcoming (next/prev depending on swap type) initialized tick
|
||||
: getNextSqrtPrice(sqrtPriceX64, liquidity, amountCalculated, amountSpecified, swapDirection);
|
||||
|
||||
const hasReachedNextTick = nextSqrtPriceX64.eq(targetSqrtPriceX64);
|
||||
|
||||
const unfixedDelta = getAmountUnfixedDelta(
|
||||
sqrtPriceX64,
|
||||
nextSqrtPriceX64,
|
||||
liquidity,
|
||||
amountSpecified,
|
||||
swapDirection
|
||||
);
|
||||
|
||||
if (!hasReachedNextTick) {
|
||||
fixedDelta = getAmountFixedDelta(
|
||||
sqrtPriceX64,
|
||||
nextSqrtPriceX64,
|
||||
liquidity,
|
||||
amountSpecified,
|
||||
swapDirection
|
||||
);
|
||||
}
|
||||
|
||||
let [inputDelta, outputDelta] = resolveTokenAmounts(fixedDelta, unfixedDelta, amountSpecified);
|
||||
|
||||
// Cap output if output specified
|
||||
if (amountSpecified == AmountSpecified.Output && outputDelta.gt(amountRemaining)) {
|
||||
outputDelta = amountRemaining;
|
||||
}
|
||||
|
||||
if (amountSpecified == AmountSpecified.Input && !hasReachedNextTick) {
|
||||
inputDelta = amountRemaining;
|
||||
} else {
|
||||
inputDelta = inputDelta.add(calculateFeesFromAmount(inputDelta, feeRatePercentage));
|
||||
}
|
||||
|
||||
return {
|
||||
nextTickIndex,
|
||||
nextSqrtPriceX64,
|
||||
input: inputDelta,
|
||||
output: outputDelta,
|
||||
tickArraysCrossed: tickArraysCrossedUpdate,
|
||||
hasReachedNextTick,
|
||||
};
|
||||
}
|
||||
|
||||
function calculateAmountAfterFees(amount: u64, feeRate: Percentage): BN {
|
||||
return amount.mul(feeRate.denominator.sub(feeRate.numerator)).div(feeRate.denominator);
|
||||
}
|
||||
|
||||
function calculateFeesFromAmount(amount: u64, feeRate: Percentage): BN {
|
||||
return MathUtil.divRoundUp(
|
||||
amount.mul(feeRate.numerator),
|
||||
feeRate.denominator.sub(feeRate.numerator)
|
||||
);
|
||||
}
|
||||
|
||||
function calculateNewLiquidity(liquidity: BN, nextLiquidityNet: BN, swapDirection: SwapDirection) {
|
||||
if (swapDirection == SwapDirection.AtoB) {
|
||||
nextLiquidityNet = nextLiquidityNet.neg();
|
||||
}
|
||||
|
||||
return liquidity.add(nextLiquidityNet);
|
||||
}
|
||||
|
||||
function resolveTokenAmounts(
|
||||
specifiedTokenAmount: BN,
|
||||
otherTokenAmount: BN,
|
||||
amountSpecified: AmountSpecified
|
||||
): [BN, BN] {
|
||||
if (amountSpecified == AmountSpecified.Input) {
|
||||
return [specifiedTokenAmount, otherTokenAmount];
|
||||
} else {
|
||||
return [otherTokenAmount, specifiedTokenAmount];
|
||||
}
|
||||
}
|
||||
|
||||
function fetchTickArray(baseInput: SwapSimulationBaseInput, tickIndex: number) {
|
||||
const {
|
||||
tickArrays,
|
||||
whirlpoolData: { tickSpacing },
|
||||
} = baseInput;
|
||||
|
||||
const startTickArray = tickArrays[0];
|
||||
invariant(!!startTickArray, `tickArray is null at index 0`);
|
||||
const sequenceStartIndex = startTickArray.startTickIndex;
|
||||
const expectedArrayIndex = Math.abs(
|
||||
Math.floor((tickIndex - sequenceStartIndex) / tickSpacing / TICK_ARRAY_SIZE)
|
||||
);
|
||||
|
||||
invariant(
|
||||
expectedArrayIndex > 0 || expectedArrayIndex < tickArrays.length,
|
||||
`tickIndex ${tickIndex} assumes array-index of ${expectedArrayIndex} and is out of bounds for sequence`
|
||||
);
|
||||
|
||||
const tickArray = tickArrays[expectedArrayIndex];
|
||||
invariant(!!tickArray, `tickArray is null at array-index ${expectedArrayIndex}`);
|
||||
|
||||
return tickArray;
|
||||
}
|
||||
|
||||
function fetchTick(baseInput: SwapSimulationBaseInput, tickIndex: number) {
|
||||
const tickArray = fetchTickArray(baseInput, tickIndex);
|
||||
const {
|
||||
whirlpoolData: { tickSpacing },
|
||||
} = baseInput;
|
||||
return TickArrayUtil.getTickFromArray(tickArray, tickIndex, tickSpacing);
|
||||
}
|
||||
|
||||
function getNextInitializedTickIndex(
|
||||
baseInput: SwapSimulationBaseInput,
|
||||
currentTickIndex: number,
|
||||
tickArraysCrossed: number
|
||||
) {
|
||||
const {
|
||||
whirlpoolData: { tickSpacing },
|
||||
swapDirection,
|
||||
} = baseInput;
|
||||
let nextInitializedTickIndex: number | undefined = undefined;
|
||||
|
||||
while (nextInitializedTickIndex === undefined) {
|
||||
const currentTickArray = fetchTickArray(baseInput, currentTickIndex);
|
||||
|
||||
let temp;
|
||||
if (swapDirection == SwapDirection.AtoB) {
|
||||
temp = TickUtil.findPreviousInitializedTickIndex(
|
||||
currentTickArray,
|
||||
currentTickIndex,
|
||||
tickSpacing
|
||||
);
|
||||
} else {
|
||||
temp = TickUtil.findNextInitializedTickIndex(currentTickArray, currentTickIndex, tickSpacing);
|
||||
}
|
||||
|
||||
if (temp) {
|
||||
nextInitializedTickIndex = temp;
|
||||
} else if (tickArraysCrossed === MAX_TICK_ARRAY_CROSSINGS) {
|
||||
if (swapDirection === SwapDirection.AtoB) {
|
||||
nextInitializedTickIndex = currentTickArray.startTickIndex;
|
||||
} else {
|
||||
nextInitializedTickIndex = currentTickArray.startTickIndex + TICK_ARRAY_SIZE * tickSpacing;
|
||||
}
|
||||
tickArraysCrossed++;
|
||||
} else {
|
||||
if (swapDirection === SwapDirection.AtoB) {
|
||||
currentTickIndex = currentTickArray.startTickIndex - 1;
|
||||
} else {
|
||||
currentTickIndex = currentTickArray.startTickIndex + TICK_ARRAY_SIZE * tickSpacing - 1;
|
||||
}
|
||||
tickArraysCrossed++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tickIndex: nextInitializedTickIndex,
|
||||
tickArraysCrossed,
|
||||
};
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"./artifacts/**/*.json"
|
||||
]
|
||||
"include": ["./**/*", "./artifacts/**/*.json"],
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["index.ts"],
|
||||
"out": "../../target/typedocs"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
/**
|
||||
* This file contains the types that the client exposes to SDK users.
|
||||
*
|
||||
* TODO: This file may or may not exist pending SDK's approach on parsing
|
||||
* the Whirlpool Accounts.
|
||||
*
|
||||
*/
|
||||
export interface WhirlpoolConfigAccount {
|
||||
feeAuthority: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
defaultFeeRate: number;
|
||||
defaultProtocolFeeRate: number;
|
||||
}
|
|
@ -9,18 +9,32 @@ import { PublicKey } from "@solana/web3.js";
|
|||
* be hard-casted to fit the type.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported parasable account names from the Whirlpool contract.
|
||||
* @category Parsables
|
||||
*/
|
||||
export enum AccountName {
|
||||
WhirlpoolsConfig = "WhirlpoolsConfig",
|
||||
Position = "Position",
|
||||
TickArray = "TickArray",
|
||||
Whirlpool = "Whirlpool",
|
||||
FeeTier = "FeeTier",
|
||||
}
|
||||
|
||||
export type TickSpacingData = {
|
||||
stable?: {};
|
||||
standard?: {};
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type WhirlpoolsConfigData = {
|
||||
feeAuthority: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
defaultFeeRate: number;
|
||||
defaultProtocolFeeRate: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type WhirlpoolRewardInfoData = {
|
||||
mint: PublicKey;
|
||||
vault: PublicKey;
|
||||
|
@ -29,10 +43,16 @@ export type WhirlpoolRewardInfoData = {
|
|||
growthGlobalX64: BN;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type WhirlpoolBumpsData = {
|
||||
whirlpoolBump: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type WhirlpoolData = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
whirlpoolBump: number[];
|
||||
|
@ -54,12 +74,18 @@ export type WhirlpoolData = {
|
|||
tickSpacing: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type TickArrayData = {
|
||||
whirlpool: PublicKey;
|
||||
startTickIndex: number;
|
||||
ticks: TickData[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type TickData = {
|
||||
initialized: boolean;
|
||||
liquidityNet: BN;
|
||||
|
@ -69,20 +95,32 @@ export type TickData = {
|
|||
rewardGrowthsOutside: BN[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type PositionRewardInfoData = {
|
||||
growthInsideCheckpoint: BN;
|
||||
amountOwed: BN;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type OpenPositionBumpsData = {
|
||||
positionBump: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type OpenPositionWithMetadataBumpsData = {
|
||||
positionBump: number;
|
||||
metadataBump: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type PositionData = {
|
||||
whirlpool: PublicKey;
|
||||
positionMint: PublicKey;
|
||||
|
@ -96,6 +134,9 @@ export type PositionData = {
|
|||
rewardInfos: PositionRewardInfoData[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Solana Accounts
|
||||
*/
|
||||
export type FeeTierData = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
tickSpacing: number;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { MintInfo } from "@solana/spl-token";
|
||||
|
||||
export type TokenInfo = MintInfo & { mint: PublicKey };
|
|
@ -1,2 +1,44 @@
|
|||
// The number of rewards supported by this whirlpool.
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
/**
|
||||
* The number of rewards supported by this whirlpool.
|
||||
* @category Constants
|
||||
*/
|
||||
export const NUM_REWARDS = 3;
|
||||
|
||||
/**
|
||||
* @category Constants
|
||||
*/
|
||||
export const MAX_TICK_ARRAY_CROSSINGS = 2;
|
||||
|
||||
/**
|
||||
* @category Constants
|
||||
*/
|
||||
export const MAX_TICK_INDEX = 443636;
|
||||
|
||||
/**
|
||||
* @category Constants
|
||||
*/
|
||||
export const MIN_TICK_INDEX = -443636;
|
||||
|
||||
/**
|
||||
* @category Constants
|
||||
*/
|
||||
export const TICK_ARRAY_SIZE = 88;
|
||||
|
||||
/**
|
||||
* @category Constants
|
||||
*/
|
||||
export const MAX_SQRT_PRICE = "79226673515401279992447579055";
|
||||
|
||||
/**
|
||||
* @category Constants
|
||||
*/
|
||||
export const MIN_SQRT_PRICE = "4295048016";
|
||||
|
||||
/**
|
||||
* @category Constants
|
||||
*/
|
||||
export const METADATA_PROGRAM_ADDRESS = new PublicKey(
|
||||
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
|
||||
);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
export type PDA = { publicKey: PublicKey; bump: number };
|
|
@ -1,5 +1,4 @@
|
|||
export * from "./ix-types";
|
||||
export * from "./account-types";
|
||||
export * from "./constants";
|
||||
export * from "./helper-types";
|
||||
export * from "./anchor-types";
|
||||
export * from "./ix-types";
|
||||
export * from "./client-types";
|
||||
|
|
|
@ -1,227 +1,29 @@
|
|||
import { u64 } from "@solana/spl-token";
|
||||
import { Keypair, PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { PDA } from "./helper-types";
|
||||
|
||||
export type InitConfigParams = {
|
||||
whirlpoolConfigKeypair: Keypair;
|
||||
feeAuthority: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
defaultProtocolFeeRate: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
export type InitFeeTierParams = {
|
||||
feeTierPda: PDA;
|
||||
feeAuthority: PublicKey;
|
||||
whirlpoolConfigKey: PublicKey;
|
||||
tickSpacing: number;
|
||||
defaultFeeRate: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
export type InitPoolParams = {
|
||||
initSqrtPrice: BN;
|
||||
whirlpoolConfigKey: PublicKey;
|
||||
tokenMintA: PublicKey;
|
||||
tokenMintB: PublicKey;
|
||||
whirlpoolPda: PDA;
|
||||
feeTierKey: PublicKey;
|
||||
tokenVaultAKeypair: Keypair;
|
||||
tokenVaultBKeypair: Keypair;
|
||||
tickSpacing: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
export type InitTickArrayParams = {
|
||||
whirlpool: PublicKey;
|
||||
tickArrayPda: PDA;
|
||||
startTick: number;
|
||||
funder: PublicKey;
|
||||
};
|
||||
|
||||
export type InitializeRewardParams = {
|
||||
rewardAuthority: PublicKey;
|
||||
funder: PublicKey;
|
||||
whirlpool: PublicKey;
|
||||
rewardMint: PublicKey;
|
||||
rewardVaultKeypair: Keypair;
|
||||
rewardIndex: number;
|
||||
};
|
||||
|
||||
export type SetRewardEmissionsParams = {
|
||||
rewardAuthority: PublicKey;
|
||||
whirlpool: PublicKey;
|
||||
rewardIndex: number;
|
||||
rewardVault: PublicKey;
|
||||
emissionsPerSecondX64: BN;
|
||||
};
|
||||
|
||||
export type OpenPositionParams = {
|
||||
funder: PublicKey;
|
||||
ownerKey: PublicKey;
|
||||
positionPda: PDA;
|
||||
metadataPda?: PDA;
|
||||
positionMintAddress: PublicKey;
|
||||
positionTokenAccountAddress: PublicKey;
|
||||
whirlpoolKey: PublicKey;
|
||||
tickLowerIndex: number;
|
||||
tickUpperIndex: number;
|
||||
};
|
||||
|
||||
export type ClosePositionParams = {
|
||||
positionAuthority: PublicKey;
|
||||
receiver: PublicKey;
|
||||
position: PublicKey;
|
||||
positionMint: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
};
|
||||
|
||||
export type IncreaseLiquidityParams = {
|
||||
liquidityAmount: BN;
|
||||
tokenMaxA: u64;
|
||||
tokenMaxB: u64;
|
||||
whirlpool: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
tickArrayLower: PublicKey;
|
||||
tickArrayUpper: PublicKey;
|
||||
};
|
||||
|
||||
export type DecreaseLiquidityParams = {
|
||||
liquidityAmount: BN;
|
||||
tokenMinA: u64;
|
||||
tokenMinB: u64;
|
||||
whirlpool: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
tickArrayLower: PublicKey;
|
||||
tickArrayUpper: PublicKey;
|
||||
};
|
||||
|
||||
export type UpdateFeesAndRewardsParams = {
|
||||
whirlpool: PublicKey;
|
||||
position: PublicKey;
|
||||
tickArrayLower: PublicKey;
|
||||
tickArrayUpper: PublicKey;
|
||||
};
|
||||
|
||||
export type CollectFeesParams = {
|
||||
whirlpool: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
};
|
||||
|
||||
export type CollectRewardParams = {
|
||||
whirlpool: PublicKey;
|
||||
positionAuthority: PublicKey;
|
||||
position: PublicKey;
|
||||
positionTokenAccount: PublicKey;
|
||||
rewardOwnerAccount: PublicKey;
|
||||
rewardVault: PublicKey;
|
||||
rewardIndex: number;
|
||||
};
|
||||
|
||||
export type CollectProtocolFeesParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
whirlpool: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
tokenDestinationA: PublicKey;
|
||||
tokenDestinationB: PublicKey;
|
||||
};
|
||||
|
||||
export type SwapParams = {
|
||||
amount: u64;
|
||||
otherAmountThreshold: u64;
|
||||
sqrtPriceLimit: BN;
|
||||
amountSpecifiedIsInput: boolean;
|
||||
aToB: boolean;
|
||||
whirlpool: PublicKey;
|
||||
tokenAuthority: PublicKey;
|
||||
tokenOwnerAccountA: PublicKey;
|
||||
tokenVaultA: PublicKey;
|
||||
tokenOwnerAccountB: PublicKey;
|
||||
tokenVaultB: PublicKey;
|
||||
tickArray0: PublicKey;
|
||||
tickArray1: PublicKey;
|
||||
tickArray2: PublicKey;
|
||||
oracle: PublicKey;
|
||||
};
|
||||
|
||||
export type SetRewardEmissionsSuperAuthorityParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
newRewardEmissionsSuperAuthority: PublicKey;
|
||||
};
|
||||
|
||||
export type SetRewardAuthorityParams = {
|
||||
whirlpool: PublicKey;
|
||||
rewardAuthority: PublicKey;
|
||||
newRewardAuthority: PublicKey;
|
||||
rewardIndex: number;
|
||||
};
|
||||
|
||||
export type SetRewardAuthorityBySuperAuthorityParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
whirlpool: PublicKey;
|
||||
rewardEmissionsSuperAuthority: PublicKey;
|
||||
newRewardAuthority: PublicKey;
|
||||
rewardIndex: number;
|
||||
};
|
||||
|
||||
export type SetFeeAuthorityParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
newFeeAuthority: PublicKey;
|
||||
};
|
||||
|
||||
export type SetCollectProtocolFeesAuthorityParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
collectProtocolFeesAuthority: PublicKey;
|
||||
newCollectProtocolFeesAuthority: PublicKey;
|
||||
};
|
||||
|
||||
export type SetFeeRateParams = {
|
||||
whirlpool: PublicKey;
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
feeRate: number;
|
||||
};
|
||||
|
||||
export type SetProtocolFeeRateParams = {
|
||||
whirlpool: PublicKey;
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
protocolFeeRate: number;
|
||||
};
|
||||
|
||||
export type SetDefaultFeeRateParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
tickSpacing: number;
|
||||
defaultFeeRate: number;
|
||||
};
|
||||
|
||||
export type SetDefaultProtocolFeeRateParams = {
|
||||
whirlpoolsConfig: PublicKey;
|
||||
feeAuthority: PublicKey;
|
||||
defaultProtocolFeeRate: number;
|
||||
};
|
||||
export {
|
||||
CollectFeesParams,
|
||||
ClosePositionParams,
|
||||
CollectProtocolFeesParams,
|
||||
CollectRewardParams,
|
||||
SetCollectProtocolFeesAuthorityParams,
|
||||
SetDefaultFeeRateParams,
|
||||
SetDefaultProtocolFeeRateParams,
|
||||
SetFeeAuthorityParams,
|
||||
SetFeeRateParams,
|
||||
SetProtocolFeeRateParams,
|
||||
SetRewardAuthorityBySuperAuthorityParams,
|
||||
SetRewardAuthorityParams,
|
||||
SetRewardEmissionsParams,
|
||||
SetRewardEmissionsSuperAuthorityParams,
|
||||
IncreaseLiquidityParams,
|
||||
IncreaseLiquidityInput,
|
||||
DecreaseLiquidityInput,
|
||||
DecreaseLiquidityParams,
|
||||
OpenPositionParams,
|
||||
SwapInput,
|
||||
SwapParams,
|
||||
UpdateFeesAndRewardsParams,
|
||||
InitConfigParams,
|
||||
InitFeeTierParams,
|
||||
InitPoolParams,
|
||||
InitTickArrayParams,
|
||||
InitializeRewardParams,
|
||||
} from "../../instructions/";
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { utils } from "@project-serum/anchor";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { PDA } from "../types/public/helper-types";
|
||||
|
||||
export function findProgramAddress(seeds: (Uint8Array | Buffer)[], programId: PublicKey): PDA {
|
||||
const [publicKey, bump] = utils.publicKey.findProgramAddressSync(seeds, programId);
|
||||
return { publicKey, bump };
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { OpenPositionParams } from "../instructions";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { SystemProgram } from "@solana/web3.js";
|
||||
import { WhirlpoolContext } from "../context";
|
||||
import { Instruction, TransactionBuilder } from "@orca-so/common-sdk";
|
||||
|
||||
export function toTx(ctx: WhirlpoolContext, ix: Instruction): TransactionBuilder {
|
||||
return new TransactionBuilder(ctx.provider).addInstruction(ix);
|
||||
}
|
||||
|
||||
export function openPositionAccounts(params: OpenPositionParams) {
|
||||
const {
|
||||
funder,
|
||||
owner,
|
||||
positionPda,
|
||||
positionMintAddress,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolKey,
|
||||
} = params;
|
||||
return {
|
||||
funder: funder,
|
||||
owner,
|
||||
position: positionPda.publicKey,
|
||||
positionMint: positionMintAddress,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolKey,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
import { MathUtil, Percentage } from "@orca-so/common-sdk";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import {
|
||||
getLowerSqrtPriceFromTokenA,
|
||||
getUpperSqrtPriceFromTokenA,
|
||||
getUpperSqrtPriceFromTokenB,
|
||||
getLowerSqrtPriceFromTokenB,
|
||||
} from "./swap-utils";
|
||||
|
||||
export enum SwapDirection {
|
||||
AtoB = "Swap A to B",
|
||||
BtoA = "Swap B to A",
|
||||
}
|
||||
|
||||
export enum AmountSpecified {
|
||||
Input = "Specified input amount",
|
||||
Output = "Specified output amount",
|
||||
}
|
||||
|
||||
export enum PositionStatus {
|
||||
BelowRange,
|
||||
InRange,
|
||||
AboveRange,
|
||||
}
|
||||
|
||||
export class PositionUtil {
|
||||
private constructor() {}
|
||||
|
||||
public static getPositionStatus(
|
||||
tickCurrentIndex: number,
|
||||
tickLowerIndex: number,
|
||||
tickUpperIndex: number
|
||||
): PositionStatus {
|
||||
if (tickCurrentIndex < tickLowerIndex) {
|
||||
return PositionStatus.BelowRange;
|
||||
} else if (tickCurrentIndex < tickUpperIndex) {
|
||||
return PositionStatus.InRange;
|
||||
} else {
|
||||
return PositionStatus.AboveRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function adjustForSlippage(
|
||||
n: BN,
|
||||
{ numerator, denominator }: Percentage,
|
||||
adjustUp: boolean
|
||||
): BN {
|
||||
if (adjustUp) {
|
||||
return n.mul(denominator.add(numerator)).div(denominator);
|
||||
} else {
|
||||
return n.mul(denominator).div(denominator.add(numerator));
|
||||
}
|
||||
}
|
||||
|
||||
export function adjustAmountForSlippage(
|
||||
amountIn: BN,
|
||||
amountOut: BN,
|
||||
{ numerator, denominator }: Percentage,
|
||||
amountSpecified: AmountSpecified
|
||||
): BN {
|
||||
if (amountSpecified === AmountSpecified.Input) {
|
||||
return amountOut.mul(denominator).div(denominator.add(numerator));
|
||||
} else {
|
||||
return amountIn.mul(denominator.add(numerator)).div(denominator);
|
||||
}
|
||||
}
|
||||
|
||||
export function getLiquidityFromTokenA(
|
||||
amount: BN,
|
||||
sqrtPriceLowerX64: BN,
|
||||
sqrtPriceUpperX64: BN,
|
||||
roundUp: boolean
|
||||
) {
|
||||
const result = amount
|
||||
.mul(sqrtPriceLowerX64)
|
||||
.mul(sqrtPriceUpperX64)
|
||||
.div(sqrtPriceUpperX64.sub(sqrtPriceLowerX64));
|
||||
if (roundUp) {
|
||||
return MathUtil.shiftRightRoundUp(result);
|
||||
} else {
|
||||
return result.shrn(64);
|
||||
}
|
||||
}
|
||||
|
||||
export function getLiquidityFromTokenB(
|
||||
amount: BN,
|
||||
sqrtPriceLowerX64: BN,
|
||||
sqrtPriceUpperX64: BN,
|
||||
roundUp: boolean
|
||||
) {
|
||||
const numerator = amount.shln(64);
|
||||
const denominator = sqrtPriceUpperX64.sub(sqrtPriceLowerX64);
|
||||
if (roundUp) {
|
||||
return MathUtil.divRoundUp(numerator, denominator);
|
||||
} else {
|
||||
return numerator.div(denominator);
|
||||
}
|
||||
}
|
||||
|
||||
function orderSqrtPrice(sqrtPrice0X64: BN, sqrtPrice1X64: BN): [BN, BN] {
|
||||
if (sqrtPrice0X64.lt(sqrtPrice1X64)) {
|
||||
return [sqrtPrice0X64, sqrtPrice1X64];
|
||||
} else {
|
||||
return [sqrtPrice1X64, sqrtPrice0X64];
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenAFromLiquidity(
|
||||
liquidity: BN,
|
||||
sqrtPrice0X64: BN,
|
||||
sqrtPrice1X64: BN,
|
||||
roundUp: boolean
|
||||
) {
|
||||
const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64);
|
||||
|
||||
const numerator = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64)).shln(64);
|
||||
const denominator = sqrtPriceUpperX64.mul(sqrtPriceLowerX64);
|
||||
if (roundUp) {
|
||||
return MathUtil.divRoundUp(numerator, denominator);
|
||||
} else {
|
||||
return numerator.div(denominator);
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenBFromLiquidity(
|
||||
liquidity: BN,
|
||||
sqrtPrice0X64: BN,
|
||||
sqrtPrice1X64: BN,
|
||||
roundUp: boolean
|
||||
) {
|
||||
const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64);
|
||||
|
||||
const result = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64));
|
||||
if (roundUp) {
|
||||
return MathUtil.shiftRightRoundUp(result);
|
||||
} else {
|
||||
return result.shrn(64);
|
||||
}
|
||||
}
|
||||
|
||||
export function getAmountFixedDelta(
|
||||
currentSqrtPriceX64: BN,
|
||||
targetSqrtPriceX64: BN,
|
||||
liquidity: BN,
|
||||
amountSpecified: AmountSpecified,
|
||||
swapDirection: SwapDirection
|
||||
) {
|
||||
if ((amountSpecified == AmountSpecified.Input) == (swapDirection == SwapDirection.AtoB)) {
|
||||
return getTokenAFromLiquidity(
|
||||
liquidity,
|
||||
currentSqrtPriceX64,
|
||||
targetSqrtPriceX64,
|
||||
amountSpecified == AmountSpecified.Input
|
||||
);
|
||||
} else {
|
||||
return getTokenBFromLiquidity(
|
||||
liquidity,
|
||||
currentSqrtPriceX64,
|
||||
targetSqrtPriceX64,
|
||||
amountSpecified == AmountSpecified.Input
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function getAmountUnfixedDelta(
|
||||
currentSqrtPriceX64: BN,
|
||||
targetSqrtPriceX64: BN,
|
||||
liquidity: BN,
|
||||
amountSpecified: AmountSpecified,
|
||||
swapDirection: SwapDirection
|
||||
) {
|
||||
if ((amountSpecified == AmountSpecified.Input) == (swapDirection == SwapDirection.AtoB)) {
|
||||
return getTokenBFromLiquidity(
|
||||
liquidity,
|
||||
currentSqrtPriceX64,
|
||||
targetSqrtPriceX64,
|
||||
amountSpecified == AmountSpecified.Output
|
||||
);
|
||||
} else {
|
||||
return getTokenAFromLiquidity(
|
||||
liquidity,
|
||||
currentSqrtPriceX64,
|
||||
targetSqrtPriceX64,
|
||||
amountSpecified == AmountSpecified.Output
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function getNextSqrtPrice(
|
||||
sqrtPriceX64: BN,
|
||||
liquidity: BN,
|
||||
amount: BN,
|
||||
amountSpecified: AmountSpecified,
|
||||
swapDirection: SwapDirection
|
||||
) {
|
||||
if (amountSpecified === AmountSpecified.Input && swapDirection === SwapDirection.AtoB) {
|
||||
return getLowerSqrtPriceFromTokenA(amount, liquidity, sqrtPriceX64);
|
||||
} else if (amountSpecified === AmountSpecified.Output && swapDirection === SwapDirection.BtoA) {
|
||||
return getUpperSqrtPriceFromTokenA(amount, liquidity, sqrtPriceX64);
|
||||
} else if (amountSpecified === AmountSpecified.Input && swapDirection === SwapDirection.BtoA) {
|
||||
return getUpperSqrtPriceFromTokenB(amount, liquidity, sqrtPriceX64);
|
||||
} else {
|
||||
return getLowerSqrtPriceFromTokenB(amount, liquidity, sqrtPriceX64);
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { findProgramAddress } from "../find-program-address";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
|
||||
const PDA_WHIRLPOOL_SEED = "whirlpool";
|
||||
const PDA_VAULT_A_SEED = "token_vault_a";
|
||||
const PDA_VAULT_B_SEED = "token_vault_b";
|
||||
const PDA_POSITION_SEED = "position";
|
||||
const PDA_METADATA_SEED = "metadata";
|
||||
const PDA_TICK_ARRAY_SEED = "tick_array";
|
||||
const PDA_FEE_TIER_SEED = "fee_tier";
|
||||
const PDA_ORACLE_SEED = "oracle";
|
||||
|
||||
export function getWhirlpoolPda(
|
||||
programId: PublicKey,
|
||||
whirlpoolConfigKey: PublicKey,
|
||||
tokenMintAKey: PublicKey,
|
||||
tokenMintBKey: PublicKey,
|
||||
tickSpacing: number
|
||||
) {
|
||||
return findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_WHIRLPOOL_SEED),
|
||||
whirlpoolConfigKey.toBuffer(),
|
||||
tokenMintAKey.toBuffer(),
|
||||
tokenMintBKey.toBuffer(),
|
||||
new BN(tickSpacing).toArrayLike(Buffer, "le", 2),
|
||||
],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
export function getWhirlpoolVaultAPda(
|
||||
programId: PublicKey,
|
||||
whirlpoolKey: PublicKey,
|
||||
tokenMintAKey: PublicKey
|
||||
) {
|
||||
return findProgramAddress(
|
||||
[Buffer.from(PDA_VAULT_A_SEED), whirlpoolKey.toBuffer(), tokenMintAKey.toBuffer()],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
export function getWhirlpoolVaultBPda(
|
||||
programId: PublicKey,
|
||||
whirlpoolKey: PublicKey,
|
||||
tokenMintBKey: PublicKey
|
||||
) {
|
||||
return findProgramAddress(
|
||||
[Buffer.from(PDA_VAULT_B_SEED), whirlpoolKey.toBuffer(), tokenMintBKey.toBuffer()],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
export function getPositionPda(programId: PublicKey, positionMintKey: PublicKey) {
|
||||
return findProgramAddress(
|
||||
[Buffer.from(PDA_POSITION_SEED), positionMintKey.toBuffer()],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
export const METADATA_PROGRAM_ADDRESS = new PublicKey(
|
||||
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
|
||||
);
|
||||
export function getPositionMetadataPda(positionMintKey: PublicKey) {
|
||||
return findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_METADATA_SEED),
|
||||
METADATA_PROGRAM_ADDRESS.toBuffer(),
|
||||
positionMintKey.toBuffer(),
|
||||
],
|
||||
METADATA_PROGRAM_ADDRESS
|
||||
);
|
||||
}
|
||||
|
||||
export function getTickArrayPda(
|
||||
programId: PublicKey,
|
||||
whirlpoolAddress: PublicKey,
|
||||
startTick: number
|
||||
) {
|
||||
return findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_TICK_ARRAY_SEED),
|
||||
whirlpoolAddress.toBuffer(),
|
||||
Buffer.from(startTick.toString()),
|
||||
],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
export function getFeeTierPda(
|
||||
programId: PublicKey,
|
||||
whirlpoolsConfigAddress: PublicKey,
|
||||
tickSpacing: number
|
||||
) {
|
||||
return findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_FEE_TIER_SEED),
|
||||
whirlpoolsConfigAddress.toBuffer(),
|
||||
new BN(tickSpacing).toArrayLike(Buffer, "le", 2),
|
||||
],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
export function getOraclePda(programId: PublicKey, whirlpoolAddress: PublicKey) {
|
||||
return findProgramAddress([Buffer.from(PDA_ORACLE_SEED), whirlpoolAddress.toBuffer()], programId);
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
export * from "./addresses";
|
||||
export * from "./math";
|
||||
export * from "./parse";
|
||||
export * from "./pda-utils";
|
||||
export * from "./price-math";
|
||||
export * from "./tick-utils";
|
||||
export * from "./liquidity";
|
||||
export * from "./pool-utils";
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
import { BN } from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { min } from "bn.js";
|
||||
import Decimal from "decimal.js";
|
||||
import {
|
||||
fromX64_BN,
|
||||
fromX64_Decimal,
|
||||
tickIndexToSqrtPriceX64,
|
||||
toX64,
|
||||
toX64_BN,
|
||||
toX64_Decimal,
|
||||
} from ".";
|
||||
|
||||
export type TokenAmounts = {
|
||||
tokenA: u64;
|
||||
tokenB: u64;
|
||||
};
|
||||
|
||||
export function toTokenAmount(a: number, b: number): TokenAmounts {
|
||||
return {
|
||||
tokenA: new u64(a.toString()),
|
||||
tokenB: new u64(b.toString()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the liquidity amount required to increase/decrease liquidity.
|
||||
*
|
||||
* // TODO: At the top end of the price range, tick calcuation is off therefore the results can be off
|
||||
*
|
||||
* @param currTick - Whirlpool's current tick index (aka price)
|
||||
* @param lowerTick - Position lower tick index
|
||||
* @param upperTick - Position upper tick index
|
||||
* @param tokenAmount - The desired amount of tokens to deposit/withdraw
|
||||
* @returns An estimated amount of liquidity needed to deposit/withdraw the desired amount of tokens.
|
||||
*/
|
||||
export function estimateLiquidityFromTokenAmounts(
|
||||
currTick: number,
|
||||
lowerTick: number,
|
||||
upperTick: number,
|
||||
tokenAmount: TokenAmounts
|
||||
): BN {
|
||||
if (upperTick < lowerTick) {
|
||||
throw new Error("upper tick cannot be lower than the lower tick");
|
||||
}
|
||||
|
||||
const currSqrtPrice = tickIndexToSqrtPriceX64(currTick);
|
||||
const lowerSqrtPrice = tickIndexToSqrtPriceX64(lowerTick);
|
||||
const upperSqrtPrice = tickIndexToSqrtPriceX64(upperTick);
|
||||
|
||||
if (currTick >= upperTick) {
|
||||
return estLiquidityForTokenB(upperSqrtPrice, lowerSqrtPrice, tokenAmount.tokenB);
|
||||
} else if (currTick < lowerTick) {
|
||||
return estLiquidityForTokenA(lowerSqrtPrice, upperSqrtPrice, tokenAmount.tokenA);
|
||||
} else {
|
||||
const estLiquidityAmountA = estLiquidityForTokenA(
|
||||
currSqrtPrice,
|
||||
upperSqrtPrice,
|
||||
tokenAmount.tokenA
|
||||
);
|
||||
const estLiquidityAmountB = estLiquidityForTokenB(
|
||||
currSqrtPrice,
|
||||
lowerSqrtPrice,
|
||||
tokenAmount.tokenB
|
||||
);
|
||||
return BN.min(estLiquidityAmountA, estLiquidityAmountB);
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenAmountsFromLiquidity(
|
||||
liquidity: u64,
|
||||
currentPrice: u64,
|
||||
lowerPrice: u64,
|
||||
upperPrice: u64,
|
||||
round_up: boolean
|
||||
): TokenAmounts {
|
||||
const _liquidity = new Decimal(liquidity.toString());
|
||||
const _currentPrice = new Decimal(currentPrice.toString());
|
||||
const _lowerPrice = new Decimal(lowerPrice.toString());
|
||||
const _upperPrice = new Decimal(upperPrice.toString());
|
||||
let tokenA, tokenB;
|
||||
if (currentPrice.lt(lowerPrice)) {
|
||||
// x = L * (pb - pa) / (pa * pb)
|
||||
tokenA = toX64_Decimal(_liquidity)
|
||||
.mul(_upperPrice.sub(_lowerPrice))
|
||||
.div(_lowerPrice.mul(_upperPrice));
|
||||
tokenB = new Decimal(0);
|
||||
} else if (currentPrice.lt(upperPrice)) {
|
||||
// x = L * (pb - p) / (p * pb)
|
||||
// y = L * (p - pa)
|
||||
tokenA = toX64_Decimal(_liquidity)
|
||||
.mul(_upperPrice.sub(_currentPrice))
|
||||
.div(_currentPrice.mul(_upperPrice));
|
||||
tokenB = fromX64_Decimal(_liquidity.mul(_currentPrice.sub(_lowerPrice)));
|
||||
} else {
|
||||
// y = L * (pb - pa)
|
||||
tokenA = new Decimal(0);
|
||||
tokenB = fromX64_Decimal(_liquidity.mul(_upperPrice.sub(_lowerPrice)));
|
||||
}
|
||||
|
||||
// TODO: round up
|
||||
if (round_up) {
|
||||
return {
|
||||
tokenA: new u64(tokenA.ceil().toString()),
|
||||
tokenB: new u64(tokenB.ceil().toString()),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
tokenA: new u64(tokenA.floor().toString()),
|
||||
tokenB: new u64(tokenB.floor().toString()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Convert this function based on Delta A = Delta L * (1/sqrt(lower) - 1/sqrt(upper))
|
||||
function estLiquidityForTokenA(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) {
|
||||
const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2);
|
||||
const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2);
|
||||
|
||||
const num = fromX64_BN(tokenAmount.mul(upperSqrtPriceX64).mul(lowerSqrtPriceX64));
|
||||
const dem = upperSqrtPriceX64.sub(lowerSqrtPriceX64);
|
||||
|
||||
return num.div(dem);
|
||||
}
|
||||
|
||||
// Convert this function based on Delta B = Delta L * (sqrt_price(upper) - sqrt_price(lower))
|
||||
function estLiquidityForTokenB(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) {
|
||||
const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2);
|
||||
const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2);
|
||||
|
||||
const delta = upperSqrtPriceX64.sub(lowerSqrtPriceX64);
|
||||
|
||||
return toX64_BN(tokenAmount).div(delta);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { BN } from "@project-serum/anchor";
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
export function toX64_BN(num: BN): BN {
|
||||
return num.mul(new BN(2).pow(new BN(64)));
|
||||
}
|
||||
|
||||
export function toX64_Decimal(num: Decimal): Decimal {
|
||||
return num.mul(Decimal.pow(2, 64));
|
||||
}
|
||||
|
||||
export function toX64(num: Decimal): BN {
|
||||
return new BN(num.mul(Decimal.pow(2, 64)).floor().toFixed());
|
||||
}
|
||||
|
||||
export function fromX64(num: BN): Decimal {
|
||||
return new Decimal(num.toString()).mul(Decimal.pow(2, -64));
|
||||
}
|
||||
|
||||
export function fromX64_Decimal(num: Decimal): Decimal {
|
||||
return num.mul(Decimal.pow(2, -64));
|
||||
}
|
||||
|
||||
export function fromX64_BN(num: BN): BN {
|
||||
return num.div(new BN(2).pow(new BN(64)));
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
import { AccountsCoder, Coder, Idl } from "@project-serum/anchor";
|
||||
import { WhirlpoolConfigAccount } from "../..";
|
||||
import {
|
||||
AccountName,
|
||||
PositionData,
|
||||
TickArrayData,
|
||||
WhirlpoolData,
|
||||
} from "../../types/public/anchor-types";
|
||||
import * as WhirlpoolIDL from "../../artifacts/whirlpool.json";
|
||||
|
||||
const WhirlpoolCoder = new Coder(WhirlpoolIDL as Idl);
|
||||
|
||||
export function parseWhirlpoolsConfig(data: Buffer): WhirlpoolConfigAccount | null {
|
||||
return parse(AccountName.WhirlpoolsConfig, data);
|
||||
}
|
||||
|
||||
export function parseWhirlpool(data: Buffer): WhirlpoolData | null {
|
||||
return parse(AccountName.Whirlpool, data);
|
||||
}
|
||||
|
||||
export function parsePosition(data: Buffer): PositionData | null {
|
||||
return parse(AccountName.Position, data);
|
||||
}
|
||||
|
||||
export function parseTickArray(data: Buffer): TickArrayData | null {
|
||||
return parse(AccountName.TickArray, data);
|
||||
}
|
||||
|
||||
function parse(accountName: AccountName, data: Buffer) {
|
||||
const discriminator = AccountsCoder.accountDiscriminator(accountName);
|
||||
if (discriminator.compare(data.slice(0, 8))) {
|
||||
console.error("incorrect account name during parsing");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return WhirlpoolCoder.accounts.decode(accountName, data);
|
||||
} catch (_e) {
|
||||
console.error("unknown account name during parsing");
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
import { AddressUtil } from "@orca-so/common-sdk";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { METADATA_PROGRAM_ADDRESS } from "../../types/public";
|
||||
import { PriceMath } from "./price-math";
|
||||
import { TickUtil } from "./tick-utils";
|
||||
|
||||
const PDA_WHIRLPOOL_SEED = "whirlpool";
|
||||
const PDA_POSITION_SEED = "position";
|
||||
const PDA_METADATA_SEED = "metadata";
|
||||
const PDA_TICK_ARRAY_SEED = "tick_array";
|
||||
const PDA_FEE_TIER_SEED = "fee_tier";
|
||||
const PDA_ORACLE_SEED = "oracle";
|
||||
|
||||
/**
|
||||
* @category Program Derived Addresses
|
||||
*/
|
||||
export class PDAUtil {
|
||||
/**
|
||||
*
|
||||
* @param programId
|
||||
* @param whirlpoolsConfigKey
|
||||
* @param tokenMintAKey
|
||||
* @param tokenMintBKey
|
||||
* @param tickSpacing
|
||||
* @returns
|
||||
*/
|
||||
public static getWhirlpool(
|
||||
programId: PublicKey,
|
||||
whirlpoolsConfigKey: PublicKey,
|
||||
tokenMintAKey: PublicKey,
|
||||
tokenMintBKey: PublicKey,
|
||||
tickSpacing: number
|
||||
) {
|
||||
return AddressUtil.findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_WHIRLPOOL_SEED),
|
||||
whirlpoolsConfigKey.toBuffer(),
|
||||
tokenMintAKey.toBuffer(),
|
||||
tokenMintBKey.toBuffer(),
|
||||
new BN(tickSpacing).toArrayLike(Buffer, "le", 2),
|
||||
],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Program Derived Addresses
|
||||
* @param programId
|
||||
* @param positionMintKey
|
||||
* @returns
|
||||
*/
|
||||
public static getPosition(programId: PublicKey, positionMintKey: PublicKey) {
|
||||
return AddressUtil.findProgramAddress(
|
||||
[Buffer.from(PDA_POSITION_SEED), positionMintKey.toBuffer()],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Program Derived Addresses
|
||||
* @param positionMintKey
|
||||
* @returns
|
||||
*/
|
||||
public static getPositionMetadata(positionMintKey: PublicKey) {
|
||||
return AddressUtil.findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_METADATA_SEED),
|
||||
METADATA_PROGRAM_ADDRESS.toBuffer(),
|
||||
positionMintKey.toBuffer(),
|
||||
],
|
||||
METADATA_PROGRAM_ADDRESS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Program Derived Addresses
|
||||
* @param programId
|
||||
* @param whirlpoolAddress
|
||||
* @param startTick
|
||||
* @returns
|
||||
*/
|
||||
public static getTickArray(programId: PublicKey, whirlpoolAddress: PublicKey, startTick: number) {
|
||||
return AddressUtil.findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_TICK_ARRAY_SEED),
|
||||
whirlpoolAddress.toBuffer(),
|
||||
Buffer.from(startTick.toString()),
|
||||
],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDA of the tick array containing tickIndex.
|
||||
* tickArrayOffset can be used to get neighboring tick arrays.
|
||||
*
|
||||
* @param tickIndex
|
||||
* @param tickSpacing
|
||||
* @param whirlpool
|
||||
* @param programId
|
||||
* @param tickArrayOffset
|
||||
* @returns
|
||||
*/
|
||||
public static getTickArrayFromTickIndex(
|
||||
tickIndex: number,
|
||||
tickSpacing: number,
|
||||
whirlpool: PublicKey,
|
||||
programId: PublicKey,
|
||||
tickArrayOffset = 0
|
||||
) {
|
||||
const startIndex = TickUtil.getStartTickIndex(tickIndex, tickSpacing, tickArrayOffset);
|
||||
return PDAUtil.getTickArray(
|
||||
AddressUtil.toPubKey(programId),
|
||||
AddressUtil.toPubKey(whirlpool),
|
||||
startIndex
|
||||
);
|
||||
}
|
||||
|
||||
public static getTickArrayFromSqrtPrice(
|
||||
sqrtPriceX64: BN,
|
||||
tickSpacing: number,
|
||||
whirlpool: PublicKey,
|
||||
programId: PublicKey,
|
||||
tickArrayOffset = 0
|
||||
) {
|
||||
const tickIndex = PriceMath.sqrtPriceX64ToTickIndex(sqrtPriceX64);
|
||||
return PDAUtil.getTickArrayFromTickIndex(
|
||||
tickIndex,
|
||||
tickSpacing,
|
||||
whirlpool,
|
||||
programId,
|
||||
tickArrayOffset
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Program Derived Addresses
|
||||
* @param programId
|
||||
* @param whirlpoolsConfigAddress
|
||||
* @param tickSpacing
|
||||
* @returns
|
||||
*/
|
||||
public static getFeeTier(
|
||||
programId: PublicKey,
|
||||
whirlpoolsConfigAddress: PublicKey,
|
||||
tickSpacing: number
|
||||
) {
|
||||
return AddressUtil.findProgramAddress(
|
||||
[
|
||||
Buffer.from(PDA_FEE_TIER_SEED),
|
||||
whirlpoolsConfigAddress.toBuffer(),
|
||||
new BN(tickSpacing).toArrayLike(Buffer, "le", 2),
|
||||
],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Program Derived Addresses
|
||||
* @param programId
|
||||
* @param whirlpoolAddress
|
||||
* @returns
|
||||
*/
|
||||
public static getOracle(programId: PublicKey, whirlpoolAddress: PublicKey) {
|
||||
return AddressUtil.findProgramAddress(
|
||||
[Buffer.from(PDA_ORACLE_SEED), whirlpoolAddress.toBuffer()],
|
||||
programId
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
import { AddressUtil, MathUtil, Percentage } from "@orca-so/common-sdk";
|
||||
import { Address, BN } from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import Decimal from "decimal.js";
|
||||
import { MAX_TICK_ARRAY_CROSSINGS, WhirlpoolRewardInfoData } from "../../types/public";
|
||||
import { PDAUtil } from "./pda-utils";
|
||||
import { PriceMath } from "./price-math";
|
||||
import { TickUtil } from "./tick-utils";
|
||||
|
||||
/**
|
||||
* @category Whirlpool Utils
|
||||
*/
|
||||
export class PoolUtil {
|
||||
private constructor() {}
|
||||
|
||||
public static isRewardInitialized(rewardInfo: WhirlpoolRewardInfoData): boolean {
|
||||
return (
|
||||
!PublicKey.default.equals(rewardInfo.mint) && !PublicKey.default.equals(rewardInfo.vault)
|
||||
);
|
||||
}
|
||||
|
||||
public static getFeeRate(feeRate: number): Percentage {
|
||||
/**
|
||||
* Smart Contract comment: https://github.com/orca-so/whirlpool/blob/main/programs/whirlpool/src/state/whirlpool.rs#L9-L11
|
||||
* // Stored as hundredths of a basis point
|
||||
* // u16::MAX corresponds to ~6.5%
|
||||
* pub fee_rate: u16,
|
||||
*/
|
||||
return Percentage.fromFraction(feeRate, 1e6); // TODO
|
||||
}
|
||||
|
||||
public static getProtocolFeeRate(protocolFeeRate: number): Percentage {
|
||||
/**
|
||||
* Smart Contract comment: https://github.com/orca-so/whirlpool/blob/main/programs/whirlpool/src/state/whirlpool.rs#L13-L14
|
||||
* // Stored as a basis point
|
||||
* pub protocol_fee_rate: u16,
|
||||
*/
|
||||
return Percentage.fromFraction(protocolFeeRate, 1e4); // TODO
|
||||
}
|
||||
|
||||
public static orderMints(mintX: Address, mintY: Address): [Address, Address] {
|
||||
let mintA, mintB;
|
||||
if (
|
||||
Buffer.compare(
|
||||
AddressUtil.toPubKey(mintX).toBuffer(),
|
||||
AddressUtil.toPubKey(mintY).toBuffer()
|
||||
) < 0
|
||||
) {
|
||||
mintA = mintX;
|
||||
mintB = mintY;
|
||||
} else {
|
||||
mintA = mintY;
|
||||
mintB = mintX;
|
||||
}
|
||||
|
||||
return [mintA, mintB];
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Whirlpool Utils
|
||||
* @param liquidity
|
||||
* @param currentPrice
|
||||
* @param lowerPrice
|
||||
* @param upperPrice
|
||||
* @param round_up
|
||||
* @returns
|
||||
*/
|
||||
public static getTokenAmountsFromLiquidity(
|
||||
liquidity: u64,
|
||||
currentPrice: u64,
|
||||
lowerPrice: u64,
|
||||
upperPrice: u64,
|
||||
round_up: boolean
|
||||
): TokenAmounts {
|
||||
const _liquidity = new Decimal(liquidity.toString());
|
||||
const _currentPrice = new Decimal(currentPrice.toString());
|
||||
const _lowerPrice = new Decimal(lowerPrice.toString());
|
||||
const _upperPrice = new Decimal(upperPrice.toString());
|
||||
let tokenA, tokenB;
|
||||
if (currentPrice.lt(lowerPrice)) {
|
||||
// x = L * (pb - pa) / (pa * pb)
|
||||
tokenA = MathUtil.toX64_Decimal(_liquidity)
|
||||
.mul(_upperPrice.sub(_lowerPrice))
|
||||
.div(_lowerPrice.mul(_upperPrice));
|
||||
tokenB = new Decimal(0);
|
||||
} else if (currentPrice.lt(upperPrice)) {
|
||||
// x = L * (pb - p) / (p * pb)
|
||||
// y = L * (p - pa)
|
||||
tokenA = MathUtil.toX64_Decimal(_liquidity)
|
||||
.mul(_upperPrice.sub(_currentPrice))
|
||||
.div(_currentPrice.mul(_upperPrice));
|
||||
tokenB = MathUtil.fromX64_Decimal(_liquidity.mul(_currentPrice.sub(_lowerPrice)));
|
||||
} else {
|
||||
// y = L * (pb - pa)
|
||||
tokenA = new Decimal(0);
|
||||
tokenB = MathUtil.fromX64_Decimal(_liquidity.mul(_upperPrice.sub(_lowerPrice)));
|
||||
}
|
||||
|
||||
// TODO: round up
|
||||
if (round_up) {
|
||||
return {
|
||||
tokenA: new u64(tokenA.ceil().toString()),
|
||||
tokenB: new u64(tokenB.ceil().toString()),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
tokenA: new u64(tokenA.floor().toString()),
|
||||
tokenB: new u64(tokenB.floor().toString()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the liquidity amount required to increase/decrease liquidity.
|
||||
*
|
||||
* // TODO: At the top end of the price range, tick calcuation is off therefore the results can be off
|
||||
*
|
||||
* @category Whirlpool Utils
|
||||
* @param currTick - Whirlpool's current tick index (aka price)
|
||||
* @param lowerTick - Position lower tick index
|
||||
* @param upperTick - Position upper tick index
|
||||
* @param tokenAmount - The desired amount of tokens to deposit/withdraw
|
||||
* @returns An estimated amount of liquidity needed to deposit/withdraw the desired amount of tokens.
|
||||
*/
|
||||
public static estimateLiquidityFromTokenAmounts(
|
||||
currTick: number,
|
||||
lowerTick: number,
|
||||
upperTick: number,
|
||||
tokenAmount: TokenAmounts
|
||||
): BN {
|
||||
if (upperTick < lowerTick) {
|
||||
throw new Error("upper tick cannot be lower than the lower tick");
|
||||
}
|
||||
|
||||
const currSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(currTick);
|
||||
const lowerSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(lowerTick);
|
||||
const upperSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(upperTick);
|
||||
|
||||
if (currTick >= upperTick) {
|
||||
return estLiquidityForTokenB(upperSqrtPrice, lowerSqrtPrice, tokenAmount.tokenB);
|
||||
} else if (currTick < lowerTick) {
|
||||
return estLiquidityForTokenA(lowerSqrtPrice, upperSqrtPrice, tokenAmount.tokenA);
|
||||
} else {
|
||||
const estLiquidityAmountA = estLiquidityForTokenA(
|
||||
currSqrtPrice,
|
||||
upperSqrtPrice,
|
||||
tokenAmount.tokenA
|
||||
);
|
||||
const estLiquidityAmountB = estLiquidityForTokenB(
|
||||
currSqrtPrice,
|
||||
lowerSqrtPrice,
|
||||
tokenAmount.tokenB
|
||||
);
|
||||
return BN.min(estLiquidityAmountA, estLiquidityAmountB);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the current tick-index, returns the dervied PDA and fetched data
|
||||
* for the tick-arrays that this swap may traverse across.
|
||||
*
|
||||
* TODO: Handle the edge case where the first tick-array may be the previous array of
|
||||
* expected start-index due to slippage.
|
||||
*
|
||||
* @category Whirlpool Utils
|
||||
* @param tickCurrentIndex - The current tickIndex for the Whirlpool to swap on.
|
||||
* @param tickSpacing - The tickSpacing for the Whirlpool.
|
||||
* @param aToB - The direction of the trade.
|
||||
* @param programId - The Whirlpool programId which the Whirlpool lives on.
|
||||
* @param whirlpoolAddress - PublicKey of the whirlpool to swap on.
|
||||
* @returns An array of PublicKey[] for the tickArray accounts that this swap may traverse across.
|
||||
*/
|
||||
public static getTickArrayPublicKeysForSwap(
|
||||
tickCurrentIndex: number,
|
||||
tickSpacing: number,
|
||||
aToB: boolean,
|
||||
programId: PublicKey,
|
||||
whirlpoolAddress: PublicKey
|
||||
) {
|
||||
let offset = 0;
|
||||
let tickArrayAddresses: PublicKey[] = [];
|
||||
for (let i = 0; i <= MAX_TICK_ARRAY_CROSSINGS; i++) {
|
||||
const startIndex = TickUtil.getStartTickIndex(tickCurrentIndex, tickSpacing, offset);
|
||||
const pda = PDAUtil.getTickArray(programId, whirlpoolAddress, startIndex);
|
||||
tickArrayAddresses.push(pda.publicKey);
|
||||
offset = aToB ? offset - 1 : offset + 1;
|
||||
}
|
||||
|
||||
return tickArrayAddresses;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Whirlpool Utils
|
||||
*/
|
||||
export type TokenAmounts = {
|
||||
tokenA: u64;
|
||||
tokenB: u64;
|
||||
};
|
||||
|
||||
/**
|
||||
* @category Whirlpool Utils
|
||||
*/
|
||||
export function toTokenAmount(a: number, b: number): TokenAmounts {
|
||||
return {
|
||||
tokenA: new u64(a.toString()),
|
||||
tokenB: new u64(b.toString()),
|
||||
};
|
||||
}
|
||||
|
||||
// Convert this function based on Delta A = Delta L * (1/sqrt(lower) - 1/sqrt(upper))
|
||||
function estLiquidityForTokenA(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) {
|
||||
const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2);
|
||||
const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2);
|
||||
|
||||
const num = MathUtil.fromX64_BN(tokenAmount.mul(upperSqrtPriceX64).mul(lowerSqrtPriceX64));
|
||||
const dem = upperSqrtPriceX64.sub(lowerSqrtPriceX64);
|
||||
|
||||
return num.div(dem);
|
||||
}
|
||||
|
||||
// Convert this function based on Delta B = Delta L * (sqrt_price(upper) - sqrt_price(lower))
|
||||
function estLiquidityForTokenB(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) {
|
||||
const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2);
|
||||
const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2);
|
||||
|
||||
const delta = upperSqrtPriceX64.sub(lowerSqrtPriceX64);
|
||||
|
||||
return MathUtil.toX64_BN(tokenAmount).div(delta);
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import Decimal from "decimal.js";
|
||||
import { MAX_TICK_INDEX, MIN_TICK_INDEX, MAX_SQRT_PRICE, MIN_SQRT_PRICE } from "../../types/public";
|
||||
import { TickUtil } from "./tick-utils";
|
||||
|
||||
const BIT_PRECISION = 14;
|
||||
const LOG_B_2_X32 = "59543866431248";
|
||||
const LOG_B_P_ERR_MARGIN_LOWER_X64 = "184467440737095516";
|
||||
const LOG_B_P_ERR_MARGIN_UPPER_X64 = "15793534762490258745";
|
||||
|
||||
/**
|
||||
* A collection of utility functions to convert between price, tickIndex and sqrtPrice.
|
||||
*
|
||||
* @category Whirlpool Utils
|
||||
*/
|
||||
export class PriceMath {
|
||||
public static priceToSqrtPriceX64(price: Decimal, decimalsA: number, decimalsB: number): BN {
|
||||
return MathUtil.toX64(price.mul(Decimal.pow(10, decimalsB - decimalsA)).sqrt());
|
||||
}
|
||||
|
||||
public static sqrtPriceX64ToPrice(
|
||||
sqrtPriceX64: BN,
|
||||
decimalsA: number,
|
||||
decimalsB: number
|
||||
): Decimal {
|
||||
return MathUtil.fromX64(sqrtPriceX64)
|
||||
.pow(2)
|
||||
.mul(Decimal.pow(10, decimalsA - decimalsB));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tickIndex
|
||||
* @returns
|
||||
*/
|
||||
public static tickIndexToSqrtPriceX64(tickIndex: number): BN {
|
||||
if (tickIndex > MAX_TICK_INDEX || tickIndex < MIN_TICK_INDEX) {
|
||||
throw new Error("Provided tick index does not fit within supported tick index range.");
|
||||
}
|
||||
|
||||
if (tickIndex > 0) {
|
||||
return new BN(tickIndexToSqrtPricePositive(tickIndex));
|
||||
} else {
|
||||
return new BN(tickIndexToSqrtPriceNegative(tickIndex));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sqrtPriceX64
|
||||
* @returns
|
||||
*/
|
||||
public static sqrtPriceX64ToTickIndex(sqrtPriceX64: BN): number {
|
||||
if (sqrtPriceX64.gt(new BN(MAX_SQRT_PRICE)) || sqrtPriceX64.lt(new BN(MIN_SQRT_PRICE))) {
|
||||
throw new Error("Provided sqrtPrice is not within the supported sqrtPrice range.");
|
||||
}
|
||||
|
||||
const msb = sqrtPriceX64.bitLength() - 1;
|
||||
const adjustedMsb = new BN(msb - 64);
|
||||
const log2pIntegerX32 = signedShiftLeft(adjustedMsb, 32, 128);
|
||||
|
||||
let bit = new BN("8000000000000000", "hex");
|
||||
let precision = 0;
|
||||
let log2pFractionX64 = new BN(0);
|
||||
|
||||
let r = msb >= 64 ? sqrtPriceX64.shrn(msb - 63) : sqrtPriceX64.shln(63 - msb);
|
||||
|
||||
while (bit.gt(new BN(0)) && precision < BIT_PRECISION) {
|
||||
r = r.mul(r);
|
||||
let rMoreThanTwo = r.shrn(127);
|
||||
r = r.shrn(63 + rMoreThanTwo.toNumber());
|
||||
log2pFractionX64 = log2pFractionX64.add(bit.mul(rMoreThanTwo));
|
||||
bit = bit.shrn(1);
|
||||
precision += 1;
|
||||
}
|
||||
|
||||
const log2pFractionX32 = log2pFractionX64.shrn(32);
|
||||
|
||||
const log2pX32 = log2pIntegerX32.add(log2pFractionX32);
|
||||
const logbpX64 = log2pX32.mul(new BN(LOG_B_2_X32));
|
||||
|
||||
const tickLow = signedShiftRight(
|
||||
logbpX64.sub(new BN(LOG_B_P_ERR_MARGIN_LOWER_X64)),
|
||||
64,
|
||||
128
|
||||
).toNumber();
|
||||
const tickHigh = signedShiftRight(
|
||||
logbpX64.add(new BN(LOG_B_P_ERR_MARGIN_UPPER_X64)),
|
||||
64,
|
||||
128
|
||||
).toNumber();
|
||||
|
||||
if (tickLow == tickHigh) {
|
||||
return tickLow;
|
||||
} else {
|
||||
const derivedTickHighSqrtPriceX64 = PriceMath.tickIndexToSqrtPriceX64(tickHigh);
|
||||
if (derivedTickHighSqrtPriceX64.lte(sqrtPriceX64)) {
|
||||
return tickHigh;
|
||||
} else {
|
||||
return tickLow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static tickIndexToPrice(tickIndex: number, decimalsA: number, decimalsB: number): Decimal {
|
||||
return PriceMath.sqrtPriceX64ToPrice(
|
||||
PriceMath.tickIndexToSqrtPriceX64(tickIndex),
|
||||
decimalsA,
|
||||
decimalsB
|
||||
);
|
||||
}
|
||||
|
||||
public static priceToTickIndex(price: Decimal, decimalsA: number, decimalsB: number): number {
|
||||
return PriceMath.sqrtPriceX64ToTickIndex(
|
||||
PriceMath.priceToSqrtPriceX64(price, decimalsA, decimalsB)
|
||||
);
|
||||
}
|
||||
|
||||
public static priceToInitializableTickIndex(
|
||||
price: Decimal,
|
||||
decimalsA: number,
|
||||
decimalsB: number,
|
||||
tickSpacing: number
|
||||
): number {
|
||||
return TickUtil.getInitializableTickIndex(
|
||||
PriceMath.priceToTickIndex(price, decimalsA, decimalsB),
|
||||
tickSpacing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Private Functions
|
||||
|
||||
function tickIndexToSqrtPricePositive(tick: number) {
|
||||
let ratio: BN;
|
||||
|
||||
if ((tick & 1) != 0) {
|
||||
ratio = new BN("79232123823359799118286999567");
|
||||
} else {
|
||||
ratio = new BN("79228162514264337593543950336");
|
||||
}
|
||||
|
||||
if ((tick & 2) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79236085330515764027303304731")), 96, 256);
|
||||
}
|
||||
if ((tick & 4) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79244008939048815603706035061")), 96, 256);
|
||||
}
|
||||
if ((tick & 8) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79259858533276714757314932305")), 96, 256);
|
||||
}
|
||||
if ((tick & 16) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79291567232598584799939703904")), 96, 256);
|
||||
}
|
||||
if ((tick & 32) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79355022692464371645785046466")), 96, 256);
|
||||
}
|
||||
if ((tick & 64) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79482085999252804386437311141")), 96, 256);
|
||||
}
|
||||
if ((tick & 128) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79736823300114093921829183326")), 96, 256);
|
||||
}
|
||||
if ((tick & 256) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("80248749790819932309965073892")), 96, 256);
|
||||
}
|
||||
if ((tick & 512) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("81282483887344747381513967011")), 96, 256);
|
||||
}
|
||||
if ((tick & 1024) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("83390072131320151908154831281")), 96, 256);
|
||||
}
|
||||
if ((tick & 2048) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("87770609709833776024991924138")), 96, 256);
|
||||
}
|
||||
if ((tick & 4096) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("97234110755111693312479820773")), 96, 256);
|
||||
}
|
||||
if ((tick & 8192) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("119332217159966728226237229890")), 96, 256);
|
||||
}
|
||||
if ((tick & 16384) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("179736315981702064433883588727")), 96, 256);
|
||||
}
|
||||
if ((tick & 32768) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("407748233172238350107850275304")), 96, 256);
|
||||
}
|
||||
if ((tick & 65536) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("2098478828474011932436660412517")), 96, 256);
|
||||
}
|
||||
if ((tick & 131072) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("55581415166113811149459800483533")), 96, 256);
|
||||
}
|
||||
if ((tick & 262144) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("38992368544603139932233054999993551")), 96, 256);
|
||||
}
|
||||
|
||||
return signedShiftRight(ratio, 32, 256);
|
||||
}
|
||||
|
||||
function tickIndexToSqrtPriceNegative(tickIndex: number) {
|
||||
let tick = Math.abs(tickIndex);
|
||||
let ratio: BN;
|
||||
|
||||
if ((tick & 1) != 0) {
|
||||
ratio = new BN("18445821805675392311");
|
||||
} else {
|
||||
ratio = new BN("18446744073709551616");
|
||||
}
|
||||
|
||||
if ((tick & 2) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18444899583751176498")), 64, 256);
|
||||
}
|
||||
if ((tick & 4) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18443055278223354162")), 64, 256);
|
||||
}
|
||||
if ((tick & 8) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18439367220385604838")), 64, 256);
|
||||
}
|
||||
if ((tick & 16) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18431993317065449817")), 64, 256);
|
||||
}
|
||||
if ((tick & 32) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18417254355718160513")), 64, 256);
|
||||
}
|
||||
if ((tick & 64) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18387811781193591352")), 64, 256);
|
||||
}
|
||||
if ((tick & 128) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18329067761203520168")), 64, 256);
|
||||
}
|
||||
if ((tick & 256) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18212142134806087854")), 64, 256);
|
||||
}
|
||||
if ((tick & 512) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("17980523815641551639")), 64, 256);
|
||||
}
|
||||
if ((tick & 1024) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("17526086738831147013")), 64, 256);
|
||||
}
|
||||
if ((tick & 2048) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("16651378430235024244")), 64, 256);
|
||||
}
|
||||
if ((tick & 4096) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("15030750278693429944")), 64, 256);
|
||||
}
|
||||
if ((tick & 8192) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("12247334978882834399")), 64, 256);
|
||||
}
|
||||
if ((tick & 16384) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("8131365268884726200")), 64, 256);
|
||||
}
|
||||
if ((tick & 32768) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("3584323654723342297")), 64, 256);
|
||||
}
|
||||
if ((tick & 65536) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("696457651847595233")), 64, 256);
|
||||
}
|
||||
if ((tick & 131072) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("26294789957452057")), 64, 256);
|
||||
}
|
||||
if ((tick & 262144) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("37481735321082")), 64, 256);
|
||||
}
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
function signedShiftLeft(n0: BN, shiftBy: number, bitWidth: number) {
|
||||
let twosN0 = n0.toTwos(bitWidth).shln(shiftBy);
|
||||
twosN0.imaskn(bitWidth + 1);
|
||||
return twosN0.fromTwos(bitWidth);
|
||||
}
|
||||
|
||||
function signedShiftRight(n0: BN, shiftBy: number, bitWidth: number) {
|
||||
let twoN0 = n0.toTwos(bitWidth).shrn(shiftBy);
|
||||
twoN0.imaskn(bitWidth - shiftBy + 1);
|
||||
return twoN0.fromTwos(bitWidth - shiftBy);
|
||||
}
|
|
@ -1,229 +1,203 @@
|
|||
import { BN } from "@project-serum/anchor";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Address, BN } from "@project-serum/anchor";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
MAX_TICK_INDEX,
|
||||
MIN_TICK_INDEX,
|
||||
TickArrayData,
|
||||
TickData,
|
||||
TICK_ARRAY_SIZE,
|
||||
} from "../../types/public";
|
||||
import { PDAUtil } from "./pda-utils";
|
||||
|
||||
export const MAX_TICK_INDEX = 443636;
|
||||
export const MIN_TICK_INDEX = -443636;
|
||||
export const TICK_ARRAY_SIZE = 88;
|
||||
|
||||
export const MAX_SQRT_PRICE = "79226673515401279992447579055";
|
||||
export const MIN_SQRT_PRICE = "4295048016";
|
||||
|
||||
const BIT_PRECISION = 14;
|
||||
const LOG_B_2_X32 = "59543866431248";
|
||||
const LOG_B_P_ERR_MARGIN_LOWER_X64 = "184467440737095516";
|
||||
const LOG_B_P_ERR_MARGIN_UPPER_X64 = "15793534762490258745";
|
||||
|
||||
export function tickIndexToSqrtPriceX64(tickIndex: number): BN {
|
||||
if (tickIndex > MAX_TICK_INDEX || tickIndex < MIN_TICK_INDEX) {
|
||||
throw new Error("Provided tick index does not fit within supported tick index range.");
|
||||
}
|
||||
|
||||
if (tickIndex > 0) {
|
||||
return new BN(tickIndexToSqrtPricePositive(tickIndex));
|
||||
} else {
|
||||
return new BN(tickIndexToSqrtPriceNegative(tickIndex));
|
||||
}
|
||||
enum TickSearchDirection {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
export function sqrtPriceX64ToTickIndex(sqrtPriceX64: BN): number {
|
||||
if (sqrtPriceX64.gt(new BN(MAX_SQRT_PRICE)) || sqrtPriceX64.lt(new BN(MIN_SQRT_PRICE))) {
|
||||
throw new Error("Provided sqrtPrice is not within the supported sqrtPrice range.");
|
||||
/**
|
||||
* A collection of utility functions when interacting with Ticks.
|
||||
* @category Whirlpool Utils
|
||||
*/
|
||||
export class TickUtil {
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* Get the startIndex of the tick array containing tickIndex.
|
||||
*
|
||||
* @param tickIndex
|
||||
* @param tickSpacing
|
||||
* @param offset can be used to get neighboring tick array startIndex.
|
||||
* @returns
|
||||
*/
|
||||
public static getStartTickIndex(tickIndex: number, tickSpacing: number, offset = 0): number {
|
||||
const realIndex = Math.floor(tickIndex / tickSpacing / TICK_ARRAY_SIZE);
|
||||
const startTickIndex = (realIndex + offset) * tickSpacing * TICK_ARRAY_SIZE;
|
||||
|
||||
const ticksInArray = TICK_ARRAY_SIZE * tickSpacing;
|
||||
const minTickIndex = MIN_TICK_INDEX - ((MIN_TICK_INDEX % ticksInArray) + ticksInArray);
|
||||
invariant(startTickIndex >= minTickIndex, "startTickIndex is too small");
|
||||
invariant(startTickIndex <= MAX_TICK_INDEX, "startTickIndex is too large");
|
||||
return startTickIndex;
|
||||
}
|
||||
|
||||
const msb = sqrtPriceX64.bitLength() - 1;
|
||||
const adjustedMsb = new BN(msb - 64);
|
||||
const log2pIntegerX32 = signedShiftLeft(adjustedMsb, 32, 128);
|
||||
|
||||
let bit = new BN("8000000000000000", "hex");
|
||||
let precision = 0;
|
||||
let log2pFractionX64 = new BN(0);
|
||||
|
||||
let r = msb >= 64 ? sqrtPriceX64.shrn(msb - 63) : sqrtPriceX64.shln(63 - msb);
|
||||
|
||||
while (bit.gt(new BN(0)) && precision < BIT_PRECISION) {
|
||||
r = r.mul(r);
|
||||
let rMoreThanTwo = r.shrn(127);
|
||||
r = r.shrn(63 + rMoreThanTwo.toNumber());
|
||||
log2pFractionX64 = log2pFractionX64.add(bit.mul(rMoreThanTwo));
|
||||
bit = bit.shrn(1);
|
||||
precision += 1;
|
||||
/**
|
||||
* Get the nearest (rounding down) valid tick index from the tickIndex.
|
||||
* A valid tick index is a point on the tick spacing grid line.
|
||||
*/
|
||||
public static getInitializableTickIndex(tickIndex: number, tickSpacing: number): number {
|
||||
return tickIndex - (tickIndex % tickSpacing);
|
||||
}
|
||||
|
||||
const log2pFractionX32 = log2pFractionX64.shrn(32);
|
||||
public static getNextInitializableTickIndex(tickIndex: number, tickSpacing: number) {
|
||||
return TickUtil.getInitializableTickIndex(tickIndex, tickSpacing) + tickSpacing;
|
||||
}
|
||||
|
||||
const log2pX32 = log2pIntegerX32.add(log2pFractionX32);
|
||||
const logbpX64 = log2pX32.mul(new BN(LOG_B_2_X32));
|
||||
public static getPrevInitializableTickIndex(tickIndex: number, tickSpacing: number) {
|
||||
return TickUtil.getInitializableTickIndex(tickIndex, tickSpacing) - tickSpacing;
|
||||
}
|
||||
|
||||
const tickLow = signedShiftRight(
|
||||
logbpX64.sub(new BN(LOG_B_P_ERR_MARGIN_LOWER_X64)),
|
||||
64,
|
||||
128
|
||||
).toNumber();
|
||||
const tickHigh = signedShiftRight(
|
||||
logbpX64.add(new BN(LOG_B_P_ERR_MARGIN_UPPER_X64)),
|
||||
64,
|
||||
128
|
||||
).toNumber();
|
||||
/**
|
||||
* Get the previous initialized tick index within the same tick array.
|
||||
*
|
||||
* @param account
|
||||
* @param currentTickIndex
|
||||
* @param tickSpacing
|
||||
* @returns
|
||||
*/
|
||||
public static findPreviousInitializedTickIndex(
|
||||
account: TickArrayData,
|
||||
currentTickIndex: number,
|
||||
tickSpacing: number
|
||||
): number | null {
|
||||
return TickUtil.findInitializedTick(
|
||||
account,
|
||||
currentTickIndex,
|
||||
tickSpacing,
|
||||
TickSearchDirection.Left
|
||||
);
|
||||
}
|
||||
|
||||
if (tickLow == tickHigh) {
|
||||
return tickLow;
|
||||
} else {
|
||||
const derivedTickHighSqrtPriceX64 = tickIndexToSqrtPriceX64(tickHigh);
|
||||
if (derivedTickHighSqrtPriceX64.lte(sqrtPriceX64)) {
|
||||
return tickHigh;
|
||||
} else {
|
||||
return tickLow;
|
||||
/**
|
||||
* Get the next initialized tick index within the same tick array.
|
||||
* @param account
|
||||
* @param currentTickIndex
|
||||
* @param tickSpacing
|
||||
* @returns
|
||||
*/
|
||||
public static findNextInitializedTickIndex(
|
||||
account: TickArrayData,
|
||||
currentTickIndex: number,
|
||||
tickSpacing: number
|
||||
): number | null {
|
||||
return TickUtil.findInitializedTick(
|
||||
account,
|
||||
currentTickIndex,
|
||||
tickSpacing,
|
||||
TickSearchDirection.Right
|
||||
);
|
||||
}
|
||||
|
||||
private static findInitializedTick(
|
||||
account: TickArrayData,
|
||||
currentTickIndex: number,
|
||||
tickSpacing: number,
|
||||
searchDirection: TickSearchDirection
|
||||
): number | null {
|
||||
const currentTickArrayIndex = tickIndexToInnerIndex(
|
||||
account.startTickIndex,
|
||||
currentTickIndex,
|
||||
tickSpacing
|
||||
);
|
||||
|
||||
const increment = searchDirection === TickSearchDirection.Right ? 1 : -1;
|
||||
|
||||
let stepInitializedTickArrayIndex =
|
||||
searchDirection === TickSearchDirection.Right
|
||||
? currentTickArrayIndex + increment
|
||||
: currentTickArrayIndex;
|
||||
while (
|
||||
stepInitializedTickArrayIndex >= 0 &&
|
||||
stepInitializedTickArrayIndex < account.ticks.length
|
||||
) {
|
||||
if (account.ticks[stepInitializedTickArrayIndex]?.initialized) {
|
||||
return innerIndexToTickIndex(
|
||||
account.startTickIndex,
|
||||
stepInitializedTickArrayIndex,
|
||||
tickSpacing
|
||||
);
|
||||
}
|
||||
|
||||
stepInitializedTickArrayIndex += increment;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static checkTickInBounds(tick: number) {
|
||||
return tick <= MAX_TICK_INDEX && tick >= MIN_TICK_INDEX;
|
||||
}
|
||||
|
||||
public static isTickInitializable(tick: number, tickSpacing: number) {
|
||||
return tick % tickSpacing === 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function getStartTickIndex(tickIndex: number, tickSpacing: number): number {
|
||||
const ticksInArray = tickSpacing * TICK_ARRAY_SIZE;
|
||||
return Math.floor(tickIndex / ticksInArray) * ticksInArray;
|
||||
export class TickArrayUtil {
|
||||
/**
|
||||
* Get the tick from tickArray with a global tickIndex.
|
||||
*/
|
||||
public static getTickFromArray(
|
||||
tickArray: TickArrayData,
|
||||
tickIndex: number,
|
||||
tickSpacing: number
|
||||
): TickData {
|
||||
const realIndex = tickIndexToInnerIndex(tickArray.startTickIndex, tickIndex, tickSpacing);
|
||||
const tick = tickArray.ticks[realIndex];
|
||||
invariant(
|
||||
!!tick,
|
||||
`tick realIndex out of range - start - ${tickArray.startTickIndex} index - ${tickIndex}, realIndex - ${realIndex}`
|
||||
);
|
||||
return tick;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param tickLowerIndex
|
||||
* @param tickUpperIndex
|
||||
* @param tickSpacing
|
||||
* @param whirlpool
|
||||
* @param programId
|
||||
* @returns
|
||||
*/
|
||||
public static getAdjacentTickArrays(
|
||||
tickLowerIndex: number,
|
||||
tickUpperIndex: number,
|
||||
tickSpacing: number,
|
||||
whirlpool: PublicKey,
|
||||
programId: PublicKey
|
||||
): [PublicKey, PublicKey] {
|
||||
return [
|
||||
PDAUtil.getTickArrayFromTickIndex(tickLowerIndex, tickSpacing, whirlpool, programId)
|
||||
.publicKey,
|
||||
PDAUtil.getTickArrayFromTickIndex(tickUpperIndex, tickSpacing, whirlpool, programId)
|
||||
.publicKey,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function tickIndexToSqrtPricePositive(tick: number) {
|
||||
let ratio: BN;
|
||||
|
||||
if ((tick & 1) != 0) {
|
||||
ratio = new BN("79232123823359799118286999567");
|
||||
} else {
|
||||
ratio = new BN("79228162514264337593543950336");
|
||||
}
|
||||
|
||||
if ((tick & 2) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79236085330515764027303304731")), 96, 256);
|
||||
}
|
||||
if ((tick & 4) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79244008939048815603706035061")), 96, 256);
|
||||
}
|
||||
if ((tick & 8) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79259858533276714757314932305")), 96, 256);
|
||||
}
|
||||
if ((tick & 16) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79291567232598584799939703904")), 96, 256);
|
||||
}
|
||||
if ((tick & 32) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79355022692464371645785046466")), 96, 256);
|
||||
}
|
||||
if ((tick & 64) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79482085999252804386437311141")), 96, 256);
|
||||
}
|
||||
if ((tick & 128) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("79736823300114093921829183326")), 96, 256);
|
||||
}
|
||||
if ((tick & 256) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("80248749790819932309965073892")), 96, 256);
|
||||
}
|
||||
if ((tick & 512) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("81282483887344747381513967011")), 96, 256);
|
||||
}
|
||||
if ((tick & 1024) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("83390072131320151908154831281")), 96, 256);
|
||||
}
|
||||
if ((tick & 2048) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("87770609709833776024991924138")), 96, 256);
|
||||
}
|
||||
if ((tick & 4096) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("97234110755111693312479820773")), 96, 256);
|
||||
}
|
||||
if ((tick & 8192) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("119332217159966728226237229890")), 96, 256);
|
||||
}
|
||||
if ((tick & 16384) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("179736315981702064433883588727")), 96, 256);
|
||||
}
|
||||
if ((tick & 32768) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("407748233172238350107850275304")), 96, 256);
|
||||
}
|
||||
if ((tick & 65536) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("2098478828474011932436660412517")), 96, 256);
|
||||
}
|
||||
if ((tick & 131072) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("55581415166113811149459800483533")), 96, 256);
|
||||
}
|
||||
if ((tick & 262144) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("38992368544603139932233054999993551")), 96, 256);
|
||||
}
|
||||
|
||||
return signedShiftRight(ratio, 32, 256);
|
||||
function tickIndexToInnerIndex(
|
||||
startTickIndex: number,
|
||||
tickIndex: number,
|
||||
tickSpacing: number
|
||||
): number {
|
||||
return Math.floor((tickIndex - startTickIndex) / tickSpacing);
|
||||
}
|
||||
|
||||
function tickIndexToSqrtPriceNegative(tickIndex: number) {
|
||||
let tick = Math.abs(tickIndex);
|
||||
let ratio: BN;
|
||||
|
||||
if ((tick & 1) != 0) {
|
||||
ratio = new BN("18445821805675392311");
|
||||
} else {
|
||||
ratio = new BN("18446744073709551616");
|
||||
}
|
||||
|
||||
if ((tick & 2) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18444899583751176498")), 64, 256);
|
||||
}
|
||||
if ((tick & 4) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18443055278223354162")), 64, 256);
|
||||
}
|
||||
if ((tick & 8) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18439367220385604838")), 64, 256);
|
||||
}
|
||||
if ((tick & 16) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18431993317065449817")), 64, 256);
|
||||
}
|
||||
if ((tick & 32) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18417254355718160513")), 64, 256);
|
||||
}
|
||||
if ((tick & 64) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18387811781193591352")), 64, 256);
|
||||
}
|
||||
if ((tick & 128) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18329067761203520168")), 64, 256);
|
||||
}
|
||||
if ((tick & 256) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("18212142134806087854")), 64, 256);
|
||||
}
|
||||
if ((tick & 512) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("17980523815641551639")), 64, 256);
|
||||
}
|
||||
if ((tick & 1024) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("17526086738831147013")), 64, 256);
|
||||
}
|
||||
if ((tick & 2048) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("16651378430235024244")), 64, 256);
|
||||
}
|
||||
if ((tick & 4096) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("15030750278693429944")), 64, 256);
|
||||
}
|
||||
if ((tick & 8192) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("12247334978882834399")), 64, 256);
|
||||
}
|
||||
if ((tick & 16384) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("8131365268884726200")), 64, 256);
|
||||
}
|
||||
if ((tick & 32768) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("3584323654723342297")), 64, 256);
|
||||
}
|
||||
if ((tick & 65536) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("696457651847595233")), 64, 256);
|
||||
}
|
||||
if ((tick & 131072) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("26294789957452057")), 64, 256);
|
||||
}
|
||||
if ((tick & 262144) != 0) {
|
||||
ratio = signedShiftRight(ratio.mul(new BN("37481735321082")), 64, 256);
|
||||
}
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
function signedShiftLeft(n0: BN, shiftBy: number, bitWidth: number) {
|
||||
let twosN0 = n0.toTwos(bitWidth).shln(shiftBy);
|
||||
twosN0.imaskn(bitWidth + 1);
|
||||
return twosN0.fromTwos(bitWidth);
|
||||
}
|
||||
|
||||
function signedShiftRight(n0: BN, shiftBy: number, bitWidth: number) {
|
||||
let twoN0 = n0.toTwos(bitWidth).shrn(shiftBy);
|
||||
twoN0.imaskn(bitWidth - shiftBy + 1);
|
||||
return twoN0.fromTwos(bitWidth - shiftBy);
|
||||
function innerIndexToTickIndex(
|
||||
startTickIndex: number,
|
||||
tickArrayIndex: number,
|
||||
tickSpacing: number
|
||||
): number {
|
||||
return startTickIndex + tickArrayIndex * tickSpacing;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import BN from "bn.js";
|
||||
|
||||
export function getLowerSqrtPriceFromTokenA(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN {
|
||||
const numerator = liquidity.mul(sqrtPriceX64).shln(64);
|
||||
const denominator = liquidity.shln(64).add(amount.mul(sqrtPriceX64));
|
||||
|
||||
// always round up
|
||||
return MathUtil.divRoundUp(numerator, denominator);
|
||||
}
|
||||
|
||||
export function getUpperSqrtPriceFromTokenA(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN {
|
||||
const numerator = liquidity.mul(sqrtPriceX64).shln(64);
|
||||
const denominator = liquidity.shln(64).sub(amount.mul(sqrtPriceX64));
|
||||
|
||||
// always round up
|
||||
return MathUtil.divRoundUp(numerator, denominator);
|
||||
}
|
||||
|
||||
export function getLowerSqrtPriceFromTokenB(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN {
|
||||
// always round down
|
||||
return sqrtPriceX64.sub(MathUtil.divRoundUp(amount.shln(64), liquidity));
|
||||
}
|
||||
|
||||
export function getUpperSqrtPriceFromTokenB(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN {
|
||||
// always round down (rounding up a negative number)
|
||||
return sqrtPriceX64.add(amount.shln(64).div(liquidity));
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
import { Provider } from "@project-serum/anchor";
|
||||
import { Transaction, Signer, TransactionInstruction } from "@solana/web3.js";
|
||||
|
||||
export const EMPTY_INSTRUCTION: Instruction = {
|
||||
instructions: [],
|
||||
cleanupInstructions: [],
|
||||
signers: [],
|
||||
};
|
||||
|
||||
export type Instruction = {
|
||||
instructions: TransactionInstruction[];
|
||||
cleanupInstructions: TransactionInstruction[];
|
||||
signers: Signer[];
|
||||
};
|
||||
|
||||
export type TransactionPayload = {
|
||||
transaction: Transaction;
|
||||
signers: Signer[];
|
||||
};
|
||||
|
||||
export class TransactionBuilder {
|
||||
private provider: Provider;
|
||||
private instructions: Instruction[];
|
||||
private signers: Signer[];
|
||||
|
||||
constructor(provider: Provider) {
|
||||
this.provider = provider;
|
||||
this.instructions = [];
|
||||
this.signers = [];
|
||||
}
|
||||
|
||||
addInstruction(instruction: Instruction): TransactionBuilder {
|
||||
this.instructions.push(instruction);
|
||||
return this;
|
||||
}
|
||||
|
||||
addSigner(signer: Signer): TransactionBuilder {
|
||||
this.signers.push(signer);
|
||||
return this;
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.instructions.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses all instructions & signers in this builder
|
||||
* into one single instruction
|
||||
* @param compressPost Compress all post instructions into the instructions field
|
||||
* @returns Instruction object containing all
|
||||
*/
|
||||
compressIx(compressPost: boolean): Instruction {
|
||||
let instructions: TransactionInstruction[] = [];
|
||||
let cleanupInstructions: TransactionInstruction[] = [];
|
||||
let signers: Signer[] = [];
|
||||
this.instructions.forEach((curr) => {
|
||||
instructions = instructions.concat(curr.instructions);
|
||||
// Cleanup instructions should execute in reverse order
|
||||
cleanupInstructions = curr.cleanupInstructions.concat(cleanupInstructions);
|
||||
signers = signers.concat(curr.signers);
|
||||
});
|
||||
|
||||
if (compressPost) {
|
||||
instructions = instructions.concat(cleanupInstructions);
|
||||
cleanupInstructions = [];
|
||||
}
|
||||
|
||||
return {
|
||||
instructions: [...instructions],
|
||||
cleanupInstructions: [...cleanupInstructions],
|
||||
signers,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a transaction payload with the gathered instructions
|
||||
* @returns a TransactionPayload object that can be excuted or agregated into other transactions
|
||||
*/
|
||||
async build(): Promise<TransactionPayload> {
|
||||
const recentBlockHash = (await this.provider.connection.getRecentBlockhash("singleGossip"))
|
||||
.blockhash;
|
||||
|
||||
const transaction = new Transaction({
|
||||
recentBlockhash: recentBlockHash,
|
||||
feePayer: this.provider.wallet.publicKey,
|
||||
});
|
||||
|
||||
const ix = this.compressIx(true);
|
||||
|
||||
transaction.add(...ix.instructions);
|
||||
transaction.feePayer = this.provider.wallet.publicKey;
|
||||
|
||||
return {
|
||||
transaction: transaction,
|
||||
signers: ix.signers.concat(this.signers),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a transaction payload with the gathered instructions, sign it with the provider and send it out
|
||||
* @returns the txId of the transaction
|
||||
*/
|
||||
async buildAndExecute(): Promise<string> {
|
||||
const tx = await this.build();
|
||||
return this.provider.send(tx.transaction, tx.signers, { commitment: "confirmed" });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
import { Percentage, TransactionBuilder } from "@orca-so/common-sdk";
|
||||
import { Address } from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "./context";
|
||||
import { AccountFetcher } from "./network/public";
|
||||
import {
|
||||
WhirlpoolData,
|
||||
PositionData,
|
||||
IncreaseLiquidityInput,
|
||||
DecreaseLiquidityInput,
|
||||
} from "./types/public";
|
||||
import { TokenInfo } from "./types/public/client-types";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SwapQuote } from "./quotes/public";
|
||||
import { WhirlpoolClientImpl } from "./impl/whirlpool-client-impl";
|
||||
|
||||
export interface WhirlpoolClient {
|
||||
/**
|
||||
* Get a Whirlpool object to interact with the Whirlpool account at the given address.
|
||||
* @param poolAddress the address of the Whirlpool account
|
||||
* @return a Whirlpool object to interact with
|
||||
*/
|
||||
getPool: (poolAddress: Address) => Promise<Whirlpool>;
|
||||
|
||||
/**
|
||||
* Get a Position object to interact with the Position account at the given address.
|
||||
* @param positionAddress the address of the Position account
|
||||
* @return a Position object to interact with
|
||||
*/
|
||||
getPosition: (positionAddress: Address) => Promise<Position>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a WhirlpoolClient instance to help interact with Whirlpools accounts with.
|
||||
*
|
||||
* @param ctx - WhirlpoolContext object
|
||||
* @param fetcher - AccountFetcher instance to help fetch data with.
|
||||
* @returns a WhirlpoolClient instance to help with interacting with Whirlpools accounts.
|
||||
*/
|
||||
export function buildWhirlpoolClient(ctx: WhirlpoolContext, fetcher: AccountFetcher) {
|
||||
return new WhirlpoolClientImpl(ctx, fetcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to interact with a Whirlpool account and build complex transactions.
|
||||
*/
|
||||
export interface Whirlpool {
|
||||
/**
|
||||
* Return the most recently fetched Whirlpool account data.
|
||||
* @return most recently fetched WhirlpoolData for this address.
|
||||
*/
|
||||
getData: () => WhirlpoolData;
|
||||
|
||||
/**
|
||||
* Fetch and return the most recently fetched Whirlpool account data.
|
||||
* @return the most up to date WhirlpoolData for this address.
|
||||
*/
|
||||
refreshData: () => Promise<WhirlpoolData>;
|
||||
|
||||
/**
|
||||
* Get the TokenInfo for token A of this pool.
|
||||
* @return TokenInfo for token A
|
||||
*/
|
||||
getTokenAInfo: () => TokenInfo;
|
||||
|
||||
/**
|
||||
* Get the TokenInfo for token B of this pool.
|
||||
* @return TokenInfo for token B
|
||||
*/
|
||||
getTokenBInfo: () => TokenInfo;
|
||||
|
||||
// TODO: add functionality to check whether tick arrays exists
|
||||
/**
|
||||
* Initialize a set of tick-arrays to support the provided ticks.
|
||||
*
|
||||
* This function does not ensure the provided tick index are within range and not initialized.
|
||||
*
|
||||
* If `funder` is provided, the funder wallet has to sign this transaction.
|
||||
*
|
||||
* @param ticks - A group of ticks that define the desired tick-arrays to initialize
|
||||
* @param funder - optional - the wallet that will fund the cost needed to initialize the position. If null, the WhirlpoolContext wallet is used.
|
||||
* @return a transaction that will initialize the defined tick-arrays if executed.
|
||||
*/
|
||||
initTickArrayForTicks: (ticks: number[], funder?: Address) => TransactionBuilder;
|
||||
|
||||
/**
|
||||
* Open and fund a position on this Whirlpool.
|
||||
*
|
||||
* User has to ensure the TickArray for tickLower and tickUpper has been initialized prior to calling this function.
|
||||
*
|
||||
* If `funder` is provided, the funder wallet has to sign this transaction.
|
||||
*
|
||||
* @param tickLower - the tick index for the lower bound of this position
|
||||
* @param tickUpper - the tick index for the upper bound of this position
|
||||
* @param liquidityInput - an InputLiquidityInput type to define the desired liquidity amount to deposit
|
||||
* @param sourceWallet - optional - the wallet to withdraw tokens to deposit into the position. If null, the WhirlpoolContext wallet is used.
|
||||
* @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used.
|
||||
* @param funder - optional - the wallet that will fund the cost needed to initialize the position. If null, the WhirlpoolContext wallet is used.
|
||||
* @return `positionMint` - the position to be created. `tx` - The transaction containing the instructions to perform the operation on chain.
|
||||
*/
|
||||
openPosition: (
|
||||
tickLower: number,
|
||||
tickUpper: number,
|
||||
liquidityInput: IncreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address,
|
||||
funder?: Address
|
||||
) => Promise<{ positionMint: PublicKey; tx: TransactionBuilder }>;
|
||||
|
||||
/**
|
||||
* Open and fund a position with meta-data on this Whirlpool.
|
||||
*
|
||||
* User has to ensure the TickArray for tickLower and tickUpper has been initialized prior to calling this function.
|
||||
*
|
||||
* If `sourceWallet`, `positionWallet` or `funder` is provided, the wallet owners have to sign this transaction.
|
||||
*
|
||||
* @param tickLower - the tick index for the lower bound of this position
|
||||
* @param tickUpper - the tick index for the upper bound of this position
|
||||
* @param liquidityInput - input that defines the desired liquidity amount and maximum tokens willing to be to deposited.
|
||||
* @param sourceWallet - optional - the wallet to withdraw tokens to deposit into the position. If null, the WhirlpoolContext wallet is used.
|
||||
* @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used.
|
||||
* @param funder - optional - the wallet that will fund the cost needed to initialize the position. If null, the WhirlpoolContext wallet is used.
|
||||
* @return `positionMint` - the position to be created. `tx` - The transaction containing the instructions to perform the operation on chain.
|
||||
*/
|
||||
openPositionWithMetadata: (
|
||||
tickLower: number,
|
||||
tickUpper: number,
|
||||
liquidityInput: IncreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address,
|
||||
funder?: Address
|
||||
) => Promise<{ positionMint: PublicKey; tx: TransactionBuilder }>;
|
||||
|
||||
/**
|
||||
* Withdraw all tokens from a position, close the account and burn the position token.
|
||||
*
|
||||
* Users have to collect all fees and rewards from this position prior to closing the account.
|
||||
*
|
||||
* If `positionWallet` is provided, the wallet owner has to sign this transaction.
|
||||
*
|
||||
* @param positionAddress - The address of the position account.
|
||||
* @param slippageTolerance - The amount of slippage the caller is willing to accept when withdrawing liquidity.
|
||||
* @param destinationWallet - optional - The wallet that the tokens withdrawn will be sent to. If null, the WhirlpoolContext wallet is used.
|
||||
* @param positionWallet - optional - The wallet that houses the position token that corresponds to this position address. If null, the WhirlpoolContext wallet is used.
|
||||
*/
|
||||
closePosition: (
|
||||
positionAddress: Address,
|
||||
slippageTolerance: Percentage,
|
||||
destinationWallet?: Address,
|
||||
positionWallet?: Address
|
||||
) => Promise<TransactionBuilder>;
|
||||
|
||||
/**
|
||||
* Perform a swap between tokenA and tokenB on this pool.
|
||||
*
|
||||
* @param quote - A quote on the desired tokenIn and tokenOut for this swap. Use @link {swapQuote} to generate this object.
|
||||
* @param wallet - The wallet that tokens will be withdrawn and deposit into. If null, the WhirlpoolContext wallet is used.
|
||||
* @return a transaction that will perform the swap once executed.
|
||||
*/
|
||||
swap: (quote: SwapQuote, wallet?: PublicKey) => Promise<TransactionBuilder>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to interact with a Position account and build complex transactions.
|
||||
*/
|
||||
export interface Position {
|
||||
/**
|
||||
* Return the most recently fetched Position account data.
|
||||
* @return most recently fetched PositionData for this address.
|
||||
*/
|
||||
getData: () => PositionData;
|
||||
|
||||
/**
|
||||
* Fetch and return the most recently fetched Position account data.
|
||||
* @return the most up to date PositionData for this address.
|
||||
*/
|
||||
refreshData: () => Promise<PositionData>;
|
||||
|
||||
/**
|
||||
* Deposit additional tokens into this postiion.
|
||||
*
|
||||
* If `sourceWallet`, `positionWallet` is provided, the wallet owners have to sign this transaction.
|
||||
*
|
||||
* @param liquidityInput - input that defines the desired liquidity amount and maximum tokens willing to be to deposited.
|
||||
* @param sourceWallet - optional - the wallet to withdraw tokens to deposit into the position. If null, the WhirlpoolContext wallet is used.
|
||||
* @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used.
|
||||
* @return the transaction that will deposit the tokens into the position when executed.
|
||||
*/
|
||||
increaseLiquidity: (
|
||||
liquidityInput: IncreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address
|
||||
) => Promise<TransactionBuilder>;
|
||||
|
||||
/**
|
||||
* Withdraw liquidity from this position.
|
||||
*
|
||||
* If `positionWallet` is provided, the wallet owners have to sign this transaction.
|
||||
*
|
||||
* @param liquidityInput - input that defines the desired liquidity amount and minimum tokens willing to be to withdrawn from the position.
|
||||
* @param sourceWallet - optional - the wallet to deposit tokens into when withdrawing from the position. If null, the WhirlpoolContext wallet is used.
|
||||
* @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used.
|
||||
* @return the transaction that will deposit the tokens into the position when executed.
|
||||
*/
|
||||
decreaseLiquidity: (
|
||||
liquidityInput: DecreaseLiquidityInput,
|
||||
sourceWallet?: Address,
|
||||
positionWallet?: Address
|
||||
) => Promise<TransactionBuilder>;
|
||||
|
||||
// TODO: Implement Collect fees
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { InitConfigParams } from "../src/types/public/ix-types";
|
||||
import { generateDefaultConfigParams } from "./utils/test-builders";
|
||||
import { ONE_SOL, systemTransferTx } from "./utils";
|
||||
|
||||
describe("initialize_config", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
|
||||
let initializedConfigInfo: InitConfigParams;
|
||||
|
||||
it("successfully init a WhirlpoolsConfig account", async () => {
|
||||
const { configInitInfo } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
|
||||
const configAccount = await client.getConfig(configInitInfo.whirlpoolConfigKeypair.publicKey);
|
||||
|
||||
assert.ok(
|
||||
configAccount.collectProtocolFeesAuthority.equals(configInitInfo.collectProtocolFeesAuthority)
|
||||
);
|
||||
|
||||
assert.ok(configAccount.feeAuthority.equals(configInitInfo.feeAuthority));
|
||||
|
||||
assert.ok(
|
||||
configAccount.rewardEmissionsSuperAuthority.equals(
|
||||
configInitInfo.rewardEmissionsSuperAuthority
|
||||
)
|
||||
);
|
||||
|
||||
assert.equal(configAccount.defaultProtocolFeeRate, configInitInfo.defaultProtocolFeeRate);
|
||||
|
||||
initializedConfigInfo = configInitInfo;
|
||||
});
|
||||
|
||||
it("fail on passing in already initialized whirlpool account", async () => {
|
||||
let infoWithDupeConfigKey = {
|
||||
...generateDefaultConfigParams(context).configInitInfo,
|
||||
whirlpoolConfigKeypair: initializedConfigInfo.whirlpoolConfigKeypair,
|
||||
};
|
||||
await assert.rejects(client.initConfigTx(infoWithDupeConfigKey).buildAndExecute(), /0x0/);
|
||||
});
|
||||
|
||||
it("succeeds when funder is different than account paying for transaction fee", async () => {
|
||||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute();
|
||||
const { configInitInfo } = generateDefaultConfigParams(context, funderKeypair.publicKey);
|
||||
await client.initConfigTx(configInitInfo).addSigner(funderKeypair).buildAndExecute();
|
||||
});
|
||||
});
|
|
@ -1,8 +1,7 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initTestPool, initTestPoolWithLiquidity, openPosition } from "./utils/init-utils";
|
||||
import { WhirlpoolContext } from "../../src/context";
|
||||
import { initTestPool, initTestPoolWithLiquidity, openPosition } from "../utils/init-utils";
|
||||
import {
|
||||
approveToken,
|
||||
createAndMintToTokenAccount,
|
||||
|
@ -11,40 +10,39 @@ import {
|
|||
TickSpacing,
|
||||
transfer,
|
||||
ZERO_BN,
|
||||
} from "./utils";
|
||||
import { WhirlpoolTestFixture } from "./utils/fixture";
|
||||
} from "../utils";
|
||||
import { WhirlpoolTestFixture } from "../utils/fixture";
|
||||
import { WhirlpoolIx } from "../../src";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("close_position", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
|
||||
it("successfully closes an open position", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const { params } = await openPosition(client, poolInitInfo.whirlpoolPda.publicKey, 0, 128);
|
||||
const { params } = await openPosition(ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128);
|
||||
const receiverKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
await client
|
||||
.closePositionTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
receiver: receiverKeypair.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const supplyResponse = await provider.connection.getTokenSupply(params.positionMintAddress);
|
||||
assert.equal(supplyResponse.value.uiAmount, 0);
|
||||
|
||||
assert.equal(await provider.connection.getAccountInfo(params.positionPda.publicKey), undefined);
|
||||
assert.equal(
|
||||
await provider.connection.getAccountInfo(params.positionTokenAccountAddress),
|
||||
undefined
|
||||
);
|
||||
assert.equal(await provider.connection.getAccountInfo(params.positionTokenAccount), undefined);
|
||||
|
||||
const receiverAccount = await provider.connection.getAccountInfo(receiverKeypair.publicKey);
|
||||
const lamports = receiverAccount?.lamports;
|
||||
|
@ -52,82 +50,74 @@ describe("close_position", () => {
|
|||
});
|
||||
|
||||
it("succeeds if the position is delegated", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const owner = anchor.web3.Keypair.generate();
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params } = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
owner.publicKey
|
||||
);
|
||||
|
||||
await approveToken(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
delegate.publicKey,
|
||||
1,
|
||||
owner
|
||||
);
|
||||
await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner);
|
||||
await setAuthority(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
ctx.provider,
|
||||
params.positionTokenAccount,
|
||||
delegate.publicKey,
|
||||
"CloseAccount",
|
||||
owner
|
||||
);
|
||||
|
||||
await client
|
||||
.closePositionTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: delegate.publicKey,
|
||||
receiver: owner.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("succeeds with the owner's signature even if the token is delegated", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const owner = anchor.web3.Keypair.generate();
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params } = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
owner.publicKey
|
||||
);
|
||||
|
||||
await approveToken(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
delegate.publicKey,
|
||||
1,
|
||||
owner
|
||||
);
|
||||
await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner);
|
||||
|
||||
await client
|
||||
.closePositionTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: owner.publicKey,
|
||||
receiver: owner.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
)
|
||||
.addSigner(owner)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("succeeds with position token that was transferred to new owner", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -142,44 +132,47 @@ describe("close_position", () => {
|
|||
|
||||
await transfer(provider, position.tokenAccount, newOwnerPositionTokenAccount, 1);
|
||||
|
||||
await client
|
||||
.closePositionTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: newOwner.publicKey,
|
||||
receiver: newOwner.publicKey,
|
||||
position: position.publicKey,
|
||||
positionMint: position.mintKeypair.publicKey,
|
||||
positionTokenAccount: newOwnerPositionTokenAccount,
|
||||
})
|
||||
)
|
||||
.addSigner(newOwner)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("fails to close a position with liquidity", async () => {
|
||||
const { positionInfo } = await initTestPoolWithLiquidity(client);
|
||||
const { positionInfo } = await initTestPoolWithLiquidity(ctx);
|
||||
|
||||
const receiverKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
receiver: receiverKeypair.publicKey,
|
||||
position: positionInfo.positionPda.publicKey,
|
||||
positionMint: positionInfo.positionMintAddress,
|
||||
positionTokenAccount: positionInfo.positionTokenAccountAddress,
|
||||
positionTokenAccount: positionInfo.positionTokenAccount,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x1775/ // ClosePositionNotEmpty
|
||||
);
|
||||
});
|
||||
|
||||
it("fails if owner is not signer", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const owner = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params } = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
|
@ -187,70 +180,66 @@ describe("close_position", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: owner.publicKey,
|
||||
receiver: owner.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails if delegate is not signer", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const owner = anchor.web3.Keypair.generate();
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params } = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
owner.publicKey
|
||||
);
|
||||
|
||||
await approveToken(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
delegate.publicKey,
|
||||
1,
|
||||
owner
|
||||
);
|
||||
await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner);
|
||||
await setAuthority(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
ctx.provider,
|
||||
params.positionTokenAccount,
|
||||
delegate.publicKey,
|
||||
"CloseAccount",
|
||||
owner
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: delegate.publicKey,
|
||||
receiver: owner.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails if the authority does not match", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const owner = anchor.web3.Keypair.generate();
|
||||
const fakeOwner = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params } = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
|
@ -258,14 +247,16 @@ describe("close_position", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: fakeOwner.publicKey,
|
||||
receiver: owner.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
)
|
||||
.addSigner(fakeOwner)
|
||||
.buildAndExecute(),
|
||||
/0x1783/ // MissingOrInvalidDelegate
|
||||
|
@ -273,7 +264,7 @@ describe("close_position", () => {
|
|||
});
|
||||
|
||||
it("fails if position token account does not contain exactly one token", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -286,57 +277,54 @@ describe("close_position", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
receiver: provider.wallet.publicKey,
|
||||
position: position.publicKey,
|
||||
positionMint: position.mintKeypair.publicKey,
|
||||
positionTokenAccount: fakePositionTokenAccount,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails if delegated amount is 0", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const owner = anchor.web3.Keypair.generate();
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params } = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
owner.publicKey
|
||||
);
|
||||
|
||||
await approveToken(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
delegate.publicKey,
|
||||
0,
|
||||
owner
|
||||
);
|
||||
await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 0, owner);
|
||||
await setAuthority(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
ctx.provider,
|
||||
params.positionTokenAccount,
|
||||
delegate.publicKey,
|
||||
"CloseAccount",
|
||||
owner
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: delegate.publicKey,
|
||||
receiver: owner.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1784/ // InvalidPositionTokenAmount
|
||||
|
@ -344,44 +332,40 @@ describe("close_position", () => {
|
|||
});
|
||||
|
||||
it("fails if positionAuthority does not match delegate", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const owner = anchor.web3.Keypair.generate();
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
const fakeDelegate = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params } = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
owner.publicKey
|
||||
);
|
||||
|
||||
await approveToken(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
delegate.publicKey,
|
||||
1,
|
||||
owner
|
||||
);
|
||||
await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner);
|
||||
await setAuthority(
|
||||
client.context.provider,
|
||||
params.positionTokenAccountAddress,
|
||||
ctx.provider,
|
||||
params.positionTokenAccount,
|
||||
delegate.publicKey,
|
||||
"CloseAccount",
|
||||
owner
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: fakeDelegate.publicKey,
|
||||
receiver: owner.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
)
|
||||
.addSigner(fakeDelegate)
|
||||
.buildAndExecute(),
|
||||
/0x1783/ // MissingOrInvalidDelegate
|
||||
|
@ -389,7 +373,7 @@ describe("close_position", () => {
|
|||
});
|
||||
|
||||
it("fails if position token account mint does not match position mint", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -402,21 +386,22 @@ describe("close_position", () => {
|
|||
const fakePositionTokenAccount = await createAndMintToTokenAccount(provider, tokenMintA, 1);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
receiver: provider.wallet.publicKey,
|
||||
position: position.publicKey,
|
||||
positionMint: position.mintKeypair.publicKey,
|
||||
positionTokenAccount: fakePositionTokenAccount,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails if position_mint does not match position's position_mint field", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -427,15 +412,16 @@ describe("close_position", () => {
|
|||
const position = positions[0];
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.closePositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
receiver: provider.wallet.publicKey,
|
||||
position: position.publicKey,
|
||||
positionMint: tokenMintA,
|
||||
positionTokenAccount: position.tokenAccount,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
||||
);
|
||||
});
|
|
@ -1,27 +1,38 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import Decimal from "decimal.js";
|
||||
import {
|
||||
approveToken,
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
PositionData,
|
||||
collectFeesQuote,
|
||||
TickArrayData,
|
||||
TickUtil,
|
||||
WhirlpoolData,
|
||||
WhirlpoolIx,
|
||||
PDAUtil,
|
||||
TickArrayUtil,
|
||||
} from "../../src";
|
||||
import {
|
||||
TickSpacing,
|
||||
ZERO_BN,
|
||||
createTokenAccount,
|
||||
getTokenBalance,
|
||||
TickSpacing,
|
||||
approveToken,
|
||||
transfer,
|
||||
ZERO_BN,
|
||||
} from "./utils";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { getOraclePda, getPositionPda, getTickArrayPda, toX64 } from "../src";
|
||||
import { WhirlpoolTestFixture } from "./utils/fixture";
|
||||
import Decimal from "decimal.js";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
} from "../utils";
|
||||
import { WhirlpoolTestFixture } from "../utils/fixture";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("collect_fees", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully collect fees", async () => {
|
||||
// In same tick array - start index 22528
|
||||
|
@ -29,7 +40,7 @@ describe("collect_fees", () => {
|
|||
const tickUpperIndex = 33536;
|
||||
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }, // In range position
|
||||
|
@ -49,23 +60,24 @@ describe("collect_fees", () => {
|
|||
positions,
|
||||
} = fixture.getInfos();
|
||||
|
||||
const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
const positionBeforeSwap = await client.getPosition(positions[0].publicKey);
|
||||
const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
const positionBeforeSwap = (await fetcher.getPosition(positions[0].publicKey)) as PositionData;
|
||||
assert.ok(positionBeforeSwap.feeOwedA.eq(ZERO_BN));
|
||||
assert.ok(positionBeforeSwap.feeOwedB.eq(ZERO_BN));
|
||||
|
||||
const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey);
|
||||
const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey);
|
||||
|
||||
// Accrue fees in token A
|
||||
await client
|
||||
.swapTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.swapIx(ctx.program, {
|
||||
amount: new u64(200_000),
|
||||
otherAmountThreshold: ZERO_BN,
|
||||
sqrtPriceLimit: toX64(new Decimal(4)),
|
||||
sqrtPriceLimit: MathUtil.toX64(new Decimal(4)),
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB: true,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tokenAuthority: context.wallet.publicKey,
|
||||
tokenAuthority: ctx.wallet.publicKey,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
|
@ -75,18 +87,19 @@ describe("collect_fees", () => {
|
|||
tickArray2: tickArrayPda.publicKey,
|
||||
oracle: oraclePda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
// Accrue fees in token B
|
||||
await client
|
||||
.swapTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.swapIx(ctx.program, {
|
||||
amount: new u64(200_000),
|
||||
otherAmountThreshold: ZERO_BN,
|
||||
sqrtPriceLimit: toX64(new Decimal(5)),
|
||||
sqrtPriceLimit: MathUtil.toX64(new Decimal(5)),
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB: false,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tokenAuthority: context.wallet.publicKey,
|
||||
tokenAuthority: ctx.wallet.publicKey,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
|
@ -96,26 +109,44 @@ describe("collect_fees", () => {
|
|||
tickArray2: tickArrayPda.publicKey,
|
||||
oracle: oraclePda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
await client
|
||||
.updateFeesAndRewards({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const positionBeforeCollect = await client.getPosition(positions[0].publicKey);
|
||||
const positionBeforeCollect = (await fetcher.getPosition(
|
||||
positions[0].publicKey,
|
||||
true
|
||||
)) as PositionData;
|
||||
assert.ok(positionBeforeCollect.feeOwedA.eq(new u64(581)));
|
||||
assert.ok(positionBeforeCollect.feeOwedB.eq(new u64(581)));
|
||||
|
||||
const feeAccountA = await createTokenAccount(provider, tokenMintA, provider.wallet.publicKey);
|
||||
const feeAccountB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey);
|
||||
|
||||
await client
|
||||
.collectFeesTx({
|
||||
// Generate collect fees expectation
|
||||
const whirlpoolData = (await fetcher.getPool(whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
const tickArrayData = (await fetcher.getTickArray(tickArrayPda.publicKey)) as TickArrayData;
|
||||
const lowerTick = TickArrayUtil.getTickFromArray(tickArrayData, tickLowerIndex, tickSpacing);
|
||||
const upperTick = TickArrayUtil.getTickFromArray(tickArrayData, tickUpperIndex, tickSpacing);
|
||||
const expectation = collectFeesQuote({
|
||||
whirlpool: whirlpoolData,
|
||||
position: positionBeforeCollect,
|
||||
tickLower: lowerTick,
|
||||
tickUpper: upperTick,
|
||||
});
|
||||
|
||||
// Perform collect fees tx
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -125,32 +156,34 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
const positionAfter = await client.getPosition(positions[0].publicKey);
|
||||
).buildAndExecute();
|
||||
const positionAfter = (await fetcher.getPosition(positions[0].publicKey, true)) as PositionData;
|
||||
const feeBalanceA = await getTokenBalance(provider, feeAccountA);
|
||||
const feeBalanceB = await getTokenBalance(provider, feeAccountB);
|
||||
assert.equal(feeBalanceA, "581");
|
||||
assert.equal(feeBalanceB, "581");
|
||||
|
||||
assert.equal(feeBalanceA, expectation.feeOwedA);
|
||||
assert.equal(feeBalanceB, expectation.feeOwedB);
|
||||
assert.ok(positionAfter.feeOwedA.eq(ZERO_BN));
|
||||
assert.ok(positionAfter.feeOwedB.eq(ZERO_BN));
|
||||
|
||||
// Assert out of range position values
|
||||
await client
|
||||
.updateFeesAndRewards({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[1].publicKey,
|
||||
tickArrayLower: positions[1].tickArrayLower,
|
||||
tickArrayUpper: positions[1].tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
const outOfRangePosition = await client.getPosition(positions[1].publicKey);
|
||||
assert.ok(outOfRangePosition.feeOwedA.eq(ZERO_BN));
|
||||
assert.ok(outOfRangePosition.feeOwedB.eq(ZERO_BN));
|
||||
).buildAndExecute();
|
||||
const outOfRangePosition = await fetcher.getPosition(positions[1].publicKey, true);
|
||||
assert.ok(outOfRangePosition?.feeOwedA.eq(ZERO_BN));
|
||||
assert.ok(outOfRangePosition?.feeOwedB.eq(ZERO_BN));
|
||||
});
|
||||
|
||||
it("successfully collect fees with approved delegate", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: new u64(10_000_000) }, // In range position
|
||||
|
@ -167,8 +200,9 @@ describe("collect_fees", () => {
|
|||
const delegate = anchor.web3.Keypair.generate();
|
||||
await approveToken(provider, position.tokenAccount, delegate.publicKey, 1);
|
||||
|
||||
await client
|
||||
.collectFeesTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: position.publicKey,
|
||||
|
@ -178,13 +212,14 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("successfully collect fees with owner even if there is approved delegate", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: new u64(10_000_000) }, // In range position
|
||||
|
@ -201,8 +236,9 @@ describe("collect_fees", () => {
|
|||
const delegate = anchor.web3.Keypair.generate();
|
||||
await approveToken(provider, position.tokenAccount, delegate.publicKey, 1);
|
||||
|
||||
await client
|
||||
.collectFeesTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: position.publicKey,
|
||||
|
@ -212,12 +248,12 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
});
|
||||
|
||||
it("successfully collect fees with transferred position token", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: new u64(10_000_000) }, // In range position
|
||||
|
@ -240,8 +276,9 @@ describe("collect_fees", () => {
|
|||
|
||||
await transfer(provider, position.tokenAccount, newOwnerPositionTokenAccount, 1);
|
||||
|
||||
await client
|
||||
.collectFeesTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: newOwner.publicKey,
|
||||
position: position.publicKey,
|
||||
|
@ -251,6 +288,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
)
|
||||
.addSigner(newOwner)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
@ -260,7 +298,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -273,11 +311,12 @@ describe("collect_fees", () => {
|
|||
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda: whirlpoolPda2 },
|
||||
} = await initTestPool(client, tickSpacing);
|
||||
} = await initTestPool(ctx, tickSpacing);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda2.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -287,7 +326,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d1/ // ConstraintHasOne
|
||||
);
|
||||
});
|
||||
|
@ -297,7 +336,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -315,8 +354,9 @@ describe("collect_fees", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -326,15 +366,16 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
await transfer(provider, positions[0].tokenAccount, positionTokenAccount2, 1);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -344,7 +385,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
@ -354,7 +395,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -368,8 +409,9 @@ describe("collect_fees", () => {
|
|||
const delegate = anchor.web3.Keypair.generate();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -379,6 +421,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1783/ // MissingOrInvalidDelegate
|
||||
|
@ -390,7 +433,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -405,8 +448,9 @@ describe("collect_fees", () => {
|
|||
await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 2);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -416,6 +460,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1784/ // InvalidPositionTokenAmount
|
||||
|
@ -427,7 +472,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -442,8 +487,9 @@ describe("collect_fees", () => {
|
|||
await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -453,7 +499,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
@ -463,7 +509,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -481,8 +527,9 @@ describe("collect_fees", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -492,7 +539,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
@ -502,7 +549,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -523,8 +570,9 @@ describe("collect_fees", () => {
|
|||
const fakeVaultB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -534,13 +582,14 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: fakeVaultA,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -550,7 +599,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: fakeVaultB,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
||||
);
|
||||
});
|
||||
|
@ -560,7 +609,7 @@ describe("collect_fees", () => {
|
|||
const tickLowerIndex = 29440;
|
||||
const tickUpperIndex = 33536;
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -589,8 +638,9 @@ describe("collect_fees", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -600,13 +650,14 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectFeesTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -616,7 +667,7 @@ describe("collect_fees", () => {
|
|||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
|
@ -1,20 +1,20 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { getOraclePda, toX64 } from "../src";
|
||||
import { WhirlpoolTestFixture } from "./utils/fixture";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import Decimal from "decimal.js";
|
||||
import { createTokenAccount, getTokenBalance, TickSpacing, ZERO_BN } from "./utils";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx, PDAUtil } from "../../src";
|
||||
import { TickSpacing, ZERO_BN, createTokenAccount, getTokenBalance } from "../utils";
|
||||
import { WhirlpoolTestFixture } from "../utils/fixture";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("collect_protocol_fees", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully collects fees", async () => {
|
||||
// In same tick array - start index 22528
|
||||
|
@ -22,7 +22,7 @@ describe("collect_protocol_fees", () => {
|
|||
const tickUpperIndex = 33536;
|
||||
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }],
|
||||
});
|
||||
|
@ -35,40 +35,43 @@ describe("collect_protocol_fees", () => {
|
|||
tokenMintB,
|
||||
},
|
||||
configKeypairs: { feeAuthorityKeypair, collectProtocolFeesAuthorityKeypair },
|
||||
configInitInfo: { whirlpoolConfigKeypair },
|
||||
configInitInfo: { whirlpoolsConfigKeypair: whirlpoolsConfigKeypair },
|
||||
tokenAccountA,
|
||||
tokenAccountB,
|
||||
positions,
|
||||
} = fixture.getInfos();
|
||||
|
||||
await client
|
||||
.setProtocolFeeRateIx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setProtocolFeeRateIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
protocolFeeRate: 2500,
|
||||
})
|
||||
)
|
||||
.addSigner(feeAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
const poolBefore = await client.getPool(whirlpoolPda.publicKey);
|
||||
assert.ok(poolBefore.protocolFeeOwedA.eq(ZERO_BN));
|
||||
assert.ok(poolBefore.protocolFeeOwedB.eq(ZERO_BN));
|
||||
const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(poolBefore?.protocolFeeOwedA.eq(ZERO_BN));
|
||||
assert.ok(poolBefore?.protocolFeeOwedB.eq(ZERO_BN));
|
||||
|
||||
const tickArrayPda = positions[0].tickArrayLower;
|
||||
|
||||
const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey);
|
||||
const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey);
|
||||
|
||||
// Accrue fees in token A
|
||||
await client
|
||||
.swapTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.swapIx(ctx.program, {
|
||||
amount: new u64(200_000),
|
||||
otherAmountThreshold: ZERO_BN,
|
||||
sqrtPriceLimit: toX64(new Decimal(4)),
|
||||
sqrtPriceLimit: MathUtil.toX64(new Decimal(4)),
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB: true,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tokenAuthority: context.wallet.publicKey,
|
||||
tokenAuthority: ctx.wallet.publicKey,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
|
@ -78,18 +81,19 @@ describe("collect_protocol_fees", () => {
|
|||
tickArray2: tickArrayPda,
|
||||
oracle: oraclePda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
// Accrue fees in token B
|
||||
await client
|
||||
.swapTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.swapIx(ctx.program, {
|
||||
amount: new u64(200_000),
|
||||
otherAmountThreshold: ZERO_BN,
|
||||
sqrtPriceLimit: toX64(new Decimal(5)),
|
||||
sqrtPriceLimit: MathUtil.toX64(new Decimal(5)),
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB: false,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tokenAuthority: context.wallet.publicKey,
|
||||
tokenAuthority: ctx.wallet.publicKey,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
|
@ -99,25 +103,27 @@ describe("collect_protocol_fees", () => {
|
|||
tickArray2: tickArrayPda,
|
||||
oracle: oraclePda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const poolAfter = await client.getPool(whirlpoolPda.publicKey);
|
||||
assert.ok(poolAfter.protocolFeeOwedA.eq(new u64(150)));
|
||||
assert.ok(poolAfter.protocolFeeOwedB.eq(new u64(150)));
|
||||
const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(poolAfter?.protocolFeeOwedA.eq(new u64(150)));
|
||||
assert.ok(poolAfter?.protocolFeeOwedB.eq(new u64(150)));
|
||||
|
||||
const destA = await createTokenAccount(provider, tokenMintA, provider.wallet.publicKey);
|
||||
const destB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey);
|
||||
|
||||
await client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tokenDestinationA: destA,
|
||||
tokenDestinationB: destB,
|
||||
tokenOwnerAccountA: destA,
|
||||
tokenOwnerAccountB: destB,
|
||||
})
|
||||
)
|
||||
.addSigner(collectProtocolFeesAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
|
@ -125,13 +131,13 @@ describe("collect_protocol_fees", () => {
|
|||
const balanceDestB = await getTokenBalance(provider, destB);
|
||||
assert.equal(balanceDestA, "150");
|
||||
assert.equal(balanceDestB, "150");
|
||||
assert.ok(poolBefore.protocolFeeOwedA.eq(ZERO_BN));
|
||||
assert.ok(poolBefore.protocolFeeOwedB.eq(ZERO_BN));
|
||||
assert.ok(poolBefore?.protocolFeeOwedA.eq(ZERO_BN));
|
||||
assert.ok(poolBefore?.protocolFeeOwedB.eq(ZERO_BN));
|
||||
});
|
||||
|
||||
it("fails to collect fees without the authority's signature", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) },
|
||||
|
@ -140,30 +146,31 @@ describe("collect_protocol_fees", () => {
|
|||
const {
|
||||
poolInitInfo: { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair },
|
||||
configKeypairs: { collectProtocolFeesAuthorityKeypair },
|
||||
configInitInfo: { whirlpoolConfigKeypair },
|
||||
configInitInfo: { whirlpoolsConfigKeypair },
|
||||
tokenAccountA,
|
||||
tokenAccountB,
|
||||
} = fixture.getInfos();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tokenDestinationA: tokenAccountA,
|
||||
tokenDestinationB: tokenAccountB,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when collect_protocol_fees_authority is invalid", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) },
|
||||
|
@ -172,22 +179,24 @@ describe("collect_protocol_fees", () => {
|
|||
const {
|
||||
poolInitInfo: { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair },
|
||||
configKeypairs: { rewardEmissionsSuperAuthorityKeypair },
|
||||
configInitInfo: { whirlpoolConfigKeypair },
|
||||
configInitInfo: { whirlpoolsConfigKeypair },
|
||||
tokenAccountA,
|
||||
tokenAccountB,
|
||||
} = fixture.getInfos();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
collectProtocolFeesAuthority: rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tokenDestinationA: tokenAccountA,
|
||||
tokenDestinationB: tokenAccountB,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
})
|
||||
)
|
||||
.addSigner(rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
||||
|
@ -196,7 +205,7 @@ describe("collect_protocol_fees", () => {
|
|||
|
||||
it("fails when whirlpool does not match config", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) },
|
||||
|
@ -205,25 +214,27 @@ describe("collect_protocol_fees", () => {
|
|||
const {
|
||||
poolInitInfo: { tokenVaultAKeypair, tokenVaultBKeypair },
|
||||
configKeypairs: { collectProtocolFeesAuthorityKeypair },
|
||||
configInitInfo: { whirlpoolConfigKeypair },
|
||||
configInitInfo: { whirlpoolsConfigKeypair },
|
||||
tokenAccountA,
|
||||
tokenAccountB,
|
||||
} = fixture.getInfos();
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda: whirlpoolPda2 },
|
||||
} = await initTestPool(client, tickSpacing);
|
||||
} = await initTestPool(ctx, tickSpacing);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolPda2.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tokenDestinationA: tokenAccountA,
|
||||
tokenDestinationB: tokenAccountB,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
})
|
||||
)
|
||||
.addSigner(collectProtocolFeesAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7d1/ // ConstraintHasOne
|
||||
|
@ -232,7 +243,7 @@ describe("collect_protocol_fees", () => {
|
|||
|
||||
it("fails when vaults do not match whirlpool vaults", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) },
|
||||
|
@ -247,7 +258,7 @@ describe("collect_protocol_fees", () => {
|
|||
tokenMintB,
|
||||
},
|
||||
configKeypairs: { collectProtocolFeesAuthorityKeypair },
|
||||
configInitInfo: { whirlpoolConfigKeypair },
|
||||
configInitInfo: { whirlpoolsConfigKeypair: whirlpoolsConfigKeypair },
|
||||
tokenAccountA,
|
||||
tokenAccountB,
|
||||
} = fixture.getInfos();
|
||||
|
@ -256,32 +267,36 @@ describe("collect_protocol_fees", () => {
|
|||
const fakeVaultB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
tokenVaultA: fakeVaultA,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tokenDestinationA: tokenAccountA,
|
||||
tokenDestinationB: tokenAccountB,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
})
|
||||
)
|
||||
.addSigner(collectProtocolFeesAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: fakeVaultB,
|
||||
tokenDestinationA: tokenAccountA,
|
||||
tokenDestinationB: tokenAccountB,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
})
|
||||
)
|
||||
.addSigner(collectProtocolFeesAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
||||
|
@ -290,7 +305,7 @@ describe("collect_protocol_fees", () => {
|
|||
|
||||
it("fails when destination mints do not match whirlpool mints", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [
|
||||
{ tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) },
|
||||
|
@ -305,7 +320,7 @@ describe("collect_protocol_fees", () => {
|
|||
tokenMintB,
|
||||
},
|
||||
configKeypairs: { collectProtocolFeesAuthorityKeypair },
|
||||
configInitInfo: { whirlpoolConfigKeypair },
|
||||
configInitInfo: { whirlpoolsConfigKeypair: whirlpoolsConfigKepair },
|
||||
tokenAccountA,
|
||||
tokenAccountB,
|
||||
} = fixture.getInfos();
|
||||
|
@ -314,32 +329,36 @@ describe("collect_protocol_fees", () => {
|
|||
const invalidDestB = await createTokenAccount(provider, tokenMintA, provider.wallet.publicKey);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKepair.publicKey,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tokenDestinationA: invalidDestA,
|
||||
tokenDestinationB: tokenAccountB,
|
||||
tokenOwnerAccountA: invalidDestA,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
})
|
||||
)
|
||||
.addSigner(collectProtocolFeesAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectProtocolFeesTx({
|
||||
whirlpoolsConfig: whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectProtocolFeesIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKepair.publicKey,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tokenDestinationA: tokenAccountA,
|
||||
tokenDestinationB: invalidDestB,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenOwnerAccountB: invalidDestB,
|
||||
})
|
||||
)
|
||||
.addSigner(collectProtocolFeesAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
|
@ -1,43 +1,70 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import Decimal from "decimal.js";
|
||||
import {
|
||||
approveToken,
|
||||
createAndMintToTokenAccount,
|
||||
createMint,
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
NUM_REWARDS,
|
||||
collectRewardsQuote,
|
||||
WhirlpoolData,
|
||||
PositionData,
|
||||
TickArrayData,
|
||||
TickUtil,
|
||||
WhirlpoolIx,
|
||||
TickArrayUtil,
|
||||
} from "../../src";
|
||||
import {
|
||||
TickSpacing,
|
||||
sleep,
|
||||
createTokenAccount,
|
||||
getTokenBalance,
|
||||
sleep,
|
||||
TickSpacing,
|
||||
transfer,
|
||||
ZERO_BN,
|
||||
} from "./utils";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { NUM_REWARDS, toX64 } from "../src";
|
||||
import Decimal from "decimal.js";
|
||||
import { WhirlpoolTestFixture } from "./utils/fixture";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
approveToken,
|
||||
transfer,
|
||||
createMint,
|
||||
createAndMintToTokenAccount,
|
||||
} from "../utils";
|
||||
import { WhirlpoolTestFixture } from "../utils/fixture";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("collect_reward", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully collect rewards", async () => {
|
||||
const vaultStartBalance = 1_000_000;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
const lowerTickIndex = -1280,
|
||||
upperTickIndex = 1280,
|
||||
tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: tickSpacing,
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
{
|
||||
tickLowerIndex: lowerTickIndex,
|
||||
tickUpperIndex: upperTickIndex,
|
||||
liquidityAmount: new anchor.BN(1_000_000),
|
||||
},
|
||||
],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: toX64(new Decimal(10)), vaultAmount: new u64(vaultStartBalance) },
|
||||
{ emissionsPerSecondX64: toX64(new Decimal(10)), vaultAmount: new u64(vaultStartBalance) },
|
||||
{ emissionsPerSecondX64: toX64(new Decimal(10)), vaultAmount: new u64(vaultStartBalance) },
|
||||
{
|
||||
emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)),
|
||||
vaultAmount: new u64(vaultStartBalance),
|
||||
},
|
||||
{
|
||||
emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)),
|
||||
vaultAmount: new u64(vaultStartBalance),
|
||||
},
|
||||
{
|
||||
emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)),
|
||||
vaultAmount: new u64(vaultStartBalance),
|
||||
},
|
||||
],
|
||||
});
|
||||
const {
|
||||
|
@ -46,23 +73,52 @@ describe("collect_reward", () => {
|
|||
rewards,
|
||||
} = fixture.getInfos();
|
||||
await sleep(500);
|
||||
await client
|
||||
.updateFeesAndRewards({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: positions[0].tickArrayLower,
|
||||
tickArrayUpper: positions[0].tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
// Generate collect reward expectation
|
||||
const pool = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
const positionPreCollect = (await fetcher.getPosition(
|
||||
positions[0].publicKey,
|
||||
true
|
||||
)) as PositionData;
|
||||
const tickArrayLower = (await fetcher.getTickArray(
|
||||
positions[0].tickArrayLower,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
|
||||
const tickArrayUpper = (await fetcher.getTickArray(
|
||||
positions[0].tickArrayUpper,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
const lowerTick = TickArrayUtil.getTickFromArray(tickArrayLower, lowerTickIndex, tickSpacing);
|
||||
const upperTick = TickArrayUtil.getTickFromArray(tickArrayUpper, upperTickIndex, tickSpacing);
|
||||
|
||||
const expectation = collectRewardsQuote({
|
||||
whirlpool: pool,
|
||||
position: positionPreCollect,
|
||||
tickLower: lowerTick,
|
||||
tickUpper: upperTick,
|
||||
});
|
||||
|
||||
// Perform collect rewards tx
|
||||
for (let i = 0; i < NUM_REWARDS; i++) {
|
||||
const rewardOwnerAccount = await createTokenAccount(
|
||||
provider,
|
||||
rewards[i].rewardMint,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
await client
|
||||
.collectRewardTx({
|
||||
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -71,30 +127,33 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[i].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: i,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const collectedBalance = parseInt(await getTokenBalance(provider, rewardOwnerAccount));
|
||||
assert.ok(collectedBalance > 0);
|
||||
assert.ok(collectedBalance === expectation[i]?.toNumber());
|
||||
const vaultBalance = parseInt(
|
||||
await getTokenBalance(provider, rewards[i].rewardVaultKeypair.publicKey)
|
||||
);
|
||||
assert.equal(vaultStartBalance - collectedBalance, vaultBalance);
|
||||
const position = await client.getPosition(positions[0].publicKey);
|
||||
assert.equal(position.rewardInfos[i].amountOwed, 0);
|
||||
assert.ok(position.rewardInfos[i].growthInsideCheckpoint.gte(ZERO_BN));
|
||||
const position = await fetcher.getPosition(positions[0].publicKey, true);
|
||||
assert.equal(position?.rewardInfos[i].amountOwed, 0);
|
||||
assert.ok(position?.rewardInfos[i].growthInsideCheckpoint.gte(ZERO_BN));
|
||||
}
|
||||
});
|
||||
|
||||
it("successfully collect reward with a position authority delegate", async () => {
|
||||
const vaultStartBalance = 1_000_000;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(vaultStartBalance) },
|
||||
{
|
||||
emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)),
|
||||
vaultAmount: new u64(vaultStartBalance),
|
||||
},
|
||||
],
|
||||
});
|
||||
const {
|
||||
|
@ -108,20 +167,22 @@ describe("collect_reward", () => {
|
|||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await client
|
||||
.updateFeesAndRewards({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: positions[0].tickArrayLower,
|
||||
tickArrayUpper: positions[0].tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1);
|
||||
|
||||
await client
|
||||
.collectRewardTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -130,20 +191,24 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("successfully collect reward with transferred position token", async () => {
|
||||
const vaultStartBalance = 1_000_000;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(vaultStartBalance) },
|
||||
{
|
||||
emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)),
|
||||
vaultAmount: new u64(vaultStartBalance),
|
||||
},
|
||||
],
|
||||
});
|
||||
const {
|
||||
|
@ -165,17 +230,19 @@ describe("collect_reward", () => {
|
|||
);
|
||||
await transfer(provider, positions[0].tokenAccount, delegatePositionAccount, 1);
|
||||
|
||||
await client
|
||||
.updateFeesAndRewards({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: positions[0].tickArrayLower,
|
||||
tickArrayUpper: positions[0].tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
await client
|
||||
.collectRewardTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -184,20 +251,24 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("successfully collect reward with owner even when there is a delegate", async () => {
|
||||
const vaultStartBalance = 1_000_000;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(vaultStartBalance) },
|
||||
{
|
||||
emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)),
|
||||
vaultAmount: new u64(vaultStartBalance),
|
||||
},
|
||||
],
|
||||
});
|
||||
const {
|
||||
|
@ -211,20 +282,22 @@ describe("collect_reward", () => {
|
|||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await client
|
||||
.updateFeesAndRewards({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: positions[0].tickArrayLower,
|
||||
tickArrayUpper: positions[0].tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1);
|
||||
|
||||
await client
|
||||
.collectRewardTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -233,13 +306,13 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
});
|
||||
|
||||
it("fails when reward index references an uninitialized reward", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
|
@ -256,8 +329,9 @@ describe("collect_reward", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -266,33 +340,36 @@ describe("collect_reward", () => {
|
|||
rewardVault: anchor.web3.PublicKey.default,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0xbbf/ // AccountNotInitialized
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position does not match whirlpool", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const { positions, rewards } = fixture.getInfos();
|
||||
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda },
|
||||
} = await initTestPool(client, TickSpacing.Standard);
|
||||
} = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const rewardOwnerAccount = await createTokenAccount(
|
||||
provider,
|
||||
rewards[0].rewardMint,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -301,19 +378,21 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d1/ // ConstraintHasOne
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position token account does not have exactly one token", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda },
|
||||
|
@ -333,8 +412,9 @@ describe("collect_reward", () => {
|
|||
);
|
||||
await transfer(provider, positions[0].tokenAccount, otherPositionAcount, 1);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -343,19 +423,21 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position token account mint does not match position mint", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda, tokenMintA },
|
||||
|
@ -371,8 +453,9 @@ describe("collect_reward", () => {
|
|||
|
||||
const fakePositionTokenAccount = await createAndMintToTokenAccount(provider, tokenMintA, 1);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -381,19 +464,21 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position authority is not approved delegate for position token account", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda },
|
||||
|
@ -407,8 +492,9 @@ describe("collect_reward", () => {
|
|||
);
|
||||
const delegate = anchor.web3.Keypair.generate();
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -417,6 +503,7 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1783/ // MissingOrInvalidDelegate
|
||||
|
@ -424,13 +511,15 @@ describe("collect_reward", () => {
|
|||
});
|
||||
|
||||
it("fails when position authority is not authorized for exactly one token", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda },
|
||||
|
@ -445,8 +534,9 @@ describe("collect_reward", () => {
|
|||
const delegate = anchor.web3.Keypair.generate();
|
||||
await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 2);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -455,6 +545,7 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1784/ // InvalidPositionTokenAmount
|
||||
|
@ -462,13 +553,15 @@ describe("collect_reward", () => {
|
|||
});
|
||||
|
||||
it("fails when position authority was not a signer", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda },
|
||||
|
@ -483,8 +576,9 @@ describe("collect_reward", () => {
|
|||
const delegate = anchor.web3.Keypair.generate();
|
||||
await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: delegate.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -493,19 +587,21 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when reward vault does not match whirlpool reward vault", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda },
|
||||
|
@ -518,8 +614,9 @@ describe("collect_reward", () => {
|
|||
provider.wallet.publicKey
|
||||
);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -528,19 +625,21 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewardOwnerAccount,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when reward owner account mint does not match whirlpool reward mint", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda, tokenMintA },
|
||||
|
@ -553,8 +652,9 @@ describe("collect_reward", () => {
|
|||
provider.wallet.publicKey
|
||||
);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -563,19 +663,21 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when reward index is out of bounds", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [
|
||||
{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) },
|
||||
],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda, tokenMintA },
|
||||
|
@ -588,8 +690,9 @@ describe("collect_reward", () => {
|
|||
provider.wallet.publicKey
|
||||
);
|
||||
await assert.rejects(
|
||||
client
|
||||
.collectRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -598,7 +701,7 @@ describe("collect_reward", () => {
|
|||
rewardVault: rewards[0].rewardVaultKeypair.publicKey,
|
||||
rewardIndex: 4,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Program failed to complete/ // index out of bounds
|
||||
);
|
||||
});
|
|
@ -1,47 +1,64 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import * as assert from "assert";
|
||||
import { initTestPool, initTickArray, openPosition } from "./utils/init-utils";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import Decimal from "decimal.js";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
WhirlpoolData,
|
||||
TickArrayData,
|
||||
PositionData,
|
||||
WhirlpoolIx,
|
||||
} from "../../src";
|
||||
import {
|
||||
TickSpacing,
|
||||
assertTick,
|
||||
approveToken,
|
||||
createAndMintToTokenAccount,
|
||||
createMint,
|
||||
createTokenAccount,
|
||||
transfer,
|
||||
} from "./utils/token";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { toX64 } from "../src";
|
||||
import Decimal from "decimal.js";
|
||||
import { assertTick, TickSpacing, ZERO_BN } from "./utils";
|
||||
import { WhirlpoolTestFixture } from "./utils/fixture";
|
||||
ZERO_BN,
|
||||
createAndMintToTokenAccount,
|
||||
createMint,
|
||||
} from "../utils";
|
||||
import { WhirlpoolTestFixture } from "../utils/fixture";
|
||||
import { initTestPool, openPosition, initTickArray } from "../utils/init-utils";
|
||||
import { decreaseLiquidityQuoteByLiquidityWithParams } from "../../src/quotes/public/decrease-liquidity-quote";
|
||||
import { MathUtil, Percentage } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("decrease_liquidity", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
|
||||
// TODO: Convert these test cases to take a non-zero min token amount to verify that the calculation is working.
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully decrease liquidity from position in one tick array", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const tickLower = 7168,
|
||||
tickUpper = 8960;
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1.48)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1.48)),
|
||||
positions: [{ tickLowerIndex: tickLower, tickUpperIndex: tickUpper, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, tokenAccountA, tokenAccountB, positions } = fixture.getInfos();
|
||||
const { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair } = poolInitInfo;
|
||||
const poolBefore = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
|
||||
const removeAmount = new u64(1_000_000);
|
||||
await client
|
||||
.decreaseLiquidityTx({
|
||||
liquidityAmount: removeAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
const removalQuote = decreaseLiquidityQuoteByLiquidityWithParams({
|
||||
liquidity: new u64(1_000_000),
|
||||
sqrtPrice: poolBefore.sqrtPrice,
|
||||
slippageTolerance: Percentage.fromFraction(1, 100),
|
||||
tickCurrentIndex: poolBefore.tickCurrentIndex,
|
||||
tickLowerIndex: tickLower,
|
||||
tickUpperIndex: tickUpper,
|
||||
});
|
||||
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
...removalQuote,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
|
@ -53,39 +70,51 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: positions[0].tickArrayLower,
|
||||
tickArrayUpper: positions[0].tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const remainingLiquidity = liquidityAmount.sub(removeAmount);
|
||||
const poolAfter = await client.getPool(whirlpoolPda.publicKey);
|
||||
const remainingLiquidity = liquidityAmount.sub(removalQuote.liquidityAmount);
|
||||
const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp));
|
||||
assert.ok(poolAfter.liquidity.eq(remainingLiquidity));
|
||||
|
||||
const position = await client.getPosition(positions[0].publicKey);
|
||||
assert.ok(position.liquidity.eq(remainingLiquidity));
|
||||
const position = await fetcher.getPosition(positions[0].publicKey, true);
|
||||
assert.ok(position?.liquidity.eq(remainingLiquidity));
|
||||
|
||||
const tickArray = await client.getTickArray(positions[0].tickArrayLower);
|
||||
const tickArray = (await fetcher.getTickArray(
|
||||
positions[0].tickArrayLower,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
assertTick(tickArray.ticks[56], true, remainingLiquidity, remainingLiquidity);
|
||||
assertTick(tickArray.ticks[70], true, remainingLiquidity, remainingLiquidity.neg());
|
||||
});
|
||||
|
||||
it("successfully decrease liquidity from position in two tick arrays", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const tickLower = -1280,
|
||||
tickUpper = 1280;
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair } = poolInitInfo;
|
||||
const position = positions[0];
|
||||
const poolBefore = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
|
||||
const removeAmount = new u64(1_000_000);
|
||||
await client
|
||||
.decreaseLiquidityTx({
|
||||
liquidityAmount: removeAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
const removalQuote = decreaseLiquidityQuoteByLiquidityWithParams({
|
||||
liquidity: new u64(1_000_000),
|
||||
sqrtPrice: poolBefore.sqrtPrice,
|
||||
slippageTolerance: Percentage.fromFraction(1, 100),
|
||||
tickCurrentIndex: poolBefore.tickCurrentIndex,
|
||||
tickLowerIndex: tickLower,
|
||||
tickUpperIndex: tickUpper,
|
||||
});
|
||||
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
...removalQuote,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: position.publicKey,
|
||||
|
@ -97,28 +126,34 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const remainingLiquidity = liquidityAmount.sub(removeAmount);
|
||||
const poolAfter = await client.getPool(whirlpoolPda.publicKey);
|
||||
const remainingLiquidity = liquidityAmount.sub(removalQuote.liquidityAmount);
|
||||
const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
|
||||
assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp));
|
||||
assert.ok(poolAfter.liquidity.eq(remainingLiquidity));
|
||||
|
||||
const positionAfter = await client.getPosition(position.publicKey);
|
||||
const positionAfter = (await fetcher.getPosition(position.publicKey, true)) as PositionData;
|
||||
assert.ok(positionAfter.liquidity.eq(remainingLiquidity));
|
||||
|
||||
const tickArrayLower = await client.getTickArray(position.tickArrayLower);
|
||||
const tickArrayLower = (await fetcher.getTickArray(
|
||||
position.tickArrayLower,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
assertTick(tickArrayLower.ticks[78], true, remainingLiquidity, remainingLiquidity);
|
||||
const tickArrayUpper = await client.getTickArray(position.tickArrayUpper);
|
||||
const tickArrayUpper = (await fetcher.getTickArray(
|
||||
position.tickArrayUpper,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
assertTick(tickArrayUpper.ticks[10], true, remainingLiquidity, remainingLiquidity.neg());
|
||||
});
|
||||
|
||||
it("successfully decrease liquidity with approved delegate", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -133,8 +168,9 @@ describe("decrease_liquidity", () => {
|
|||
|
||||
const removeAmount = new u64(1_000_000);
|
||||
|
||||
await client
|
||||
.decreaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: removeAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -149,15 +185,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("successfully decrease liquidity with owner even if there is approved delegate", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1.48)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1.48)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -172,8 +209,9 @@ describe("decrease_liquidity", () => {
|
|||
|
||||
const removeAmount = new u64(1_000_000);
|
||||
|
||||
await client
|
||||
.decreaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: removeAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -188,14 +226,14 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
});
|
||||
|
||||
it("successfully decrease liquidity with transferred position token", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1.48)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1.48)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -211,8 +249,9 @@ describe("decrease_liquidity", () => {
|
|||
);
|
||||
await transfer(provider, position.tokenAccount, newOwnerPositionTokenAccount, 1);
|
||||
|
||||
await client
|
||||
.decreaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: removeAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -227,15 +266,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(newOwner)
|
||||
.buildAndExecute();
|
||||
});
|
||||
|
||||
it("fails when liquidity amount is zero", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -243,8 +283,9 @@ describe("decrease_liquidity", () => {
|
|||
const position = positions[0];
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: new u64(0),
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -259,15 +300,15 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x177c/ // LiquidityZero
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position has insufficient liquidity for the withdraw amount", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -275,8 +316,9 @@ describe("decrease_liquidity", () => {
|
|||
const position = positions[0];
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: new u64(1_000),
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -291,16 +333,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x177f/ // LiquidityUnderflow
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when token min a subceeded", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(0.005)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(0.005)),
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -308,8 +350,9 @@ describe("decrease_liquidity", () => {
|
|||
const position = positions[0];
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(1_000_000),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -324,24 +367,25 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x1782/ // TokenMinSubceeded
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when token min b subceeded", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(5)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(5)),
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair } = poolInitInfo;
|
||||
const position = positions[0];
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(1_000_000),
|
||||
|
@ -356,16 +400,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x1782/ // TokenMinSubceeded
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position account does not have exactly 1 token", async () => {
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -380,8 +424,9 @@ describe("decrease_liquidity", () => {
|
|||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -396,7 +441,7 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
|
@ -404,8 +449,9 @@ describe("decrease_liquidity", () => {
|
|||
await transfer(provider, position.tokenAccount, newPositionTokenAccount, 1);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -420,16 +466,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position token account mint does not match position mint", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -439,8 +485,9 @@ describe("decrease_liquidity", () => {
|
|||
const invalidPositionTokenAccount = await createAndMintToTokenAccount(provider, tokenMintA, 1);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -455,30 +502,31 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // A raw constraint was violated
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position does not match whirlpool", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda } = poolInitInfo;
|
||||
const tickArray = positions[0].tickArrayLower;
|
||||
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const {
|
||||
params: { positionPda, positionTokenAccountAddress },
|
||||
} = await openPosition(client, poolInitInfo2.whirlpoolPda.publicKey, 7168, 8960);
|
||||
params: { positionPda, positionTokenAccount: positionTokenAccountAddress },
|
||||
} = await openPosition(ctx, poolInitInfo2.whirlpoolPda.publicKey, 7168, 8960);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -493,16 +541,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: tickArray,
|
||||
tickArrayUpper: tickArray,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d1/ // A has_one constraint was violated
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when token vaults do not match whirlpool vaults", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -513,8 +561,9 @@ describe("decrease_liquidity", () => {
|
|||
const fakeVaultB = await createAndMintToTokenAccount(provider, tokenMintB, 1_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -529,13 +578,14 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -550,16 +600,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when owner token account mint does not match whirlpool token mint", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -569,8 +619,9 @@ describe("decrease_liquidity", () => {
|
|||
const position = positions[0];
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -585,13 +636,14 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -606,16 +658,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position authority is not approved delegate for position token account", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -627,8 +679,9 @@ describe("decrease_liquidity", () => {
|
|||
await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -643,6 +696,7 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1783/ // MissingOrInvalidDelegate
|
||||
|
@ -651,9 +705,9 @@ describe("decrease_liquidity", () => {
|
|||
|
||||
it("fails when position authority is not authorized for exactly 1 token", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -666,8 +720,9 @@ describe("decrease_liquidity", () => {
|
|||
await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -682,6 +737,7 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1784/ // InvalidPositionTokenAmount
|
||||
|
@ -690,9 +746,9 @@ describe("decrease_liquidity", () => {
|
|||
|
||||
it("fails when position authority was not a signer", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -705,8 +761,9 @@ describe("decrease_liquidity", () => {
|
|||
await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(167_000),
|
||||
|
@ -721,16 +778,16 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: position.tickArrayLower,
|
||||
tickArrayUpper: position.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when tick arrays do not match the position", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -739,15 +796,16 @@ describe("decrease_liquidity", () => {
|
|||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayLowerPda },
|
||||
} = await initTickArray(client, whirlpoolPda.publicKey, 11264);
|
||||
} = await initTickArray(ctx, whirlpoolPda.publicKey, 11264);
|
||||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayUpperPda },
|
||||
} = await initTickArray(client, whirlpoolPda.publicKey, 22528);
|
||||
} = await initTickArray(ctx, whirlpoolPda.publicKey, 22528);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -762,35 +820,36 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x1779/ // TicKNotFound
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when the tick arrays are for a different whirlpool", async () => {
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(2.2)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda } = poolInitInfo;
|
||||
const position = positions[0];
|
||||
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayLowerPda },
|
||||
} = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, -11264);
|
||||
} = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, -11264);
|
||||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayUpperPda },
|
||||
} = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, 0);
|
||||
} = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, 0);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.decreaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMinA: new u64(0),
|
||||
tokenMinB: new u64(0),
|
||||
|
@ -805,7 +864,7 @@ describe("decrease_liquidity", () => {
|
|||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d1/ // A has one constraint was violated
|
||||
);
|
||||
});
|
|
@ -1,72 +1,72 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import * as assert from "assert";
|
||||
import { initTestPool, initTickArray, openPosition } from "./utils/init-utils";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import Decimal from "decimal.js";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
WhirlpoolData,
|
||||
PositionData,
|
||||
TickArrayData,
|
||||
TickUtil,
|
||||
PriceMath,
|
||||
WhirlpoolIx,
|
||||
PDAUtil,
|
||||
} from "../../src";
|
||||
import {
|
||||
TickSpacing,
|
||||
ZERO_BN,
|
||||
getTokenBalance,
|
||||
assertTick,
|
||||
approveToken,
|
||||
createAndMintToTokenAccount,
|
||||
createMint,
|
||||
MAX_U64,
|
||||
createTokenAccount,
|
||||
getTokenBalance,
|
||||
transfer,
|
||||
} from "./utils/token";
|
||||
createMint,
|
||||
} from "../utils";
|
||||
import { WhirlpoolTestFixture } from "../utils/fixture";
|
||||
import { initTestPool, initTickArray, openPosition } from "../utils/init-utils";
|
||||
import {
|
||||
estimateLiquidityFromTokenAmounts,
|
||||
getStartTickIndex,
|
||||
getTickArrayPda,
|
||||
tickIndexToSqrtPriceX64,
|
||||
toTokenAmount,
|
||||
toX64,
|
||||
TransactionBuilder,
|
||||
} from "../src";
|
||||
import Decimal from "decimal.js";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { assertTick, MAX_U64, TickSpacing, ZERO_BN } from "./utils";
|
||||
import { WhirlpoolTestFixture } from "./utils/fixture";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { buildIncreaseLiquidityIx } from "../src/instructions/increase-liquidity-ix";
|
||||
import {
|
||||
buildOpenPositionIx,
|
||||
buildOpenPositionWithMetadataIx,
|
||||
} from "../src/instructions/open-position-ix";
|
||||
import {
|
||||
generateDefaultInitTickArrayParams,
|
||||
generateDefaultOpenPositionParams,
|
||||
} from "./utils/test-builders";
|
||||
import { buildInitTickArrayIx } from "../src/instructions/initialize-tick-array-ix";
|
||||
generateDefaultInitTickArrayParams,
|
||||
} from "../utils/test-builders";
|
||||
import { PoolUtil, toTokenAmount } from "../../src/utils/public/pool-utils";
|
||||
import { MathUtil, TransactionBuilder } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("increase_liquidity", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("increase liquidity of a position spanning two tick arrays", async () => {
|
||||
const currTick = 0;
|
||||
const tickLowerIndex = -1280,
|
||||
tickUpperIndex = 1280;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }],
|
||||
initialSqrtPrice: tickIndexToSqrtPriceX64(currTick),
|
||||
initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick),
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda } = poolInitInfo;
|
||||
const positionInitInfo = positions[0];
|
||||
|
||||
const poolBefore = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
const tokenAmount = toTokenAmount(167_000, 167_000);
|
||||
const liquidityAmount = estimateLiquidityFromTokenAmounts(
|
||||
const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts(
|
||||
currTick,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
tokenAmount
|
||||
);
|
||||
|
||||
await client
|
||||
.increaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: tokenAmount.tokenA,
|
||||
tokenMaxB: tokenAmount.tokenB,
|
||||
|
@ -81,12 +81,12 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const position = await client.getPosition(positionInitInfo.publicKey);
|
||||
const position = (await fetcher.getPosition(positionInitInfo.publicKey, true)) as PositionData;
|
||||
assert.ok(position.liquidity.eq(liquidityAmount));
|
||||
|
||||
const poolAfter = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp));
|
||||
assert.equal(
|
||||
await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey),
|
||||
|
@ -96,11 +96,17 @@ describe("increase_liquidity", () => {
|
|||
await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey),
|
||||
tokenAmount.tokenB.toString()
|
||||
);
|
||||
assert.ok(poolAfter.liquidity.eq(new BN(liquidityAmount)));
|
||||
assert.ok(poolAfter.liquidity.eq(new anchor.BN(liquidityAmount)));
|
||||
|
||||
const tickArrayLower = await client.getTickArray(positionInitInfo.tickArrayLower);
|
||||
const tickArrayLower = (await fetcher.getTickArray(
|
||||
positionInitInfo.tickArrayLower,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
assertTick(tickArrayLower.ticks[78], true, liquidityAmount, liquidityAmount);
|
||||
const tickArrayUpper = await client.getTickArray(positionInitInfo.tickArrayUpper);
|
||||
const tickArrayUpper = (await fetcher.getTickArray(
|
||||
positionInitInfo.tickArrayUpper,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
assertTick(tickArrayUpper.ticks[10], true, liquidityAmount, liquidityAmount.neg());
|
||||
});
|
||||
|
||||
|
@ -108,26 +114,27 @@ describe("increase_liquidity", () => {
|
|||
const currTick = 500;
|
||||
const tickLowerIndex = 7168;
|
||||
const tickUpperIndex = 8960;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }],
|
||||
initialSqrtPrice: tickIndexToSqrtPriceX64(currTick),
|
||||
initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick),
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda } = poolInitInfo;
|
||||
const positionInitInfo = positions[0];
|
||||
const poolBefore = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
|
||||
const tokenAmount = toTokenAmount(1_000_000, 0);
|
||||
const liquidityAmount = estimateLiquidityFromTokenAmounts(
|
||||
const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts(
|
||||
currTick,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
tokenAmount
|
||||
);
|
||||
|
||||
await client
|
||||
.increaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: tokenAmount.tokenA,
|
||||
tokenMaxB: tokenAmount.tokenB,
|
||||
|
@ -142,7 +149,7 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
assert.equal(
|
||||
await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey),
|
||||
|
@ -155,15 +162,18 @@ describe("increase_liquidity", () => {
|
|||
);
|
||||
|
||||
const expectedLiquidity = new anchor.BN(liquidityAmount);
|
||||
const position = await client.getPosition(positionInitInfo.publicKey);
|
||||
const position = (await fetcher.getPosition(positionInitInfo.publicKey, true)) as PositionData;
|
||||
assert.ok(position.liquidity.eq(expectedLiquidity));
|
||||
|
||||
const tickArray = await client.getTickArray(positionInitInfo.tickArrayLower);
|
||||
const tickArray = (await fetcher.getTickArray(
|
||||
positionInitInfo.tickArrayLower,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
|
||||
assertTick(tickArray.ticks[56], true, expectedLiquidity, expectedLiquidity);
|
||||
assertTick(tickArray.ticks[70], true, expectedLiquidity, expectedLiquidity.neg());
|
||||
|
||||
const poolAfter = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp));
|
||||
assert.equal(poolAfter.liquidity, 0);
|
||||
});
|
||||
|
@ -172,16 +182,16 @@ describe("increase_liquidity", () => {
|
|||
const currTick = 500;
|
||||
const tickLowerIndex = 7168;
|
||||
const tickUpperIndex = 8960;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: tickIndexToSqrtPriceX64(currTick),
|
||||
initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick),
|
||||
});
|
||||
const { poolInitInfo, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda, tickSpacing } = poolInitInfo;
|
||||
const poolBefore = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
|
||||
const tokenAmount = toTokenAmount(1_000_000, 0);
|
||||
const liquidityAmount = estimateLiquidityFromTokenAmounts(
|
||||
const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts(
|
||||
currTick,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
|
@ -189,58 +199,58 @@ describe("increase_liquidity", () => {
|
|||
);
|
||||
|
||||
const { params, mint } = await generateDefaultOpenPositionParams(
|
||||
client.context,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
context.wallet.publicKey
|
||||
ctx.wallet.publicKey
|
||||
);
|
||||
|
||||
const tickArrayLower = getTickArrayPda(
|
||||
client.context.program.programId,
|
||||
const tickArrayLower = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
getStartTickIndex(tickLowerIndex, tickSpacing)
|
||||
TickUtil.getStartTickIndex(tickLowerIndex, tickSpacing)
|
||||
).publicKey;
|
||||
|
||||
const tickArrayUpper = getTickArrayPda(
|
||||
client.context.program.programId,
|
||||
const tickArrayUpper = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
getStartTickIndex(tickUpperIndex, tickSpacing)
|
||||
TickUtil.getStartTickIndex(tickUpperIndex, tickSpacing)
|
||||
).publicKey;
|
||||
|
||||
await new TransactionBuilder(client.context.provider)
|
||||
await new TransactionBuilder(ctx.provider)
|
||||
// TODO: create a ComputeBudgetInstruction to request more compute
|
||||
.addInstruction(
|
||||
buildInitTickArrayIx(
|
||||
client.context,
|
||||
WhirlpoolIx.initTickArrayIx(
|
||||
ctx.program,
|
||||
generateDefaultInitTickArrayParams(
|
||||
client.context,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
getStartTickIndex(tickLowerIndex, tickSpacing)
|
||||
TickUtil.getStartTickIndex(tickLowerIndex, tickSpacing)
|
||||
)
|
||||
)
|
||||
)
|
||||
// .addInstruction(
|
||||
// buildInitTickArrayIx(client.context, generateDefaultInitTickArrayParams(
|
||||
// client.context,
|
||||
// buildtoTx(ctx, WhirlpoolIx.initTickArrayIx(generateDefaultInitTickArrayParams(
|
||||
// ctx,
|
||||
// whirlpoolPda.publicKey,
|
||||
// getStartTickIndex(pos[0].tickLowerIndex + TICK_ARRAY_SIZE * tickSpacing, tickSpacing),
|
||||
// ))
|
||||
// )
|
||||
.addInstruction(buildOpenPositionIx(client.context, params))
|
||||
.addInstruction(WhirlpoolIx.openPositionIx(ctx.program, params))
|
||||
// .addInstruction(
|
||||
// buildOpenPositionWithMetadataIx(client.context, params)
|
||||
// buildWhirlpoolIx.openPositionWithMetadataIx(ctx.program, params)
|
||||
// )
|
||||
.addSigner(mint)
|
||||
.addInstruction(
|
||||
buildIncreaseLiquidityIx(client.context, {
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: tokenAmount.tokenA,
|
||||
tokenMaxB: tokenAmount.tokenB,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey,
|
||||
|
@ -262,15 +272,18 @@ describe("increase_liquidity", () => {
|
|||
);
|
||||
|
||||
const expectedLiquidity = new anchor.BN(liquidityAmount);
|
||||
const position = await client.getPosition(params.positionPda.publicKey);
|
||||
const position = (await fetcher.getPosition(
|
||||
params.positionPda.publicKey,
|
||||
true
|
||||
)) as PositionData;
|
||||
assert.ok(position.liquidity.eq(expectedLiquidity));
|
||||
|
||||
const tickArray = await client.getTickArray(tickArrayLower);
|
||||
const tickArray = (await fetcher.getTickArray(tickArrayLower, true)) as TickArrayData;
|
||||
|
||||
assertTick(tickArray.ticks[56], true, expectedLiquidity, expectedLiquidity);
|
||||
assertTick(tickArray.ticks[70], true, expectedLiquidity, expectedLiquidity.neg());
|
||||
|
||||
const poolAfter = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp));
|
||||
assert.equal(poolAfter.liquidity, 0);
|
||||
});
|
||||
|
@ -279,18 +292,18 @@ describe("increase_liquidity", () => {
|
|||
const currTick = 1300;
|
||||
const tickLowerIndex = -1280,
|
||||
tickUpperIndex = 1280;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }],
|
||||
initialSqrtPrice: tickIndexToSqrtPriceX64(currTick),
|
||||
initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick),
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda } = poolInitInfo;
|
||||
const positionInitInfo = positions[0];
|
||||
|
||||
const poolBefore = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
const tokenAmount = toTokenAmount(0, 167_000);
|
||||
const liquidityAmount = estimateLiquidityFromTokenAmounts(
|
||||
const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts(
|
||||
currTick,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
|
@ -302,8 +315,9 @@ describe("increase_liquidity", () => {
|
|||
await approveToken(provider, tokenAccountA, delegate.publicKey, 1_000_000);
|
||||
await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000);
|
||||
|
||||
await client
|
||||
.increaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: tokenAmount.tokenA,
|
||||
tokenMaxB: tokenAmount.tokenB,
|
||||
|
@ -318,13 +332,14 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute();
|
||||
|
||||
const position = await client.getPosition(positionInitInfo.publicKey);
|
||||
const position = (await fetcher.getPosition(positionInitInfo.publicKey, true)) as PositionData;
|
||||
assert.ok(position.liquidity.eq(liquidityAmount));
|
||||
|
||||
const poolAfter = await client.getPool(whirlpoolPda.publicKey);
|
||||
const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp));
|
||||
assert.equal(
|
||||
await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey),
|
||||
|
@ -336,18 +351,24 @@ describe("increase_liquidity", () => {
|
|||
);
|
||||
assert.equal(poolAfter.liquidity, 0);
|
||||
|
||||
const tickArrayLower = await client.getTickArray(positionInitInfo.tickArrayLower);
|
||||
const tickArrayLower = (await fetcher.getTickArray(
|
||||
positionInitInfo.tickArrayLower,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
assertTick(tickArrayLower.ticks[78], true, liquidityAmount, liquidityAmount);
|
||||
const tickArrayUpper = await client.getTickArray(positionInitInfo.tickArrayUpper);
|
||||
const tickArrayUpper = (await fetcher.getTickArray(
|
||||
positionInitInfo.tickArrayUpper,
|
||||
true
|
||||
)) as TickArrayData;
|
||||
assertTick(tickArrayUpper.ticks[10], true, liquidityAmount, liquidityAmount.neg());
|
||||
});
|
||||
|
||||
it("add maximum amount of liquidity near minimum price", async () => {
|
||||
const currTick = -443621;
|
||||
const { poolInitInfo } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Stable,
|
||||
tickIndexToSqrtPriceX64(currTick)
|
||||
PriceMath.tickIndexToSqrtPriceX64(currTick)
|
||||
);
|
||||
|
||||
const { tokenMintA, tokenMintB, whirlpoolPda } = poolInitInfo;
|
||||
|
@ -356,31 +377,32 @@ describe("increase_liquidity", () => {
|
|||
|
||||
const {
|
||||
params: { tickArrayPda },
|
||||
} = await initTickArray(client, whirlpoolPda.publicKey, -444224);
|
||||
} = await initTickArray(ctx, whirlpoolPda.publicKey, -444224);
|
||||
|
||||
const tickLowerIndex = -443632;
|
||||
const tickUpperIndex = -443624;
|
||||
const positionInfo = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex
|
||||
);
|
||||
const { positionPda, positionTokenAccountAddress } = positionInfo.params;
|
||||
const { positionPda, positionTokenAccount: positionTokenAccountAddress } = positionInfo.params;
|
||||
|
||||
const tokenAmount = {
|
||||
tokenA: new u64(0),
|
||||
tokenB: MAX_U64,
|
||||
};
|
||||
const estLiquidityAmount = estimateLiquidityFromTokenAmounts(
|
||||
const estLiquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts(
|
||||
currTick,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
tokenAmount
|
||||
);
|
||||
|
||||
await client
|
||||
.increaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: estLiquidityAmount,
|
||||
tokenMaxA: tokenAmount.tokenA,
|
||||
tokenMaxB: tokenAmount.tokenB,
|
||||
|
@ -395,18 +417,18 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const position = await client.getPosition(positionPda.publicKey);
|
||||
const position = (await fetcher.getPosition(positionPda.publicKey, true)) as PositionData;
|
||||
assert.ok(position.liquidity.eq(estLiquidityAmount));
|
||||
});
|
||||
|
||||
it("add maximum amount of liquidity near maximum price", async () => {
|
||||
const currTick = 443635;
|
||||
const { poolInitInfo } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Stable,
|
||||
tickIndexToSqrtPriceX64(currTick)
|
||||
PriceMath.tickIndexToSqrtPriceX64(currTick)
|
||||
);
|
||||
|
||||
const { tokenMintA, tokenMintB, whirlpoolPda } = poolInitInfo;
|
||||
|
@ -415,31 +437,32 @@ describe("increase_liquidity", () => {
|
|||
|
||||
const {
|
||||
params: { tickArrayPda },
|
||||
} = await initTickArray(client, whirlpoolPda.publicKey, 436480);
|
||||
} = await initTickArray(ctx, whirlpoolPda.publicKey, 436480);
|
||||
|
||||
const tickLowerIndex = 436488;
|
||||
const tickUpperIndex = 436496;
|
||||
const positionInfo = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex
|
||||
);
|
||||
const { positionPda, positionTokenAccountAddress } = positionInfo.params;
|
||||
const { positionPda, positionTokenAccount: positionTokenAccountAddress } = positionInfo.params;
|
||||
|
||||
const tokenAmount = {
|
||||
tokenA: new u64(0),
|
||||
tokenB: MAX_U64,
|
||||
};
|
||||
const estLiquidityAmount = estimateLiquidityFromTokenAmounts(
|
||||
const estLiquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts(
|
||||
currTick,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
tokenAmount
|
||||
);
|
||||
|
||||
await client
|
||||
.increaseLiquidityTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: estLiquidityAmount,
|
||||
tokenMaxA: tokenAmount.tokenA,
|
||||
tokenMaxB: tokenAmount.tokenB,
|
||||
|
@ -454,14 +477,14 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
const position = await client.getPosition(positionPda.publicKey);
|
||||
const position = (await fetcher.getPosition(positionPda.publicKey, true)) as PositionData;
|
||||
assert.ok(position.liquidity.eq(estLiquidityAmount));
|
||||
});
|
||||
|
||||
it("fails with zero liquidity amount", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -470,8 +493,9 @@ describe("increase_liquidity", () => {
|
|||
const positionInitInfo = positions[0];
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount: ZERO_BN,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -486,15 +510,15 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x177c/ // LiquidityZero
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when token max a exceeded", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
initialSqrtPrice: toX64(new Decimal(1)),
|
||||
initialSqrtPrice: MathUtil.toX64(new Decimal(1)),
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
|
@ -504,8 +528,9 @@ describe("increase_liquidity", () => {
|
|||
const liquidityAmount = new u64(6_500_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(999_999_999),
|
||||
|
@ -520,13 +545,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x1781/ // TokenMaxExceeded
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when token max b exceeded", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -537,8 +562,9 @@ describe("increase_liquidity", () => {
|
|||
const liquidityAmount = new u64(6_500_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(999_999_999),
|
||||
tokenMaxB: new u64(0),
|
||||
|
@ -553,13 +579,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x1781/ // TokenMaxExceeded
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position account does not have exactly 1 token", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -577,8 +603,9 @@ describe("increase_liquidity", () => {
|
|||
const liquidityAmount = new u64(6_500_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -593,7 +620,7 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
|
@ -601,8 +628,9 @@ describe("increase_liquidity", () => {
|
|||
await transfer(provider, positionInitInfo.tokenAccount, newPositionTokenAccount, 1);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -617,13 +645,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position token account mint does not match position mint", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -637,8 +665,9 @@ describe("increase_liquidity", () => {
|
|||
const liquidityAmount = new u64(6_500_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -653,7 +682,7 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // A raw constraint was violated
|
||||
);
|
||||
});
|
||||
|
@ -661,31 +690,33 @@ describe("increase_liquidity", () => {
|
|||
it("fails when position does not match whirlpool", async () => {
|
||||
const tickLowerIndex = 7168;
|
||||
const tickUpperIndex = 8960;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
const { poolInitInfo, tokenAccountA, tokenAccountB } = fixture.getInfos();
|
||||
const { whirlpoolPda } = poolInitInfo;
|
||||
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const positionInitInfo = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
poolInitInfo2.whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex
|
||||
);
|
||||
const { positionPda, positionTokenAccountAddress } = positionInitInfo.params;
|
||||
const { positionPda, positionTokenAccount: positionTokenAccountAddress } =
|
||||
positionInitInfo.params;
|
||||
|
||||
const {
|
||||
params: { tickArrayPda },
|
||||
} = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, 0);
|
||||
} = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, 0);
|
||||
|
||||
const liquidityAmount = new u64(6_500_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -700,13 +731,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d1/ // A has_one constraint was violated
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when token vaults do not match whirlpool vaults", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -719,8 +750,9 @@ describe("increase_liquidity", () => {
|
|||
const fakeVaultB = await createAndMintToTokenAccount(provider, tokenMintB, 1_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -735,13 +767,14 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -756,13 +789,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when owner token account mint does not match whirlpool token mint", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -775,8 +808,9 @@ describe("increase_liquidity", () => {
|
|||
const invalidTokenAccount = await createAndMintToTokenAccount(provider, invalidMint, 1_000_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -791,13 +825,14 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(1_000_000),
|
||||
|
@ -812,13 +847,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d3/ // ConstraintRaw
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position authority is not approved delegate for position token account", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -834,8 +869,9 @@ describe("increase_liquidity", () => {
|
|||
await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(167_000),
|
||||
|
@ -850,6 +886,7 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1783/ // MissingOrInvalidDelegate
|
||||
|
@ -857,7 +894,7 @@ describe("increase_liquidity", () => {
|
|||
});
|
||||
|
||||
it("fails when position authority is not authorized for exactly 1 token", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -874,8 +911,9 @@ describe("increase_liquidity", () => {
|
|||
await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(167_000),
|
||||
|
@ -890,6 +928,7 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x1784/ // InvalidPositionTokenAmount
|
||||
|
@ -897,7 +936,7 @@ describe("increase_liquidity", () => {
|
|||
});
|
||||
|
||||
it("fails when position authority was not a signer", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -914,8 +953,9 @@ describe("increase_liquidity", () => {
|
|||
await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(167_000),
|
||||
|
@ -930,13 +970,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when position authority is not approved for token owner accounts", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -951,8 +991,9 @@ describe("increase_liquidity", () => {
|
|||
await approveToken(provider, positionInitInfo.tokenAccount, delegate.publicKey, 1);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(167_000),
|
||||
|
@ -967,6 +1008,7 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: positionInitInfo.tickArrayLower,
|
||||
tickArrayUpper: positionInitInfo.tickArrayUpper,
|
||||
})
|
||||
)
|
||||
.addSigner(delegate)
|
||||
.buildAndExecute(),
|
||||
/0x4/ // owner does not match
|
||||
|
@ -974,7 +1016,7 @@ describe("increase_liquidity", () => {
|
|||
});
|
||||
|
||||
it("fails when tick arrays do not match the position", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -984,17 +1026,18 @@ describe("increase_liquidity", () => {
|
|||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayLowerPda },
|
||||
} = await initTickArray(client, whirlpoolPda.publicKey, 11264);
|
||||
} = await initTickArray(ctx, whirlpoolPda.publicKey, 11264);
|
||||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayUpperPda },
|
||||
} = await initTickArray(client, whirlpoolPda.publicKey, 22528);
|
||||
} = await initTickArray(ctx, whirlpoolPda.publicKey, 22528);
|
||||
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(167_000),
|
||||
|
@ -1009,13 +1052,13 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x1779/ // TicKNotFound
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when the tick arrays are for a different whirlpool", async () => {
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -1023,21 +1066,22 @@ describe("increase_liquidity", () => {
|
|||
const { whirlpoolPda } = poolInitInfo;
|
||||
const positionInitInfo = positions[0];
|
||||
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayLowerPda },
|
||||
} = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, -11264);
|
||||
} = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, -11264);
|
||||
|
||||
const {
|
||||
params: { tickArrayPda: tickArrayUpperPda },
|
||||
} = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, 0);
|
||||
} = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, 0);
|
||||
|
||||
const liquidityAmount = new u64(1_250_000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.increaseLiquidityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.increaseLiquidityIx(ctx.program, {
|
||||
liquidityAmount,
|
||||
tokenMaxA: new u64(0),
|
||||
tokenMaxB: new u64(167_000),
|
||||
|
@ -1052,7 +1096,7 @@ describe("increase_liquidity", () => {
|
|||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7d1/ // A has one constraint was violated
|
||||
);
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
WhirlpoolsConfigData,
|
||||
WhirlpoolIx,
|
||||
InitConfigParams,
|
||||
} from "../../src";
|
||||
import { systemTransferTx, ONE_SOL } from "../utils";
|
||||
import { generateDefaultConfigParams } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("initialize_config", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
let initializedConfigInfo: InitConfigParams;
|
||||
|
||||
it("successfully init a WhirlpoolsConfig account", async () => {
|
||||
const { configInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
const configAccount = (await fetcher.getConfig(
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey
|
||||
)) as WhirlpoolsConfigData;
|
||||
|
||||
assert.ok(
|
||||
configAccount.collectProtocolFeesAuthority.equals(configInitInfo.collectProtocolFeesAuthority)
|
||||
);
|
||||
|
||||
assert.ok(configAccount.feeAuthority.equals(configInitInfo.feeAuthority));
|
||||
|
||||
assert.ok(
|
||||
configAccount.rewardEmissionsSuperAuthority.equals(
|
||||
configInitInfo.rewardEmissionsSuperAuthority
|
||||
)
|
||||
);
|
||||
|
||||
assert.equal(configAccount.defaultProtocolFeeRate, configInitInfo.defaultProtocolFeeRate);
|
||||
|
||||
initializedConfigInfo = configInitInfo;
|
||||
});
|
||||
|
||||
it("fail on passing in already initialized whirlpool account", async () => {
|
||||
let infoWithDupeConfigKey = {
|
||||
...generateDefaultConfigParams(ctx).configInitInfo,
|
||||
whirlpoolsConfigKeypair: initializedConfigInfo.whirlpoolsConfigKeypair,
|
||||
};
|
||||
await assert.rejects(
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.initializeConfigIx(ctx.program, infoWithDupeConfigKey)
|
||||
).buildAndExecute(),
|
||||
/0x0/
|
||||
);
|
||||
});
|
||||
|
||||
it("succeeds when funder is different than account paying for transaction fee", async () => {
|
||||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute();
|
||||
const { configInitInfo } = generateDefaultConfigParams(ctx, funderKeypair.publicKey);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo))
|
||||
.addSigner(funderKeypair)
|
||||
.buildAndExecute();
|
||||
});
|
||||
});
|
|
@ -1,74 +1,73 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initFeeTier } from "./utils/init-utils";
|
||||
import { WhirlpoolContext, AccountFetcher, FeeTierData, WhirlpoolIx, PDAUtil } from "../../src";
|
||||
import { TickSpacing, systemTransferTx, ONE_SOL } from "../utils";
|
||||
import { initFeeTier } from "../utils/init-utils";
|
||||
import {
|
||||
generateDefaultConfigParams,
|
||||
generateDefaultInitFeeTierParams,
|
||||
} from "./utils/test-builders";
|
||||
import { getFeeTierPda } from "../src";
|
||||
import { systemTransferTx, ONE_SOL, TickSpacing } from "./utils";
|
||||
} from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("initialize_fee_tier", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully init a FeeRate stable account", async () => {
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
const testTickSpacing = TickSpacing.Stable;
|
||||
const { params } = await initFeeTier(
|
||||
client,
|
||||
ctx,
|
||||
configInitInfo,
|
||||
configKeypairs.feeAuthorityKeypair,
|
||||
testTickSpacing,
|
||||
800
|
||||
);
|
||||
|
||||
const generatedPda = getFeeTierPda(
|
||||
client.context.program.programId,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
const generatedPda = PDAUtil.getFeeTier(
|
||||
ctx.program.programId,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
testTickSpacing
|
||||
);
|
||||
|
||||
const feeTierAccount = await client.getFeeTier(generatedPda.publicKey);
|
||||
const feeTierAccount = (await fetcher.getFeeTier(generatedPda.publicKey)) as FeeTierData;
|
||||
|
||||
assert.ok(feeTierAccount.tickSpacing == params.tickSpacing);
|
||||
assert.ok(feeTierAccount.defaultFeeRate == params.defaultFeeRate);
|
||||
});
|
||||
|
||||
it("successfully init a FeeRate standard account", async () => {
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
const testTickSpacing = TickSpacing.Standard;
|
||||
const { params } = await initFeeTier(
|
||||
client,
|
||||
ctx,
|
||||
configInitInfo,
|
||||
configKeypairs.feeAuthorityKeypair,
|
||||
testTickSpacing,
|
||||
3000
|
||||
);
|
||||
|
||||
const feeTierAccount = await client.getFeeTier(params.feeTierPda.publicKey);
|
||||
const feeTierAccount = (await fetcher.getFeeTier(params.feeTierPda.publicKey)) as FeeTierData;
|
||||
|
||||
assert.ok(feeTierAccount.tickSpacing == params.tickSpacing);
|
||||
assert.ok(feeTierAccount.defaultFeeRate == params.defaultFeeRate);
|
||||
});
|
||||
|
||||
it("successfully init a FeeRate with another funder wallet", async () => {
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute();
|
||||
|
||||
await initFeeTier(
|
||||
client,
|
||||
ctx,
|
||||
configInitInfo,
|
||||
configKeypairs.feeAuthorityKeypair,
|
||||
TickSpacing.Stable,
|
||||
|
@ -78,12 +77,12 @@ describe("initialize_fee_tier", () => {
|
|||
});
|
||||
|
||||
it("fails when default fee rate exceeds max", async () => {
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
initFeeTier(
|
||||
client,
|
||||
ctx,
|
||||
configInitInfo,
|
||||
configKeypairs.feeAuthorityKeypair,
|
||||
TickSpacing.Stable,
|
||||
|
@ -94,41 +93,46 @@ describe("initialize_fee_tier", () => {
|
|||
});
|
||||
|
||||
it("fails when fee authority is not a signer", async () => {
|
||||
const { configInitInfo } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.initFeeTierTx(
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.initializeFeeTierIx(
|
||||
ctx.program,
|
||||
generateDefaultInitFeeTierParams(
|
||||
client.context,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
ctx,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
configInitInfo.feeAuthority,
|
||||
TickSpacing.Stable,
|
||||
3000
|
||||
)
|
||||
)
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when invalid fee authority provided", async () => {
|
||||
const { configInitInfo } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
const fakeFeeAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.initFeeTierTx(
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.initializeFeeTierIx(
|
||||
ctx.program,
|
||||
generateDefaultInitFeeTierParams(
|
||||
client.context,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
ctx,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
fakeFeeAuthorityKeypair.publicKey,
|
||||
TickSpacing.Stable,
|
||||
3000
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSigner(fakeFeeAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7dc/ // ConstraintAddress
|
|
@ -1,45 +1,48 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import Decimal from "decimal.js";
|
||||
import {
|
||||
createMint,
|
||||
asyncAssertTokenVault,
|
||||
ZERO_BN,
|
||||
systemTransferTx,
|
||||
ONE_SOL,
|
||||
TickSpacing,
|
||||
} from "./utils";
|
||||
import { buildTestPoolParams, initTestPool } from "./utils/init-utils";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import {
|
||||
getWhirlpoolPda,
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
WhirlpoolData,
|
||||
InitPoolParams,
|
||||
toX64,
|
||||
sqrtPriceX64ToTickIndex,
|
||||
MAX_SQRT_PRICE,
|
||||
MIN_SQRT_PRICE,
|
||||
} from "../src";
|
||||
import Decimal from "decimal.js";
|
||||
PriceMath,
|
||||
WhirlpoolIx,
|
||||
PDAUtil,
|
||||
} from "../../src";
|
||||
import {
|
||||
TickSpacing,
|
||||
ZERO_BN,
|
||||
asyncAssertTokenVault,
|
||||
systemTransferTx,
|
||||
ONE_SOL,
|
||||
createMint,
|
||||
} from "../utils";
|
||||
import { initTestPool, buildTestPoolParams } from "../utils/init-utils";
|
||||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("initialize_pool", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully init a Standard account", async () => {
|
||||
const price = toX64(new Decimal(5));
|
||||
const price = MathUtil.toX64(new Decimal(5));
|
||||
const { configInitInfo, poolInitInfo, feeTierParams } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard,
|
||||
price
|
||||
);
|
||||
const whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
|
||||
const expectedWhirlpoolPda = getWhirlpoolPda(
|
||||
const expectedWhirlpoolPda = PDAUtil.getWhirlpool(
|
||||
program.programId,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
poolInitInfo.tokenMintA,
|
||||
poolInitInfo.tokenMintB,
|
||||
TickSpacing.Standard
|
||||
|
@ -48,7 +51,7 @@ describe("initialize_pool", () => {
|
|||
assert.ok(poolInitInfo.whirlpoolPda.publicKey.equals(expectedWhirlpoolPda.publicKey));
|
||||
assert.equal(expectedWhirlpoolPda.bump, whirlpool.whirlpoolBump[0]);
|
||||
|
||||
assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolConfigKey));
|
||||
assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolsConfig));
|
||||
assert.ok(whirlpool.tokenMintA.equals(poolInitInfo.tokenMintA));
|
||||
assert.ok(whirlpool.tokenVaultA.equals(poolInitInfo.tokenVaultAKeypair.publicKey));
|
||||
|
||||
|
@ -61,7 +64,10 @@ describe("initialize_pool", () => {
|
|||
assert.ok(whirlpool.sqrtPrice.eq(new anchor.BN(poolInitInfo.initSqrtPrice.toString())));
|
||||
assert.ok(whirlpool.liquidity.eq(ZERO_BN));
|
||||
|
||||
assert.equal(whirlpool.tickCurrentIndex, sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice));
|
||||
assert.equal(
|
||||
whirlpool.tickCurrentIndex,
|
||||
PriceMath.sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice)
|
||||
);
|
||||
|
||||
assert.ok(whirlpool.protocolFeeOwedA.eq(ZERO_BN));
|
||||
assert.ok(whirlpool.protocolFeeOwedB.eq(ZERO_BN));
|
||||
|
@ -89,15 +95,15 @@ describe("initialize_pool", () => {
|
|||
});
|
||||
|
||||
it("successfully init a Stable account", async () => {
|
||||
const price = toX64(new Decimal(5));
|
||||
const price = MathUtil.toX64(new Decimal(5));
|
||||
const { configInitInfo, poolInitInfo, feeTierParams } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Stable,
|
||||
price
|
||||
);
|
||||
const whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
|
||||
assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolConfigKey));
|
||||
assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolsConfig));
|
||||
assert.ok(whirlpool.tokenMintA.equals(poolInitInfo.tokenMintA));
|
||||
assert.ok(whirlpool.tokenVaultA.equals(poolInitInfo.tokenVaultAKeypair.publicKey));
|
||||
|
||||
|
@ -110,7 +116,10 @@ describe("initialize_pool", () => {
|
|||
assert.ok(whirlpool.sqrtPrice.eq(new anchor.BN(poolInitInfo.initSqrtPrice.toString())));
|
||||
assert.ok(whirlpool.liquidity.eq(ZERO_BN));
|
||||
|
||||
assert.equal(whirlpool.tickCurrentIndex, sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice));
|
||||
assert.equal(
|
||||
whirlpool.tickCurrentIndex,
|
||||
PriceMath.sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice)
|
||||
);
|
||||
|
||||
assert.ok(whirlpool.protocolFeeOwedA.eq(ZERO_BN));
|
||||
assert.ok(whirlpool.protocolFeeOwedB.eq(ZERO_BN));
|
||||
|
@ -140,11 +149,11 @@ describe("initialize_pool", () => {
|
|||
it("succeeds when funder is different than account paying for transaction fee", async () => {
|
||||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute();
|
||||
await initTestPool(client, TickSpacing.Standard, toX64(new Decimal(5)), funderKeypair);
|
||||
await initTestPool(ctx, TickSpacing.Standard, MathUtil.toX64(new Decimal(5)), funderKeypair);
|
||||
});
|
||||
|
||||
it("fails when tokenVaultA mint does not match tokenA mint", async () => {
|
||||
const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard);
|
||||
const otherTokenPublicKey = await createMint(provider);
|
||||
|
||||
const modifiedPoolInitInfo: InitPoolParams = {
|
||||
|
@ -153,13 +162,13 @@ describe("initialize_pool", () => {
|
|||
};
|
||||
|
||||
await assert.rejects(
|
||||
client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(),
|
||||
toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(),
|
||||
/failed to complete|seeds|unauthorized/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when tokenVaultB mint does not match tokenB mint", async () => {
|
||||
const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard);
|
||||
const otherTokenPublicKey = await createMint(provider);
|
||||
|
||||
const modifiedPoolInitInfo: InitPoolParams = {
|
||||
|
@ -168,20 +177,17 @@ describe("initialize_pool", () => {
|
|||
};
|
||||
|
||||
await assert.rejects(
|
||||
client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(),
|
||||
toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(),
|
||||
/failed to complete|seeds|unauthorized/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when token mints are in the wrong order", async () => {
|
||||
const { poolInitInfo, configInitInfo } = await buildTestPoolParams(
|
||||
client,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const { poolInitInfo, configInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard);
|
||||
|
||||
const whirlpoolPda = getWhirlpoolPda(
|
||||
context.program.programId,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
const whirlpoolPda = PDAUtil.getWhirlpool(
|
||||
ctx.program.programId,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
poolInitInfo.tokenMintB,
|
||||
poolInitInfo.tokenMintA,
|
||||
TickSpacing.Stable
|
||||
|
@ -196,20 +202,17 @@ describe("initialize_pool", () => {
|
|||
};
|
||||
|
||||
await assert.rejects(
|
||||
client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(),
|
||||
toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(),
|
||||
/custom program error: 0x1788/ // InvalidTokenMintOrder
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when the same token mint is passed in", async () => {
|
||||
const { poolInitInfo, configInitInfo } = await buildTestPoolParams(
|
||||
client,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const { poolInitInfo, configInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard);
|
||||
|
||||
const whirlpoolPda = getWhirlpoolPda(
|
||||
context.program.programId,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
const whirlpoolPda = PDAUtil.getWhirlpool(
|
||||
ctx.program.programId,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
poolInitInfo.tokenMintA,
|
||||
poolInitInfo.tokenMintA,
|
||||
TickSpacing.Stable
|
||||
|
@ -223,13 +226,13 @@ describe("initialize_pool", () => {
|
|||
};
|
||||
|
||||
await assert.rejects(
|
||||
client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(),
|
||||
toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(),
|
||||
/custom program error: 0x1788/ // InvalidTokenMintOrder
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when sqrt-price exceeds max", async () => {
|
||||
const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard);
|
||||
const otherTokenPublicKey = await createMint(provider);
|
||||
|
||||
const modifiedPoolInitInfo: InitPoolParams = {
|
||||
|
@ -238,13 +241,13 @@ describe("initialize_pool", () => {
|
|||
};
|
||||
|
||||
await assert.rejects(
|
||||
client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(),
|
||||
toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(),
|
||||
/custom program error: 0x177b/ // SqrtPriceOutOfBounds
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when sqrt-price subceeds min", async () => {
|
||||
const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard);
|
||||
|
||||
const modifiedPoolInitInfo: InitPoolParams = {
|
||||
...poolInitInfo,
|
||||
|
@ -252,7 +255,7 @@ describe("initialize_pool", () => {
|
|||
};
|
||||
|
||||
await assert.rejects(
|
||||
client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(),
|
||||
toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(),
|
||||
/custom program error: 0x177b/ // SqrtPriceOutOfBounds
|
||||
);
|
||||
});
|
|
@ -1,36 +1,38 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initializeReward, initTestPool } from "./utils/init-utils";
|
||||
|
||||
import { createMint, ONE_SOL, systemTransferTx, TickSpacing } from "./utils";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src";
|
||||
import { TickSpacing, systemTransferTx, ONE_SOL, createMint } from "../utils";
|
||||
import { initTestPool, initializeReward } from "../utils/init-utils";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("initialize_reward", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully initializes reward at index 0", async () => {
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const { params } = await initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0
|
||||
);
|
||||
|
||||
const whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const whirlpool = (await fetcher.getPool(
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
true
|
||||
)) as WhirlpoolData;
|
||||
|
||||
assert.ok(whirlpool.rewardInfos[0].mint.equals(params.rewardMint));
|
||||
assert.ok(whirlpool.rewardInfos[0].vault.equals(params.rewardVaultKeypair.publicKey));
|
||||
|
||||
await assert.rejects(
|
||||
initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0
|
||||
|
@ -39,13 +41,16 @@ describe("initialize_reward", () => {
|
|||
);
|
||||
|
||||
const { params: params2 } = await initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
1
|
||||
);
|
||||
|
||||
const whirlpool2 = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const whirlpool2 = (await fetcher.getPool(
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
true
|
||||
)) as WhirlpoolData;
|
||||
|
||||
assert.ok(whirlpool2.rewardInfos[0].mint.equals(params.rewardMint));
|
||||
assert.ok(whirlpool2.rewardInfos[0].vault.equals(params.rewardVaultKeypair.publicKey));
|
||||
|
@ -56,11 +61,11 @@ describe("initialize_reward", () => {
|
|||
});
|
||||
|
||||
it("succeeds when funder is different than account paying for transaction fee", async () => {
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute();
|
||||
await initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
|
@ -69,11 +74,11 @@ describe("initialize_reward", () => {
|
|||
});
|
||||
|
||||
it("fails to initialize reward at index 1", async () => {
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
await assert.rejects(
|
||||
initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
1
|
||||
|
@ -83,11 +88,11 @@ describe("initialize_reward", () => {
|
|||
});
|
||||
|
||||
it("fails to initialize reward at out-of-bound index", async () => {
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
await assert.rejects(
|
||||
initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
3
|
||||
|
@ -96,11 +101,12 @@ describe("initialize_reward", () => {
|
|||
});
|
||||
|
||||
it("fails to initialize if authority signature is missing", async () => {
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.initializeRewardTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.initializeRewardIx(ctx.program, {
|
||||
rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
funder: provider.wallet.publicKey,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
|
@ -108,7 +114,7 @@ describe("initialize_reward", () => {
|
|||
rewardVaultKeypair: anchor.web3.Keypair.generate(),
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute()
|
||||
).buildAndExecute()
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,75 +1,82 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import * as assert from "assert";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initTestPool, initTickArray } from "./utils/init-utils";
|
||||
import { generateDefaultInitTickArrayParams } from "./utils/test-builders";
|
||||
import { InitPoolParams, InitTickArrayParams, TICK_ARRAY_SIZE } from "../src";
|
||||
import { ONE_SOL, systemTransferTx, TickSpacing } from "./utils";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
TICK_ARRAY_SIZE,
|
||||
InitTickArrayParams,
|
||||
InitPoolParams,
|
||||
TickArrayData,
|
||||
WhirlpoolIx,
|
||||
} from "../../src";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
import { TickSpacing, systemTransferTx, ONE_SOL } from "../utils";
|
||||
import { initTestPool, initTickArray } from "../utils/init-utils";
|
||||
import { generateDefaultInitTickArrayParams } from "../utils/test-builders";
|
||||
|
||||
describe("initialize_tick_array", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully init a TickArray account", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const startTick = TICK_ARRAY_SIZE * tickSpacing * 2;
|
||||
|
||||
const tickArrayInitInfo = generateDefaultInitTickArrayParams(
|
||||
context,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
startTick
|
||||
);
|
||||
|
||||
await client.initTickArrayTx(tickArrayInitInfo).buildAndExecute();
|
||||
assertTickArrayInitialized(client, tickArrayInitInfo, poolInitInfo, startTick);
|
||||
await toTx(ctx, WhirlpoolIx.initTickArrayIx(ctx.program, tickArrayInitInfo)).buildAndExecute();
|
||||
assertTickArrayInitialized(ctx, tickArrayInitInfo, poolInitInfo, startTick);
|
||||
});
|
||||
|
||||
it("successfully init a TickArray account with a negative index", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const startTick = TICK_ARRAY_SIZE * tickSpacing * -2;
|
||||
|
||||
const tickArrayInitInfo = generateDefaultInitTickArrayParams(
|
||||
context,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
startTick
|
||||
);
|
||||
|
||||
await client.initTickArrayTx(tickArrayInitInfo).buildAndExecute();
|
||||
assertTickArrayInitialized(client, tickArrayInitInfo, poolInitInfo, startTick);
|
||||
await toTx(ctx, WhirlpoolIx.initTickArrayIx(ctx.program, tickArrayInitInfo)).buildAndExecute();
|
||||
assertTickArrayInitialized(ctx, tickArrayInitInfo, poolInitInfo, startTick);
|
||||
});
|
||||
|
||||
it("succeeds when funder is different than account paying for transaction fee", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute();
|
||||
await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const startTick = TICK_ARRAY_SIZE * tickSpacing * 3;
|
||||
await initTickArray(client, poolInitInfo.whirlpoolPda.publicKey, startTick, funderKeypair);
|
||||
await initTickArray(ctx, poolInitInfo.whirlpoolPda.publicKey, startTick, funderKeypair);
|
||||
});
|
||||
|
||||
it("fails when start tick index is not a valid start index", async () => {
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const startTick = TICK_ARRAY_SIZE * tickSpacing * 2 + 1;
|
||||
|
||||
const params = generateDefaultInitTickArrayParams(
|
||||
context,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
startTick
|
||||
);
|
||||
|
||||
try {
|
||||
await client.initTickArrayTx(params).buildAndExecute();
|
||||
await toTx(ctx, WhirlpoolIx.initTickArrayIx(ctx.program, params)).buildAndExecute();
|
||||
assert.fail(
|
||||
"should fail if start-tick is not a multiple of tick spacing and num ticks in array"
|
||||
);
|
||||
|
@ -80,12 +87,14 @@ describe("initialize_tick_array", () => {
|
|||
});
|
||||
|
||||
async function assertTickArrayInitialized(
|
||||
client: WhirlpoolClient,
|
||||
ctx: WhirlpoolContext,
|
||||
tickArrayInitInfo: InitTickArrayParams,
|
||||
poolInitInfo: InitPoolParams,
|
||||
startTick: number
|
||||
) {
|
||||
let tickArrayData = await client.getTickArray(tickArrayInitInfo.tickArrayPda.publicKey);
|
||||
let tickArrayData = (await fetcher.getTickArray(
|
||||
tickArrayInitInfo.tickArrayPda.publicKey
|
||||
)) as TickArrayData;
|
||||
assert.ok(tickArrayData.whirlpool.equals(poolInitInfo.whirlpoolPda.publicKey));
|
||||
assert.ok(tickArrayData.startTickIndex == startTick);
|
||||
}
|
|
@ -1,43 +1,45 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../../src/context";
|
||||
import { WhirlpoolClient } from "../../src/client";
|
||||
import { initTestPool, openPosition } from "../utils/init-utils";
|
||||
import { buildOpenPositionIx } from "../../src/instructions/open-position-ix";
|
||||
import { generateDefaultOpenPositionParams } from "../utils/test-builders";
|
||||
import { TickSpacing } from "../utils";
|
||||
import { WhirlpoolContext } from "../../../src/context";
|
||||
import { initTestPool, openPosition } from "../../utils/init-utils";
|
||||
import { generateDefaultOpenPositionParams } from "../../utils/test-builders";
|
||||
import { TickSpacing } from "../../utils";
|
||||
import { AccountFetcher, WhirlpoolIx } from "../../../src";
|
||||
import { toTx } from "../../../src/utils/instructions-util";
|
||||
|
||||
describe("position management tests", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully closes and opens a position in one transaction", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const { params } = await openPosition(client, poolInitInfo.whirlpoolPda.publicKey, 0, 128);
|
||||
const { params } = await openPosition(ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128);
|
||||
const receiverKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
const { params: newParams, mint } = await generateDefaultOpenPositionParams(
|
||||
context,
|
||||
ctx,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
context.wallet.publicKey,
|
||||
context.wallet.publicKey
|
||||
ctx.wallet.publicKey,
|
||||
ctx.wallet.publicKey
|
||||
);
|
||||
|
||||
await client
|
||||
.closePositionTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.closePositionIx(ctx.program, {
|
||||
positionAuthority: provider.wallet.publicKey,
|
||||
receiver: receiverKeypair.publicKey,
|
||||
position: params.positionPda.publicKey,
|
||||
positionMint: params.positionMintAddress,
|
||||
positionTokenAccount: params.positionTokenAccountAddress,
|
||||
positionTokenAccount: params.positionTokenAccount,
|
||||
})
|
||||
.addInstruction(buildOpenPositionIx(context, newParams))
|
||||
)
|
||||
.addInstruction(WhirlpoolIx.openPositionIx(ctx.program, newParams))
|
||||
.addSigner(mint)
|
||||
.buildAndExecute();
|
||||
|
|
@ -2,36 +2,39 @@ import * as assert from "assert";
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { web3 } from "@project-serum/anchor";
|
||||
import { Keypair } from "@solana/web3.js";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { initTestPool, openPosition } from "./utils/init-utils";
|
||||
import {
|
||||
getPositionPda,
|
||||
InitPoolParams,
|
||||
MAX_TICK_INDEX,
|
||||
MIN_TICK_INDEX,
|
||||
OpenPositionParams,
|
||||
PDA,
|
||||
} from "../src";
|
||||
import {
|
||||
createMint,
|
||||
createMintInstructions,
|
||||
mintToByAuthority,
|
||||
ONE_SOL,
|
||||
systemTransferTx,
|
||||
TickSpacing,
|
||||
ZERO_BN,
|
||||
} from "./utils";
|
||||
import { ASSOCIATED_TOKEN_PROGRAM_ID, Token } from "@solana/spl-token";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { generateDefaultOpenPositionParams } from "./utils/test-builders";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
OpenPositionParams,
|
||||
InitPoolParams,
|
||||
PositionData,
|
||||
MAX_TICK_INDEX,
|
||||
MIN_TICK_INDEX,
|
||||
WhirlpoolIx,
|
||||
PDAUtil,
|
||||
} from "../../src";
|
||||
import {
|
||||
TickSpacing,
|
||||
systemTransferTx,
|
||||
ONE_SOL,
|
||||
ZERO_BN,
|
||||
mintToByAuthority,
|
||||
createMint,
|
||||
createMintInstructions,
|
||||
} from "../utils";
|
||||
import { initTestPool, openPosition } from "../utils/init-utils";
|
||||
import { generateDefaultOpenPositionParams } from "../utils/test-builders";
|
||||
import { PDA } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("open_position", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
let defaultParams: OpenPositionParams;
|
||||
let defaultMint: Keypair;
|
||||
|
@ -42,11 +45,11 @@ describe("open_position", () => {
|
|||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
before(async () => {
|
||||
poolInitInfo = (await initTestPool(client, TickSpacing.Standard)).poolInitInfo;
|
||||
poolInitInfo = (await initTestPool(ctx, TickSpacing.Standard)).poolInitInfo;
|
||||
whirlpoolPda = poolInitInfo.whirlpoolPda;
|
||||
|
||||
const { params, mint } = await generateDefaultOpenPositionParams(
|
||||
client.context,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
|
@ -59,14 +62,14 @@ describe("open_position", () => {
|
|||
|
||||
it("successfully opens position and verify position address contents", async () => {
|
||||
const positionInitInfo = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex
|
||||
);
|
||||
const { positionPda, positionMintAddress } = positionInitInfo.params;
|
||||
|
||||
const position = await client.getPosition(positionPda.publicKey);
|
||||
const position = (await fetcher.getPosition(positionPda.publicKey)) as PositionData;
|
||||
|
||||
assert.strictEqual(position.tickLowerIndex, tickLowerIndex);
|
||||
assert.strictEqual(position.tickUpperIndex, tickUpperIndex);
|
||||
|
@ -83,7 +86,7 @@ describe("open_position", () => {
|
|||
|
||||
it("succeeds when funder is different than account paying for transaction fee", async () => {
|
||||
await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
|
@ -96,16 +99,17 @@ describe("open_position", () => {
|
|||
const newOwner = web3.Keypair.generate();
|
||||
|
||||
const positionInitInfo = await openPosition(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
newOwner.publicKey
|
||||
);
|
||||
const { positionMintAddress, positionTokenAccountAddress } = positionInitInfo.params;
|
||||
const { positionMintAddress, positionTokenAccount: positionTokenAccountAddress } =
|
||||
positionInitInfo.params;
|
||||
|
||||
const token = new Token(
|
||||
context.connection,
|
||||
ctx.connection,
|
||||
positionMintAddress,
|
||||
TOKEN_PROGRAM_ID,
|
||||
web3.Keypair.generate()
|
||||
|
@ -127,12 +131,17 @@ describe("open_position", () => {
|
|||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
anotherMintKey,
|
||||
context.provider.wallet.publicKey
|
||||
ctx.provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.openPositionTx({ ...defaultParams, positionTokenAccountAddress })
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.openPositionIx(ctx.program, {
|
||||
...defaultParams,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
})
|
||||
)
|
||||
.addSigner(defaultMint)
|
||||
.buildAndExecute(),
|
||||
/An account required by the instruction is missing/
|
||||
|
@ -143,7 +152,7 @@ describe("open_position", () => {
|
|||
async function assertTicksFail(lowerTick: number, upperTick: number) {
|
||||
await assert.rejects(
|
||||
openPosition(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
lowerTick,
|
||||
upperTick,
|
||||
|
@ -181,7 +190,7 @@ describe("open_position", () => {
|
|||
|
||||
it("fail when position mint already exists", async () => {
|
||||
const positionMintKeypair = anchor.web3.Keypair.generate();
|
||||
const positionPda = getPositionPda(context.program.programId, positionMintKeypair.publicKey);
|
||||
const positionPda = PDAUtil.getPosition(ctx.program.programId, positionMintKeypair.publicKey);
|
||||
|
||||
const positionTokenAccountAddress = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
|
@ -202,17 +211,19 @@ describe("open_position", () => {
|
|||
await provider.send(tx, [positionMintKeypair], { commitment: "confirmed" });
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.openPositionTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.openPositionIx(ctx.program, {
|
||||
funder: provider.wallet.publicKey,
|
||||
ownerKey: provider.wallet.publicKey,
|
||||
owner: provider.wallet.publicKey,
|
||||
positionPda,
|
||||
positionMintAddress: positionMintKeypair.publicKey,
|
||||
positionTokenAccountAddress: positionTokenAccountAddress,
|
||||
whirlpoolKey: whirlpoolPda.publicKey,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 128,
|
||||
})
|
||||
)
|
||||
.addSigner(positionMintKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x0/
|
|
@ -3,43 +3,43 @@ import * as anchor from "@project-serum/anchor";
|
|||
import { Metadata } from "@metaplex-foundation/mpl-token-metadata";
|
||||
import { web3 } from "@project-serum/anchor";
|
||||
import { Keypair, PublicKey } from "@solana/web3.js";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { initTestPool, openPositionWithMetadata } from "./utils/init-utils";
|
||||
import {
|
||||
getPositionMetadataPda,
|
||||
getPositionPda,
|
||||
InitPoolParams,
|
||||
MAX_TICK_INDEX,
|
||||
METADATA_PROGRAM_ADDRESS,
|
||||
MIN_TICK_INDEX,
|
||||
OpenPositionParams,
|
||||
OpenPositionWithMetadataBumpsData,
|
||||
PDA,
|
||||
TransactionBuilder,
|
||||
} from "../src";
|
||||
import { openPositionAccounts } from "../src/instructions/open-position-ix";
|
||||
import {
|
||||
createMint,
|
||||
createMintInstructions,
|
||||
mintToByAuthority,
|
||||
ONE_SOL,
|
||||
systemTransferTx,
|
||||
TickSpacing,
|
||||
ZERO_BN,
|
||||
} from "./utils";
|
||||
import { ASSOCIATED_TOKEN_PROGRAM_ID, Token } from "@solana/spl-token";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { generateDefaultOpenPositionParams } from "./utils/test-builders";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
OpenPositionParams,
|
||||
InitPoolParams,
|
||||
PositionData,
|
||||
MAX_TICK_INDEX,
|
||||
MIN_TICK_INDEX,
|
||||
OpenPositionWithMetadataBumpsData,
|
||||
METADATA_PROGRAM_ADDRESS,
|
||||
WhirlpoolIx,
|
||||
PDAUtil,
|
||||
} from "../../src";
|
||||
import {
|
||||
TickSpacing,
|
||||
systemTransferTx,
|
||||
ONE_SOL,
|
||||
ZERO_BN,
|
||||
mintToByAuthority,
|
||||
createMint,
|
||||
createMintInstructions,
|
||||
} from "../utils";
|
||||
import { initTestPool, openPositionWithMetadata } from "../utils/init-utils";
|
||||
import { generateDefaultOpenPositionParams } from "../utils/test-builders";
|
||||
import { openPositionAccounts, toTx } from "../../src/utils/instructions-util";
|
||||
import { PDA, TransactionBuilder } from "@orca-so/common-sdk";
|
||||
|
||||
describe("open_position_with_metadata", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
let defaultParams: Required<OpenPositionParams>;
|
||||
let defaultParams: Required<OpenPositionParams & { metadataPda: PDA }>;
|
||||
let defaultMint: Keypair;
|
||||
const tickLowerIndex = 0;
|
||||
const tickUpperIndex = 128;
|
||||
|
@ -48,11 +48,11 @@ describe("open_position_with_metadata", () => {
|
|||
const funderKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
before(async () => {
|
||||
poolInitInfo = (await initTestPool(client, TickSpacing.Standard)).poolInitInfo;
|
||||
poolInitInfo = (await initTestPool(ctx, TickSpacing.Standard)).poolInitInfo;
|
||||
whirlpoolPda = poolInitInfo.whirlpoolPda;
|
||||
|
||||
const { params, mint } = await generateDefaultOpenPositionParams(
|
||||
client.context,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
0,
|
||||
128,
|
||||
|
@ -75,13 +75,13 @@ describe("open_position_with_metadata", () => {
|
|||
|
||||
it("successfully opens position and verify position address contents", async () => {
|
||||
const positionInitInfo = await openPositionWithMetadata(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex
|
||||
);
|
||||
const { positionPda, metadataPda, positionMintAddress } = positionInitInfo.params;
|
||||
const position = await client.getPosition(positionPda.publicKey);
|
||||
const position = (await fetcher.getPosition(positionPda.publicKey)) as PositionData;
|
||||
|
||||
assert.strictEqual(position.tickLowerIndex, tickLowerIndex);
|
||||
assert.strictEqual(position.tickUpperIndex, tickUpperIndex);
|
||||
|
@ -99,7 +99,7 @@ describe("open_position_with_metadata", () => {
|
|||
|
||||
it("succeeds when funder is different than account paying for transaction fee", async () => {
|
||||
const { params } = await openPositionWithMetadata(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
|
@ -114,17 +114,20 @@ describe("open_position_with_metadata", () => {
|
|||
const newOwner = web3.Keypair.generate();
|
||||
|
||||
const positionInitInfo = await openPositionWithMetadata(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
newOwner.publicKey
|
||||
);
|
||||
const { metadataPda, positionMintAddress, positionTokenAccountAddress } =
|
||||
positionInitInfo.params;
|
||||
const {
|
||||
metadataPda,
|
||||
positionMintAddress,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
} = positionInitInfo.params;
|
||||
|
||||
const token = new Token(
|
||||
context.connection,
|
||||
ctx.connection,
|
||||
positionMintAddress,
|
||||
TOKEN_PROGRAM_ID,
|
||||
web3.Keypair.generate()
|
||||
|
@ -148,12 +151,17 @@ describe("open_position_with_metadata", () => {
|
|||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
anotherMintKey,
|
||||
context.provider.wallet.publicKey
|
||||
ctx.provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.openPositionWithMetadataTx({ ...defaultParams, positionTokenAccountAddress })
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.openPositionWithMetadataIx(ctx.program, {
|
||||
...defaultParams,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
})
|
||||
)
|
||||
.addSigner(defaultMint)
|
||||
.buildAndExecute(),
|
||||
/An account required by the instruction is missing/
|
||||
|
@ -164,7 +172,7 @@ describe("open_position_with_metadata", () => {
|
|||
async function assertTicksFail(lowerTick: number, upperTick: number) {
|
||||
await assert.rejects(
|
||||
openPositionWithMetadata(
|
||||
client,
|
||||
ctx,
|
||||
whirlpoolPda.publicKey,
|
||||
lowerTick,
|
||||
upperTick,
|
||||
|
@ -202,8 +210,8 @@ describe("open_position_with_metadata", () => {
|
|||
|
||||
it("fail when position mint already exists", async () => {
|
||||
const positionMintKeypair = anchor.web3.Keypair.generate();
|
||||
const positionPda = getPositionPda(context.program.programId, positionMintKeypair.publicKey);
|
||||
const metadataPda = getPositionMetadataPda(positionMintKeypair.publicKey);
|
||||
const positionPda = PDAUtil.getPosition(ctx.program.programId, positionMintKeypair.publicKey);
|
||||
const metadataPda = PDAUtil.getPositionMetadata(positionMintKeypair.publicKey);
|
||||
|
||||
const positionTokenAccountAddress = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
|
@ -224,17 +232,19 @@ describe("open_position_with_metadata", () => {
|
|||
await provider.send(tx, [positionMintKeypair], { commitment: "confirmed" });
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.openPositionWithMetadataTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.openPositionWithMetadataIx(ctx.program, {
|
||||
...defaultParams,
|
||||
positionPda,
|
||||
metadataPda,
|
||||
positionMintAddress: positionMintKeypair.publicKey,
|
||||
positionTokenAccountAddress: positionTokenAccountAddress,
|
||||
whirlpoolKey: whirlpoolPda.publicKey,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
})
|
||||
)
|
||||
.addSigner(positionMintKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x0/
|
||||
|
@ -258,7 +268,7 @@ describe("open_position_with_metadata", () => {
|
|||
metadataBump: metadataPda.bump,
|
||||
};
|
||||
|
||||
const ix = context.program.instruction.openPositionWithMetadata(
|
||||
const ix = ctx.program.instruction.openPositionWithMetadata(
|
||||
bumps,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex,
|
||||
|
@ -284,11 +294,13 @@ describe("open_position_with_metadata", () => {
|
|||
const notMintKeypair = Keypair.generate();
|
||||
const invalidParams = {
|
||||
...defaultParams,
|
||||
metadataPda: getPositionMetadataPda(notMintKeypair.publicKey),
|
||||
metadataPda: PDAUtil.getPositionMetadata(notMintKeypair.publicKey),
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
client.openPositionWithMetadataTx(invalidParams).addSigner(defaultMint).buildAndExecute(),
|
||||
toTx(ctx, WhirlpoolIx.openPositionWithMetadataIx(ctx.program, invalidParams))
|
||||
.addSigner(defaultMint)
|
||||
.buildAndExecute(),
|
||||
// Invalid Metadata Key
|
||||
// https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/error.rs#L36
|
||||
/0x5/
|
||||
|
@ -297,7 +309,7 @@ describe("open_position_with_metadata", () => {
|
|||
|
||||
it("fails with non-program metadata program", async () => {
|
||||
const notMetadataProgram = Keypair.generate();
|
||||
const tx = new TransactionBuilder(context.provider).addInstruction(
|
||||
const tx = new TransactionBuilder(ctx.provider).addInstruction(
|
||||
buildOpenWithAccountOverrides({
|
||||
metadataProgram: notMetadataProgram.publicKey,
|
||||
})
|
||||
|
@ -312,7 +324,7 @@ describe("open_position_with_metadata", () => {
|
|||
});
|
||||
|
||||
it("fails with non-metadata program ", async () => {
|
||||
const tx = new TransactionBuilder(context.provider).addInstruction(
|
||||
const tx = new TransactionBuilder(ctx.provider).addInstruction(
|
||||
buildOpenWithAccountOverrides({
|
||||
metadataProgram: TOKEN_PROGRAM_ID,
|
||||
})
|
||||
|
@ -328,7 +340,7 @@ describe("open_position_with_metadata", () => {
|
|||
|
||||
it("fails with non-valid update_authority program", async () => {
|
||||
const notUpdateAuth = Keypair.generate();
|
||||
const tx = new TransactionBuilder(context.provider).addInstruction(
|
||||
const tx = new TransactionBuilder(ctx.provider).addInstruction(
|
||||
buildOpenWithAccountOverrides({
|
||||
metadataUpdateAuth: notUpdateAuth.publicKey,
|
||||
})
|
|
@ -1,32 +1,36 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { generateDefaultConfigParams } from "./utils/test-builders";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolsConfigData, WhirlpoolIx } from "../../src";
|
||||
import { generateDefaultConfigParams } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_collect_protocol_fee_authority", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully set_collect_protocol_fee_authority", async () => {
|
||||
const {
|
||||
configInitInfo,
|
||||
configKeypairs: { collectProtocolFeesAuthorityKeypair },
|
||||
} = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
} = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
const newAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
await client
|
||||
.setCollectProtocolFeesAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setCollectProtocolFeesAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
newCollectProtocolFeesAuthority: newAuthorityKeypair.publicKey,
|
||||
})
|
||||
)
|
||||
.addSigner(collectProtocolFeesAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
const config = await client.getConfig(configInitInfo.whirlpoolConfigKeypair.publicKey);
|
||||
const config = (await fetcher.getConfig(
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey
|
||||
)) as WhirlpoolsConfigData;
|
||||
assert.ok(config.collectProtocolFeesAuthority.equals(newAuthorityKeypair.publicKey));
|
||||
});
|
||||
|
||||
|
@ -34,33 +38,35 @@ describe("set_collect_protocol_fee_authority", () => {
|
|||
const {
|
||||
configInitInfo,
|
||||
configKeypairs: { collectProtocolFeesAuthorityKeypair },
|
||||
} = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
} = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setCollectProtocolFeesAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setCollectProtocolFeesAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey,
|
||||
newCollectProtocolFeesAuthority: provider.wallet.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails if invalid collect_protocol_fee_authority provided", async () => {
|
||||
const { configInitInfo } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setCollectProtocolFeesAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setCollectProtocolFeesAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
collectProtocolFeesAuthority: provider.wallet.publicKey,
|
||||
newCollectProtocolFeesAuthority: provider.wallet.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7dc/ // An address constraint was violated
|
||||
);
|
||||
});
|
|
@ -1,52 +1,60 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { getFeeTierPda, getWhirlpoolPda, InitPoolParams } from "../src";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { createInOrderMints, generateDefaultConfigParams } from "./utils/test-builders";
|
||||
import { TickSpacing } from "./utils";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
WhirlpoolData,
|
||||
InitPoolParams,
|
||||
WhirlpoolIx,
|
||||
PDAUtil,
|
||||
} from "../../src";
|
||||
import { TickSpacing } from "../utils";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { createInOrderMints, generateDefaultConfigParams } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_default_fee_rate", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully set_default_fee_rate", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs, feeTierParams } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newDefaultFeeRate = 45;
|
||||
|
||||
// Fetch initial whirlpool and check it is default
|
||||
let whirlpool_0 = await client.getPool(whirlpoolKey);
|
||||
let whirlpool_0 = (await fetcher.getPool(whirlpoolKey)) as WhirlpoolData;
|
||||
assert.equal(whirlpool_0.feeRate, feeTierParams.defaultFeeRate);
|
||||
|
||||
await client
|
||||
.setDefaultFeeRateIx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setDefaultFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
defaultFeeRate: newDefaultFeeRate,
|
||||
})
|
||||
)
|
||||
.addSigner(feeAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
// Setting the default rate did not change existing whirlpool fee rate
|
||||
whirlpool_0 = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
whirlpool_0 = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
assert.equal(whirlpool_0.feeRate, feeTierParams.defaultFeeRate);
|
||||
|
||||
// Newly initialized whirlpools have new default fee rate
|
||||
const [tokenMintA, tokenMintB] = await createInOrderMints(context);
|
||||
const whirlpoolPda = getWhirlpoolPda(
|
||||
context.program.programId,
|
||||
const [tokenMintA, tokenMintB] = await createInOrderMints(ctx);
|
||||
const whirlpoolPda = PDAUtil.getWhirlpool(
|
||||
ctx.program.programId,
|
||||
whirlpoolsConfigKey,
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
|
@ -64,26 +72,28 @@ describe("set_default_fee_rate", () => {
|
|||
tokenVaultBKeypair,
|
||||
tickSpacing: TickSpacing.Stable,
|
||||
};
|
||||
await client.initPoolTx(newPoolInitInfo).buildAndExecute();
|
||||
await toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, newPoolInitInfo)).buildAndExecute();
|
||||
|
||||
const whirlpool_1 = await client.getPool(whirlpoolPda.publicKey);
|
||||
const whirlpool_1 = (await fetcher.getPool(whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
assert.equal(whirlpool_1.feeRate, newDefaultFeeRate);
|
||||
});
|
||||
|
||||
it("fails when default fee rate exceeds max", async () => {
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newDefaultFeeRate = 20_000;
|
||||
await assert.rejects(
|
||||
client
|
||||
.setDefaultFeeRateIx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setDefaultFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
defaultFeeRate: newDefaultFeeRate,
|
||||
})
|
||||
)
|
||||
.addSigner(feeAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x178c/ // FeeRateMaxExceeded
|
||||
|
@ -91,18 +101,20 @@ describe("set_default_fee_rate", () => {
|
|||
});
|
||||
|
||||
it("fails when fee tier account has not been initialized", async () => {
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setDefaultFeeRateIx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setDefaultFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
tickSpacing: TickSpacing.Standard,
|
||||
defaultFeeRate: 500,
|
||||
})
|
||||
)
|
||||
.addSigner(feeAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0xbc4/ // AccountNotInitialized
|
||||
|
@ -110,12 +122,12 @@ describe("set_default_fee_rate", () => {
|
|||
});
|
||||
|
||||
it("fails when fee authority is not a signer", async () => {
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
const feeTierPda = getFeeTierPda(
|
||||
context.program.programId,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
const feeTierPda = PDAUtil.getFeeTier(
|
||||
ctx.program.programId,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
|
@ -133,12 +145,12 @@ describe("set_default_fee_rate", () => {
|
|||
});
|
||||
|
||||
it("fails when invalid fee authority provided", async () => {
|
||||
const { configInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const { configInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const fakeFeeAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
const feeTierPda = getFeeTierPda(
|
||||
context.program.programId,
|
||||
configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
const feeTierPda = PDAUtil.getFeeTier(
|
||||
ctx.program.programId,
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
|
@ -1,50 +1,58 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { getWhirlpoolPda, InitPoolParams } from "../src";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { createInOrderMints } from "./utils/test-builders";
|
||||
import { TickSpacing } from "./utils";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
WhirlpoolData,
|
||||
InitPoolParams,
|
||||
WhirlpoolIx,
|
||||
PDAUtil,
|
||||
} from "../../src";
|
||||
import { TickSpacing } from "../utils";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { createInOrderMints } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_default_protocol_fee_rate", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully set_default_protocol_fee_rate", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newDefaultProtocolFeeRate = 45;
|
||||
|
||||
// Fetch initial whirlpool and check it is default
|
||||
let whirlpool_0 = await client.getPool(whirlpoolKey);
|
||||
let whirlpool_0 = (await fetcher.getPool(whirlpoolKey)) as WhirlpoolData;
|
||||
assert.equal(whirlpool_0.protocolFeeRate, configInitInfo.defaultProtocolFeeRate);
|
||||
|
||||
await client
|
||||
.setDefaultProtocolFeeRateIx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setDefaultProtocolFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
defaultProtocolFeeRate: newDefaultProtocolFeeRate,
|
||||
})
|
||||
)
|
||||
.addSigner(feeAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
// Setting the default rate did not change existing whirlpool fee rate
|
||||
whirlpool_0 = await client.getPool(whirlpoolKey);
|
||||
whirlpool_0 = (await fetcher.getPool(whirlpoolKey)) as WhirlpoolData;
|
||||
assert.equal(whirlpool_0.protocolFeeRate, configInitInfo.defaultProtocolFeeRate);
|
||||
|
||||
const [tokenMintA, tokenMintB] = await createInOrderMints(context);
|
||||
const whirlpoolPda = getWhirlpoolPda(
|
||||
context.program.programId,
|
||||
const [tokenMintA, tokenMintB] = await createInOrderMints(ctx);
|
||||
const whirlpoolPda = PDAUtil.getWhirlpool(
|
||||
ctx.program.programId,
|
||||
whirlpoolsConfigKey,
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
|
@ -62,25 +70,27 @@ describe("set_default_protocol_fee_rate", () => {
|
|||
tokenVaultBKeypair,
|
||||
tickSpacing: TickSpacing.Stable,
|
||||
};
|
||||
await client.initPoolTx(newPoolInitInfo).buildAndExecute();
|
||||
await toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, newPoolInitInfo)).buildAndExecute();
|
||||
|
||||
const whirlpool_1 = await client.getPool(whirlpoolPda.publicKey);
|
||||
const whirlpool_1 = (await fetcher.getPool(whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
assert.equal(whirlpool_1.protocolFeeRate, newDefaultProtocolFeeRate);
|
||||
});
|
||||
|
||||
it("fails when default fee rate exceeds max", async () => {
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newDefaultProtocolFeeRate = 20_000;
|
||||
await assert.rejects(
|
||||
client
|
||||
.setDefaultProtocolFeeRateIx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setDefaultProtocolFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
defaultProtocolFeeRate: newDefaultProtocolFeeRate,
|
||||
})
|
||||
)
|
||||
.addSigner(feeAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x178d/ // ProtocolFeeRateMaxExceeded
|
||||
|
@ -88,8 +98,8 @@ describe("set_default_protocol_fee_rate", () => {
|
|||
});
|
||||
|
||||
it("fails when fee authority is not a signer", async () => {
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newDefaultProtocolFeeRate = 1000;
|
||||
|
@ -105,8 +115,8 @@ describe("set_default_protocol_fee_rate", () => {
|
|||
});
|
||||
|
||||
it("fails when invalid fee authority provided", async () => {
|
||||
const { configInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const { configInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const fakeFeeAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
const newDefaultProtocolFeeRate = 1000;
|
|
@ -0,0 +1,73 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolsConfigData, WhirlpoolIx } from "../../src";
|
||||
import { generateDefaultConfigParams } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_fee_authority", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully set_fee_authority", async () => {
|
||||
const {
|
||||
configInitInfo,
|
||||
configKeypairs: { feeAuthorityKeypair },
|
||||
} = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
const newAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setFeeAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
newFeeAuthority: newAuthorityKeypair.publicKey,
|
||||
})
|
||||
)
|
||||
.addSigner(feeAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
const config = (await fetcher.getConfig(
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey
|
||||
)) as WhirlpoolsConfigData;
|
||||
assert.ok(config.feeAuthority.equals(newAuthorityKeypair.publicKey));
|
||||
});
|
||||
|
||||
it("fails if current fee_authority is not a signer", async () => {
|
||||
const {
|
||||
configInitInfo,
|
||||
configKeypairs: { feeAuthorityKeypair },
|
||||
} = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setFeeAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
newFeeAuthority: provider.wallet.publicKey,
|
||||
})
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails if invalid fee_authority provided", async () => {
|
||||
const { configInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setFeeAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
feeAuthority: provider.wallet.publicKey,
|
||||
newFeeAuthority: provider.wallet.publicKey,
|
||||
})
|
||||
).buildAndExecute(),
|
||||
/0x7dc/ // An address constraint was violated
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,31 +1,30 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { generateDefaultConfigParams } from "./utils/test-builders";
|
||||
import { TickSpacing } from "./utils";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src";
|
||||
import { TickSpacing } from "../utils";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { generateDefaultConfigParams } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_fee_rate", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully sets_fee_rate", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs, feeTierParams } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newFeeRate = 50;
|
||||
|
||||
let whirlpool = await client.getPool(whirlpoolKey);
|
||||
let whirlpool = (await fetcher.getPool(whirlpoolKey, true)) as WhirlpoolData;
|
||||
|
||||
assert.equal(whirlpool.feeRate, feeTierParams.defaultFeeRate);
|
||||
|
||||
|
@ -38,28 +37,30 @@ describe("set_fee_rate", () => {
|
|||
signers: [feeAuthorityKeypair],
|
||||
});
|
||||
|
||||
whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.equal(whirlpool.feeRate, newFeeRate);
|
||||
});
|
||||
|
||||
it("fails when fee rate exceeds max", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newFeeRate = 20_000;
|
||||
await assert.rejects(
|
||||
client
|
||||
.setFeeRateIx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
feeRate: newFeeRate,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.feeAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x178c/ // FeeRateMaxExceeded
|
||||
|
@ -68,43 +69,47 @@ describe("set_fee_rate", () => {
|
|||
|
||||
it("fails when fee authority is not signer", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newFeeRate = 1000;
|
||||
await assert.rejects(
|
||||
client
|
||||
.setFeeRateIx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
feeRate: newFeeRate,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when whirlpool and whirlpools config don't match", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(otherConfigInitInfo).buildAndExecute();
|
||||
const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.initializeConfigIx(ctx.program, otherConfigInitInfo)
|
||||
).buildAndExecute();
|
||||
|
||||
const newFeeRate = 1000;
|
||||
await assert.rejects(
|
||||
context.program.rpc.setFeeRate(newFeeRate, {
|
||||
ctx.program.rpc.setFeeRate(newFeeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig: otherConfigInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
whirlpoolsConfig: otherConfigInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
},
|
||||
|
@ -115,16 +120,16 @@ describe("set_fee_rate", () => {
|
|||
});
|
||||
|
||||
it("fails when fee authority is invalid", async () => {
|
||||
const { poolInitInfo, configInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
|
||||
const fakeAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
const newFeeRate = 1000;
|
||||
await assert.rejects(
|
||||
context.program.rpc.setFeeRate(newFeeRate, {
|
||||
ctx.program.rpc.setFeeRate(newFeeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: fakeAuthorityKeypair.publicKey,
|
||||
},
|
|
@ -1,30 +1,30 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { generateDefaultConfigParams } from "./utils/test-builders";
|
||||
import { TickSpacing } from "./utils";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src";
|
||||
import { TickSpacing } from "../utils";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { generateDefaultConfigParams } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_protocol_fee_rate", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully sets_protocol_fee_rate", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newProtocolFeeRate = 50;
|
||||
|
||||
let whirlpool = await client.getPool(whirlpoolKey);
|
||||
let whirlpool = (await fetcher.getPool(whirlpoolKey, true)) as WhirlpoolData;
|
||||
|
||||
assert.equal(whirlpool.protocolFeeRate, configInitInfo.defaultProtocolFeeRate);
|
||||
|
||||
|
@ -37,28 +37,30 @@ describe("set_protocol_fee_rate", () => {
|
|||
signers: [feeAuthorityKeypair],
|
||||
});
|
||||
|
||||
whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.equal(whirlpool.protocolFeeRate, newProtocolFeeRate);
|
||||
});
|
||||
|
||||
it("fails when protocol fee rate exceeds max", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newProtocolFeeRate = 3_000;
|
||||
await assert.rejects(
|
||||
client
|
||||
.setProtocolFeeRateIx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setProtocolFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
protocolFeeRate: newProtocolFeeRate,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.feeAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x178d/ // ProtocolFeeRateMaxExceeded
|
||||
|
@ -67,40 +69,44 @@ describe("set_protocol_fee_rate", () => {
|
|||
|
||||
it("fails when fee authority is not signer", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey;
|
||||
const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const newProtocolFeeRate = 1000;
|
||||
await assert.rejects(
|
||||
client
|
||||
.setProtocolFeeRateIx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setProtocolFeeRateIx(ctx.program, {
|
||||
whirlpoolsConfig: whirlpoolsConfigKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
protocolFeeRate: newProtocolFeeRate,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when whirlpool and whirlpools config don't match", async () => {
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair;
|
||||
|
||||
const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(otherConfigInitInfo).buildAndExecute();
|
||||
const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.initializeConfigIx(ctx.program, otherConfigInitInfo)
|
||||
).buildAndExecute();
|
||||
|
||||
const newProtocolFeeRate = 1000;
|
||||
await assert.rejects(
|
||||
context.program.rpc.setProtocolFeeRate(newProtocolFeeRate, {
|
||||
ctx.program.rpc.setProtocolFeeRate(newProtocolFeeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig: otherConfigInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
whirlpoolsConfig: otherConfigInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: feeAuthorityKeypair.publicKey,
|
||||
},
|
||||
|
@ -111,15 +117,15 @@ describe("set_protocol_fee_rate", () => {
|
|||
});
|
||||
|
||||
it("fails when fee authority is invalid", async () => {
|
||||
const { poolInitInfo, configInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
|
||||
const fakeAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
const newProtocolFeeRate = 1000;
|
||||
await assert.rejects(
|
||||
context.program.rpc.setProtocolFeeRate(newProtocolFeeRate, {
|
||||
ctx.program.rpc.setProtocolFeeRate(newProtocolFeeRate, {
|
||||
accounts: {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: whirlpoolKey,
|
||||
feeAuthority: fakeAuthorityKeypair.publicKey,
|
||||
},
|
|
@ -1,28 +1,32 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { NUM_REWARDS } from "../src/types/public";
|
||||
import { TransactionBuilder } from "../src/utils/transactions/transactions-builder";
|
||||
import { buildSetRewardAuthorityIx } from "../src/instructions/set-reward-authority-ix";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { TickSpacing } from "./utils";
|
||||
import {
|
||||
WhirlpoolContext,
|
||||
AccountFetcher,
|
||||
NUM_REWARDS,
|
||||
WhirlpoolData,
|
||||
WhirlpoolIx,
|
||||
} from "../../src";
|
||||
import { TickSpacing } from "../utils";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { TransactionBuilder } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_reward_authority", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully set_reward_authority at every reward index", async () => {
|
||||
const { configKeypairs, poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { configKeypairs, poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const newKeypairs = generateKeypairs(NUM_REWARDS);
|
||||
const txBuilder = new TransactionBuilder(provider);
|
||||
for (let i = 0; i < NUM_REWARDS; i++) {
|
||||
txBuilder.addInstruction(
|
||||
buildSetRewardAuthorityIx(context, {
|
||||
WhirlpoolIx.setRewardAuthorityIx(ctx.program, {
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: newKeypairs[i].publicKey,
|
||||
|
@ -34,25 +38,27 @@ describe("set_reward_authority", () => {
|
|||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
const pool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const pool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
for (let i = 0; i < NUM_REWARDS; i++) {
|
||||
assert.ok(pool.rewardInfos[i].authority.equals(newKeypairs[i].publicKey));
|
||||
}
|
||||
});
|
||||
|
||||
it("fails when provided reward_authority does not match whirlpool reward authority", async () => {
|
||||
const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const fakeAuthority = anchor.web3.Keypair.generate();
|
||||
const newAuthority = anchor.web3.Keypair.generate();
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardAuthorityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityIx(ctx.program, {
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: fakeAuthority.publicKey,
|
||||
newRewardAuthority: newAuthority.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(fakeAuthority)
|
||||
.buildAndExecute(),
|
||||
/0x7dc/ // An address constraint was violated
|
||||
|
@ -60,26 +66,31 @@ describe("set_reward_authority", () => {
|
|||
});
|
||||
|
||||
it("fails on invalid reward index", async () => {
|
||||
const { configKeypairs, poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { configKeypairs, poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const newAuthority = anchor.web3.Keypair.generate();
|
||||
assert.throws(() => {
|
||||
client.setRewardAuthorityTx({
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: newAuthority.publicKey,
|
||||
rewardIndex: -1,
|
||||
});
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityIx(ctx.program, {
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: newAuthority.publicKey,
|
||||
rewardIndex: -1,
|
||||
})
|
||||
).buildAndExecute();
|
||||
}, /out of range/);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardAuthorityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityIx(ctx.program, {
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: newAuthority.publicKey,
|
||||
rewardIndex: 255,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute()
|
||||
// /failed to send transaction/
|
||||
|
@ -87,18 +98,19 @@ describe("set_reward_authority", () => {
|
|||
});
|
||||
|
||||
it("fails when reward_authority is not a signer", async () => {
|
||||
const { configKeypairs, poolInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { configKeypairs, poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
const newAuthority = anchor.web3.Keypair.generate();
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardAuthorityTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityIx(ctx.program, {
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: newAuthority.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
|
@ -1,54 +1,58 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { TickSpacing } from "./utils";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src";
|
||||
import { TickSpacing } from "../utils";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_reward_authority_by_super_authority", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully set_reward_authority_by_super_authority", async () => {
|
||||
const { configKeypairs, poolInitInfo, configInitInfo } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
const newAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
await client
|
||||
.setRewardAuthorityBySuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardEmissionsSuperAuthority:
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: newAuthorityKeypair.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
const pool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
const pool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData;
|
||||
assert.ok(pool.rewardInfos[0].authority.equals(newAuthorityKeypair.publicKey));
|
||||
});
|
||||
|
||||
it("fails if invalid whirlpool provided", async () => {
|
||||
const { configKeypairs, configInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { configKeypairs, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda: invalidPool },
|
||||
} = await initTestPool(client, TickSpacing.Standard);
|
||||
} = await initTestPool(ctx, TickSpacing.Standard);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardAuthorityBySuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: invalidPool.publicKey,
|
||||
rewardEmissionsSuperAuthority:
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: provider.wallet.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7d1/ // A has_one constraint was violated
|
||||
|
@ -56,18 +60,20 @@ describe("set_reward_authority_by_super_authority", () => {
|
|||
});
|
||||
|
||||
it("fails if invalid super authority provided", async () => {
|
||||
const { poolInitInfo, configInitInfo } = await initTestPool(client, TickSpacing.Standard);
|
||||
const { poolInitInfo, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard);
|
||||
const invalidSuperAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardAuthorityBySuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardEmissionsSuperAuthority: invalidSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: provider.wallet.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
)
|
||||
.addSigner(invalidSuperAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7dc/ // An address constraint was violated
|
||||
|
@ -76,55 +82,60 @@ describe("set_reward_authority_by_super_authority", () => {
|
|||
|
||||
it("fails if super authority is not a signer", async () => {
|
||||
const { configKeypairs, poolInitInfo, configInitInfo } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardAuthorityBySuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardEmissionsSuperAuthority:
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: provider.wallet.publicKey,
|
||||
rewardIndex: 0,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
||||
|
||||
it("fails on invalid reward index", async () => {
|
||||
const { configKeypairs, poolInitInfo, configInitInfo } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
assert.throws(() => {
|
||||
client
|
||||
.setRewardAuthorityBySuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardEmissionsSuperAuthority:
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: provider.wallet.publicKey,
|
||||
rewardIndex: -1,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
}, /out of range/);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardAuthorityBySuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardEmissionsSuperAuthority:
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardAuthority: provider.wallet.publicKey,
|
||||
rewardIndex: 200,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x178a/ // InvalidRewardIndex
|
|
@ -1,22 +1,22 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import * as assert from "assert";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { createAndMintToTokenAccount, mintToByAuthority, TickSpacing, ZERO_BN } from "./utils";
|
||||
import { initializeReward, initTestPool } from "./utils/init-utils";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
import { TickSpacing, mintToByAuthority, ZERO_BN, createAndMintToTokenAccount } from "../utils";
|
||||
import { initTestPool, initializeReward } from "../utils/init-utils";
|
||||
|
||||
describe("set_reward_emissions", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
const emissionsPerSecondX64 = new anchor.BN(10_000).shln(64).div(new anchor.BN(60 * 60 * 24));
|
||||
|
||||
it("successfully set_reward_emissions", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
|
@ -25,7 +25,7 @@ describe("set_reward_emissions", () => {
|
|||
const {
|
||||
params: { rewardVaultKeypair, rewardMint },
|
||||
} = await initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex
|
||||
|
@ -33,39 +33,46 @@ describe("set_reward_emissions", () => {
|
|||
|
||||
await mintToByAuthority(provider, rewardMint, rewardVaultKeypair.publicKey, 10000);
|
||||
|
||||
await client
|
||||
.setRewardEmissionsTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsIx(ctx.program, {
|
||||
rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex,
|
||||
rewardVault: rewardVaultKeypair.publicKey,
|
||||
rewardVaultKey: rewardVaultKeypair.publicKey,
|
||||
emissionsPerSecondX64,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
let whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
let whirlpool = (await fetcher.getPool(
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
true
|
||||
)) as WhirlpoolData;
|
||||
assert.ok(whirlpool.rewardInfos[0].emissionsPerSecondX64.eq(emissionsPerSecondX64));
|
||||
|
||||
// Successfuly set emissions back to zero
|
||||
await client
|
||||
.setRewardEmissionsTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsIx(ctx.program, {
|
||||
rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex,
|
||||
rewardVault: rewardVaultKeypair.publicKey,
|
||||
rewardVaultKey: rewardVaultKeypair.publicKey,
|
||||
emissionsPerSecondX64: ZERO_BN,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey);
|
||||
whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey, true)) as WhirlpoolData;
|
||||
assert.ok(whirlpool.rewardInfos[0].emissionsPerSecondX64.eq(ZERO_BN));
|
||||
});
|
||||
|
||||
it("fails when token vault does not contain at least 1 day of emission runway", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
|
@ -74,21 +81,23 @@ describe("set_reward_emissions", () => {
|
|||
const {
|
||||
params: { rewardVaultKeypair },
|
||||
} = await initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardEmissionsTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsIx(ctx.program, {
|
||||
rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex,
|
||||
rewardVault: rewardVaultKeypair.publicKey,
|
||||
rewardVaultKey: rewardVaultKeypair.publicKey,
|
||||
emissionsPerSecondX64,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x178b/ // RewardVaultAmountInsufficient
|
||||
|
@ -97,7 +106,7 @@ describe("set_reward_emissions", () => {
|
|||
|
||||
it("fails if provided reward vault does not match whirlpool reward vault", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
|
@ -105,7 +114,7 @@ describe("set_reward_emissions", () => {
|
|||
const {
|
||||
params: { rewardVaultKeypair, rewardMint },
|
||||
} = await initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex
|
||||
|
@ -114,14 +123,16 @@ describe("set_reward_emissions", () => {
|
|||
const fakeVault = await createAndMintToTokenAccount(provider, rewardMint, 10000);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardEmissionsTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsIx(ctx.program, {
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority,
|
||||
rewardVault: fakeVault,
|
||||
rewardVaultKey: fakeVault,
|
||||
rewardIndex,
|
||||
emissionsPerSecondX64,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0x7dc/ // An address constraint was violated
|
||||
|
@ -130,21 +141,23 @@ describe("set_reward_emissions", () => {
|
|||
|
||||
it("cannot set emission for an uninitialized reward", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
const rewardIndex = 0;
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardEmissionsTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsIx(ctx.program, {
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority,
|
||||
rewardVault: anchor.web3.PublicKey.default,
|
||||
rewardVaultKey: anchor.web3.PublicKey.default,
|
||||
rewardIndex: rewardIndex,
|
||||
emissionsPerSecondX64,
|
||||
})
|
||||
)
|
||||
.addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute(),
|
||||
/0xbbf/ // AccountOwnedByWrongProgram
|
||||
|
@ -153,29 +166,30 @@ describe("set_reward_emissions", () => {
|
|||
|
||||
it("cannot set emission without the authority's signature", async () => {
|
||||
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
|
||||
client,
|
||||
ctx,
|
||||
TickSpacing.Standard
|
||||
);
|
||||
|
||||
const rewardIndex = 0;
|
||||
|
||||
await initializeReward(
|
||||
client,
|
||||
ctx,
|
||||
configKeypairs.rewardEmissionsSuperAuthorityKeypair,
|
||||
poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardEmissionsTx({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsIx(ctx.program, {
|
||||
rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority,
|
||||
whirlpool: poolInitInfo.whirlpoolPda.publicKey,
|
||||
rewardIndex,
|
||||
rewardVault: provider.wallet.publicKey, // TODO fix
|
||||
rewardVaultKey: provider.wallet.publicKey, // TODO fix
|
||||
emissionsPerSecondX64,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/Signature verification failed/
|
||||
);
|
||||
});
|
|
@ -1,35 +1,39 @@
|
|||
import * as assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { generateDefaultConfigParams } from "./utils/test-builders";
|
||||
import { WhirlpoolContext, AccountFetcher, WhirlpoolsConfigData, WhirlpoolIx } from "../../src";
|
||||
import { generateDefaultConfigParams } from "../utils/test-builders";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("set_reward_emissions_super_authority", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully set_reward_emissions_super_authority with super authority keypair", async () => {
|
||||
const {
|
||||
configInitInfo,
|
||||
configKeypairs: { rewardEmissionsSuperAuthorityKeypair },
|
||||
} = generateDefaultConfigParams(context);
|
||||
} = generateDefaultConfigParams(ctx);
|
||||
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
const newAuthorityKeypair = anchor.web3.Keypair.generate();
|
||||
|
||||
await client
|
||||
.setRewardEmissionsSuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsSuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
rewardEmissionsSuperAuthority: rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardEmissionsSuperAuthority: newAuthorityKeypair.publicKey,
|
||||
})
|
||||
)
|
||||
.addSigner(rewardEmissionsSuperAuthorityKeypair)
|
||||
.buildAndExecute();
|
||||
|
||||
const config = await client.getConfig(configInitInfo.whirlpoolConfigKeypair.publicKey);
|
||||
const config = (await fetcher.getConfig(
|
||||
configInitInfo.whirlpoolsConfigKeypair.publicKey
|
||||
)) as WhirlpoolsConfigData;
|
||||
assert.ok(config.rewardEmissionsSuperAuthority.equals(newAuthorityKeypair.publicKey));
|
||||
});
|
||||
|
||||
|
@ -37,13 +41,13 @@ describe("set_reward_emissions_super_authority", () => {
|
|||
const {
|
||||
configInitInfo,
|
||||
configKeypairs: { rewardEmissionsSuperAuthorityKeypair },
|
||||
} = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
} = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
context.program.rpc.setRewardEmissionsSuperAuthority({
|
||||
ctx.program.rpc.setRewardEmissionsSuperAuthority({
|
||||
accounts: {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
rewardEmissionsSuperAuthority: rewardEmissionsSuperAuthorityKeypair.publicKey,
|
||||
newRewardEmissionsSuperAuthority: provider.wallet.publicKey,
|
||||
},
|
||||
|
@ -53,17 +57,18 @@ describe("set_reward_emissions_super_authority", () => {
|
|||
});
|
||||
|
||||
it("fails if incorrect reward_emissions_super_authority is passed in", async () => {
|
||||
const { configInitInfo } = generateDefaultConfigParams(context);
|
||||
await client.initConfigTx(configInitInfo).buildAndExecute();
|
||||
const { configInitInfo } = generateDefaultConfigParams(ctx);
|
||||
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.setRewardEmissionsSuperAuthorityTx({
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey,
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.setRewardEmissionsSuperAuthorityIx(ctx.program, {
|
||||
whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey,
|
||||
rewardEmissionsSuperAuthority: provider.wallet.publicKey,
|
||||
newRewardEmissionsSuperAuthority: provider.wallet.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x7dc/ // An address constraint was violated
|
||||
);
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -2,19 +2,19 @@ import * as anchor from "@project-serum/anchor";
|
|||
import * as assert from "assert";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import Decimal from "decimal.js";
|
||||
import { getOraclePda, getTickArrayPda, toX64 } from "../src";
|
||||
import { WhirlpoolClient } from "../src/client";
|
||||
import { WhirlpoolContext } from "../src/context";
|
||||
import { sleep, TickSpacing, ZERO_BN } from "./utils";
|
||||
import { WhirlpoolTestFixture } from "./utils/fixture";
|
||||
import { initTestPool } from "./utils/init-utils";
|
||||
import { WhirlpoolContext, AccountFetcher, PositionData, WhirlpoolIx, PDAUtil } from "../../src";
|
||||
import { TickSpacing, ZERO_BN, sleep } from "../utils";
|
||||
import { WhirlpoolTestFixture } from "../utils/fixture";
|
||||
import { initTestPool } from "../utils/init-utils";
|
||||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { toTx } from "../../src/utils/instructions-util";
|
||||
|
||||
describe("update_fees_and_rewards", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const context = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const client = new WhirlpoolClient(context);
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
const fetcher = new AccountFetcher(ctx.connection);
|
||||
|
||||
it("successfully updates fees and rewards", async () => {
|
||||
// In same tick array - start index 22528
|
||||
|
@ -22,10 +22,12 @@ describe("update_fees_and_rewards", () => {
|
|||
const tickUpperIndex = 33536;
|
||||
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(1_000_000) }],
|
||||
rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }],
|
||||
rewards: [
|
||||
{ emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) },
|
||||
],
|
||||
});
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair },
|
||||
|
@ -34,25 +36,29 @@ describe("update_fees_and_rewards", () => {
|
|||
positions,
|
||||
} = fixture.getInfos();
|
||||
|
||||
const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
|
||||
const positionBefore = await client.getPosition(positions[0].publicKey);
|
||||
const positionBefore = (await fetcher.getPosition(
|
||||
positions[0].publicKey,
|
||||
true
|
||||
)) as PositionData;
|
||||
assert.ok(positionBefore.feeGrowthCheckpointA.eq(ZERO_BN));
|
||||
assert.ok(positionBefore.feeGrowthCheckpointB.eq(ZERO_BN));
|
||||
assert.ok(positionBefore.rewardInfos[0].amountOwed.eq(ZERO_BN));
|
||||
assert.ok(positionBefore.rewardInfos[0].growthInsideCheckpoint.eq(ZERO_BN));
|
||||
|
||||
const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey);
|
||||
const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey);
|
||||
|
||||
await client
|
||||
.swapTx({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.swapIx(ctx.program, {
|
||||
amount: new u64(100_000),
|
||||
otherAmountThreshold: ZERO_BN,
|
||||
sqrtPriceLimit: toX64(new Decimal(4.95)),
|
||||
sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)),
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB: true,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tokenAuthority: context.wallet.publicKey,
|
||||
tokenAuthority: ctx.wallet.publicKey,
|
||||
tokenOwnerAccountA: tokenAccountA,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenOwnerAccountB: tokenAccountB,
|
||||
|
@ -62,19 +68,20 @@ describe("update_fees_and_rewards", () => {
|
|||
tickArray2: tickArrayPda.publicKey,
|
||||
oracle: oraclePda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
).buildAndExecute();
|
||||
|
||||
await sleep(1_000);
|
||||
|
||||
await client
|
||||
.updateFeesAndRewards({
|
||||
await toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute();
|
||||
const positionAfter = await client.getPosition(positions[0].publicKey);
|
||||
).buildAndExecute();
|
||||
const positionAfter = (await fetcher.getPosition(positions[0].publicKey, true)) as PositionData;
|
||||
assert.ok(positionAfter.feeOwedA.gt(positionBefore.feeOwedA));
|
||||
assert.ok(positionAfter.feeOwedB.eq(ZERO_BN));
|
||||
assert.ok(positionAfter.feeGrowthCheckpointA.gt(positionBefore.feeGrowthCheckpointA));
|
||||
|
@ -94,7 +101,7 @@ describe("update_fees_and_rewards", () => {
|
|||
const tickUpperIndex = 33536;
|
||||
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -103,17 +110,18 @@ describe("update_fees_and_rewards", () => {
|
|||
positions,
|
||||
} = fixture.getInfos();
|
||||
|
||||
const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.updateFeesAndRewards({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0x177c/ // LiquidityZero
|
||||
);
|
||||
});
|
||||
|
@ -125,24 +133,25 @@ describe("update_fees_and_rewards", () => {
|
|||
const tickSpacing = TickSpacing.Standard;
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda },
|
||||
} = await initTestPool(client, tickSpacing);
|
||||
const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
} = await initTestPool(ctx, tickSpacing);
|
||||
const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528);
|
||||
|
||||
const other = await new WhirlpoolTestFixture(client).init({
|
||||
const other = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(1_000_000) }],
|
||||
});
|
||||
const { positions: otherPositions } = other.getInfos();
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.updateFeesAndRewards({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: otherPositions[0].publicKey,
|
||||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0xbbf/ // AccountOwnedByWrongProgram
|
||||
);
|
||||
});
|
||||
|
@ -153,7 +162,7 @@ describe("update_fees_and_rewards", () => {
|
|||
const tickUpperIndex = 33536;
|
||||
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new anchor.BN(1_000_000) }],
|
||||
});
|
||||
|
@ -162,17 +171,18 @@ describe("update_fees_and_rewards", () => {
|
|||
positions,
|
||||
} = fixture.getInfos();
|
||||
|
||||
const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 0);
|
||||
const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 0);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.updateFeesAndRewards({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0xbbf/ // AccountOwnedByWrongProgram
|
||||
);
|
||||
});
|
||||
|
@ -183,7 +193,7 @@ describe("update_fees_and_rewards", () => {
|
|||
const tickUpperIndex = 33536;
|
||||
|
||||
const tickSpacing = TickSpacing.Standard;
|
||||
const fixture = await new WhirlpoolTestFixture(client).init({
|
||||
const fixture = await new WhirlpoolTestFixture(ctx).init({
|
||||
tickSpacing,
|
||||
positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }],
|
||||
});
|
||||
|
@ -194,23 +204,24 @@ describe("update_fees_and_rewards", () => {
|
|||
|
||||
const {
|
||||
poolInitInfo: { whirlpoolPda: otherWhirlpoolPda },
|
||||
} = await initTestPool(client, tickSpacing);
|
||||
} = await initTestPool(ctx, tickSpacing);
|
||||
|
||||
const tickArrayPda = getTickArrayPda(
|
||||
context.program.programId,
|
||||
const tickArrayPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
otherWhirlpoolPda.publicKey,
|
||||
22528
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
client
|
||||
.updateFeesAndRewards({
|
||||
toTx(
|
||||
ctx,
|
||||
WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
position: positions[0].publicKey,
|
||||
tickArrayLower: tickArrayPda.publicKey,
|
||||
tickArrayUpper: tickArrayPda.publicKey,
|
||||
})
|
||||
.buildAndExecute(),
|
||||
).buildAndExecute(),
|
||||
/0xbbf/ // AccountOwnedByWrongProgram
|
||||
);
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue