diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 174896a1..d948685d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,10 @@ on: - release_* env: - SOLANA_CLI_VERSION: 2.1.0 + SOLANA_CLI_VERSION: 2.3.13 NODE_VERSION: 18.20.6 ANCHOR_CLI_VERSION: 0.31.0 - TOOLCHAIN: 1.76.0 + TOOLCHAIN: 1.85.0 jobs: program_changed_files: diff --git a/Anchor.toml b/Anchor.toml index 371b8599..84bdd8e8 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -17,4 +17,4 @@ test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" [toolchain] anchor_version = "0.31.0" -solana_version = "2.1.0" +solana_version = "2.3.13" diff --git a/CHANGELOG.md b/CHANGELOG.md index c437df22..514a914d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,12 +21,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes +## cp_amm [0.2.0] + +### Added + +- Pool now will track reserves balances `(token_a_amount, token_b_amount)` if `pool.layout_version == 1`. For pool layout_version 0, operator can call the new endpoint `fix_pool_layout_version` to pump pool version. +- Add a new `collect_fee_mode (Compounding)`, in the new collect fee mode, fee will be collected in quote token, and a percentage of fee (configurable) will be added in reserves for compounding. In the new collect fee mode, the pool doesn't have concentrated price range, instead following constant-product formula `token_a_amount * token_b_amount = constant`. +- Endpoints `create_config`, `initialize_customizable_pool` and `initialize_pool_with_dynamic_config` will allow user to create pool with `collect_fee_mode == Compounding`, and config for `compounding_fee_bps`. + +### Changed + +- Related to event `EvtSwap2`, in `swap_result` field, `partner_fee` will be replaced by `compounding_fee`, now total_trading_fee will be calculated as `swap_result.claiming_fee + swap_result.compounding_fee` + +### Removed + +- Removed `partner` field from Pool struct +- Removed unused `partner_fee` feature and the `claim_partner_fee` endpoint + +### Breaking Changes + +- Quote function will be changed by the new fee mode + ## cp_amm [0.1.8][PR #177](https://github.com/MeteoraAg/damm-v2/pull/177) ### Added - New endpoint `fix_pool_params` and `fix_config_fee_params` to allow `operator` to fix invalid scheduler params that causes blocking operation on `update_pool_fees` endpoint. -- New endpoint `lock_inner_position`, that allow to vest liquidity without external `Vesting` account for better composability. +- New endpoint `lock_inner_position`, that allow to vest liquidity without external `Vesting` account for better composability. ### Changed diff --git a/Cargo.lock b/Cargo.lock index c5fc7a42..3f575cac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,24 +730,23 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cp-amm" -version = "0.1.8" +version = "0.2.0" dependencies = [ "anchor-lang", "anchor-spl", "bytemuck", "const-crypto", - "jupiter", "num", "num-traits", "num_enum", - "pinocchio", + "pinocchio 0.9.0", "pinocchio-token", "pinocchio-token-2022", "proptest", + "protocol-zap", "ruint", "spl-token-metadata-interface", "static_assertions", - "zap", ] [[package]] @@ -1113,9 +1112,9 @@ dependencies = [ [[package]] name = "jupiter" version = "0.1.0" +source = "git+https://github.com/MeteoraAg/zap-program?rev=064c58b317b9a85f212c0de72caea286fc72fdb4#064c58b317b9a85f212c0de72caea286fc72fdb4" dependencies = [ "anchor-lang", - "bytemuck", ] [[package]] @@ -1450,13 +1449,30 @@ name = "pinocchio" version = "0.9.0" source = "git+https://github.com/anza-xyz/pinocchio.git?rev=17b0e862c01a868ea07ef81a2f8a9b4a504bdfed#17b0e862c01a868ea07ef81a2f8a9b4a504bdfed" +[[package]] +name = "pinocchio" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" + +[[package]] +name = "pinocchio-pubkey" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0225638cadcbebae8932cb7f49cb5da7c15c21beb19f048f05a5ca7d93f065" +dependencies = [ + "five8_const", + "pinocchio 0.9.2", + "sha2-const-stable", +] + [[package]] name = "pinocchio-pubkey" version = "0.3.0" source = "git+https://github.com/anza-xyz/pinocchio.git?rev=17b0e862c01a868ea07ef81a2f8a9b4a504bdfed#17b0e862c01a868ea07ef81a2f8a9b4a504bdfed" dependencies = [ "five8_const", - "pinocchio", + "pinocchio 0.9.0", "sha2-const-stable", ] @@ -1465,8 +1481,8 @@ name = "pinocchio-token" version = "0.4.0" source = "git+https://github.com/anza-xyz/pinocchio.git?rev=17b0e862c01a868ea07ef81a2f8a9b4a504bdfed#17b0e862c01a868ea07ef81a2f8a9b4a504bdfed" dependencies = [ - "pinocchio", - "pinocchio-pubkey", + "pinocchio 0.9.0", + "pinocchio-pubkey 0.3.0 (git+https://github.com/anza-xyz/pinocchio.git?rev=17b0e862c01a868ea07ef81a2f8a9b4a504bdfed)", ] [[package]] @@ -1474,8 +1490,8 @@ name = "pinocchio-token-2022" version = "0.1.0" source = "git+https://github.com/anza-xyz/pinocchio.git?rev=17b0e862c01a868ea07ef81a2f8a9b4a504bdfed#17b0e862c01a868ea07ef81a2f8a9b4a504bdfed" dependencies = [ - "pinocchio", - "pinocchio-pubkey", + "pinocchio 0.9.0", + "pinocchio-pubkey 0.3.0 (git+https://github.com/anza-xyz/pinocchio.git?rev=17b0e862c01a868ea07ef81a2f8a9b4a504bdfed)", ] [[package]] @@ -1554,6 +1570,21 @@ dependencies = [ "unarray", ] +[[package]] +name = "protocol-zap" +version = "0.1.0" +source = "git+https://github.com/MeteoraAg/zap-program?rev=064c58b317b9a85f212c0de72caea286fc72fdb4#064c58b317b9a85f212c0de72caea286fc72fdb4" +dependencies = [ + "borsh 0.10.3", + "jupiter", + "num_enum", + "pinocchio 0.9.2", + "pinocchio-pubkey 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ruint", + "thiserror 2.0.12", + "zap-sdk", +] + [[package]] name = "qstring" version = "0.7.2" @@ -1782,11 +1813,12 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rust-sdk" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "bytemuck", "cp-amm", + "proptest", "ruint", ] @@ -3686,11 +3718,11 @@ dependencies = [ ] [[package]] -name = "zap" +name = "zap-sdk" version = "0.1.0" +source = "git+https://github.com/MeteoraAg/zap-program?rev=064c58b317b9a85f212c0de72caea286fc72fdb4#064c58b317b9a85f212c0de72caea286fc72fdb4" dependencies = [ - "anchor-lang", - "bytemuck", + "solana-pubkey", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 96ffd8a8..6726bb65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["programs/*", "rust-sdk", "libs/*"] +members = ["programs/*", "rust-sdk"] resolver = "2" [profile.release] @@ -16,4 +16,5 @@ codegen-units = 1 anchor-lang = { version = "0.31.0", features = ["init-if-needed"] } anchor-spl = "0.31.0" anchor-client = "0.31.0" -bytemuck = { version = "1.20.0"} \ No newline at end of file +bytemuck = { version = "1.20.0"} +proptest = "1.2.0" \ No newline at end of file diff --git a/README.md b/README.md index 957a390b..0b6f6f1e 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,6 @@ MCPA is a brand new AMM program of Meteora that includes almost all features fro - fund_reward: fund reward for on-chain liquidity mining - withdraw_ineligible_reward: withdraw ineligible reward -### Partner (aka Launchpad) -- claim_partner_fee: claim partner fee - ### Token deployer - initialize_pool: create a new pool from a static config key - initialize_pool_with_dynamic_config: create a new pool from a dynamic config key @@ -54,7 +51,7 @@ MCPA is a brand new AMM program of Meteora that includes almost all features fro ## Config key state - vault_config_key: alpha-vault address that is able to buy pool before activation_point - pool_creator_authority: if this address is non-default, then only this address can create pool with that config key (for launchpad) -- pool_fees: includes base fee scheduler, dynamic-fee, protocol fee percent, partner fee percent, and referral fee percent configuration +- pool_fees: includes base fee scheduler, dynamic-fee, protocol fee percent, and referral fee percent configuration - activation_type: determines whether pools are run in slot or timestamp - collect_fee_mode: determines whether pool should collect fees in both tokens or only one token - sqrt_min_price: square root of min price for pools diff --git a/idls/jupiter.json b/idls/jupiter.json deleted file mode 100644 index 9ea99c95..00000000 --- a/idls/jupiter.json +++ /dev/null @@ -1,2279 +0,0 @@ -{ - "address": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", - "metadata": { - "name": "jupiter", - "version": "0.1.0", - "spec": "0.1.0", - "address": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", - "description": "Jupiter aggregator program" - }, - "instructions": [ - { - "name": "claim", - "discriminator": [62, 198, 214, 193, 213, 159, 108, 210], - "accounts": [ - { - "name": "wallet", - "writable": true, - "address": "7JQeyNK55fkUPUmEotupBFpiBGpgEQYLe8Ht1VdSfxcP" - }, - { - "name": "program_authority", - "writable": true - }, - { - "name": "system_program", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "claim_token", - "discriminator": [116, 206, 27, 191, 166, 19, 0, 73], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "wallet", - "address": "7JQeyNK55fkUPUmEotupBFpiBGpgEQYLe8Ht1VdSfxcP" - }, - { - "name": "program_authority" - }, - { - "name": "program_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "wallet" - }, - { - "kind": "account", - "path": "token_program" - }, - { - "kind": "account", - "path": "mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, - 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89 - ] - } - } - }, - { - "name": "mint" - }, - { - "name": "token_program" - }, - { - "name": "associated_token_program", - "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" - }, - { - "name": "system_program", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "close_token", - "discriminator": [26, 74, 236, 151, 104, 64, 183, 249], - "accounts": [ - { - "name": "operator", - "signer": true, - "address": "9RAufBfjGQjDfrwxeyKmZWPADHSb8HcoqCdrmpqvCr1g" - }, - { - "name": "wallet", - "writable": true, - "address": "7JQeyNK55fkUPUmEotupBFpiBGpgEQYLe8Ht1VdSfxcP" - }, - { - "name": "program_authority" - }, - { - "name": "program_token_account", - "writable": true - }, - { - "name": "mint", - "writable": true - }, - { - "name": "token_program" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - }, - { - "name": "burn_all", - "type": "bool" - } - ] - }, - { - "name": "create_token_ledger", - "discriminator": [232, 242, 197, 253, 240, 143, 129, 52], - "accounts": [ - { - "name": "token_ledger", - "writable": true, - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "system_program", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "create_token_account", - "discriminator": [147, 241, 123, 100, 244, 132, 174, 118], - "accounts": [ - { - "name": "token_account", - "writable": true - }, - { - "name": "user", - "writable": true, - "signer": true - }, - { - "name": "mint" - }, - { - "name": "token_program" - }, - { - "name": "system_program", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "bump", - "type": "u8" - } - ] - }, - { - "name": "exact_out_route", - "discriminator": [208, 51, 239, 151, 123, 43, 237, 92], - "accounts": [ - { - "name": "token_program" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "user_source_token_account", - "writable": true - }, - { - "name": "user_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true, - "optional": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "platform_fee_account", - "writable": true, - "optional": true - }, - { - "name": "token_2022_program", - "optional": true - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStep" - } - } - } - }, - { - "name": "out_amount", - "type": "u64" - }, - { - "name": "quoted_in_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "route", - "discriminator": [229, 23, 203, 151, 122, 227, 173, 42], - "accounts": [ - { - "name": "token_program" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "user_source_token_account", - "writable": true - }, - { - "name": "user_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true, - "optional": true - }, - { - "name": "destination_mint" - }, - { - "name": "platform_fee_account", - "writable": true, - "optional": true - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStep" - } - } - } - }, - { - "name": "in_amount", - "type": "u64" - }, - { - "name": "quoted_out_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "route_with_token_ledger", - "discriminator": [150, 86, 71, 116, 167, 93, 14, 104], - "accounts": [ - { - "name": "token_program" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "user_source_token_account", - "writable": true - }, - { - "name": "user_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true, - "optional": true - }, - { - "name": "destination_mint" - }, - { - "name": "platform_fee_account", - "writable": true, - "optional": true - }, - { - "name": "token_ledger" - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStep" - } - } - } - }, - { - "name": "quoted_out_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "set_token_ledger", - "discriminator": [228, 85, 185, 112, 78, 79, 77, 2], - "accounts": [ - { - "name": "token_ledger", - "writable": true - }, - { - "name": "token_account" - } - ], - "args": [] - }, - { - "name": "shared_accounts_exact_out_route", - "discriminator": [176, 209, 105, 168, 154, 125, 69, 62], - "accounts": [ - { - "name": "token_program" - }, - { - "name": "program_authority" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "source_token_account", - "writable": true - }, - { - "name": "program_source_token_account", - "writable": true - }, - { - "name": "program_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "platform_fee_account", - "writable": true, - "optional": true - }, - { - "name": "token_2022_program", - "optional": true - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - }, - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStep" - } - } - } - }, - { - "name": "out_amount", - "type": "u64" - }, - { - "name": "quoted_in_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "shared_accounts_route", - "discriminator": [193, 32, 155, 51, 65, 214, 156, 129], - "accounts": [ - { - "name": "token_program" - }, - { - "name": "program_authority" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "source_token_account", - "writable": true - }, - { - "name": "program_source_token_account", - "writable": true - }, - { - "name": "program_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "platform_fee_account", - "writable": true, - "optional": true - }, - { - "name": "token_2022_program", - "optional": true - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - }, - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStep" - } - } - } - }, - { - "name": "in_amount", - "type": "u64" - }, - { - "name": "quoted_out_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "shared_accounts_route_with_token_ledger", - "discriminator": [230, 121, 143, 80, 119, 159, 106, 170], - "accounts": [ - { - "name": "token_program" - }, - { - "name": "program_authority" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "source_token_account", - "writable": true - }, - { - "name": "program_source_token_account", - "writable": true - }, - { - "name": "program_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "platform_fee_account", - "writable": true, - "optional": true - }, - { - "name": "token_2022_program", - "optional": true - }, - { - "name": "token_ledger" - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - }, - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStep" - } - } - } - }, - { - "name": "quoted_out_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "name": "exact_out_route_v2", - "discriminator": [157, 138, 184, 82, 21, 244, 243, 36], - "accounts": [ - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "user_source_token_account", - "writable": true - }, - { - "name": "user_destination_token_account", - "writable": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "source_token_program" - }, - { - "name": "destination_token_program" - }, - { - "name": "destination_token_account", - "writable": true, - "optional": true - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "out_amount", - "type": "u64" - }, - { - "name": "quoted_in_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u16" - }, - { - "name": "positive_slippage_bps", - "type": "u16" - }, - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStepV2" - } - } - } - } - ], - "returns": "u64" - }, - { - "name": "route_v2", - "discriminator": [187, 100, 250, 204, 49, 196, 175, 20], - "accounts": [ - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "user_source_token_account", - "writable": true - }, - { - "name": "user_destination_token_account", - "writable": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "source_token_program" - }, - { - "name": "destination_token_program" - }, - { - "name": "destination_token_account", - "writable": true, - "optional": true - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "in_amount", - "type": "u64" - }, - { - "name": "quoted_out_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u16" - }, - { - "name": "positive_slippage_bps", - "type": "u16" - }, - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStepV2" - } - } - } - } - ], - "returns": "u64" - }, - { - "name": "shared_accounts_exact_out_route_v2", - "discriminator": [53, 96, 229, 202, 216, 187, 250, 24], - "accounts": [ - { - "name": "program_authority" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "source_token_account", - "writable": true - }, - { - "name": "program_source_token_account", - "writable": true - }, - { - "name": "program_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "source_token_program" - }, - { - "name": "destination_token_program" - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - }, - { - "name": "out_amount", - "type": "u64" - }, - { - "name": "quoted_in_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u16" - }, - { - "name": "positive_slippage_bps", - "type": "u16" - }, - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStepV2" - } - } - } - } - ], - "returns": "u64" - }, - { - "name": "shared_accounts_route_v2", - "discriminator": [209, 152, 83, 147, 124, 254, 216, 233], - "accounts": [ - { - "name": "program_authority" - }, - { - "name": "user_transfer_authority", - "signer": true - }, - { - "name": "source_token_account", - "writable": true - }, - { - "name": "program_source_token_account", - "writable": true - }, - { - "name": "program_destination_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true - }, - { - "name": "source_mint" - }, - { - "name": "destination_mint" - }, - { - "name": "source_token_program" - }, - { - "name": "destination_token_program" - }, - { - "name": "event_authority", - "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - }, - { - "name": "in_amount", - "type": "u64" - }, - { - "name": "quoted_out_amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - }, - { - "name": "platform_fee_bps", - "type": "u16" - }, - { - "name": "positive_slippage_bps", - "type": "u16" - }, - { - "name": "route_plan", - "type": { - "vec": { - "defined": { - "name": "RoutePlanStepV2" - } - } - } - } - ], - "returns": "u64" - } - ], - "accounts": [ - { - "name": "TokenLedger", - "discriminator": [156, 247, 9, 188, 54, 108, 85, 77] - } - ], - "events": [ - { - "name": "FeeEvent", - "discriminator": [73, 79, 78, 127, 184, 213, 13, 220] - }, - { - "name": "SwapEvent", - "discriminator": [64, 198, 205, 232, 38, 8, 113, 226] - }, - { - "name": "SwapsEvent", - "discriminator": [152, 47, 78, 235, 192, 96, 110, 106] - }, - { - "name": "CandidateSwapResults", - "discriminator": [45, 9, 244, 30, 229, 52, 168, 123] - }, - { - "name": "BestSwapOutAmountViolation", - "discriminator": [124, 66, 196, 51, 218, 173, 46, 93] - } - ], - "errors": [ - { - "code": 6000, - "name": "EmptyRoute", - "msg": "Empty route" - }, - { - "code": 6001, - "name": "SlippageToleranceExceeded", - "msg": "Slippage tolerance exceeded" - }, - { - "code": 6002, - "name": "InvalidCalculation", - "msg": "Invalid calculation" - }, - { - "code": 6003, - "name": "MissingPlatformFeeAccount", - "msg": "Missing platform fee account" - }, - { - "code": 6004, - "name": "InvalidSlippage", - "msg": "Invalid slippage" - }, - { - "code": 6005, - "name": "NotEnoughPercent", - "msg": "Not enough percent to 100" - }, - { - "code": 6006, - "name": "InvalidInputIndex", - "msg": "Token input index is invalid" - }, - { - "code": 6007, - "name": "InvalidOutputIndex", - "msg": "Token output index is invalid" - }, - { - "code": 6008, - "name": "NotEnoughAccountKeys", - "msg": "Not Enough Account keys" - }, - { - "code": 6009, - "name": "NonZeroMinimumOutAmountNotSupported", - "msg": "Non zero minimum out amount not supported" - }, - { - "code": 6010, - "name": "InvalidRoutePlan", - "msg": "Invalid route plan" - }, - { - "code": 6011, - "name": "InvalidReferralAuthority", - "msg": "Invalid referral authority" - }, - { - "code": 6012, - "name": "LedgerTokenAccountDoesNotMatch", - "msg": "Token account doesn't match the ledger" - }, - { - "code": 6013, - "name": "InvalidTokenLedger", - "msg": "Invalid token ledger" - }, - { - "code": 6014, - "name": "IncorrectTokenProgramID", - "msg": "Token program ID is invalid" - }, - { - "code": 6015, - "name": "TokenProgramNotProvided", - "msg": "Token program not provided" - }, - { - "code": 6016, - "name": "SwapNotSupported", - "msg": "Swap not supported" - }, - { - "code": 6017, - "name": "ExactOutAmountNotMatched", - "msg": "Exact out amount doesn't match" - }, - { - "code": 6018, - "name": "SourceAndDestinationMintCannotBeTheSame", - "msg": "Source mint and destination mint cannot the same" - }, - { - "code": 6019, - "name": "InvalidMint", - "msg": "Invalid mint" - }, - { - "code": 6020, - "name": "InvalidProgramAuthority", - "msg": "Invalid program authority" - }, - { - "code": 6021, - "name": "InvalidOutputTokenAccount", - "msg": "Invalid output token account" - }, - { - "code": 6022, - "name": "InvalidFeeWallet", - "msg": "Invalid fee wallet" - }, - { - "code": 6023, - "name": "InvalidAuthority", - "msg": "Invalid authority" - }, - { - "code": 6024, - "name": "InsufficientFunds", - "msg": "Insufficient funds" - }, - { - "code": 6025, - "name": "InvalidTokenAccount", - "msg": "Invalid token account" - }, - { - "code": 6026, - "name": "BondingCurveAlreadyCompleted", - "msg": "Bonding curve already completed" - } - ], - "types": [ - { - "name": "FeeEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "account", - "type": "pubkey" - }, - { - "name": "mint", - "type": "pubkey" - }, - { - "name": "amount", - "type": "u64" - } - ] - } - }, - { - "name": "RemainingAccountsInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "slices", - "type": { - "vec": { - "defined": { - "name": "RemainingAccountsSlice" - } - } - } - } - ] - } - }, - { - "name": "RemainingAccountsSlice", - "type": { - "kind": "struct", - "fields": [ - { - "name": "accounts_type", - "type": "u8" - }, - { - "name": "length", - "type": "u8" - } - ] - } - }, - { - "name": "AccountsType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "TransferHookA" - }, - { - "name": "TransferHookB" - }, - { - "name": "TransferHookReward" - }, - { - "name": "TransferHookInput" - }, - { - "name": "TransferHookIntermediate" - }, - { - "name": "TransferHookOutput" - }, - { - "name": "SupplementalTickArrays" - }, - { - "name": "SupplementalTickArraysOne" - }, - { - "name": "SupplementalTickArraysTwo" - } - ] - } - }, - { - "name": "DefiTunaAccountsType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "TransferHookA" - }, - { - "name": "TransferHookB" - }, - { - "name": "TransferHookInput" - }, - { - "name": "TransferHookIntermediate" - }, - { - "name": "TransferHookOutput" - }, - { - "name": "SupplementalTickArrays" - }, - { - "name": "SupplementalTickArraysOne" - }, - { - "name": "SupplementalTickArraysTwo" - } - ] - } - }, - { - "name": "RoutePlanStep", - "type": { - "kind": "struct", - "fields": [ - { - "name": "swap", - "type": { - "defined": { - "name": "Swap" - } - } - }, - { - "name": "percent", - "type": "u8" - }, - { - "name": "input_index", - "type": "u8" - }, - { - "name": "output_index", - "type": "u8" - } - ] - } - }, - { - "name": "RoutePlanStepV2", - "type": { - "kind": "struct", - "fields": [ - { - "name": "swap", - "type": { - "defined": { - "name": "Swap" - } - } - }, - { - "name": "bps", - "type": "u16" - }, - { - "name": "input_index", - "type": "u8" - }, - { - "name": "output_index", - "type": "u8" - } - ] - } - }, - { - "name": "Side", - "type": { - "kind": "enum", - "variants": [ - { - "name": "Bid" - }, - { - "name": "Ask" - } - ] - } - }, - { - "name": "Swap", - "type": { - "kind": "enum", - "variants": [ - { - "name": "Saber" - }, - { - "name": "SaberAddDecimalsDeposit" - }, - { - "name": "SaberAddDecimalsWithdraw" - }, - { - "name": "TokenSwap" - }, - { - "name": "Sencha" - }, - { - "name": "Step" - }, - { - "name": "Cropper" - }, - { - "name": "Raydium" - }, - { - "name": "Crema", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - } - ] - }, - { - "name": "Lifinity" - }, - { - "name": "Mercurial" - }, - { - "name": "Cykura" - }, - { - "name": "Serum", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "MarinadeDeposit" - }, - { - "name": "MarinadeUnstake" - }, - { - "name": "Aldrin", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "AldrinV2", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "Whirlpool", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - } - ] - }, - { - "name": "Invariant", - "fields": [ - { - "name": "x_to_y", - "type": "bool" - } - ] - }, - { - "name": "Meteora" - }, - { - "name": "GooseFX" - }, - { - "name": "DeltaFi", - "fields": [ - { - "name": "stable", - "type": "bool" - } - ] - }, - { - "name": "Balansol" - }, - { - "name": "MarcoPolo", - "fields": [ - { - "name": "x_to_y", - "type": "bool" - } - ] - }, - { - "name": "Dradex", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "LifinityV2" - }, - { - "name": "RaydiumClmm" - }, - { - "name": "Openbook", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "Phoenix", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "Symmetry", - "fields": [ - { - "name": "from_token_id", - "type": "u64" - }, - { - "name": "to_token_id", - "type": "u64" - } - ] - }, - { - "name": "TokenSwapV2" - }, - { - "name": "HeliumTreasuryManagementRedeemV0" - }, - { - "name": "StakeDexStakeWrappedSol" - }, - { - "name": "StakeDexSwapViaStake", - "fields": [ - { - "name": "bridge_stake_seed", - "type": "u32" - } - ] - }, - { - "name": "GooseFXV2" - }, - { - "name": "Perps" - }, - { - "name": "PerpsAddLiquidity" - }, - { - "name": "PerpsRemoveLiquidity" - }, - { - "name": "MeteoraDlmm" - }, - { - "name": "OpenBookV2", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "RaydiumClmmV2" - }, - { - "name": "StakeDexPrefundWithdrawStakeAndDepositStake", - "fields": [ - { - "name": "bridge_stake_seed", - "type": "u32" - } - ] - }, - { - "name": "Clone", - "fields": [ - { - "name": "pool_index", - "type": "u8" - }, - { - "name": "quantity_is_input", - "type": "bool" - }, - { - "name": "quantity_is_collateral", - "type": "bool" - } - ] - }, - { - "name": "SanctumS", - "fields": [ - { - "name": "src_lst_value_calc_accs", - "type": "u8" - }, - { - "name": "dst_lst_value_calc_accs", - "type": "u8" - }, - { - "name": "src_lst_index", - "type": "u32" - }, - { - "name": "dst_lst_index", - "type": "u32" - } - ] - }, - { - "name": "SanctumSAddLiquidity", - "fields": [ - { - "name": "lst_value_calc_accs", - "type": "u8" - }, - { - "name": "lst_index", - "type": "u32" - } - ] - }, - { - "name": "SanctumSRemoveLiquidity", - "fields": [ - { - "name": "lst_value_calc_accs", - "type": "u8" - }, - { - "name": "lst_index", - "type": "u32" - } - ] - }, - { - "name": "RaydiumCP" - }, - { - "name": "WhirlpoolSwapV2", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - }, - { - "name": "remaining_accounts_info", - "type": { - "option": { - "defined": { - "name": "RemainingAccountsInfo" - } - } - } - } - ] - }, - { - "name": "OneIntro" - }, - { - "name": "PumpWrappedBuy" - }, - { - "name": "PumpWrappedSell" - }, - { - "name": "PerpsV2" - }, - { - "name": "PerpsV2AddLiquidity" - }, - { - "name": "PerpsV2RemoveLiquidity" - }, - { - "name": "MoonshotWrappedBuy" - }, - { - "name": "MoonshotWrappedSell" - }, - { - "name": "StabbleStableSwap" - }, - { - "name": "StabbleWeightedSwap" - }, - { - "name": "Obric", - "fields": [ - { - "name": "x_to_y", - "type": "bool" - } - ] - }, - { - "name": "FoxBuyFromEstimatedCost" - }, - { - "name": "FoxClaimPartial", - "fields": [ - { - "name": "is_y", - "type": "bool" - } - ] - }, - { - "name": "SolFi", - "fields": [ - { - "name": "is_quote_to_base", - "type": "bool" - } - ] - }, - { - "name": "SolayerDelegateNoInit" - }, - { - "name": "SolayerUndelegateNoInit" - }, - { - "name": "TokenMill", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "DaosFunBuy" - }, - { - "name": "DaosFunSell" - }, - { - "name": "ZeroFi" - }, - { - "name": "StakeDexWithdrawWrappedSol" - }, - { - "name": "VirtualsBuy" - }, - { - "name": "VirtualsSell" - }, - { - "name": "Perena", - "fields": [ - { - "name": "in_index", - "type": "u8" - }, - { - "name": "out_index", - "type": "u8" - } - ] - }, - { - "name": "PumpSwapBuy" - }, - { - "name": "PumpSwapSell" - }, - { - "name": "Gamma" - }, - { - "name": "MeteoraDlmmSwapV2", - "fields": [ - { - "name": "remaining_accounts_info", - "type": { - "defined": { - "name": "RemainingAccountsInfo" - } - } - } - ] - }, - { - "name": "Woofi" - }, - { - "name": "MeteoraDammV2" - }, - { - "name": "MeteoraDynamicBondingCurveSwap" - }, - { - "name": "StabbleStableSwapV2" - }, - { - "name": "StabbleWeightedSwapV2" - }, - { - "name": "RaydiumLaunchlabBuy", - "fields": [ - { - "name": "share_fee_rate", - "type": "u64" - } - ] - }, - { - "name": "RaydiumLaunchlabSell", - "fields": [ - { - "name": "share_fee_rate", - "type": "u64" - } - ] - }, - { - "name": "BoopdotfunWrappedBuy" - }, - { - "name": "BoopdotfunWrappedSell" - }, - { - "name": "Plasma", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "GoonFi", - "fields": [ - { - "name": "is_bid", - "type": "bool" - }, - { - "name": "blacklist_bump", - "type": "u8" - } - ] - }, - { - "name": "HumidiFi", - "fields": [ - { - "name": "swap_id", - "type": "u64" - }, - { - "name": "is_base_to_quote", - "type": "bool" - } - ] - }, - { - "name": "MeteoraDynamicBondingCurveSwapWithRemainingAccounts" - }, - { - "name": "TesseraV", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "PumpWrappedBuyV2" - }, - { - "name": "PumpWrappedSellV2" - }, - { - "name": "PumpSwapBuyV2" - }, - { - "name": "PumpSwapSellV2" - }, - { - "name": "Heaven", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - } - ] - }, - { - "name": "SolFiV2", - "fields": [ - { - "name": "is_quote_to_base", - "type": "bool" - } - ] - }, - { - "name": "Aquifer" - }, - { - "name": "PumpWrappedBuyV3" - }, - { - "name": "PumpWrappedSellV3" - }, - { - "name": "PumpSwapBuyV3" - }, - { - "name": "PumpSwapSellV3" - }, - { - "name": "JupiterLendDeposit" - }, - { - "name": "JupiterLendRedeem" - }, - { - "name": "DefiTuna", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - }, - { - "name": "remaining_accounts_info", - "type": { - "option": { - "defined": { - "name": "RemainingAccountsInfo" - } - } - } - } - ] - }, - { - "name": "AlphaQ", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - } - ] - }, - { - "name": "RaydiumV2" - }, - { - "name": "SarosDlmm", - "fields": [ - { - "name": "swap_for_y", - "type": "bool" - } - ] - }, - { - "name": "Futarchy", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "MeteoraDammV2WithRemainingAccounts" - }, - { - "name": "Obsidian" - }, - { - "name": "WhaleStreet", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "DynamicV1", - "fields": [ - { - "name": "candidate_swaps", - "type": { - "vec": { - "defined": { - "name": "CandidateSwap" - } - } - } - } - ] - }, - { - "name": "PumpWrappedBuyV4" - }, - { - "name": "PumpWrappedSellV4" - }, - { - "name": "CarrotIssue" - }, - { - "name": "CarrotRedeem" - }, - { - "name": "Manifest", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - }, - { - "name": "BisonFi", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - } - ] - } - ] - } - }, - { - "name": "CandidateSwap", - "type": { - "kind": "enum", - "variants": [ - { - "name": "HumidiFi", - "fields": [ - { - "name": "swap_id", - "type": "u64" - }, - { - "name": "is_base_to_quote", - "type": "bool" - } - ] - }, - { - "name": "TesseraV", - "fields": [ - { - "name": "side", - "type": { - "defined": { - "name": "Side" - } - } - } - ] - } - ] - } - }, - { - "name": "SwapEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amm", - "type": "pubkey" - }, - { - "name": "input_mint", - "type": "pubkey" - }, - { - "name": "input_amount", - "type": "u64" - }, - { - "name": "output_mint", - "type": "pubkey" - }, - { - "name": "output_amount", - "type": "u64" - } - ] - } - }, - { - "name": "SwapEventV2", - "type": { - "kind": "struct", - "fields": [ - { - "name": "input_mint", - "type": "pubkey" - }, - { - "name": "input_amount", - "type": "u64" - }, - { - "name": "output_mint", - "type": "pubkey" - }, - { - "name": "output_amount", - "type": "u64" - } - ] - } - }, - { - "name": "SwapsEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "swap_events", - "type": { - "vec": { - "defined": { - "name": "SwapEventV2" - } - } - } - } - ] - } - }, - { - "name": "TokenLedger", - "type": { - "kind": "struct", - "fields": [ - { - "name": "token_account", - "type": "pubkey" - }, - { - "name": "amount", - "type": "u64" - } - ] - } - }, - { - "name": "BestSwapOutAmountViolation", - "type": { - "kind": "struct", - "fields": [ - { - "name": "expected_out_amount", - "type": "u64" - }, - { - "name": "out_amount", - "type": "u64" - } - ] - } - }, - { - "name": "CandidateSwapResult", - "type": { - "kind": "enum", - "variants": [ - { - "name": "OutAmount", - "fields": ["u64"] - }, - { - "name": "ProgramError", - "fields": ["u64"] - } - ] - } - }, - { - "name": "CandidateSwapResults", - "type": { - "kind": "struct", - "fields": [ - { - "name": "results", - "type": { - "vec": { - "defined": { - "name": "CandidateSwapResult" - } - } - } - } - ] - } - } - ] -} diff --git a/idls/zap.json b/idls/zap.json deleted file mode 100644 index 880e7bf0..00000000 --- a/idls/zap.json +++ /dev/null @@ -1,390 +0,0 @@ -{ - "address": "zapvX9M3uf5pvy4wRPAbQgdQsM1xmuiFnkfHKPvwMiz", - "metadata": { - "name": "zap", - "version": "0.2.0", - "spec": "0.1.0", - "description": "Created with Anchor" - }, - "instructions": [ - { - "name": "close_ledger_account", - "discriminator": [189, 122, 172, 13, 122, 54, 54, 51], - "accounts": [ - { - "name": "ledger", - "writable": true - }, - { - "name": "owner", - "signer": true, - "relations": ["ledger"] - }, - { - "name": "rent_receiver", - "writable": true, - "signer": true - } - ], - "args": [] - }, - { - "name": "initialize_ledger_account", - "discriminator": [120, 69, 30, 74, 76, 242, 153, 162], - "accounts": [ - { - "name": "ledger", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [117, 115, 101, 114, 95, 108, 101, 100, 103, 101, 114] - }, - { - "kind": "account", - "path": "owner" - } - ] - } - }, - { - "name": "owner", - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "system_program", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "set_ledger_balance", - "discriminator": [131, 49, 240, 17, 228, 248, 156, 54], - "accounts": [ - { - "name": "ledger", - "writable": true - }, - { - "name": "owner", - "signer": true, - "relations": ["ledger"] - } - ], - "args": [ - { - "name": "amount", - "type": "u64" - }, - { - "name": "is_token_a", - "type": "bool" - } - ] - }, - { - "name": "update_ledger_balance_after_swap", - "discriminator": [59, 206, 173, 232, 94, 57, 174, 202], - "accounts": [ - { - "name": "ledger", - "writable": true - }, - { - "name": "token_account" - }, - { - "name": "owner", - "signer": true, - "relations": ["ledger"] - } - ], - "args": [ - { - "name": "pre_source_token_balance", - "type": "u64" - }, - { - "name": "max_transfer_amount", - "type": "u64" - }, - { - "name": "is_token_a", - "type": "bool" - } - ] - }, - - { - "name": "zap_out", - "discriminator": [155, 108, 185, 112, 104, 210, 161, 64], - "accounts": [ - { - "name": "user_token_in_account", - "writable": true - }, - { - "name": "amm_program" - } - ], - "args": [ - { - "name": "params", - "type": { - "defined": { - "name": "ZapOutParameters" - } - } - } - ] - } - ], - "accounts": [ - { - "name": "UserLedger", - "discriminator": [185, 84, 101, 128, 8, 6, 160, 83] - } - ], - "errors": [ - { - "code": 6000, - "name": "MathOverflow", - "msg": "Math operation overflow" - }, - { - "code": 6001, - "name": "InvalidOffset", - "msg": "Invalid offset" - }, - { - "code": 6002, - "name": "InvalidZapOutParameters", - "msg": "Invalid zapout parameters" - }, - { - "code": 6003, - "name": "TypeCastFailed", - "msg": "Type cast error" - }, - { - "code": 6004, - "name": "AmmIsNotSupported", - "msg": "Amm program is not supported" - }, - { - "code": 6005, - "name": "InvalidPosition", - "msg": "Position is not empty" - }, - { - "code": 6006, - "name": "ExceededSlippage", - "msg": "Exceeded slippage tolerance" - }, - { - "code": 6007, - "name": "InvalidDlmmZapInParameters", - "msg": "Invalid dlmm zap in parameters" - }, - { - "code": 6008, - "name": "UnsupportedFeeMode", - "msg": "Unsupported fee mode" - } - ], - "types": [ - { - "name": "UserLedger", - "serialization": "bytemuck", - "repr": { - "kind": "c" - }, - "type": { - "kind": "struct", - "fields": [ - { - "name": "owner", - "type": "pubkey" - }, - { - "name": "amount_a", - "type": "u64" - }, - { - "name": "amount_b", - "type": "u64" - } - ] - } - }, - { - "name": "ZapOutParameters", - "type": { - "kind": "struct", - "fields": [ - { - "name": "percentage", - "type": "u8" - }, - { - "name": "offset_amount_in", - "type": "u16" - }, - { - "name": "pre_user_token_balance", - "type": "u64" - }, - { - "name": "max_swap_amount", - "type": "u64" - }, - { - "name": "payload_data", - "type": "bytes" - } - ] - } - }, - { - "name": "StrategyType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "SpotOneSide" - }, - { - "name": "CurveOneSide" - }, - { - "name": "BidAskOneSide" - }, - { - "name": "SpotBalanced" - }, - { - "name": "CurveBalanced" - }, - { - "name": "BidAskBalanced" - }, - { - "name": "SpotImBalanced" - }, - { - "name": "CurveImBalanced" - }, - { - "name": "BidAskImBalanced" - } - ] - } - }, - { - "name": "RemainingAccountsInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "slices", - "type": { - "vec": { - "defined": { - "name": "RemainingAccountsSlice" - } - } - } - } - ] - } - }, - { - "name": "RemainingAccountsSlice", - "type": { - "kind": "struct", - "fields": [ - { - "name": "accounts_type", - "type": { - "defined": { - "name": "AccountsType" - } - } - }, - { - "name": "length", - "type": "u8" - } - ] - } - }, - { - "name": "AccountsType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "TransferHookX" - }, - { - "name": "TransferHookY" - }, - { - "name": "TransferHookReward" - }, - { - "name": "TransferHookMultiReward", - "fields": ["u8"] - } - ] - } - } - ], - "constants": [ - { - "name": "DAMM_V2_SWAP_DISC", - "type": { - "array": ["u8", 8] - }, - "value": "[248, 198, 158, 145, 225, 117, 135, 200]" - }, - { - "name": "DLMM_SWAP2_DISC", - "type": { - "array": ["u8", 8] - }, - "value": "[65, 75, 63, 76, 235, 91, 91, 136]" - }, - { - "name": "JUP_V6_ROUTE_DISC", - "type": { - "array": ["u8", 8] - }, - "value": "[229, 23, 203, 151, 122, 227, 173, 42]" - }, - { - "name": "JUP_V6_SHARED_ACCOUNT_ROUTE_DISC", - "type": { - "array": ["u8", 8] - }, - "value": "[193, 32, 155, 51, 65, 214, 156, 129]" - }, - { - "name": "MAX_BASIS_POINT", - "type": "u16", - "value": "10000" - }, - { - "name": "USER_LEDGER_PREFIX", - "type": "bytes", - "value": "[117, 115, 101, 114, 95, 108, 101, 100, 103, 101, 114]" - } - ] -} diff --git a/libs/jupiter/Cargo.toml b/libs/jupiter/Cargo.toml deleted file mode 100644 index 71f2611c..00000000 --- a/libs/jupiter/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "jupiter" -version = "0.1.0" -description = "Created with Anchor" -edition = "2021" - -[dependencies] -bytemuck = { workspace = true, features = ["derive", "min_const_generics"] } -anchor-lang = { workspace = true } diff --git a/libs/jupiter/src/lib.rs b/libs/jupiter/src/lib.rs deleted file mode 100644 index 5100bfe0..00000000 --- a/libs/jupiter/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -use anchor_lang::prelude::*; - -declare_program!(jupiter); - -pub use jupiter::*; diff --git a/libs/zap/Cargo.toml b/libs/zap/Cargo.toml deleted file mode 100644 index 8769d66c..00000000 --- a/libs/zap/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "zap" -version = "0.1.0" -description = "Created with Anchor" -edition = "2021" - -[dependencies] -bytemuck = { workspace = true, features = ["derive", "min_const_generics"] } -anchor-lang = { workspace = true } \ No newline at end of file diff --git a/libs/zap/src/lib.rs b/libs/zap/src/lib.rs deleted file mode 100644 index 6b09fd8e..00000000 --- a/libs/zap/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -use anchor_lang::prelude::*; - -declare_program!(zap); - -pub use zap::*; diff --git a/programs/cp-amm/Cargo.toml b/programs/cp-amm/Cargo.toml index 0e93025f..d11dc2f4 100644 --- a/programs/cp-amm/Cargo.toml +++ b/programs/cp-amm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cp-amm" -version = "0.1.8" +version = "0.2.0" description = "Created with Anchor" edition = "2021" @@ -32,8 +32,7 @@ const-crypto = "0.3.0" pinocchio = { git = "https://github.com/anza-xyz/pinocchio.git", rev = "17b0e862c01a868ea07ef81a2f8a9b4a504bdfed" } pinocchio-token = { git = "https://github.com/anza-xyz/pinocchio.git", rev = "17b0e862c01a868ea07ef81a2f8a9b4a504bdfed" } pinocchio-token-2022 = { git = "https://github.com/anza-xyz/pinocchio.git", rev = "17b0e862c01a868ea07ef81a2f8a9b4a504bdfed" } -zap = { path = "../../libs/zap" } -jupiter = { path = "../../libs/jupiter" } +protocol-zap = { git = "https://github.com/MeteoraAg/zap-program", rev = "064c58b317b9a85f212c0de72caea286fc72fdb4" } [dev-dependencies] -proptest = "1.2.0" +proptest = { workspace = true} diff --git a/programs/cp-amm/src/base_fee/fee_market_cap_scheduler.rs b/programs/cp-amm/src/base_fee/fee_market_cap_scheduler.rs index a38acf63..9c1ed184 100644 --- a/programs/cp-amm/src/base_fee/fee_market_cap_scheduler.rs +++ b/programs/cp-amm/src/base_fee/fee_market_cap_scheduler.rs @@ -28,7 +28,6 @@ pub struct BorshFeeMarketCapScheduler { pub reduction_factor: u64, // Must at offset 26 (without memory alignment padding) pub base_fee_mode: u8, - pub padding: [u8; 3], } static_assertions::const_assert_eq!( diff --git a/programs/cp-amm/src/base_fee/fee_rate_limiter.rs b/programs/cp-amm/src/base_fee/fee_rate_limiter.rs index 90d3bd57..dde0cbac 100644 --- a/programs/cp-amm/src/base_fee/fee_rate_limiter.rs +++ b/programs/cp-amm/src/base_fee/fee_rate_limiter.rs @@ -46,7 +46,6 @@ pub struct BorshFeeRateLimiter { pub reference_amount: u64, // Must at offset 26 (without memory alignment padding) pub base_fee_mode: u8, - pub padding: [u8; 3], } static_assertions::const_assert_eq!( BaseFeeParameters::INIT_SPACE, diff --git a/programs/cp-amm/src/base_fee/fee_time_scheduler.rs b/programs/cp-amm/src/base_fee/fee_time_scheduler.rs index 6b33a5c7..77ba8e5f 100644 --- a/programs/cp-amm/src/base_fee/fee_time_scheduler.rs +++ b/programs/cp-amm/src/base_fee/fee_time_scheduler.rs @@ -26,7 +26,6 @@ pub struct BorshFeeTimeScheduler { pub reduction_factor: u64, // Must at offset 26 (without memory alignment padding) pub base_fee_mode: u8, - pub padding: [u8; 3], } static_assertions::const_assert_eq!( diff --git a/programs/cp-amm/src/constants.rs b/programs/cp-amm/src/constants.rs index 2df92890..9a954fc2 100644 --- a/programs/cp-amm/src/constants.rs +++ b/programs/cp-amm/src/constants.rs @@ -1,7 +1,5 @@ use anchor_lang::constant; use anchor_lang::prelude::*; -use anchor_spl::associated_token::ID as ASSOCIATED_TOKEN_PROGRAM_ID; -use anchor_spl::token::ID as TOKEN_PROGRAM_ID; pub const MIN_SQRT_PRICE: u128 = 4295048016; @@ -56,7 +54,7 @@ pub const SPLIT_POSITION_DENOMINATOR: u32 = 1_000_000_000; // 1b pub const MAX_RATE_LIMITER_DURATION_IN_SECONDS: u32 = 60 * 60 * 12; // 12 hours pub const MAX_RATE_LIMITER_DURATION_IN_SLOTS: u32 = 108000; // 12 hours -pub const MAX_OPERATION: u8 = 11; +pub const MAX_OPERATION: u8 = 12; static_assertions::const_assert_eq!( MAX_RATE_LIMITER_DURATION_IN_SECONDS * 1000 / 400, @@ -68,63 +66,6 @@ const OKX_SMART_WALLET: Pubkey = pub const RATE_LIMITER_STACK_WHITELIST_PROGRAMS: [[u8; 32]; 1] = [OKX_SMART_WALLET.to_bytes()]; -pub mod zap { - use super::*; - use ::zap::zap; - use const_crypto::ed25519; - - pub const JUP_V6: Pubkey = pubkey!("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"); - - pub const JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET: usize = 1 + 2 + 8 + 8; // Due to jupiter parameters have dynamic length type (vec), we have to do parameters_data.length - JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET - pub const JUP_V6_SHARED_ACCOUNT_ROUTE_SOURCE_ACCOUNT_INDEX: usize = 3; - pub const JUP_V6_SHARED_ACCOUNT_ROUTE_DESTINATION_ACCOUNT_INDEX: usize = 6; - - pub const JUP_V6_ROUTE_AMOUNT_IN_REVERSE_OFFSET: usize = 1 + 2 + 8 + 8; - pub const JUP_V6_ROUTE_SOURCE_ACCOUNT_INDEX: usize = 2; - pub const JUP_V6_ROUTE_DESTINATION_ACCOUNT_INDEX: usize = 4; - - pub const DAMM_V2_SWAP_AMOUNT_IN_OFFSET: u16 = 8; - pub const DAMM_V2_SWAP_SOURCE_ACCOUNT_INDEX: usize = 2; - pub const DAMM_V2_SWAP_DESTINATION_ACCOUNT_INDEX: usize = 3; - - pub const USDC_ADDRESS: Pubkey = - Pubkey::from_str_const("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); - - pub const SOL_ADDRESS: Pubkey = - Pubkey::from_str_const("So11111111111111111111111111111111111111112"); - - pub const MINTS_DISALLOWED_TO_ZAP_OUT: [Pubkey; 2] = [USDC_ADDRESS, SOL_ADDRESS]; - - pub const TREASURY_USDC_ADDRESS: Pubkey = Pubkey::new_from_array( - ed25519::derive_program_address( - &[ - &treasury::ID.to_bytes(), - &TOKEN_PROGRAM_ID.to_bytes(), - &USDC_ADDRESS.to_bytes(), - ], - &ASSOCIATED_TOKEN_PROGRAM_ID.to_bytes(), - ) - .0, - ); - - pub const TREASURY_SOL_ADDRESS: Pubkey = Pubkey::new_from_array( - ed25519::derive_program_address( - &[ - &treasury::ID.to_bytes(), - &TOKEN_PROGRAM_ID.to_bytes(), - &SOL_ADDRESS.to_bytes(), - ], - &ASSOCIATED_TOKEN_PROGRAM_ID.to_bytes(), - ) - .0, - ); - - pub const DAMM_V2_SWAP_DISC_REF: &[u8] = &zap::constants::DAMM_V2_SWAP_DISC; - pub const JUP_V6_ROUTE_DISC_REF: &[u8] = &zap::constants::JUP_V6_ROUTE_DISC; - pub const JUP_V6_SHARED_ACCOUNT_ROUTE_DISC_REF: &[u8] = - &zap::constants::JUP_V6_SHARED_ACCOUNT_ROUTE_DISC; -} - pub mod activation { #[cfg(not(feature = "local"))] pub const SLOT_BUFFER: u64 = 9000; // 1 slot = 400 mls => 1 hour @@ -183,24 +124,24 @@ pub mod fee { /// Max basis point. 100% in pct #[constant] - pub const MAX_BASIS_POINT: u64 = 10_000; + pub const MAX_BASIS_POINT: u16 = 10_000; pub const MIN_FEE_BPS: u64 = 1; // 0.01% #[constant] pub const MIN_FEE_NUMERATOR: u64 = 100_000; static_assertions::const_assert_eq!( - MAX_FEE_BPS_V0 * FEE_DENOMINATOR / MAX_BASIS_POINT, + MAX_FEE_BPS_V0 * FEE_DENOMINATOR / MAX_BASIS_POINT as u64, MAX_FEE_NUMERATOR_V0 ); static_assertions::const_assert_eq!( - MAX_FEE_BPS_V1 * FEE_DENOMINATOR / MAX_BASIS_POINT, + MAX_FEE_BPS_V1 * FEE_DENOMINATOR / MAX_BASIS_POINT as u64, MAX_FEE_NUMERATOR_V1 ); static_assertions::const_assert_eq!( - MIN_FEE_BPS * FEE_DENOMINATOR / MAX_BASIS_POINT, + MIN_FEE_BPS * FEE_DENOMINATOR / MAX_BASIS_POINT as u64, MIN_FEE_NUMERATOR ); @@ -208,25 +149,22 @@ pub mod fee { pub const HOST_FEE_PERCENT: u8 = 20; // 20% of protocol fee - pub const PARTNER_FEE_PERCENT: u8 = 0; // percentage of partner fee - static_assertions::const_assert!(PROTOCOL_FEE_PERCENT <= 50); static_assertions::const_assert!(HOST_FEE_PERCENT <= 50); - static_assertions::const_assert!(PARTNER_FEE_PERCENT <= 50); #[constant] pub const CURRENT_POOL_VERSION: u8 = 1; - pub fn get_max_fee_numerator(pool_version: u8) -> Result { - match pool_version { + pub fn get_max_fee_numerator(fee_version: u8) -> Result { + match fee_version { 0 => Ok(MAX_FEE_NUMERATOR_V0), 1 => Ok(MAX_FEE_NUMERATOR_V1), _ => Err(PoolError::InvalidPoolVersion.into()), } } - pub fn get_max_fee_bps(pool_version: u8) -> Result { - match pool_version { + pub fn get_max_fee_bps(fee_version: u8) -> Result { + match fee_version { 0 => Ok(MAX_FEE_BPS_V0), 1 => Ok(MAX_FEE_BPS_V1), _ => Err(PoolError::InvalidPoolVersion.into()), diff --git a/programs/cp-amm/src/curve.rs b/programs/cp-amm/src/curve.rs deleted file mode 100644 index eec95334..00000000 --- a/programs/cp-amm/src/curve.rs +++ /dev/null @@ -1,251 +0,0 @@ -use anchor_lang::prelude::*; -use ruint::aliases::U256; - -use crate::{ - safe_math::SafeMath, - u128x128_math::{mul_div_u256, Rounding}, - PoolError, -}; - -pub const RESOLUTION: u8 = 64; - -pub fn get_initialize_amounts( - sqrt_min_price: u128, - sqrt_max_price: u128, - sqrt_price: u128, - liquidity: u128, -) -> Result<(u64, u64)> { - // BASE TOKEN - let amount_a = - get_delta_amount_a_unsigned(sqrt_price, sqrt_max_price, liquidity, Rounding::Up)?; - // QUOTE TOKEN - let amount_b = - get_delta_amount_b_unsigned(sqrt_min_price, sqrt_price, liquidity, Rounding::Up)?; - Ok((amount_a, amount_b)) -} - -/// Gets the delta amount_a for given liquidity and price range -/// -/// # Formula -/// -/// * `Δa = L * (1 / √P_lower - 1 / √P_upper)` -/// * i.e. `L * (√P_upper - √P_lower) / (√P_upper * √P_lower)` -pub fn get_delta_amount_a_unsigned( - lower_sqrt_price: u128, - upper_sqrt_price: u128, - liquidity: u128, - round: Rounding, -) -> Result { - let result = get_delta_amount_a_unsigned_unchecked( - lower_sqrt_price, - upper_sqrt_price, - liquidity, - round, - )?; - require!(result <= U256::from(u64::MAX), PoolError::MathOverflow); - return Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?); -} - -/// * i.e. `L * (√P_upper - √P_lower) / (√P_upper * √P_lower)` -pub fn get_delta_amount_a_unsigned_unchecked( - lower_sqrt_price: u128, - upper_sqrt_price: u128, - liquidity: u128, - round: Rounding, -) -> Result { - let numerator_1 = U256::from(liquidity); - let numerator_2 = U256::from(upper_sqrt_price - lower_sqrt_price); - - let denominator = U256::from(lower_sqrt_price).safe_mul(U256::from(upper_sqrt_price))?; - - assert!(denominator > U256::ZERO); - let result = mul_div_u256(numerator_1, numerator_2, denominator, round) - .ok_or_else(|| PoolError::MathOverflow)?; - return Ok(result); -} - -/// Gets the delta amount_b for given liquidity and price range -/// Δb = L * (√P_upper - √P_lower) -pub fn get_delta_amount_b_unsigned( - lower_sqrt_price: u128, - upper_sqrt_price: u128, - liquidity: u128, - round: Rounding, -) -> Result { - let result = get_delta_amount_b_unsigned_unchecked( - lower_sqrt_price, - upper_sqrt_price, - liquidity, - round, - )?; - require!(result <= U256::from(u64::MAX), PoolError::MathOverflow); - return Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?); -} - -// Δb = L * (√P_upper - √P_lower) -pub fn get_delta_amount_b_unsigned_unchecked( - lower_sqrt_price: u128, - upper_sqrt_price: u128, - liquidity: u128, - round: Rounding, -) -> Result { - let liquidity = U256::from(liquidity); - let delta_sqrt_price = U256::from(upper_sqrt_price.safe_sub(lower_sqrt_price)?); - let prod = liquidity.safe_mul(delta_sqrt_price)?; - - match round { - Rounding::Up => { - let denominator = U256::from(1).safe_shl((RESOLUTION as usize) * 2)?; - let result = prod.div_ceil(denominator); - Ok(result) - } - Rounding::Down => { - let (result, _) = prod.overflowing_shr((RESOLUTION as usize) * 2); - Ok(result) - } - } -} - -/// Gets the next sqrt price given an input amount of token_a or token_b -/// Throws if price or liquidity are 0, or if the next price is out of bounds -pub fn get_next_sqrt_price_from_input( - sqrt_price: u128, - liquidity: u128, - amount_in: u64, - a_for_b: bool, -) -> Result { - assert!(sqrt_price > 0); - assert!(liquidity > 0); - - // round to make sure that we don't pass the target price - if a_for_b { - get_next_sqrt_price_from_amount_in_a_rounding_up(sqrt_price, liquidity, amount_in) - } else { - get_next_sqrt_price_from_amount_in_b_rounding_down(sqrt_price, liquidity, amount_in) - } -} - -/// Gets the next sqrt price given an output amount of token_a or token_b -/// Throws if price or liquidity are 0, or if the next price is out of bounds -pub fn get_next_sqrt_price_from_output( - sqrt_price: u128, - liquidity: u128, - amount_out: u64, - a_for_b: bool, -) -> Result { - assert!(sqrt_price > 0); - assert!(liquidity > 0); - - // round to make sure that we don't pass the target price - if a_for_b { - get_next_sqrt_price_from_amount_out_b_rounding_down(sqrt_price, liquidity, amount_out) - } else { - get_next_sqrt_price_from_amount_out_a_rounding_up(sqrt_price, liquidity, amount_out) - } -} - -/// Gets the next sqrt price √P' given a delta of token_a -/// -/// Always round up because -/// 1. In the exact output case, token_a supply decreases leading to price increase. -/// Move price up so that exact output is met. -/// 2. In the exact input case, token_a supply increases leading to price decrease. -/// Do not round down to minimize price impact. We only need to meet input -/// change and not guarantee exact output. -/// -/// Use function for exact input or exact output swaps for token_a -/// -/// # Formula -/// -/// * `√P' = √P * L / (L + Δa * √P)` -/// * If Δa * √P overflows, use alternate form `√P' = L / (L/√P + Δa)` -/// -/// # Proof -/// -/// For constant L, -/// -/// L = a * √P -/// a' = a + Δa -/// a' * √P' = a * √P -/// (a + Δa) * √P' = a * √P -/// √P' = (a * √P) / (a + Δa) -/// a = L/√P -/// √P' = √P * L / (L + Δa * √P) -/// -pub fn get_next_sqrt_price_from_amount_in_a_rounding_up( - sqrt_price: u128, - liquidity: u128, - amount: u64, -) -> Result { - if amount == 0 { - return Ok(sqrt_price); - } - let sqrt_price = U256::from(sqrt_price); - let liquidity = U256::from(liquidity); - - let product = U256::from(amount).safe_mul(sqrt_price)?; - let denominator = liquidity.safe_add(U256::from(product))?; - let result = mul_div_u256(liquidity, sqrt_price, denominator, Rounding::Up) - .ok_or_else(|| PoolError::MathOverflow)?; - return Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?); -} - -/// √P' = √P * L / (L - Δa * √P) -pub fn get_next_sqrt_price_from_amount_out_a_rounding_up( - sqrt_price: u128, - liquidity: u128, - amount: u64, -) -> Result { - if amount == 0 { - return Ok(sqrt_price); - } - let sqrt_price = U256::from(sqrt_price); - let liquidity = U256::from(liquidity); - - let product = U256::from(amount).safe_mul(sqrt_price)?; - let denominator = liquidity.safe_sub(U256::from(product))?; - let result = mul_div_u256(liquidity, sqrt_price, denominator, Rounding::Up) - .ok_or_else(|| PoolError::MathOverflow)?; - return Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?); -} - -/// Gets the next sqrt price given a delta of token_b -/// -/// Always round down because -/// 1. In the exact output case, token_b supply decreases leading to price decrease. -/// Move price down by rounding down so that exact output of token_a is met. -/// 2. In the exact input case, token_b supply increases leading to price increase. -/// Do not round down to minimize price impact. We only need to meet input -/// change and not guarantee exact output for token_a. -/// -/// -/// # Formula -/// -/// * `√P' = √P + Δb / L` -/// -pub fn get_next_sqrt_price_from_amount_in_b_rounding_down( - sqrt_price: u128, - liquidity: u128, - amount: u64, -) -> Result { - let quotient = U256::from(amount) - .safe_shl((RESOLUTION * 2) as usize)? - .safe_div(U256::from(liquidity))?; - - let result = U256::from(sqrt_price).safe_add(quotient)?; - Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) -} - -/// `√P' = √P - Δb / L` -pub fn get_next_sqrt_price_from_amount_out_b_rounding_down( - sqrt_price: u128, - liquidity: u128, - amount: u64, -) -> Result { - let quotient = U256::from(amount) - .safe_shl((RESOLUTION * 2) as usize)? - .div_ceil(U256::from(liquidity)); - - let result = U256::from(sqrt_price).safe_sub(quotient)?; - Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) -} diff --git a/programs/cp-amm/src/error.rs b/programs/cp-amm/src/error.rs index b6441faa..0afb83cc 100644 --- a/programs/cp-amm/src/error.rs +++ b/programs/cp-amm/src/error.rs @@ -1,5 +1,6 @@ //! Error module includes error messages and codes of the program use anchor_lang::prelude::*; +use protocol_zap::error::ProtozolZapError; /// Error messages and codes of the program #[error_code] @@ -205,4 +206,24 @@ pub enum PoolError { #[msg("Invalid zap accounts")] InvalidZapAccounts, + + #[msg("Invalid compounding fee bps")] + InvalidCompoundingFeeBps, +} + +impl From for PoolError { + fn from(e: ProtozolZapError) -> Self { + match e { + ProtozolZapError::MathOverflow => PoolError::MathOverflow, + ProtozolZapError::InvalidZapOutParameters => PoolError::InvalidZapOutParameters, + ProtozolZapError::TypeCastFailed => PoolError::TypeCastFailed, + ProtozolZapError::MissingZapOutInstruction => PoolError::MissingZapOutInstruction, + ProtozolZapError::InvalidWithdrawProtocolFeeZapAccounts => { + PoolError::InvalidWithdrawProtocolFeeZapAccounts + } + ProtozolZapError::MintRestrictedFromZap => PoolError::MintRestrictedFromZap, + ProtozolZapError::CpiDisabled => PoolError::CpiDisabled, + ProtozolZapError::InvalidZapAccounts => PoolError::InvalidZapAccounts, + } + } } diff --git a/programs/cp-amm/src/event.rs b/programs/cp-amm/src/event.rs index 404ef078..ad93997b 100644 --- a/programs/cp-amm/src/event.rs +++ b/programs/cp-amm/src/event.rs @@ -104,7 +104,6 @@ pub struct EvtSwap2 { pub collect_fee_mode: u8, pub has_referral: bool, pub params: SwapParameters2, - // excluded_transfer_fee_amount_in is swap_result.included_fee_amount_in pub swap_result: SwapResult2, pub included_transfer_fee_amount_in: u64, pub included_transfer_fee_amount_out: u64, @@ -141,13 +140,6 @@ pub struct EvtClaimProtocolFee { pub token_b_amount: u64, } -#[event] -pub struct EvtClaimPartnerFee { - pub pool: Pubkey, - pub token_a_amount: u64, - pub token_b_amount: u64, -} - #[event] pub struct EvtSetPoolStatus { pub pool: Pubkey, diff --git a/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_customizable_pool.rs b/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_customizable_pool.rs index 37f38b3a..485b4d67 100644 --- a/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_customizable_pool.rs +++ b/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_customizable_pool.rs @@ -15,15 +15,15 @@ use crate::{ }, MAX_SQRT_PRICE, MIN_SQRT_PRICE, }, - create_position_nft, - curve::get_initialize_amounts, + create_position_nft, get_initial_pool_information, params::{activation::ActivationParams, fee_parameters::PoolFeeParameters}, + safe_math::SafeCast, state::{CollectFeeMode, Pool, PoolType, Position}, token::{ calculate_transfer_fee_included_amount, get_token_program_flags, is_supported_mint, is_token_badge_initialized, transfer_from_user, }, - EvtCreatePosition, EvtInitializePool, PoolError, + EvtCreatePosition, EvtInitializePool, InitialPoolInformation, PoolError, }; use super::{max_key, min_key}; @@ -50,30 +50,57 @@ pub struct InitializeCustomizablePoolParameters { pub activation_point: Option, } -impl InitializeCustomizablePoolParameters { - pub fn validate(&self) -> Result<()> { - require!( - self.sqrt_min_price >= MIN_SQRT_PRICE && self.sqrt_max_price <= MAX_SQRT_PRICE, - PoolError::InvalidPriceRange - ); +pub fn validate_initial_sqrt_price( + collect_fee_mode: CollectFeeMode, + sqrt_price: u128, + sqrt_min_price: u128, + sqrt_max_price: u128, +) -> Result<()> { + if collect_fee_mode == CollectFeeMode::Compounding { + // we still have a boundary for initial sqrt price require!( - self.sqrt_price >= self.sqrt_min_price && self.sqrt_price <= self.sqrt_max_price, + sqrt_price >= MIN_SQRT_PRICE && sqrt_price <= MAX_SQRT_PRICE, PoolError::InvalidPriceRange ); - + } else { require!( - self.sqrt_min_price < self.sqrt_max_price, + sqrt_price >= sqrt_min_price && sqrt_price <= sqrt_max_price, PoolError::InvalidPriceRange ); + } + Ok(()) +} - require!(self.liquidity > 0, PoolError::InvalidMinimumLiquidity); - +impl InitializeCustomizablePoolParameters { + pub fn validate(&self) -> Result<()> { let activation_type = ActivationType::try_from(self.activation_type) .map_err(|_| PoolError::InvalidActivationType)?; // validate fee let collect_fee_mode = CollectFeeMode::try_from(self.collect_fee_mode) .map_err(|_| PoolError::InvalidCollectFeeMode)?; + if collect_fee_mode != CollectFeeMode::Compounding { + // we only care for price range if collect fee mode is not Compounding + require!( + self.sqrt_min_price >= MIN_SQRT_PRICE && self.sqrt_max_price <= MAX_SQRT_PRICE, + PoolError::InvalidPriceRange + ); + + require!( + self.sqrt_min_price < self.sqrt_max_price, + PoolError::InvalidPriceRange + ); + } + + validate_initial_sqrt_price( + collect_fee_mode, + self.sqrt_price, + self.sqrt_min_price, + self.sqrt_max_price, + )?; + + require!(self.liquidity > 0, PoolError::InvalidMinimumLiquidity); + self.pool_fees.validate(collect_fee_mode, activation_type)?; // validate activation @@ -263,8 +290,21 @@ pub fn handle_initialize_customizable_pool<'c: 'info, 'info>( .. } = params; - let (token_a_amount, token_b_amount) = - get_initialize_amounts(sqrt_min_price, sqrt_max_price, sqrt_price, liquidity)?; + let InitialPoolInformation { + token_a_amount, + token_b_amount, + initial_liquidity, + sqrt_min_price, + sqrt_max_price, + sqrt_price, + } = get_initial_pool_information( + collect_fee_mode.safe_cast()?, + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + )?; + require!( token_a_amount > 0 || token_b_amount > 0, PoolError::AmountIsZero @@ -291,7 +331,6 @@ pub fn handle_initialize_customizable_pool<'c: 'info, 'info>( ctx.accounts.token_a_vault.key(), ctx.accounts.token_b_vault.key(), alpha_vault, - Pubkey::default(), sqrt_min_price, sqrt_max_price, sqrt_price, @@ -302,6 +341,8 @@ pub fn handle_initialize_customizable_pool<'c: 'info, 'info>( liquidity, collect_fee_mode, pool_type, + token_a_amount, + token_b_amount, ); let mut position = ctx.accounts.position.load_init()?; @@ -309,7 +350,7 @@ pub fn handle_initialize_customizable_pool<'c: 'info, 'info>( &mut pool, ctx.accounts.pool.key(), ctx.accounts.position_nft_mint.key(), - liquidity, + initial_liquidity, ); // create position nft diff --git a/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool.rs b/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool.rs index 87e84b15..0dd30ab0 100644 --- a/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool.rs +++ b/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool.rs @@ -14,15 +14,16 @@ use crate::{ constants::seeds::{ POOL_PREFIX, POSITION_NFT_ACCOUNT_PREFIX, POSITION_PREFIX, TOKEN_VAULT_PREFIX, }, - create_position_nft, - curve::get_initialize_amounts, + create_position_nft, get_initial_pool_information, params::activation::ActivationParams, + safe_math::SafeCast, state::{Config, ConfigType, Pool, PoolType, Position}, token::{ calculate_transfer_fee_included_amount, get_token_program_flags, is_supported_mint, is_token_badge_initialized, transfer_from_user, }, - EvtCreatePosition, EvtInitializePool, PoolError, + validate_initial_sqrt_price, EvtCreatePosition, EvtInitializePool, InitialPoolInformation, + PoolError, }; // To fix IDL generation: https://github.com/coral-xyz/anchor/issues/3209 @@ -243,12 +244,22 @@ pub fn handle_initialize_pool<'c: 'info, 'info>( config.activation_type, )?); - require!( - sqrt_price >= config.sqrt_min_price && sqrt_price <= config.sqrt_max_price, - PoolError::InvalidPriceRange - ); + validate_initial_sqrt_price( + config.collect_fee_mode.safe_cast()?, + sqrt_price, + config.sqrt_min_price, + config.sqrt_max_price, + )?; - let (token_a_amount, token_b_amount) = get_initialize_amounts( + let InitialPoolInformation { + token_a_amount, + token_b_amount, + initial_liquidity, + sqrt_min_price, + sqrt_max_price, + sqrt_price, + } = get_initial_pool_information( + config.collect_fee_mode.safe_cast()?, config.sqrt_min_price, config.sqrt_max_price, sqrt_price, @@ -274,9 +285,8 @@ pub fn handle_initialize_pool<'c: 'info, 'info>( ctx.accounts.token_a_vault.key(), ctx.accounts.token_b_vault.key(), alpha_vault, - config.pool_creator_authority, - config.sqrt_min_price, - config.sqrt_max_price, + sqrt_min_price, + sqrt_max_price, sqrt_price, activation_point, config.activation_type, @@ -285,6 +295,8 @@ pub fn handle_initialize_pool<'c: 'info, 'info>( liquidity, config.collect_fee_mode, pool_type, + token_a_amount, + token_b_amount, ); // init position @@ -294,7 +306,7 @@ pub fn handle_initialize_pool<'c: 'info, 'info>( &mut pool, ctx.accounts.pool.key(), ctx.accounts.position_nft_mint.key(), - liquidity, + initial_liquidity, ); // create position nft diff --git a/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool_with_dynamic_config.rs b/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool_with_dynamic_config.rs index 8df33e60..440fd87c 100644 --- a/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool_with_dynamic_config.rs +++ b/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_pool_with_dynamic_config.rs @@ -10,15 +10,15 @@ use crate::{ constants::seeds::{ POOL_PREFIX, POSITION_NFT_ACCOUNT_PREFIX, POSITION_PREFIX, TOKEN_VAULT_PREFIX, }, - create_position_nft, - curve::get_initialize_amounts, - get_whitelisted_alpha_vault, + create_position_nft, get_initial_pool_information, get_whitelisted_alpha_vault, + safe_math::SafeCast, state::{Config, ConfigType, Pool, PoolType, Position}, token::{ calculate_transfer_fee_included_amount, get_token_program_flags, is_supported_mint, is_token_badge_initialized, transfer_from_user, }, - EvtCreatePosition, EvtInitializePool, InitializeCustomizablePoolParameters, PoolError, + EvtCreatePosition, EvtInitializePool, InitialPoolInformation, + InitializeCustomizablePoolParameters, PoolError, }; use super::{max_key, min_key}; @@ -214,8 +214,21 @@ pub fn handle_initialize_pool_with_dynamic_config<'c: 'info, 'info>( PoolError::InvalidConfigType ); - let (token_a_amount, token_b_amount) = - get_initialize_amounts(sqrt_min_price, sqrt_max_price, sqrt_price, liquidity)?; + let InitialPoolInformation { + token_a_amount, + token_b_amount, + initial_liquidity, + sqrt_min_price, + sqrt_max_price, + sqrt_price, + } = get_initial_pool_information( + collect_fee_mode.safe_cast()?, + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + )?; + require!( token_a_amount > 0 || token_b_amount > 0, PoolError::AmountIsZero @@ -242,7 +255,6 @@ pub fn handle_initialize_pool_with_dynamic_config<'c: 'info, 'info>( ctx.accounts.token_a_vault.key(), ctx.accounts.token_b_vault.key(), alpha_vault, - config.pool_creator_authority, sqrt_min_price, sqrt_max_price, sqrt_price, @@ -253,6 +265,8 @@ pub fn handle_initialize_pool_with_dynamic_config<'c: 'info, 'info>( liquidity, collect_fee_mode, pool_type, + token_a_amount, + token_b_amount, ); let mut position = ctx.accounts.position.load_init()?; @@ -260,7 +274,7 @@ pub fn handle_initialize_pool_with_dynamic_config<'c: 'info, 'info>( &mut pool, ctx.accounts.pool.key(), ctx.accounts.position_nft_mint.key(), - liquidity, + initial_liquidity, ); // create position nft diff --git a/programs/cp-amm/src/instructions/ix_add_liquidity.rs b/programs/cp-amm/src/instructions/ix_add_liquidity.rs index cf169041..6bf0fcd2 100644 --- a/programs/cp-amm/src/instructions/ix_add_liquidity.rs +++ b/programs/cp-amm/src/instructions/ix_add_liquidity.rs @@ -3,7 +3,7 @@ use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use crate::{ get_pool_access_validator, - state::{ModifyLiquidityResult, Pool, Position}, + state::{Pool, Position}, token::{calculate_transfer_fee_included_amount, transfer_from_user}, u128x128_math::Rounding, EvtLiquidityChange, PoolError, @@ -93,23 +93,29 @@ pub fn handle_add_liquidity( let mut pool = ctx.accounts.pool.load_mut()?; + pool.update_layout_version_if_needed()?; + let mut position = ctx.accounts.position.load_mut()?; // update current pool reward & postion reward before any logic let current_time = Clock::get()?.unix_timestamp as u64; position.update_rewards(&mut pool, current_time)?; - let ModifyLiquidityResult { - token_a_amount, - token_b_amount, - } = pool.get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Up)?; + let liquidity_handler = pool.get_liquidity_handler()?; + let (token_a_amount, token_b_amount) = + liquidity_handler.get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Up)?; require!( token_a_amount > 0 || token_b_amount > 0, PoolError::AmountIsZero ); - pool.apply_add_liquidity(&mut position, liquidity_delta)?; + pool.apply_add_liquidity( + &mut position, + liquidity_delta, + token_a_amount, + token_b_amount, + )?; let total_amount_a = calculate_transfer_fee_included_amount( &ctx.accounts @@ -156,8 +162,6 @@ pub fn handle_add_liquidity( total_amount_b, )?; - let (reserve_a_amount, reserve_b_amount) = pool.get_reserves_amount()?; - emit_cpi!(EvtLiquidityChange { pool: ctx.accounts.pool.key(), position: ctx.accounts.position.key(), @@ -169,8 +173,8 @@ pub fn handle_add_liquidity( token_b_amount, transfer_fee_included_token_a_amount: total_amount_a, transfer_fee_included_token_b_amount: total_amount_b, - reserve_b_amount, - reserve_a_amount, + reserve_a_amount: pool.token_a_amount, + reserve_b_amount: pool.token_b_amount, change_type: 0 }); diff --git a/programs/cp-amm/src/instructions/ix_remove_liquidity.rs b/programs/cp-amm/src/instructions/ix_remove_liquidity.rs index 24922c25..a30fcad6 100644 --- a/programs/cp-amm/src/instructions/ix_remove_liquidity.rs +++ b/programs/cp-amm/src/instructions/ix_remove_liquidity.rs @@ -6,7 +6,7 @@ use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use crate::{ activation_handler::ActivationHandler, const_pda, get_pool_access_validator, - state::{ModifyLiquidityResult, Pool, Position}, + state::{Pool, Position}, token::{calculate_transfer_fee_excluded_amount, transfer_from_pool}, u128x128_math::Rounding, EvtLiquidityChange, PoolError, @@ -94,6 +94,7 @@ pub fn handle_remove_liquidity( } let mut pool = ctx.accounts.pool.load_mut()?; + pool.update_layout_version_if_needed()?; let current_point = ActivationHandler::get_current_point(pool.activation_type)?; let mut position = ctx.accounts.position.load_mut()?; @@ -109,10 +110,9 @@ pub fn handle_remove_liquidity( let current_time = Clock::get()?.unix_timestamp as u64; position.update_rewards(&mut pool, current_time)?; - let ModifyLiquidityResult { - token_a_amount, - token_b_amount, - } = pool.get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Down)?; + let liquidity_handler = pool.get_liquidity_handler()?; + let (token_a_amount, token_b_amount) = + liquidity_handler.get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Down)?; require!( token_a_amount > 0 || token_b_amount > 0, @@ -146,7 +146,12 @@ pub fn handle_remove_liquidity( PoolError::ExceededSlippage ); - pool.apply_remove_liquidity(&mut position, liquidity_delta)?; + pool.apply_remove_liquidity( + &mut position, + liquidity_delta, + token_a_amount, + token_b_amount, + )?; // send to user transfer_from_pool( @@ -166,8 +171,6 @@ pub fn handle_remove_liquidity( token_b_amount, )?; - let (reserve_a_amount, reserve_b_amount) = pool.get_reserves_amount()?; - emit_cpi!(EvtLiquidityChange { pool: ctx.accounts.pool.key(), position: ctx.accounts.position.key(), @@ -179,8 +182,8 @@ pub fn handle_remove_liquidity( token_b_amount: transfer_fee_excluded_amount_b, transfer_fee_included_token_a_amount: token_a_amount, transfer_fee_included_token_b_amount: token_b_amount, - reserve_b_amount, - reserve_a_amount, + reserve_a_amount: pool.token_a_amount, + reserve_b_amount: pool.token_b_amount, change_type: 1 }); diff --git a/programs/cp-amm/src/instructions/mod.rs b/programs/cp-amm/src/instructions/mod.rs index 965c7fc4..19571049 100644 --- a/programs/cp-amm/src/instructions/mod.rs +++ b/programs/cp-amm/src/instructions/mod.rs @@ -22,8 +22,6 @@ pub mod ix_permanent_lock_position; pub use ix_permanent_lock_position::*; pub mod ix_claim_reward; pub use ix_claim_reward::*; -pub mod partner; -pub use partner::*; pub mod ix_fund_reward; pub use ix_fund_reward::*; pub mod ix_withdraw_ineligible_reward; diff --git a/programs/cp-amm/src/instructions/operator/ix_create_static_config.rs b/programs/cp-amm/src/instructions/operator/ix_create_static_config.rs index 92f1dfea..8b34d68e 100644 --- a/programs/cp-amm/src/instructions/operator/ix_create_static_config.rs +++ b/programs/cp-amm/src/instructions/operator/ix_create_static_config.rs @@ -58,15 +58,25 @@ pub fn handle_create_static_config( collect_fee_mode, } = config_parameters; - require!( - sqrt_min_price >= MIN_SQRT_PRICE && sqrt_max_price <= MAX_SQRT_PRICE, - PoolError::InvalidPriceRange - ); + let pool_collect_fee_mode = + CollectFeeMode::try_from(collect_fee_mode).map_err(|_| PoolError::InvalidCollectFeeMode)?; - require!( - sqrt_min_price < sqrt_max_price, - PoolError::InvalidPriceRange - ); + if pool_collect_fee_mode == CollectFeeMode::Compounding { + require!( + sqrt_min_price == 0 && sqrt_max_price == u128::MAX, + PoolError::InvalidPriceRange + ); + } else { + require!( + sqrt_min_price >= MIN_SQRT_PRICE && sqrt_max_price <= MAX_SQRT_PRICE, + PoolError::InvalidPriceRange + ); + + require!( + sqrt_min_price < sqrt_max_price, + PoolError::InvalidPriceRange + ); + } let has_alpha_vault = vault_config_key.ne(&Pubkey::default()); @@ -84,9 +94,6 @@ pub fn handle_create_static_config( let pool_activation_type = ActivationType::try_from(activation_type).map_err(|_| PoolError::InvalidActivationType)?; - let pool_collect_fee_mode = - CollectFeeMode::try_from(collect_fee_mode).map_err(|_| PoolError::InvalidCollectFeeMode)?; - pool_fees.validate(pool_collect_fee_mode, pool_activation_type)?; let mut config = ctx.accounts.config.load_init()?; diff --git a/programs/cp-amm/src/instructions/operator/ix_fix_pool_layout_version.rs b/programs/cp-amm/src/instructions/operator/ix_fix_pool_layout_version.rs new file mode 100644 index 00000000..56e4497c --- /dev/null +++ b/programs/cp-amm/src/instructions/operator/ix_fix_pool_layout_version.rs @@ -0,0 +1,19 @@ +use anchor_lang::prelude::*; + +use crate::state::{Operator, Pool}; + +#[derive(Accounts)] +pub struct FixPoolLayoutVersionCtx<'info> { + #[account(mut)] + pub pool: AccountLoader<'info, Pool>, + + pub operator: AccountLoader<'info, Operator>, + + pub signer: Signer<'info>, +} + +pub fn handle_fix_pool_layout_version(ctx: Context) -> Result<()> { + let mut pool = ctx.accounts.pool.load_mut()?; + pool.update_layout_version_if_needed()?; + Ok(()) +} diff --git a/programs/cp-amm/src/instructions/operator/ix_zap_protocol_fee.rs b/programs/cp-amm/src/instructions/operator/ix_zap_protocol_fee.rs new file mode 100644 index 00000000..60be516c --- /dev/null +++ b/programs/cp-amm/src/instructions/operator/ix_zap_protocol_fee.rs @@ -0,0 +1,156 @@ +use crate::token::{get_token_program_from_flag, validate_ata_token}; +use crate::PoolError; +use crate::{ + const_pda, + constants::treasury as TREASURY, + state::{Operator, Pool}, + token::transfer_from_pool, +}; +use anchor_lang::prelude::*; +use anchor_lang::solana_program::sysvar::instructions::ID as SYSVAR_IX_ID; + +use anchor_spl::associated_token::get_associated_token_address_with_program_id; +use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; +use protocol_zap::constants::MINTS_DISALLOWED_TO_ZAP_OUT; +use protocol_zap::utils::validate_zap_out_to_treasury; + +/// Accounts for zap protocol fees +#[derive(Accounts)] +pub struct ZapProtocolFee<'info> { + /// CHECK: pool authority + #[account(address = const_pda::pool_authority::ID)] + pub pool_authority: UncheckedAccount<'info>, + + #[account(mut)] + pub pool: AccountLoader<'info, Pool>, + + #[account(mut)] + pub token_vault: Box>, + + pub token_mint: Box>, + + /// CHECK: Receiver token account to receive the zap out fund. + #[account(mut)] + pub receiver_token: UncheckedAccount<'info>, + + /// zap claim fee operator + pub operator: AccountLoader<'info, Operator>, + + /// Operator + pub signer: Signer<'info>, + + /// Token program + pub token_program: Interface<'info, TokenInterface>, + + /// CHECK: Sysvar Instructions account + #[account( + address = SYSVAR_IX_ID, + )] + pub sysvar_instructions: AccountInfo<'info>, +} + +fn validate_accounts_and_return_withdraw_direction<'info>( + pool: &Pool, + token_vault: &InterfaceAccount<'info, TokenAccount>, + token_mint: &InterfaceAccount<'info, Mint>, + token_program: &Interface<'info, TokenInterface>, +) -> Result { + require!( + token_mint.key() == pool.token_a_mint || token_mint.key() == pool.token_b_mint, + PoolError::InvalidWithdrawProtocolFeeZapAccounts + ); + + let is_withdrawing_token_a = token_mint.key() == pool.token_a_mint; + + if is_withdrawing_token_a { + require!( + token_vault.key() == pool.token_a_vault, + PoolError::InvalidWithdrawProtocolFeeZapAccounts + ); + } else { + require!( + token_vault.key() == pool.token_b_vault, + PoolError::InvalidWithdrawProtocolFeeZapAccounts + ); + } + + let token_mint_ai = token_mint.to_account_info(); + require!( + *token_mint_ai.owner == token_program.key(), + PoolError::InvalidWithdrawProtocolFeeZapAccounts + ); + + Ok(is_withdrawing_token_a) +} + +// Rules: +// 1. If the token mint is SOL or USDC, then must withdraw to treasury using `claim_protocol_fee` endpoint. No zap out allowed. +// 2. If the token mint is not SOL or USDC, operator require to zap out to SOL or USDC or either one of the token of the pool +pub fn handle_zap_protocol_fee(ctx: Context, max_amount: u64) -> Result<()> { + let mut pool = ctx.accounts.pool.load_mut()?; + let is_withdrawing_a = validate_accounts_and_return_withdraw_direction( + &pool, + &ctx.accounts.token_vault, + &ctx.accounts.token_mint, + &ctx.accounts.token_program, + )?; + + require!( + !MINTS_DISALLOWED_TO_ZAP_OUT.contains(&ctx.accounts.token_mint.key().to_bytes()), + PoolError::MintRestrictedFromZap + ); + + let (amount, treasury_paired_destination_token_address) = if is_withdrawing_a { + let (amount_a, _) = pool.claim_protocol_fee(max_amount, 0)?; + + let treasury_token_b_address = get_associated_token_address_with_program_id( + &TREASURY::ID, + &pool.token_b_mint, + &get_token_program_from_flag(pool.token_b_flag)?, + ); + (amount_a, treasury_token_b_address) + } else { + let (_, amount_b) = pool.claim_protocol_fee(0, max_amount)?; + let treasury_token_a_address = get_associated_token_address_with_program_id( + &TREASURY::ID, + &pool.token_a_mint, + &get_token_program_from_flag(pool.token_a_flag)?, + ); + (amount_b, treasury_token_a_address) + }; + + require!(amount > 0, PoolError::AmountIsZero); + + drop(pool); + + let receiver_token_ai = ctx.accounts.receiver_token.to_account_info(); + + validate_ata_token( + &receiver_token_ai, + &ctx.accounts.signer.key(), + &ctx.accounts.token_mint.key(), + &ctx.accounts.token_program.key(), + )?; + + validate_zap_out_to_treasury( + amount, + &crate::ID.to_bytes(), + &ctx.accounts.receiver_token.key().to_bytes(), + &ctx.accounts.receiver_token.try_borrow_data()?, + &ctx.accounts.sysvar_instructions.try_borrow_data()?, + &TREASURY::ID.to_bytes(), + &treasury_paired_destination_token_address.to_bytes(), + ) + .map_err(|e| -> anchor_lang::error::Error { PoolError::from(e).into() })?; + + transfer_from_pool( + ctx.accounts.pool_authority.to_account_info(), + &ctx.accounts.token_mint, + &ctx.accounts.token_vault, + &receiver_token_ai, + &ctx.accounts.token_program, + amount, + )?; + + Ok(()) +} diff --git a/programs/cp-amm/src/instructions/operator/mod.rs b/programs/cp-amm/src/instructions/operator/mod.rs index c541500c..6350d476 100644 --- a/programs/cp-amm/src/instructions/operator/mod.rs +++ b/programs/cp-amm/src/instructions/operator/mod.rs @@ -20,9 +20,11 @@ pub mod ix_close_token_badge; pub use ix_close_token_badge::*; pub mod ix_update_pool_fees; pub use ix_update_pool_fees::*; -pub mod zap_protocol_fee; -pub use zap_protocol_fee::*; pub mod ix_fix_pool_fee_params; pub use ix_fix_pool_fee_params::*; pub mod ix_fix_config_fee_params; pub use ix_fix_config_fee_params::*; +pub mod ix_fix_pool_layout_version; +pub use ix_fix_pool_layout_version::*; +pub mod ix_zap_protocol_fee; +pub use ix_zap_protocol_fee::*; diff --git a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/damm_v2_zap.rs b/programs/cp-amm/src/instructions/operator/zap_protocol_fee/damm_v2_zap.rs deleted file mode 100644 index bab42366..00000000 --- a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/damm_v2_zap.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{ - constants::zap::{ - DAMM_V2_SWAP_AMOUNT_IN_OFFSET, DAMM_V2_SWAP_DESTINATION_ACCOUNT_INDEX, - DAMM_V2_SWAP_SOURCE_ACCOUNT_INDEX, - }, - instructions::zap_protocol_fee::{RawZapOutAmmInfo, ZapInfoProcessor}, -}; -use anchor_lang::prelude::*; -use zap::types::ZapOutParameters; - -pub struct ZapDammV2InfoProcessor; - -impl ZapInfoProcessor for ZapDammV2InfoProcessor { - fn validate_payload(&self, _payload: &[u8]) -> Result<()> { - Ok(()) - } - - fn extract_raw_zap_out_amm_info( - &self, - _zap_params: &ZapOutParameters, - ) -> Result { - Ok(RawZapOutAmmInfo { - source_index: DAMM_V2_SWAP_SOURCE_ACCOUNT_INDEX, - destination_index: DAMM_V2_SWAP_DESTINATION_ACCOUNT_INDEX, - amount_in_offset: DAMM_V2_SWAP_AMOUNT_IN_OFFSET, - }) - } -} diff --git a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/jup_v6_zap.rs b/programs/cp-amm/src/instructions/operator/zap_protocol_fee/jup_v6_zap.rs deleted file mode 100644 index 1aaf1328..00000000 --- a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/jup_v6_zap.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::{ - constants::zap::{ - JUP_V6_ROUTE_AMOUNT_IN_REVERSE_OFFSET, JUP_V6_ROUTE_DESTINATION_ACCOUNT_INDEX, - JUP_V6_ROUTE_SOURCE_ACCOUNT_INDEX, JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET, - JUP_V6_SHARED_ACCOUNT_ROUTE_DESTINATION_ACCOUNT_INDEX, - JUP_V6_SHARED_ACCOUNT_ROUTE_SOURCE_ACCOUNT_INDEX, - }, - instructions::zap_protocol_fee::{RawZapOutAmmInfo, ZapInfoProcessor}, - math::safe_math::{SafeCast, SafeMath}, - PoolError, -}; -use anchor_lang::prelude::*; -use jupiter::types::RoutePlanStep; -use jupiter::types::Swap; -use zap::types::ZapOutParameters; - -pub struct ZapJupV6RouteInfoProcessor; - -fn ensure_whitelisted_swap_leg(route_plan_steps: &[RoutePlanStep]) -> Result<()> { - for step in route_plan_steps { - match step.swap { - Swap::Meteora - | Swap::MeteoraDammV2 - | Swap::MeteoraDammV2WithRemainingAccounts - | Swap::MeteoraDlmm - | Swap::MeteoraDlmmSwapV2 { .. } - | Swap::Mercurial - | Swap::Whirlpool { .. } - | Swap::WhirlpoolSwapV2 { .. } - | Swap::Raydium - | Swap::RaydiumV2 - | Swap::RaydiumCP - | Swap::RaydiumClmm - | Swap::RaydiumClmmV2 => { - // whitelisted swap leg - } - _ => return Err(PoolError::InvalidZapOutParameters.into()), - } - } - - Ok(()) -} - -impl ZapInfoProcessor for ZapJupV6RouteInfoProcessor { - fn validate_payload(&self, payload: &[u8]) -> Result<()> { - let route_params = jupiter::client::args::Route::try_from_slice(payload)?; - ensure_whitelisted_swap_leg(&route_params.route_plan)?; - - Ok(()) - } - - fn extract_raw_zap_out_amm_info( - &self, - zap_params: &ZapOutParameters, - ) -> Result { - let amount_in_offset = zap_params - .payload_data - .len() - .safe_sub(JUP_V6_ROUTE_AMOUNT_IN_REVERSE_OFFSET)? - .safe_cast()?; - - Ok(RawZapOutAmmInfo { - source_index: JUP_V6_ROUTE_SOURCE_ACCOUNT_INDEX, - destination_index: JUP_V6_ROUTE_DESTINATION_ACCOUNT_INDEX, - amount_in_offset, - }) - } -} - -pub struct ZapJupV6SharedRouteInfoProcessor; - -impl ZapInfoProcessor for ZapJupV6SharedRouteInfoProcessor { - fn validate_payload(&self, payload: &[u8]) -> Result<()> { - let route_params = jupiter::client::args::SharedAccountsRoute::try_from_slice(payload)?; - ensure_whitelisted_swap_leg(&route_params.route_plan)?; - Ok(()) - } - - fn extract_raw_zap_out_amm_info( - &self, - zap_params: &ZapOutParameters, - ) -> Result { - let amount_in_offset = zap_params - .payload_data - .len() - .safe_sub(JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET)? - .safe_cast()?; - - Ok(RawZapOutAmmInfo { - source_index: JUP_V6_SHARED_ACCOUNT_ROUTE_SOURCE_ACCOUNT_INDEX, - destination_index: JUP_V6_SHARED_ACCOUNT_ROUTE_DESTINATION_ACCOUNT_INDEX, - amount_in_offset, - }) - } -} diff --git a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/mod.rs b/programs/cp-amm/src/instructions/operator/zap_protocol_fee/mod.rs deleted file mode 100644 index 4d4da849..00000000 --- a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -use anchor_lang::prelude::*; -use zap::types::ZapOutParameters; - -use crate::{ - constants::zap::{ - DAMM_V2_SWAP_DISC_REF, JUP_V6, JUP_V6_ROUTE_DISC_REF, JUP_V6_SHARED_ACCOUNT_ROUTE_DISC_REF, - }, - zap_protocol_fee::{ - damm_v2_zap::ZapDammV2InfoProcessor, - jup_v6_zap::{ZapJupV6RouteInfoProcessor, ZapJupV6SharedRouteInfoProcessor}, - }, - PoolError, -}; -mod damm_v2_zap; -mod process_zap_protocol_fee; -pub use process_zap_protocol_fee::*; -mod jup_v6_zap; - -pub struct RawZapOutAmmInfo { - source_index: usize, - destination_index: usize, - amount_in_offset: u16, -} - -pub trait ZapInfoProcessor { - fn validate_payload(&self, payload: &[u8]) -> Result<()>; - fn extract_raw_zap_out_amm_info( - &self, - zap_params: &ZapOutParameters, - ) -> Result; -} - -pub fn get_zap_amm_processor( - amm_disc: &[u8], - amm_program_address: Pubkey, -) -> Result> { - match (amm_disc, amm_program_address) { - (DAMM_V2_SWAP_DISC_REF, crate::ID_CONST) => Ok(Box::new(ZapDammV2InfoProcessor)), - (JUP_V6_ROUTE_DISC_REF, JUP_V6) => Ok(Box::new(ZapJupV6RouteInfoProcessor)), - (JUP_V6_SHARED_ACCOUNT_ROUTE_DISC_REF, JUP_V6) => { - Ok(Box::new(ZapJupV6SharedRouteInfoProcessor)) - } - _ => Err(PoolError::InvalidZapOutParameters.into()), - } -} diff --git a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/process_zap_protocol_fee.rs b/programs/cp-amm/src/instructions/operator/zap_protocol_fee/process_zap_protocol_fee.rs deleted file mode 100644 index 6b5dba6d..00000000 --- a/programs/cp-amm/src/instructions/operator/zap_protocol_fee/process_zap_protocol_fee.rs +++ /dev/null @@ -1,355 +0,0 @@ -use crate::constants::zap::{ - MINTS_DISALLOWED_TO_ZAP_OUT, TREASURY_SOL_ADDRESS, TREASURY_USDC_ADDRESS, -}; -use crate::safe_math::SafeMath; -use crate::token::{get_token_program_from_flag, validate_ata_token}; -use crate::{ - const_pda, - constants::treasury as TREASURY, - state::{Operator, Pool}, - token::transfer_from_pool, -}; -use crate::{get_zap_amm_processor, PoolError, RawZapOutAmmInfo}; -use anchor_lang::prelude::*; -use anchor_lang::solana_program::sysvar::instructions::ID as SYSVAR_IX_ID; -use anchor_lang::solana_program::sysvar::instructions::{ - load_current_index_checked, load_instruction_at_checked, -}; -use anchor_spl::associated_token::get_associated_token_address_with_program_id; -use anchor_spl::token::accessor; -use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; -use zap::types::ZapOutParameters; - -/// Accounts for zap protocol fees -#[derive(Accounts)] -pub struct ZapProtocolFee<'info> { - /// CHECK: pool authority - #[account(address = const_pda::pool_authority::ID)] - pub pool_authority: UncheckedAccount<'info>, - - #[account(mut)] - pub pool: AccountLoader<'info, Pool>, - - #[account(mut)] - pub token_vault: Box>, - - pub token_mint: Box>, - - /// CHECK: Receiver token account to receive the zap out fund. - #[account(mut)] - pub receiver_token: UncheckedAccount<'info>, - - /// zap claim fee operator - pub operator: AccountLoader<'info, Operator>, - - /// Operator - pub signer: Signer<'info>, - - /// Token program - pub token_program: Interface<'info, TokenInterface>, - - /// CHECK: Sysvar Instructions account - #[account( - address = SYSVAR_IX_ID, - )] - pub sysvar_instructions: AccountInfo<'info>, -} - -fn validate_accounts_and_return_withdraw_direction<'info>( - pool: &Pool, - token_vault: &InterfaceAccount<'info, TokenAccount>, - token_mint: &InterfaceAccount<'info, Mint>, - token_program: &Interface<'info, TokenInterface>, -) -> Result { - require!( - token_mint.key() == pool.token_a_mint || token_mint.key() == pool.token_b_mint, - PoolError::InvalidWithdrawProtocolFeeZapAccounts - ); - - let is_withdrawing_token_a = token_mint.key() == pool.token_a_mint; - - if is_withdrawing_token_a { - require!( - token_vault.key() == pool.token_a_vault, - PoolError::InvalidWithdrawProtocolFeeZapAccounts - ); - } else { - require!( - token_vault.key() == pool.token_b_vault, - PoolError::InvalidWithdrawProtocolFeeZapAccounts - ); - } - - let token_mint_ai = token_mint.to_account_info(); - require!( - *token_mint_ai.owner == token_program.key(), - PoolError::InvalidWithdrawProtocolFeeZapAccounts - ); - - Ok(is_withdrawing_token_a) -} - -// Rules: -// 1. If the token mint is SOL or USDC, then must withdraw to treasury using `claim_protocol_fee` endpoint. No zap out allowed. -// 2. If the token mint is not SOL or USDC, operator require to zap out to SOL or USDC or either one of the token of the pool -pub fn handle_zap_protocol_fee(ctx: Context, max_amount: u64) -> Result<()> { - let mut pool = ctx.accounts.pool.load_mut()?; - let is_withdrawing_a = validate_accounts_and_return_withdraw_direction( - &pool, - &ctx.accounts.token_vault, - &ctx.accounts.token_mint, - &ctx.accounts.token_program, - )?; - - require!( - !MINTS_DISALLOWED_TO_ZAP_OUT.contains(&ctx.accounts.token_mint.key()), - PoolError::MintRestrictedFromZap - ); - - let (amount, treasury_paired_destination_token_address) = if is_withdrawing_a { - let (amount_a, _) = pool.claim_protocol_fee(max_amount, 0)?; - - let treasury_token_b_address = get_associated_token_address_with_program_id( - &TREASURY::ID, - &pool.token_b_mint, - &get_token_program_from_flag(pool.token_b_flag)?, - ); - (amount_a, treasury_token_b_address) - } else { - let (_, amount_b) = pool.claim_protocol_fee(0, max_amount)?; - let treasury_token_a_address = get_associated_token_address_with_program_id( - &TREASURY::ID, - &pool.token_a_mint, - &get_token_program_from_flag(pool.token_a_flag)?, - ); - (amount_b, treasury_token_a_address) - }; - - require!(amount > 0, PoolError::AmountIsZero); - - drop(pool); - - let receiver_token_ai = ctx.accounts.receiver_token.to_account_info(); - - validate_ata_token( - &receiver_token_ai, - &ctx.accounts.signer.key(), - &ctx.accounts.token_mint.key(), - &ctx.accounts.token_program.key(), - )?; - - validate_zap_out_to_treasury( - amount, - &receiver_token_ai, - treasury_paired_destination_token_address, - &ctx.accounts.sysvar_instructions, - )?; - - transfer_from_pool( - ctx.accounts.pool_authority.to_account_info(), - &ctx.accounts.token_mint, - &ctx.accounts.token_vault, - &receiver_token_ai, - &ctx.accounts.token_program, - amount, - )?; - - Ok(()) -} - -fn validate_zap_out_to_treasury<'info>( - claimed_amount: u64, - claimer_token_account: &AccountInfo<'info>, - treasury_paired_destination_token_address: Pubkey, - sysvar_instructions_account: &AccountInfo<'info>, -) -> Result<()> { - let current_index = load_current_index_checked(sysvar_instructions_account)?; - - let current_instruction = - load_instruction_at_checked(current_index.into(), sysvar_instructions_account)?; - - // Ensure the instruction is direct instruction call - require!( - current_instruction.program_id == crate::ID, - PoolError::CpiDisabled - ); - - search_and_validate_zap_out_instruction( - current_index, - claimed_amount, - sysvar_instructions_account, - claimer_token_account, - treasury_paired_destination_token_address, - ) -} - -// Search for zap out instruction in the next instruction after the current one -fn search_and_validate_zap_out_instruction<'info>( - current_index: u16, - max_claim_amount: u64, - sysvar_instructions_account: &AccountInfo<'info>, - claimer_token_account: &AccountInfo<'info>, - treasury_paired_destination_token_address: Pubkey, -) -> Result<()> { - // Zap out instruction must be next to current instruction - let next_index = current_index.safe_add(1)?; - let ix = load_instruction_at_checked(next_index.into(), sysvar_instructions_account)?; - - require!( - ix.program_id == zap::ID, - PoolError::MissingZapOutInstruction - ); - - let disc = ix - .data - .get(..8) - .ok_or_else(|| PoolError::InvalidZapOutParameters)?; - - require!( - disc == zap::client::args::ZapOut::DISCRIMINATOR, - PoolError::MissingZapOutInstruction - ); - - let zap_params = ZapOutParameters::try_from_slice(&ix.data[8..])?; - - let ZapOutAmmInfo { - zap_user_token_in_address, - amm_source_token_address: source_token_address, - amm_destination_token_address: destination_token_address, - amount_in_offset, - } = extract_amm_accounts_and_info(&zap_params, &ix.accounts)?; - - // Zap out from operator fee receiving account - validate_zap_parameters( - &zap_params, - max_claim_amount, - amount_in_offset, - claimer_token_account, - )?; - - // There's no validation to make sure that `user_token_in_account` is the same as `amm_source_token_address` - // Operator could steal the fund by providing a fake token account with 0 to bypass the zap swap invoke - // https://github.com/MeteoraAg/zap-program/blob/117e7d5586aa27cf97e6fde6266e25ee4e496f18/programs/zap/src/instructions/ix_zap_out.rs#L91 - require!( - zap_user_token_in_address == claimer_token_account.key(), - PoolError::InvalidZapAccounts - ); - - // Zap out from operator fee receiving account - require!( - source_token_address == claimer_token_account.key(), - PoolError::InvalidZapAccounts - ); - - // Zap to paired mint in the pool, or SOL, or USDC treasury - require!( - destination_token_address == treasury_paired_destination_token_address - || destination_token_address == TREASURY_USDC_ADDRESS - || destination_token_address == TREASURY_SOL_ADDRESS, - PoolError::InvalidZapAccounts - ); - - Ok(()) -} - -fn validate_zap_parameters<'info>( - zap_params: &ZapOutParameters, - max_claim_amount: u64, - amount_in_offset: u16, - claimer_token_account: &AccountInfo<'info>, -) -> Result<()> { - require!( - zap_params.percentage == 100, - PoolError::InvalidZapOutParameters - ); - - require!( - zap_params.offset_amount_in == amount_in_offset, - PoolError::InvalidZapOutParameters - ); - - // Ensure no stealing from operator by setting a higher pre_token_balance than actual balance to steal fund - // Eg: Operator set 100 pre balance, but actual balance is 0 - // Actual claimed amount is 300 - // Zap will attempt to swap post - pre = 300 - 100 = 200 - // Leftover 100 will be stolen by operator - require!( - zap_params.pre_user_token_balance == accessor::amount(claimer_token_account)?, - PoolError::InvalidZapOutParameters - ); - - require!( - zap_params.max_swap_amount == max_claim_amount, - PoolError::InvalidZapOutParameters - ); - - Ok(()) -} - -struct ZapOutAmmInfo { - // Account used to compare delta changes with pre_balance to decide swap amount - zap_user_token_in_address: Pubkey, - amm_source_token_address: Pubkey, - amm_destination_token_address: Pubkey, - amount_in_offset: u16, -} - -fn extract_amm_accounts_and_info( - zap_params: &ZapOutParameters, - zap_account: &[AccountMeta], -) -> Result { - // Accounts in ZapOutCtx - const ZAP_OUT_ACCOUNTS_LEN: usize = 2; - - let zap_user_token_in_address = zap_account - .get(0) - .map(|acc| acc.pubkey) - .ok_or_else(|| PoolError::InvalidZapAccounts)?; - - let zap_amm_program_address = zap_account - .get(1) - .map(|acc| acc.pubkey) - .ok_or_else(|| PoolError::InvalidZapAccounts)?; - - let amm_disc = zap_params - .payload_data - .get(..8) - .ok_or_else(|| PoolError::InvalidZapOutParameters)?; - - let zap_info_processor = get_zap_amm_processor(amm_disc, zap_amm_program_address)?; - - let amm_payload = zap_params - .payload_data - .get(8..) - .ok_or_else(|| PoolError::InvalidZapOutParameters)?; - - zap_info_processor.validate_payload(&amm_payload)?; - - let RawZapOutAmmInfo { - source_index, - destination_index, - amount_in_offset, - } = zap_info_processor.extract_raw_zap_out_amm_info(zap_params)?; - - // Start from remaining accounts of zap program - let amm_accounts = zap_account - .get(ZAP_OUT_ACCOUNTS_LEN..) - .ok_or_else(|| PoolError::InvalidZapAccounts)?; - - let source_token_address = amm_accounts - .get(source_index) - .map(|acc| acc.pubkey) - .ok_or_else(|| PoolError::InvalidZapAccounts)?; - - let destination_token_address = amm_accounts - .get(destination_index) - .map(|acc| acc.pubkey) - .ok_or_else(|| PoolError::InvalidZapAccounts)?; - - Ok(ZapOutAmmInfo { - zap_user_token_in_address, - amm_source_token_address: source_token_address, - amm_destination_token_address: destination_token_address, - amount_in_offset, - }) -} diff --git a/programs/cp-amm/src/instructions/partner/ix_claim_partner_fee.rs b/programs/cp-amm/src/instructions/partner/ix_claim_partner_fee.rs deleted file mode 100644 index c995548b..00000000 --- a/programs/cp-amm/src/instructions/partner/ix_claim_partner_fee.rs +++ /dev/null @@ -1,92 +0,0 @@ -use anchor_lang::prelude::*; -use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; - -use crate::{const_pda, state::Pool, token::transfer_from_pool, EvtClaimPartnerFee}; - -/// Accounts for partner to claim fees -#[event_cpi] -#[derive(Accounts)] -pub struct ClaimPartnerFeesCtx<'info> { - /// CHECK: pool authority - #[account(address = const_pda::pool_authority::ID)] - pub pool_authority: UncheckedAccount<'info>, - - #[account( - mut, - has_one = token_a_vault, - has_one = token_b_vault, - has_one = token_a_mint, - has_one = token_b_mint, - has_one = partner, - )] - pub pool: AccountLoader<'info, Pool>, - - /// The treasury token a account - #[account(mut)] - pub token_a_account: Box>, - - /// The treasury token b account - #[account(mut)] - pub token_b_account: Box>, - - /// The vault token account for input token - #[account(mut, token::token_program = token_a_program, token::mint = token_a_mint)] - pub token_a_vault: Box>, - - /// The vault token account for output token - #[account(mut, token::token_program = token_b_program, token::mint = token_b_mint)] - pub token_b_vault: Box>, - - /// The mint of token a - pub token_a_mint: Box>, - - /// The mint of token b - pub token_b_mint: Box>, - - pub partner: Signer<'info>, - - /// Token a program - pub token_a_program: Interface<'info, TokenInterface>, - - /// Token b program - pub token_b_program: Interface<'info, TokenInterface>, -} - -/// Partner claim fees. -pub fn handle_claim_partner_fee( - ctx: Context, - max_amount_a: u64, - max_amount_b: u64, -) -> Result<()> { - let mut pool = ctx.accounts.pool.load_mut()?; - let (token_a_amount, token_b_amount) = pool.claim_partner_fee(max_amount_a, max_amount_b)?; - - if max_amount_a > 0 { - transfer_from_pool( - ctx.accounts.pool_authority.to_account_info(), - &ctx.accounts.token_a_mint, - &ctx.accounts.token_a_vault, - &ctx.accounts.token_a_account.to_account_info(), - &ctx.accounts.token_a_program, - token_a_amount, - )?; - } - - if max_amount_b > 0 { - transfer_from_pool( - ctx.accounts.pool_authority.to_account_info(), - &ctx.accounts.token_b_mint, - &ctx.accounts.token_b_vault, - &ctx.accounts.token_b_account.to_account_info(), - &ctx.accounts.token_b_program, - token_b_amount, - )?; - } - - emit_cpi!(EvtClaimPartnerFee { - pool: ctx.accounts.pool.key(), - token_a_amount, - token_b_amount - }); - Ok(()) -} diff --git a/programs/cp-amm/src/instructions/partner/mod.rs b/programs/cp-amm/src/instructions/partner/mod.rs deleted file mode 100644 index aa0f94a1..00000000 --- a/programs/cp-amm/src/instructions/partner/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod ix_claim_partner_fee; -pub use ix_claim_partner_fee::*; diff --git a/programs/cp-amm/src/instructions/swap/ix_p_swap.rs b/programs/cp-amm/src/instructions/swap/ix_p_swap.rs index 5540013a..ae895542 100644 --- a/programs/cp-amm/src/instructions/swap/ix_p_swap.rs +++ b/programs/cp-amm/src/instructions/swap/ix_p_swap.rs @@ -4,7 +4,7 @@ use crate::p_helper::{ p_accessor_mint, p_get_number_of_accounts_in_instruction, p_load_mut_unchecked, p_transfer_from_pool, p_transfer_from_user, }; -use crate::state::SwapResult2; +use crate::state::CollectFeeMode; use crate::{instruction::Swap as SwapInstruction, instruction::Swap2 as Swap2Instruction}; use crate::{ process_swap_exact_in, process_swap_exact_out, process_swap_partial_fill, EvtSwap2, @@ -17,7 +17,7 @@ use anchor_lang::solana_program::instruction::{ use pinocchio::account_info::AccountInfo; use pinocchio::sysvars::instructions::{Instructions, IntrospectedInstruction, INSTRUCTIONS_ID}; -use crate::safe_math::SafeMath; +use crate::safe_math::{SafeCast, SafeMath}; use crate::{ activation_handler::ActivationHandler, get_pool_access_validator, @@ -86,6 +86,8 @@ pub fn p_handle_swap( ); } + pool.update_layout_version_if_needed()?; + let &SwapParameters2 { amount_0, amount_1, @@ -147,7 +149,8 @@ pub fn p_handle_swap( let current_timestamp = Clock::get()?.unix_timestamp as u64; pool.update_pre_swap(current_timestamp)?; - let fee_mode = FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, has_referral)?; + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast()?; + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, has_referral); let process_swap_params = ProcessSwapParams { pool: &pool, @@ -161,7 +164,7 @@ pub fn p_handle_swap( }; let ProcessSwapResult { - swap_result, + mut swap_result, included_transfer_fee_amount_in, excluded_transfer_fee_amount_out, included_transfer_fee_amount_out, @@ -171,9 +174,10 @@ pub fn p_handle_swap( SwapMode::ExactOut => process_swap_exact_out(process_swap_params), }?; - pool.apply_swap_result(&swap_result, &fee_mode, current_timestamp)?; + pool.apply_swap_result(&swap_result, &fee_mode, trade_direction, current_timestamp)?; - let SwapResult2 { referral_fee, .. } = swap_result; + // re-update next_sqrt_price for compounding pool + swap_result.next_sqrt_price = pool.sqrt_price; // send to reserve p_transfer_from_user( @@ -204,7 +208,7 @@ pub fn p_handle_swap( token_a_vault, referral_token_account, token_a_program, - referral_fee, + swap_result.referral_fee, ) .map_err(|err| ProgramError::from(u64::from(err)))?; } else { @@ -214,14 +218,12 @@ pub fn p_handle_swap( token_b_vault, referral_token_account, token_b_program, - referral_fee, + swap_result.referral_fee, ) .map_err(|err| ProgramError::from(u64::from(err)))?; } } - let (reserve_a_amount, reserve_b_amount) = pool.get_reserves_amount()?; - p_emit_cpi( anchor_lang::Event::data(&EvtSwap2 { pool: Pubkey::new_from_array(*pool_key), @@ -234,8 +236,8 @@ pub fn p_handle_swap( included_transfer_fee_amount_in, included_transfer_fee_amount_out, excluded_transfer_fee_amount_out, - reserve_a_amount, - reserve_b_amount, + reserve_a_amount: pool.token_a_amount, + reserve_b_amount: pool.token_b_amount, }), event_authority, ) diff --git a/programs/cp-amm/src/lib.rs b/programs/cp-amm/src/lib.rs index 004d335d..e98610ca 100644 --- a/programs/cp-amm/src/lib.rs +++ b/programs/cp-amm/src/lib.rs @@ -19,7 +19,8 @@ pub use utils::*; pub mod base_fee; pub mod math; pub use math::*; -pub mod curve; +pub mod liquidity_handler; +pub use liquidity_handler::*; pub mod tests; @@ -186,15 +187,6 @@ pub mod cp_amm { instructions::handle_zap_protocol_fee(ctx, max_amount) } - #[deprecated = "We currently disable this, and could enable this in the future"] - pub fn claim_partner_fee( - ctx: Context, - max_amount_a: u64, - max_amount_b: u64, - ) -> Result<()> { - instructions::handle_claim_partner_fee(ctx, max_amount_a, max_amount_b) - } - #[access_control(is_valid_operator_role(&ctx.accounts.operator, ctx.accounts.signer.key, OperatorPermission::CloseTokenBadge))] pub fn close_token_badge(ctx: Context) -> Result<()> { instructions::handle_close_token_badge(ctx) @@ -273,19 +265,10 @@ pub mod cp_amm { pub fn swap(_ctx: Context, _params: SwapParameters) -> Result<()> { Ok(()) - // instructions::swap::handle_swap_wrapper( - // &ctx, - // SwapParameters2 { - // amount_0: params.amount_in, - // amount_1: params.minimum_amount_out, - // swap_mode: SwapMode::ExactIn.into(), - // }, - // ) } pub fn swap2(_ctx: Context, _params: SwapParameters2) -> Result<()> { Ok(()) - // instructions::swap::handle_swap_wrapper(&ctx, params) } pub fn claim_position_fee(ctx: Context) -> Result<()> { @@ -346,6 +329,11 @@ pub mod cp_amm { ) } + #[access_control(is_valid_operator_role(&ctx.accounts.operator, ctx.accounts.signer.key, OperatorPermission::FixPool))] + pub fn fix_pool_layout_version(ctx: Context) -> Result<()> { + instructions::handle_fix_pool_layout_version(ctx) + } + #[cfg(feature = "idl-build")] pub fn dummy_ix( _ctx: Context, diff --git a/programs/cp-amm/src/liquidity_handler/compounding_liquidity.rs b/programs/cp-amm/src/liquidity_handler/compounding_liquidity.rs new file mode 100644 index 00000000..23f1b885 --- /dev/null +++ b/programs/cp-amm/src/liquidity_handler/compounding_liquidity.rs @@ -0,0 +1,183 @@ +#[cfg(test)] +use crate::params::swap::TradeDirection; +use crate::{ + safe_math::{SafeCast, SafeMath}, + state::{SwapAmountFromInput, SwapAmountFromOutput}, + u128x128_math::Rounding, + utils_math::{safe_mul_div_cast_u128, safe_mul_div_cast_u64, sqrt_u256}, + InitialPoolInformation, LiquidityHandler, PoolError, +}; +use anchor_lang::prelude::*; +use ruint::aliases::U256; + +pub const DEAD_LIQUIDITY: u128 = 100 << 64; +pub struct CompoundingLiquidity { + pub token_a_amount: u64, // current token a reserve + pub token_b_amount: u64, // current token_b_reserve + pub liquidity: u128, // current liquidity +} + +impl CompoundingLiquidity { + pub fn get_initial_pool_information( + sqrt_price: u128, + liquidity: u128, + ) -> Result { + require!( + liquidity > DEAD_LIQUIDITY, + PoolError::InvalidMinimumLiquidity + ); + // a * b = liquidity ^ 2 + // b / a = sqrt_price ^ 2 + // So we can calculate b = liquidity * sqrt_price and a = liquidity / sqrt_price + let token_a_amount = get_initial_token_a(sqrt_price, liquidity)?; + let token_b_amount = get_initial_token_b(sqrt_price, liquidity)?; + Ok(InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: get_sqrt_price_from_amounts(token_a_amount, token_b_amount)?, + initial_liquidity: liquidity.safe_sub(DEAD_LIQUIDITY)?, // we lock DEAD_LIQUIDITY in pool + sqrt_min_price: 0, + sqrt_max_price: u128::MAX, + }) + } +} + +impl LiquidityHandler for CompoundingLiquidity { + fn get_amounts_for_modify_liquidity( + &self, + liquidity_delta: u128, + round: Rounding, + ) -> Result<(u64, u64)> { + let token_a_amount = safe_mul_div_cast_u128( + liquidity_delta, + self.token_a_amount.into(), + self.liquidity, + round, + )?; + let token_b_amount = safe_mul_div_cast_u128( + liquidity_delta, + self.token_b_amount.into(), + self.liquidity, + round, + )?; + + Ok((token_a_amount.safe_cast()?, token_b_amount.safe_cast()?)) + } + + fn calculate_a_to_b_from_amount_in(&self, amount_in: u64) -> Result { + // a * b = (a + amount_in) * (b - output_amount) + // => output_amount = b - a * b / (a + amount_in) = b * amount_in / (a + amount_in) + let output_amount = safe_mul_div_cast_u64( + self.token_b_amount, + amount_in, + self.token_a_amount.safe_add(amount_in)?, + Rounding::Down, + )?; + + Ok(SwapAmountFromInput { + amount_left: 0, + output_amount, + next_sqrt_price: 0, + }) + } + + fn calculate_b_to_a_from_amount_in(&self, amount_in: u64) -> Result { + // a * b = (b + amount_in) * (a - output_amount) + // => output_amount = a - a * b / (b + amount_in) = a * amount_in / (b + amount_in) + let output_amount = safe_mul_div_cast_u64( + self.token_a_amount, + amount_in, + self.token_b_amount.safe_add(amount_in)?, + Rounding::Down, + )?; + + Ok(SwapAmountFromInput { + amount_left: 0, + output_amount, + next_sqrt_price: 0, // dont need to care for next sqrt price now + }) + } + + fn calculate_a_to_b_from_partial_amount_in( + &self, + amount_in: u64, + ) -> Result { + // it is constant-product, so no price range + self.calculate_a_to_b_from_amount_in(amount_in) + } + + fn calculate_b_to_a_from_partial_amount_in( + &self, + amount_in: u64, + ) -> Result { + // it is constant-product, so no price range + self.calculate_b_to_a_from_amount_in(amount_in) + } + + fn calculate_a_to_b_from_amount_out(&self, amount_out: u64) -> Result { + // a * b = (a + amount_in) * (b - amount_out) + // => amount_in = a * b / (b - amount_out) - a = a * amount_out / (b - amount_out) + let input_amount = safe_mul_div_cast_u64( + self.token_a_amount, + amount_out, + self.token_b_amount.safe_sub(amount_out)?, + Rounding::Up, + )?; + Ok(SwapAmountFromOutput { + input_amount, + next_sqrt_price: 0, // dont need to care for next sqrt price now + }) + } + + fn calculate_b_to_a_from_amount_out(&self, amount_out: u64) -> Result { + // a * b = (b + amount_in) * (a - amount_out) + // => amount_in = a * b / (a - amount_out) - b = b * amount_out / (a - amount_out) + let input_amount = safe_mul_div_cast_u64( + self.token_b_amount, + amount_out, + self.token_a_amount.safe_sub(amount_out)?, + Rounding::Up, + )?; + Ok(SwapAmountFromOutput { + input_amount, + next_sqrt_price: 0, // dont need to care for next sqrt price now + }) + } + + fn get_reserves_amount(&self) -> Result<(u64, u64)> { + Ok((self.token_a_amount, self.token_b_amount)) + } + + // xyk, the price is determined by the ratio of reserves and it always rounded down. + fn get_next_sqrt_price(&self, _next_sqrt_price: u128) -> Result { + get_sqrt_price_from_amounts(self.token_a_amount, self.token_b_amount) + } + + #[cfg(test)] + fn get_max_amount_in(&self, _trade_direction: TradeDirection) -> Result { + Ok(std::u64::MAX) + } +} + +fn get_sqrt_price_from_amounts(token_a_amount: u64, token_b_amount: u64) -> Result { + let token_b_amount = U256::from(token_b_amount).safe_shl(128)?; + let price = token_b_amount.safe_div(U256::from(token_a_amount))?; + let sqrt_price = sqrt_u256(price).ok_or_else(|| PoolError::MathOverflow)?; + Ok(sqrt_price + .try_into() + .map_err(|_| PoolError::TypeCastFailed)?) +} + +fn get_initial_token_a(sqrt_price: u128, liquidity: u128) -> Result { + let amount = liquidity.div_ceil(sqrt_price); + Ok(amount.safe_cast()?) +} + +fn get_initial_token_b(sqrt_price: u128, liquidity: u128) -> Result { + let liquidity = U256::from(liquidity); + let sqrt_price = U256::from(sqrt_price); + let numerator = liquidity.safe_mul(sqrt_price)?; + let denominator = U256::from(1).safe_shl(128)?; + let amount = numerator.div_ceil(denominator); + Ok(amount.try_into().map_err(|_| PoolError::TypeCastFailed)?) +} diff --git a/programs/cp-amm/src/liquidity_handler/concentrated_liquidity.rs b/programs/cp-amm/src/liquidity_handler/concentrated_liquidity.rs new file mode 100644 index 00000000..7ff3567a --- /dev/null +++ b/programs/cp-amm/src/liquidity_handler/concentrated_liquidity.rs @@ -0,0 +1,508 @@ +#[cfg(test)] +use crate::params::swap::TradeDirection; +use crate::{ + safe_math::SafeMath, + state::{SwapAmountFromInput, SwapAmountFromOutput}, + u128x128_math::{mul_div_u256, Rounding}, + InitialPoolInformation, LiquidityHandler, PoolError, +}; +use anchor_lang::prelude::*; +use ruint::aliases::U256; +pub struct ConcentratedLiquidity { + pub sqrt_max_price: u128, + pub sqrt_min_price: u128, + pub sqrt_price: u128, // current sqrt price + pub liquidity: u128, // current liquidity +} + +impl ConcentratedLiquidity { + pub fn get_initial_pool_information( + sqrt_min_price: u128, + sqrt_max_price: u128, + sqrt_price: u128, + liquidity: u128, + ) -> Result { + // BASE TOKEN + let token_a_amount = + get_delta_amount_a_unsigned(sqrt_price, sqrt_max_price, liquidity, Rounding::Up)?; + // QUOTE TOKEN + let token_b_amount = + get_delta_amount_b_unsigned(sqrt_min_price, sqrt_price, liquidity, Rounding::Up)?; + Ok(InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price, + initial_liquidity: liquidity, + sqrt_max_price, + sqrt_min_price, + }) + } +} + +impl LiquidityHandler for ConcentratedLiquidity { + fn get_amounts_for_modify_liquidity( + &self, + liquidity_delta: u128, + round: Rounding, + ) -> Result<(u64, u64)> { + // finding output amount + let token_a_amount = get_delta_amount_a_unsigned( + self.sqrt_price, + self.sqrt_max_price, + liquidity_delta, + round, + )?; + + let token_b_amount = get_delta_amount_b_unsigned( + self.sqrt_min_price, + self.sqrt_price, + liquidity_delta, + round, + )?; + + Ok((token_a_amount, token_b_amount)) + } + + fn calculate_a_to_b_from_amount_in(&self, amount_in: u64) -> Result { + // finding new target price + let next_sqrt_price = + get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, true)?; + + if next_sqrt_price < self.sqrt_min_price { + return Err(PoolError::PriceRangeViolation.into()); + } + + // finding output amount + let output_amount = get_delta_amount_b_unsigned( + next_sqrt_price, + self.sqrt_price, + self.liquidity, + Rounding::Down, + )?; + + Ok(SwapAmountFromInput { + output_amount, + next_sqrt_price, + amount_left: 0, + }) + } + + fn calculate_b_to_a_from_amount_in(&self, amount_in: u64) -> Result { + // finding new target price + let next_sqrt_price = + get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, false)?; + + if next_sqrt_price > self.sqrt_max_price { + return Err(PoolError::PriceRangeViolation.into()); + } + // finding output amount + let output_amount = get_delta_amount_a_unsigned( + self.sqrt_price, + next_sqrt_price, + self.liquidity, + Rounding::Down, + )?; + + Ok(SwapAmountFromInput { + output_amount, + next_sqrt_price, + amount_left: 0, + }) + } + + fn calculate_a_to_b_from_partial_amount_in( + &self, + amount_in: u64, + ) -> Result { + let max_amount_in = get_delta_amount_a_unsigned_unchecked( + self.sqrt_min_price, + self.sqrt_price, + self.liquidity, + Rounding::Up, + )?; + + let (consumed_in_amount, next_sqrt_price) = if U256::from(amount_in) >= max_amount_in { + ( + max_amount_in + .try_into() + .map_err(|_| PoolError::TypeCastFailed)?, + self.sqrt_min_price, + ) + } else { + let next_sqrt_price = + get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, true)?; + (amount_in, next_sqrt_price) + }; + + let output_amount = get_delta_amount_b_unsigned( + next_sqrt_price, + self.sqrt_price, + self.liquidity, + Rounding::Down, + )?; + + let amount_left = amount_in.safe_sub(consumed_in_amount)?; + + Ok(SwapAmountFromInput { + output_amount, + next_sqrt_price, + amount_left, + }) + } + + fn calculate_b_to_a_from_partial_amount_in( + &self, + amount_in: u64, + ) -> Result { + let max_amount_in = get_delta_amount_b_unsigned_unchecked( + self.sqrt_price, + self.sqrt_max_price, + self.liquidity, + Rounding::Up, + )?; + + let (consumed_in_amount, next_sqrt_price) = if U256::from(amount_in) >= max_amount_in { + ( + max_amount_in + .try_into() + .map_err(|_| PoolError::TypeCastFailed)?, + self.sqrt_max_price, + ) + } else { + let next_sqrt_price = + get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, false)?; + (amount_in, next_sqrt_price) + }; + + let output_amount = get_delta_amount_a_unsigned( + self.sqrt_price, + next_sqrt_price, + self.liquidity, + Rounding::Down, + )?; + + let amount_left = amount_in.safe_sub(consumed_in_amount)?; + + Ok(SwapAmountFromInput { + output_amount, + next_sqrt_price, + amount_left, + }) + } + + fn calculate_a_to_b_from_amount_out(&self, amount_out: u64) -> Result { + let next_sqrt_price = + get_next_sqrt_price_from_output(self.sqrt_price, self.liquidity, amount_out, true)?; + + if next_sqrt_price < self.sqrt_min_price { + return Err(PoolError::PriceRangeViolation.into()); + } + + let in_amount = get_delta_amount_a_unsigned( + next_sqrt_price, + self.sqrt_price, + self.liquidity, + Rounding::Up, + )?; + + Ok(SwapAmountFromOutput { + input_amount: in_amount, + next_sqrt_price, + }) + } + + fn calculate_b_to_a_from_amount_out(&self, amount_out: u64) -> Result { + let next_sqrt_price = + get_next_sqrt_price_from_output(self.sqrt_price, self.liquidity, amount_out, false)?; + + if next_sqrt_price > self.sqrt_max_price { + return Err(PoolError::PriceRangeViolation.into()); + } + + let in_amount = get_delta_amount_b_unsigned( + self.sqrt_price, + next_sqrt_price, + self.liquidity, + Rounding::Up, + )?; + + Ok(SwapAmountFromOutput { + input_amount: in_amount, + next_sqrt_price, + }) + } + + fn get_reserves_amount(&self) -> Result<(u64, u64)> { + let reserve_a_amount = get_delta_amount_a_unsigned( + self.sqrt_price, + self.sqrt_max_price, + self.liquidity, + Rounding::Up, + )?; + + let reserve_b_amount = get_delta_amount_b_unsigned( + self.sqrt_min_price, + self.sqrt_price, + self.liquidity, + Rounding::Up, + )?; + + Ok((reserve_a_amount, reserve_b_amount)) + } + + // It does nothing because next_sqrt_price is computed by swap-path + rounding direction. + fn get_next_sqrt_price(&self, next_sqrt_price: u128) -> Result { + Ok(next_sqrt_price) + } + + #[cfg(test)] + fn get_max_amount_in(&self, trade_direction: TradeDirection) -> Result { + let amount = match trade_direction { + TradeDirection::AtoB => get_delta_amount_a_unsigned_unchecked( + self.sqrt_min_price, + self.sqrt_price, + self.liquidity, + Rounding::Up, + )?, + TradeDirection::BtoA => get_delta_amount_b_unsigned_unchecked( + self.sqrt_price, + self.sqrt_max_price, + self.liquidity, + Rounding::Up, + )?, + }; + if amount > U256::from(u64::MAX) { + Ok(u64::MAX) + } else { + Ok(amount.try_into().unwrap()) + } + } +} + +/// Gets the delta amount_a for given liquidity and price range +/// +/// # Formula +/// +/// * `Δa = L * (1 / √P_lower - 1 / √P_upper)` +/// * i.e. `L * (√P_upper - √P_lower) / (√P_upper * √P_lower)` +pub fn get_delta_amount_a_unsigned( + lower_sqrt_price: u128, + upper_sqrt_price: u128, + liquidity: u128, + round: Rounding, +) -> Result { + let result = get_delta_amount_a_unsigned_unchecked( + lower_sqrt_price, + upper_sqrt_price, + liquidity, + round, + )?; + require!(result <= U256::from(u64::MAX), PoolError::MathOverflow); + Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) +} + +/// * i.e. `L * (√P_upper - √P_lower) / (√P_upper * √P_lower)` +pub fn get_delta_amount_a_unsigned_unchecked( + lower_sqrt_price: u128, + upper_sqrt_price: u128, + liquidity: u128, + round: Rounding, +) -> Result { + let numerator_1 = U256::from(liquidity); + let numerator_2 = U256::from(upper_sqrt_price - lower_sqrt_price); + + let denominator = U256::from(lower_sqrt_price).safe_mul(U256::from(upper_sqrt_price))?; + + assert!(denominator > U256::ZERO); + let result = mul_div_u256(numerator_1, numerator_2, denominator, round) + .ok_or_else(|| PoolError::MathOverflow)?; + Ok(result) +} + +/// Gets the delta amount_b for given liquidity and price range +/// Δb = L * (√P_upper - √P_lower) +pub fn get_delta_amount_b_unsigned( + lower_sqrt_price: u128, + upper_sqrt_price: u128, + liquidity: u128, + round: Rounding, +) -> Result { + let result = get_delta_amount_b_unsigned_unchecked( + lower_sqrt_price, + upper_sqrt_price, + liquidity, + round, + )?; + require!(result <= U256::from(u64::MAX), PoolError::MathOverflow); + Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) +} + +// Δb = L * (√P_upper - √P_lower) +pub fn get_delta_amount_b_unsigned_unchecked( + lower_sqrt_price: u128, + upper_sqrt_price: u128, + liquidity: u128, + round: Rounding, +) -> Result { + let liquidity = U256::from(liquidity); + let delta_sqrt_price = U256::from(upper_sqrt_price.safe_sub(lower_sqrt_price)?); + let prod = liquidity.safe_mul(delta_sqrt_price)?; + + match round { + Rounding::Up => { + let denominator = U256::from(1).safe_shl(128)?; + let result = prod.div_ceil(denominator); + Ok(result) + } + Rounding::Down => { + let (result, _) = prod.overflowing_shr(128); + Ok(result) + } + } +} + +/// Gets the next sqrt price given an input amount of token_a or token_b +/// Throws if price or liquidity are 0, or if the next price overflow q64.64 +pub fn get_next_sqrt_price_from_input( + sqrt_price: u128, + liquidity: u128, + amount_in: u64, + a_for_b: bool, +) -> Result { + assert!(sqrt_price > 0); + assert!(liquidity > 0); + + if amount_in == 0 { + return Ok(sqrt_price); + } + + // round to make sure that we don't pass the target price + if a_for_b { + get_next_sqrt_price_from_amount_in_a_rounding_up(sqrt_price, liquidity, amount_in) + } else { + get_next_sqrt_price_from_amount_in_b_rounding_down(sqrt_price, liquidity, amount_in) + } +} + +/// Gets the next sqrt price given an output amount of token_a or token_b +/// Throws if price or liquidity are 0, or if the next price overflow q64.64 +pub fn get_next_sqrt_price_from_output( + sqrt_price: u128, + liquidity: u128, + amount_out: u64, + a_for_b: bool, +) -> Result { + assert!(sqrt_price > 0); + assert!(liquidity > 0); + + if amount_out == 0 { + return Ok(sqrt_price); + } + + // round to make sure that we don't pass the target price + if a_for_b { + get_next_sqrt_price_from_amount_out_b_rounding_down(sqrt_price, liquidity, amount_out) + } else { + get_next_sqrt_price_from_amount_out_a_rounding_up(sqrt_price, liquidity, amount_out) + } +} + +/// Gets the next sqrt price √P' given a delta of token_a +/// +/// Always round up because +/// 1. In the exact output case, token_a supply decreases leading to price increase. +/// Move price up so that exact output is met. +/// 2. In the exact input case, token_a supply increases leading to price decrease. +/// Do not round down to minimize price impact. We only need to meet input +/// change and not guarantee exact output. +/// +/// Use function for exact input or exact output swaps for token_a +/// +/// # Formula +/// +/// * `√P' = √P * L / (L + Δa * √P)` +/// * If Δa * √P overflows, use alternate form `√P' = L / (L/√P + Δa)` +/// +/// # Proof +/// +/// For constant L, +/// +/// L = a * √P +/// a' = a + Δa +/// a' * √P' = a * √P +/// (a + Δa) * √P' = a * √P +/// √P' = (a * √P) / (a + Δa) +/// a = L/√P +/// √P' = √P * L / (L + Δa * √P) +/// +pub fn get_next_sqrt_price_from_amount_in_a_rounding_up( + sqrt_price: u128, + liquidity: u128, + amount: u64, +) -> Result { + let sqrt_price = U256::from(sqrt_price); + let liquidity = U256::from(liquidity); + + let product = U256::from(amount).safe_mul(sqrt_price)?; + let denominator = liquidity.safe_add(U256::from(product))?; + let result = mul_div_u256(liquidity, sqrt_price, denominator, Rounding::Up) + .ok_or_else(|| PoolError::MathOverflow)?; + Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) +} + +/// √P' = √P * L / (L - Δa * √P) +pub fn get_next_sqrt_price_from_amount_out_a_rounding_up( + sqrt_price: u128, + liquidity: u128, + amount: u64, +) -> Result { + let sqrt_price = U256::from(sqrt_price); + let liquidity = U256::from(liquidity); + + let product = U256::from(amount).safe_mul(sqrt_price)?; + let denominator = liquidity.safe_sub(U256::from(product))?; + let result = mul_div_u256(liquidity, sqrt_price, denominator, Rounding::Up) + .ok_or_else(|| PoolError::MathOverflow)?; + Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) +} + +/// Gets the next sqrt price given a delta of token_b +/// +/// Always round down because +/// 1. In the exact output case, token_b supply decreases leading to price decrease. +/// Move price down by rounding down so that exact output of token_a is met. +/// 2. In the exact input case, token_b supply increases leading to price increase. +/// Do not round down to minimize price impact. We only need to meet input +/// change and not guarantee exact output for token_a. +/// +/// +/// # Formula +/// +/// * `√P' = √P + Δb / L` +/// +pub fn get_next_sqrt_price_from_amount_in_b_rounding_down( + sqrt_price: u128, + liquidity: u128, + amount: u64, +) -> Result { + let quotient = U256::from(amount) + .safe_shl(128)? + .safe_div(U256::from(liquidity))?; + + let result = U256::from(sqrt_price).safe_add(quotient)?; + Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) +} + +/// `√P' = √P - Δb / L` +pub fn get_next_sqrt_price_from_amount_out_b_rounding_down( + sqrt_price: u128, + liquidity: u128, + amount: u64, +) -> Result { + let quotient = U256::from(amount) + .safe_shl(128)? + .div_ceil(U256::from(liquidity)); + + let result = U256::from(sqrt_price).safe_sub(quotient)?; + Ok(result.try_into().map_err(|_| PoolError::TypeCastFailed)?) +} diff --git a/programs/cp-amm/src/liquidity_handler/mod.rs b/programs/cp-amm/src/liquidity_handler/mod.rs new file mode 100644 index 00000000..9953d3a8 --- /dev/null +++ b/programs/cp-amm/src/liquidity_handler/mod.rs @@ -0,0 +1,82 @@ +pub mod compounding_liquidity; +pub use compounding_liquidity::*; + +pub mod concentrated_liquidity; +pub use concentrated_liquidity::*; + +use anchor_lang::prelude::*; + +#[cfg(test)] +use crate::params::swap::TradeDirection; +use crate::{ + state::{CollectFeeMode, SwapAmountFromInput, SwapAmountFromOutput}, + u128x128_math::Rounding, +}; + +pub trait LiquidityHandler { + fn get_amounts_for_modify_liquidity( + &self, + liquidity_delta: u128, + round: Rounding, + ) -> Result<(u64, u64)>; + + fn calculate_a_to_b_from_amount_in(&self, amount_in: u64) -> Result; + + fn calculate_b_to_a_from_amount_in(&self, amount_in: u64) -> Result; + + fn calculate_a_to_b_from_partial_amount_in( + &self, + amount_in: u64, + ) -> Result; + + fn calculate_b_to_a_from_partial_amount_in( + &self, + amount_in: u64, + ) -> Result; + + fn calculate_a_to_b_from_amount_out(&self, amount_out: u64) -> Result; + + fn calculate_b_to_a_from_amount_out(&self, amount_out: u64) -> Result; + + fn get_reserves_amount(&self) -> Result<(u64, u64)>; + + // Note: Due to different way of concentrated liquidity and compounding liquidity calculating price, compounding and concentrated pools can update dynamic-fee volatility differently for equivalent swap price moves. + // Additionally the market cap based base fee will also behave differently: + // Concentrated Amount_In B to A -> Rounding Down + // Concentrated Amount_Out B to A -> Rounding Up + // Compounding Amount_In B to A -> Rounding Down + // Compounding Amount_Out B to A -> Rounding Down + fn get_next_sqrt_price(&self, next_sqrt_price: u128) -> Result; + + #[cfg(test)] + fn get_max_amount_in(&self, trade_direction: TradeDirection) -> Result; +} + +#[derive(Debug)] +pub struct InitialPoolInformation { + pub token_a_amount: u64, + pub token_b_amount: u64, + pub sqrt_price: u128, + pub initial_liquidity: u128, + pub sqrt_min_price: u128, + pub sqrt_max_price: u128, +} + +pub fn get_initial_pool_information( + collect_fee_mode: CollectFeeMode, + sqrt_min_price: u128, + sqrt_max_price: u128, + sqrt_price: u128, + liquidity: u128, +) -> Result { + if collect_fee_mode == CollectFeeMode::Compounding { + CompoundingLiquidity::get_initial_pool_information(sqrt_price, liquidity) + } else { + ConcentratedLiquidity::get_initial_pool_information( + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + ) + } +} diff --git a/programs/cp-amm/src/math/safe_math.rs b/programs/cp-amm/src/math/safe_math.rs index 31634b63..9a5f607a 100644 --- a/programs/cp-amm/src/math/safe_math.rs +++ b/programs/cp-amm/src/math/safe_math.rs @@ -2,7 +2,11 @@ use anchor_lang::solana_program::msg; use ruint::aliases::{U256, U512}; use std::panic::Location; -use crate::{token::TokenProgramFlags, PoolError}; +use crate::{ + state::{CollectFeeMode, LayoutVersion}, + token::TokenProgramFlags, + PoolError, +}; pub trait SafeMath: Sized { fn safe_add(self, rhs: Self) -> Result; @@ -142,6 +146,8 @@ try_into_impl!(i64, u64); try_into_impl!(usize, u16); try_into_impl!(U512, u64); try_into_impl!(u8, TokenProgramFlags); +try_into_impl!(u8, CollectFeeMode); +try_into_impl!(u8, LayoutVersion); #[cfg(test)] mod tests { diff --git a/programs/cp-amm/src/params/fee_parameters.rs b/programs/cp-amm/src/params/fee_parameters.rs index 659d45cd..f87c93aa 100644 --- a/programs/cp-amm/src/params/fee_parameters.rs +++ b/programs/cp-amm/src/params/fee_parameters.rs @@ -1,9 +1,7 @@ //! Fees module includes information about fee charges use crate::activation_handler::ActivationType; use crate::base_fee::{base_fee_parameters_to_base_fee_info, BaseFeeHandlerBuilder}; -use crate::constants::fee::{ - HOST_FEE_PERCENT, MAX_BASIS_POINT, PARTNER_FEE_PERCENT, PROTOCOL_FEE_PERCENT, -}; +use crate::constants::fee::{HOST_FEE_PERCENT, MAX_BASIS_POINT, PROTOCOL_FEE_PERCENT}; use crate::constants::{BIN_STEP_BPS_DEFAULT, BIN_STEP_BPS_U128_DEFAULT, U24_MAX}; use crate::error::PoolError; use crate::safe_math::SafeMath; @@ -16,17 +14,21 @@ use anchor_lang::prelude::*; pub struct PoolFeeParameters { /// Base fee pub base_fee: BaseFeeParameters, + /// compounding fee bps, only have value if CollectFeeMode::Compounding + pub compounding_fee_bps: u16, + /// padding for future use + pub padding: u8, /// dynamic fee pub dynamic_fee: Option, } #[derive(Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize, InitSpace, Default)] pub struct BaseFeeParameters { - pub data: [u8; 30], + pub data: [u8; 27], } impl BaseFeeParameters { - fn validate( + pub fn validate( &self, collect_fee_mode: CollectFeeMode, activation_type: ActivationType, @@ -52,14 +54,16 @@ impl PoolFeeParameters { pub fn to_pool_fees_config(&self) -> Result { let &PoolFeeParameters { base_fee, + compounding_fee_bps, dynamic_fee, + padding: _, } = self; if let Some(dynamic_fee) = dynamic_fee { Ok(PoolFeesConfig { base_fee: base_fee.to_base_fee_config()?, protocol_fee_percent: PROTOCOL_FEE_PERCENT, - partner_fee_percent: PARTNER_FEE_PERCENT, referral_fee_percent: HOST_FEE_PERCENT, + compounding_fee_bps, dynamic_fee: dynamic_fee.to_dynamic_fee_config(), ..Default::default() }) @@ -67,8 +71,8 @@ impl PoolFeeParameters { Ok(PoolFeesConfig { base_fee: base_fee.to_base_fee_config()?, protocol_fee_percent: PROTOCOL_FEE_PERCENT, - partner_fee_percent: PARTNER_FEE_PERCENT, referral_fee_percent: HOST_FEE_PERCENT, + compounding_fee_bps, ..Default::default() }) } @@ -76,14 +80,16 @@ impl PoolFeeParameters { pub fn to_pool_fees_struct(&self, init_sqrt_price: u128) -> Result { let &PoolFeeParameters { base_fee, + compounding_fee_bps, dynamic_fee, + padding: _, } = self; if let Some(dynamic_fee) = dynamic_fee { Ok(PoolFeesStruct { base_fee: base_fee.to_base_fee_struct()?, protocol_fee_percent: PROTOCOL_FEE_PERCENT, - partner_fee_percent: PARTNER_FEE_PERCENT, referral_fee_percent: HOST_FEE_PERCENT, + compounding_fee_bps, dynamic_fee: dynamic_fee.to_dynamic_fee_struct(), init_sqrt_price, ..Default::default() @@ -92,8 +98,8 @@ impl PoolFeeParameters { Ok(PoolFeesStruct { base_fee: base_fee.to_base_fee_struct()?, protocol_fee_percent: PROTOCOL_FEE_PERCENT, - partner_fee_percent: PARTNER_FEE_PERCENT, referral_fee_percent: HOST_FEE_PERCENT, + compounding_fee_bps, init_sqrt_price, ..Default::default() }) @@ -226,7 +232,12 @@ impl PoolFeeParameters { collect_fee_mode: CollectFeeMode, activation_type: ActivationType, ) -> Result<()> { + // validate compounding fee + validate_compounding_fee(collect_fee_mode, self.compounding_fee_bps)?; + + // validate base fee self.base_fee.validate(collect_fee_mode, activation_type)?; + // validate dynamic fee if let Some(dynamic_fee) = self.dynamic_fee { dynamic_fee.validate()?; } @@ -234,24 +245,21 @@ impl PoolFeeParameters { } } -#[derive(Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize, InitSpace, Default)] -pub struct PartnerInfo { - pub fee_percent: u8, - pub partner_authority: Pubkey, - pub pending_fee_a: u64, - pub pending_fee_b: u64, -} - -impl PartnerInfo { - pub fn have_partner(&self) -> bool { - self.partner_authority != Pubkey::default() - } - - pub fn validate(&self) -> Result<()> { - if !self.have_partner() { - require!(self.fee_percent == 0, PoolError::InvalidFee); - } - - Ok(()) +fn validate_compounding_fee( + collect_fee_mode: CollectFeeMode, + compounding_fee_bps: u16, +) -> Result<()> { + if collect_fee_mode == CollectFeeMode::Compounding { + // not make sense to have zero compounding_fee_bps in compounding collect fee mode + require!( + compounding_fee_bps > 0 && compounding_fee_bps <= MAX_BASIS_POINT, + PoolError::InvalidCompoundingFeeBps + ); + } else { + require!( + compounding_fee_bps == 0, + PoolError::InvalidCompoundingFeeBps + ); } + Ok(()) } diff --git a/programs/cp-amm/src/state/config.rs b/programs/cp-amm/src/state/config.rs index cc371651..8f1661e1 100644 --- a/programs/cp-amm/src/state/config.rs +++ b/programs/cp-amm/src/state/config.rs @@ -4,9 +4,7 @@ use crate::{ base_fee::base_fee_info_to_base_fee_parameters, constants::activation::*, error::PoolError, - params::fee_parameters::{ - BaseFeeParameters, DynamicFeeParameters, PartnerInfo, PoolFeeParameters, - }, + params::fee_parameters::{BaseFeeParameters, DynamicFeeParameters, PoolFeeParameters}, safe_math::SafeMath, state::fee::{BaseFeeStruct, DynamicFeeStruct, PoolFeesStruct}, }; @@ -41,10 +39,12 @@ pub struct PoolFeesConfig { pub base_fee: BaseFeeInfo, pub dynamic_fee: DynamicFeeConfig, pub protocol_fee_percent: u8, - pub partner_fee_percent: u8, + pub padding_0: u8, pub referral_fee_percent: u8, - pub padding_0: [u8; 5], - pub padding_1: [u64; 5], + pub padding_1: [u8; 3], + /// Compounding fee bps, only non-zero if collect_fee_mode is compounding + pub compounding_fee_bps: u16, + pub padding_2: [u64; 5], } const_assert_eq!(PoolFeesConfig::INIT_SPACE, 128); @@ -75,6 +75,7 @@ impl PoolFeesConfig { pub fn to_pool_fee_parameters(&self) -> Result { let &PoolFeesConfig { base_fee, + compounding_fee_bps, dynamic_fee: DynamicFeeConfig { initialized, @@ -92,6 +93,7 @@ impl PoolFeesConfig { if initialized == 1 { Ok(PoolFeeParameters { base_fee: base_fee.to_base_fee_parameters()?, + compounding_fee_bps, dynamic_fee: Some(DynamicFeeParameters { bin_step, bin_step_u128, @@ -101,10 +103,12 @@ impl PoolFeesConfig { max_volatility_accumulator, variable_fee_control, }), + ..Default::default() }) } else { Ok(PoolFeeParameters { base_fee: base_fee.to_base_fee_parameters()?, + compounding_fee_bps, ..Default::default() }) } @@ -114,8 +118,8 @@ impl PoolFeesConfig { let &PoolFeesConfig { base_fee, protocol_fee_percent, - partner_fee_percent, referral_fee_percent, + compounding_fee_bps, dynamic_fee, .. } = self; @@ -123,8 +127,8 @@ impl PoolFeesConfig { PoolFeesStruct { base_fee: base_fee.to_base_fee_struct(), protocol_fee_percent, - partner_fee_percent, referral_fee_percent, + compounding_fee_bps, dynamic_fee: dynamic_fee.to_dynamic_fee_struct(), init_sqrt_price, ..Default::default() @@ -282,14 +286,6 @@ impl Config { self.config_type = ConfigType::Dynamic.into(); } - pub fn get_partner_info(&self) -> PartnerInfo { - PartnerInfo { - partner_authority: self.pool_creator_authority, - fee_percent: self.pool_fees.partner_fee_percent, - ..Default::default() - } - } - pub fn has_alpha_vault(&self) -> bool { self.vault_config_key.ne(&Pubkey::default()) } diff --git a/programs/cp-amm/src/state/fee.rs b/programs/cp-amm/src/state/fee.rs index 44d712af..9a3b9873 100644 --- a/programs/cp-amm/src/state/fee.rs +++ b/programs/cp-amm/src/state/fee.rs @@ -23,9 +23,9 @@ use super::CollectFeeMode; #[derive(Debug, PartialEq)] pub struct FeeOnAmountResult { pub amount: u64, - pub trading_fee: u64, + pub claiming_fee: u64, + pub compounding_fee: u64, pub protocol_fee: u64, - pub partner_fee: u64, pub referral_fee: u64, } @@ -64,7 +64,6 @@ pub enum BaseFeeMode { /// trading_fee = amount * trade_fee_numerator / denominator /// protocol_fee = trading_fee * protocol_fee_percentage / 100 /// referral_fee = protocol_fee * referral_percentage / 100 -/// partner_fee = (protocol_fee - referral_fee) * partner_fee_percentage / denominator #[derive(Debug, InitSpace, Default)] pub struct PoolFeesStruct { /// Trade fees are extra token amounts that are held inside the token @@ -77,12 +76,14 @@ pub struct PoolFeesStruct { /// the protocol of the program. /// Protocol trade fee numerator pub protocol_fee_percent: u8, - /// partner fee - pub partner_fee_percent: u8, + /// padding for future use + pub padding_0: u8, /// referral fee pub referral_fee_percent: u8, /// padding - pub padding_0: [u8; 5], + pub padding_1: [u8; 3], + /// compounding fee bps, only non-zero in CollectFeeMode::Compounding + pub compounding_fee_bps: u16, /// dynamic fee pub dynamic_fee: DynamicFeeStruct, @@ -188,23 +189,22 @@ impl PoolFeesStruct { amount: u64, trade_fee_numerator: u64, has_referral: bool, - has_partner: bool, ) -> Result { let (amount, trading_fee) = PoolFeesStruct::get_excluded_fee_amount(trade_fee_numerator, amount)?; let SplitFees { - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, referral_fee, - partner_fee, - } = self.split_fees(trading_fee, has_referral, has_partner)?; + } = self.split_fees(trading_fee, has_referral)?; Ok(FeeOnAmountResult { amount, - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, - partner_fee, referral_fee, }) } @@ -237,12 +237,7 @@ impl PoolFeesStruct { Ok((included_fee_amount, fee_amount)) } - pub fn split_fees( - &self, - fee_amount: u64, - has_referral: bool, - has_partner: bool, - ) -> Result { + pub fn split_fees(&self, fee_amount: u64, has_referral: bool) -> Result { let protocol_fee = safe_mul_div_cast_u64( fee_amount, self.protocol_fee_percent.into(), @@ -253,23 +248,23 @@ impl PoolFeesStruct { // update trading fee let trading_fee: u64 = fee_amount.safe_sub(protocol_fee)?; - let referral_fee = if has_referral { - safe_mul_div_cast_u64( - protocol_fee, - self.referral_fee_percent.into(), - 100, + let (compounding_fee, claiming_fee) = if self.compounding_fee_bps > 0 { + let compounding_fee: u64 = safe_mul_div_cast_u64( + trading_fee, + self.compounding_fee_bps.into(), + MAX_BASIS_POINT.into(), Rounding::Down, - )? + )?; + let claiming_fee = trading_fee.safe_sub(compounding_fee)?; + (compounding_fee, claiming_fee) } else { - 0 + (0, trading_fee) }; - let protocol_fee_after_referral_fee = protocol_fee.safe_sub(referral_fee)?; - - let partner_fee = if has_partner && self.partner_fee_percent > 0 { + let referral_fee = if has_referral { safe_mul_div_cast_u64( - protocol_fee_after_referral_fee, - self.partner_fee_percent.into(), + protocol_fee, + self.referral_fee_percent.into(), 100, Rounding::Down, )? @@ -277,13 +272,13 @@ impl PoolFeesStruct { 0 }; - let protocol_fee = protocol_fee_after_referral_fee.safe_sub(partner_fee)?; + let protocol_fee = protocol_fee.safe_sub(referral_fee)?; Ok(SplitFees { - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, referral_fee, - partner_fee, }) } } @@ -407,13 +402,10 @@ pub struct FeeMode { impl FeeMode { pub fn get_fee_mode( - collect_fee_mode: u8, + collect_fee_mode: CollectFeeMode, trade_direction: TradeDirection, has_referral: bool, - ) -> Result { - let collect_fee_mode = CollectFeeMode::try_from(collect_fee_mode) - .map_err(|_| PoolError::InvalidCollectFeeMode)?; - + ) -> FeeMode { let (fees_on_input, fees_on_token_a) = match (collect_fee_mode, trade_direction) { // When collecting fees on output token (CollectFeeMode::BothToken, TradeDirection::AtoB) => (false, false), @@ -422,104 +414,23 @@ impl FeeMode { // When collecting fees on tokenB (CollectFeeMode::OnlyB, TradeDirection::AtoB) => (false, false), (CollectFeeMode::OnlyB, TradeDirection::BtoA) => (true, false), + + // when collecting fees on compounding + (CollectFeeMode::Compounding, TradeDirection::AtoB) => (false, false), + (CollectFeeMode::Compounding, TradeDirection::BtoA) => (true, false), }; - Ok(FeeMode { + FeeMode { fees_on_input, fees_on_token_a, has_referral, - }) + } } } pub struct SplitFees { - pub trading_fee: u64, + pub claiming_fee: u64, + pub compounding_fee: u64, pub protocol_fee: u64, pub referral_fee: u64, - pub partner_fee: u64, -} - -#[cfg(test)] -mod tests { - use crate::{params::swap::TradeDirection, state::CollectFeeMode}; - - use super::*; - - #[test] - fn test_fee_mode_output_token_a_to_b() { - let fee_mode = - FeeMode::get_fee_mode(CollectFeeMode::BothToken as u8, TradeDirection::AtoB, false) - .unwrap(); - - assert_eq!(fee_mode.fees_on_input, false); - assert_eq!(fee_mode.fees_on_token_a, false); - assert_eq!(fee_mode.has_referral, false); - } - - #[test] - fn test_fee_mode_output_token_b_to_a() { - let fee_mode = - FeeMode::get_fee_mode(CollectFeeMode::BothToken as u8, TradeDirection::BtoA, true) - .unwrap(); - - assert_eq!(fee_mode.fees_on_input, false); - assert_eq!(fee_mode.fees_on_token_a, true); - assert_eq!(fee_mode.has_referral, true); - } - - #[test] - fn test_fee_mode_quote_token_a_to_b() { - let fee_mode = - FeeMode::get_fee_mode(CollectFeeMode::OnlyB as u8, TradeDirection::AtoB, false) - .unwrap(); - - assert_eq!(fee_mode.fees_on_input, false); - assert_eq!(fee_mode.fees_on_token_a, false); - assert_eq!(fee_mode.has_referral, false); - } - - #[test] - fn test_fee_mode_quote_token_b_to_a() { - let fee_mode = - FeeMode::get_fee_mode(CollectFeeMode::OnlyB as u8, TradeDirection::BtoA, true).unwrap(); - - assert_eq!(fee_mode.fees_on_input, true); - assert_eq!(fee_mode.fees_on_token_a, false); - assert_eq!(fee_mode.has_referral, true); - } - - #[test] - fn test_invalid_collect_fee_mode() { - let result = FeeMode::get_fee_mode( - 2, // Invalid mode - TradeDirection::BtoA, - false, - ); - - assert!(result.is_err()); - } - - #[test] - fn test_fee_mode_default() { - let fee_mode = FeeMode::default(); - - assert_eq!(fee_mode.fees_on_input, false); - assert_eq!(fee_mode.fees_on_token_a, false); - assert_eq!(fee_mode.has_referral, false); - } - - // Property-based test to ensure consistent behavior - #[test] - fn test_fee_mode_properties() { - // When trading BaseToQuote, fees should never be on input - let fee_mode = - FeeMode::get_fee_mode(CollectFeeMode::OnlyB as u8, TradeDirection::AtoB, true).unwrap(); - assert_eq!(fee_mode.fees_on_input, false); - - // When using QuoteToken mode, base_token should always be false - let fee_mode = - FeeMode::get_fee_mode(CollectFeeMode::OnlyB as u8, TradeDirection::BtoA, false) - .unwrap(); - assert_eq!(fee_mode.fees_on_token_a, false); - } } diff --git a/programs/cp-amm/src/state/operator.rs b/programs/cp-amm/src/state/operator.rs index 19e3892e..b4a83725 100644 --- a/programs/cp-amm/src/state/operator.rs +++ b/programs/cp-amm/src/state/operator.rs @@ -27,6 +27,7 @@ pub enum OperatorPermission { UpdatePoolFees, // 8 ClaimProtocolFee, // 9 ZapProtocolFee, // 10 + FixPool, // 11 } #[account(zero_copy)] diff --git a/programs/cp-amm/src/state/pool.rs b/programs/cp-amm/src/state/pool.rs index 24455f15..48cf9a9e 100644 --- a/programs/cp-amm/src/state/pool.rs +++ b/programs/cp-amm/src/state/pool.rs @@ -10,14 +10,10 @@ use crate::base_fee::{BaseFeeHandlerBuilder, UpdateCliffFeeNumerator}; use crate::constants::fee::{ get_max_fee_numerator, CURRENT_POOL_VERSION, MAX_FEE_NUMERATOR_POST_UPDATE, }; -use crate::curve::{get_delta_amount_b_unsigned_unchecked, get_next_sqrt_price_from_output}; +use crate::safe_math::SafeCast; use crate::state::fee::{FeeOnAmountResult, SplitFees}; use crate::{ constants::{LIQUIDITY_SCALE, NUM_REWARDS, REWARD_INDEX_0, REWARD_INDEX_1, REWARD_RATE_SCALE}, - curve::{ - get_delta_amount_a_unsigned, get_delta_amount_a_unsigned_unchecked, - get_delta_amount_b_unsigned, get_next_sqrt_price_from_input, - }, params::swap::TradeDirection, safe_math::SafeMath, state::{ @@ -28,7 +24,10 @@ use crate::{ utils_math::{safe_mul_shr_cast, safe_shl_div_cast}, PoolError, }; -use crate::{BaseFeeUpdateMode, DynamicFeeUpdateMode, UpdatePoolFeesParameters}; +use crate::{ + BaseFeeUpdateMode, CompoundingLiquidity, ConcentratedLiquidity, DynamicFeeUpdateMode, + LiquidityHandler, UpdatePoolFeesParameters, +}; use super::fee::FeeMode; @@ -49,6 +48,9 @@ pub enum CollectFeeMode { BothToken, /// Only token B, we just need token B, because if user want to collect fee in token A, they just need to flip order of tokens OnlyB, + /// In the compounding, a percentage fees will be accumulated in liquidity, while remainings are used for clamining, fees are always be in token B + /// Pool with compounding won't have price range, instead of using constant-product formula: x * y = constant + Compounding, } /// pool status @@ -95,7 +97,7 @@ pub enum PoolType { AnchorDeserialize, AnchorSerialize, )] -pub enum PoolVersion { +pub enum LayoutVersion { V0, // 0 V1, // 1 } @@ -115,20 +117,18 @@ pub struct Pool { pub token_b_vault: Pubkey, /// Whitelisted vault to be able to buy pool before activation_point pub whitelisted_vault: Pubkey, - /// partner - pub partner: Pubkey, + /// padding, previously partner pubkey, be careful when using this field + pub padding_0: [u8; 32], /// liquidity share pub liquidity: u128, /// padding, previous reserve amount, be careful to use that field - pub _padding: u128, + pub padding_1: u128, /// protocol a fee pub protocol_a_fee: u64, /// protocol b fee pub protocol_b_fee: u64, - /// partner a fee - pub partner_a_fee: u64, - /// partner b fee - pub partner_b_fee: u64, + // padding for future use + pub padding_2: u128, /// min price pub sqrt_min_price: u128, /// max price @@ -149,10 +149,10 @@ pub struct Pool { pub collect_fee_mode: u8, /// pool type pub pool_type: u8, - /// pool version, 0: max_fee is still capped at 50%, 1: max_fee is capped at 99% - pub version: u8, + /// pool fee version, 0: max_fee is still capped at 50%, 1: max_fee is capped at 99% + pub fee_version: u8, /// padding - pub _padding_0: u8, + pub padding_3: u8, /// cumulative pub fee_a_per_liquidity: [u8; 32], // U256 /// cumulative @@ -163,8 +163,16 @@ pub struct Pool { pub metrics: PoolMetrics, /// pool creator pub creator: Pubkey, + /// token a amount + pub token_a_amount: u64, + /// token b amount + pub token_b_amount: u64, + /// layout version: version 0: haven't track token_a_amount and token_b_amount, version 1: track token_a_amount and token_b_amount + pub layout_version: u8, + /// Padding for further use + pub padding_4: [u8; 7], /// Padding for further use - pub _padding_1: [u64; 6], + pub padding_5: [u64; 3], /// Farming reward information pub reward_infos: [RewardInfo; NUM_REWARDS], } @@ -178,8 +186,7 @@ pub struct PoolMetrics { pub total_lp_b_fee: u128, pub total_protocol_a_fee: u64, pub total_protocol_b_fee: u64, - pub total_partner_a_fee: u64, - pub total_partner_b_fee: u64, + pub padding_0: [u64; 2], pub total_position: u64, pub padding: u64, } @@ -198,17 +205,14 @@ impl PoolMetrics { &mut self, lp_fee: u64, protocol_fee: u64, - partner_fee: u64, is_token_a: bool, ) -> Result<()> { if is_token_a { self.total_lp_a_fee = self.total_lp_a_fee.safe_add(lp_fee.into())?; self.total_protocol_a_fee = self.total_protocol_a_fee.safe_add(protocol_fee)?; - self.total_partner_a_fee = self.total_partner_a_fee.safe_add(partner_fee)?; } else { self.total_lp_b_fee = self.total_lp_b_fee.safe_add(lp_fee.into())?; self.total_protocol_b_fee = self.total_protocol_b_fee.safe_add(protocol_fee)?; - self.total_partner_b_fee = self.total_partner_b_fee.safe_add(partner_fee)?; } Ok(()) @@ -387,7 +391,6 @@ impl Pool { token_a_vault: Pubkey, token_b_vault: Pubkey, whitelisted_vault: Pubkey, - partner: Pubkey, sqrt_min_price: u128, sqrt_max_price: u128, sqrt_price: u128, @@ -398,6 +401,8 @@ impl Pool { liquidity: u128, collect_fee_mode: u8, pool_type: u8, + token_a_amount: u64, + token_b_amount: u64, ) { self.creator = creator; self.pool_fees = pool_fees; @@ -406,7 +411,6 @@ impl Pool { self.token_a_vault = token_a_vault; self.token_b_vault = token_b_vault; self.whitelisted_vault = whitelisted_vault; - self.partner = partner; self.sqrt_min_price = sqrt_min_price; self.sqrt_max_price = sqrt_max_price; self.activation_point = activation_point; @@ -417,7 +421,10 @@ impl Pool { self.sqrt_price = sqrt_price; self.collect_fee_mode = collect_fee_mode; self.pool_type = pool_type; - self.version = CURRENT_POOL_VERSION; // still use v0 now, after notify integrators will pump to v1 to allow higher fee numerator constraint + self.fee_version = CURRENT_POOL_VERSION; // still use v0 now, after notify integrators will pump to v1 to allow higher fee numerator constraint + self.token_a_amount = token_a_amount; + self.token_b_amount = token_b_amount; + self.layout_version = LayoutVersion::V1.into(); } pub fn pool_reward_initialized(&self) -> bool { @@ -432,11 +439,13 @@ impl Pool { current_point: u64, ) -> Result { let mut actual_protocol_fee = 0; - let mut actual_trading_fee = 0; + let mut actual_compounding_fee = 0; + let mut actual_claiming_fee = 0; let mut actual_referral_fee = 0; - let mut actual_partner_fee = 0; - let max_fee_numerator = get_max_fee_numerator(self.version)?; + let liquidity_handler = self.get_liquidity_handler()?; + + let max_fee_numerator = get_max_fee_numerator(self.fee_version)?; let included_fee_amount_out = if fee_mode.fees_on_input { amount_out @@ -456,18 +465,18 @@ impl Pool { PoolFeesStruct::get_included_fee_amount(trade_fee_numerator, amount_out)?; let SplitFees { - trading_fee, + compounding_fee, + claiming_fee, protocol_fee, referral_fee, - partner_fee, } = self .pool_fees - .split_fees(fee_amount, fee_mode.has_referral, self.has_partner())?; + .split_fees(fee_amount, fee_mode.has_referral)?; actual_protocol_fee = protocol_fee; - actual_trading_fee = trading_fee; + actual_claiming_fee = claiming_fee; + actual_compounding_fee = compounding_fee; actual_referral_fee = referral_fee; - actual_partner_fee = partner_fee; included_fee_amount_out }; @@ -476,8 +485,12 @@ impl Pool { input_amount, next_sqrt_price, } = match trade_direction { - TradeDirection::AtoB => self.calculate_a_to_b_from_amount_out(included_fee_amount_out), - TradeDirection::BtoA => self.calculate_b_to_a_from_amount_out(included_fee_amount_out), + TradeDirection::AtoB => { + liquidity_handler.calculate_a_to_b_from_amount_out(included_fee_amount_out) + } + TradeDirection::BtoA => { + liquidity_handler.calculate_b_to_a_from_amount_out(included_fee_amount_out) + } }?; let included_fee_input_amount = if fee_mode.fees_on_input { @@ -496,18 +509,18 @@ impl Pool { PoolFeesStruct::get_included_fee_amount(trade_fee_numerator, input_amount)?; let SplitFees { - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, referral_fee, - partner_fee, } = self .pool_fees - .split_fees(fee_amount, fee_mode.has_referral, self.has_partner())?; + .split_fees(fee_amount, fee_mode.has_referral)?; actual_protocol_fee = protocol_fee; - actual_trading_fee = trading_fee; + actual_claiming_fee = claiming_fee; + actual_compounding_fee = compounding_fee; actual_referral_fee = referral_fee; - actual_partner_fee = partner_fee; included_fee_input_amount } else { @@ -520,9 +533,9 @@ impl Pool { excluded_fee_input_amount: input_amount, output_amount: amount_out, next_sqrt_price, - trading_fee: actual_trading_fee, + claiming_fee: actual_claiming_fee, + compounding_fee: actual_compounding_fee, protocol_fee: actual_protocol_fee, - partner_fee: actual_partner_fee, referral_fee: actual_referral_fee, }) } @@ -535,11 +548,13 @@ impl Pool { current_point: u64, ) -> Result { let mut actual_protocol_fee = 0; - let mut actual_trading_fee = 0; + let mut actual_claiming_fee = 0; + let mut actual_compounding_fee = 0; let mut actual_referral_fee = 0; - let mut actual_partner_fee = 0; - let max_fee_numerator = get_max_fee_numerator(self.version)?; + let liquidity_handler = self.get_liquidity_handler()?; + + let max_fee_numerator = get_max_fee_numerator(self.fee_version)?; let trade_fee_numerator = self .pool_fees @@ -555,21 +570,20 @@ impl Pool { let mut actual_amount_in = if fee_mode.fees_on_input { let FeeOnAmountResult { amount, - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, - partner_fee, referral_fee, } = self.pool_fees.get_fee_on_amount( amount_in, trade_fee_numerator, fee_mode.has_referral, - self.has_partner(), )?; actual_protocol_fee = protocol_fee; - actual_trading_fee = trading_fee; + actual_claiming_fee = claiming_fee; + actual_compounding_fee = compounding_fee; actual_referral_fee = referral_fee; - actual_partner_fee = partner_fee; amount } else { @@ -581,8 +595,12 @@ impl Pool { output_amount, next_sqrt_price, } = match trade_direction { - TradeDirection::AtoB => self.calculate_a_to_b_from_partial_amount_in(actual_amount_in), - TradeDirection::BtoA => self.calculate_b_to_a_from_partial_amount_in(actual_amount_in), + TradeDirection::AtoB => { + liquidity_handler.calculate_a_to_b_from_partial_amount_in(actual_amount_in) + } + TradeDirection::BtoA => { + liquidity_handler.calculate_b_to_a_from_partial_amount_in(actual_amount_in) + } }?; let included_fee_input_amount = if amount_left > 0 { @@ -604,20 +622,18 @@ impl Pool { PoolFeesStruct::get_included_fee_amount(trade_fee_numerator, actual_amount_in)?; let SplitFees { - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, referral_fee, - partner_fee, - } = self.pool_fees.split_fees( - fee_amount, - fee_mode.has_referral, - self.has_partner(), - )?; + } = self + .pool_fees + .split_fees(fee_amount, fee_mode.has_referral)?; actual_protocol_fee = protocol_fee; - actual_trading_fee = trading_fee; + actual_claiming_fee = claiming_fee; + actual_compounding_fee = compounding_fee; actual_referral_fee = referral_fee; - actual_partner_fee = partner_fee; included_fee_amount_in } else { @@ -632,21 +648,20 @@ impl Pool { } else { let FeeOnAmountResult { amount, - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, - partner_fee, referral_fee, } = self.pool_fees.get_fee_on_amount( output_amount, trade_fee_numerator, fee_mode.has_referral, - self.has_partner(), )?; actual_protocol_fee = protocol_fee; - actual_trading_fee = trading_fee; + actual_claiming_fee = claiming_fee; + actual_compounding_fee = compounding_fee; actual_referral_fee = referral_fee; - actual_partner_fee = partner_fee; amount }; @@ -657,9 +672,9 @@ impl Pool { amount_left, output_amount: actual_amount_out, next_sqrt_price, - trading_fee: actual_trading_fee, + claiming_fee: actual_claiming_fee, + compounding_fee: actual_compounding_fee, protocol_fee: actual_protocol_fee, - partner_fee: actual_partner_fee, referral_fee: actual_referral_fee, }) } @@ -672,11 +687,13 @@ impl Pool { current_point: u64, ) -> Result { let mut actual_protocol_fee = 0; - let mut actual_trading_fee = 0; + let mut actual_claiming_fee = 0; + let mut actual_compounding_fee = 0; let mut actual_referral_fee = 0; - let mut actual_partner_fee = 0; - let max_fee_numerator = get_max_fee_numerator(self.version)?; + let liquidity_handler = self.get_liquidity_handler()?; + + let max_fee_numerator = get_max_fee_numerator(self.fee_version)?; // We can compute the trade_fee_numerator here. Instead of separately for amount_in, and amount_out. // This is because FeeRateLimiter (fee rate scale based on amount) only applied when fee_mode.fees_on_input @@ -696,21 +713,20 @@ impl Pool { let actual_amount_in = if fee_mode.fees_on_input { let FeeOnAmountResult { amount, - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, - partner_fee, referral_fee, } = self.pool_fees.get_fee_on_amount( amount_in, trade_fee_numerator, fee_mode.has_referral, - self.has_partner(), )?; + actual_claiming_fee = claiming_fee; + actual_compounding_fee = compounding_fee; actual_protocol_fee = protocol_fee; - actual_trading_fee = trading_fee; actual_referral_fee = referral_fee; - actual_partner_fee = partner_fee; amount } else { @@ -722,8 +738,12 @@ impl Pool { next_sqrt_price, amount_left, } = match trade_direction { - TradeDirection::AtoB => self.calculate_a_to_b_from_amount_in(actual_amount_in), - TradeDirection::BtoA => self.calculate_b_to_a_from_amount_in(actual_amount_in), + TradeDirection::AtoB => { + liquidity_handler.calculate_a_to_b_from_amount_in(actual_amount_in) + } + TradeDirection::BtoA => { + liquidity_handler.calculate_b_to_a_from_amount_in(actual_amount_in) + } }?; let actual_amount_out = if fee_mode.fees_on_input { @@ -731,21 +751,20 @@ impl Pool { } else { let FeeOnAmountResult { amount, - trading_fee, + claiming_fee, + compounding_fee, protocol_fee, - partner_fee, referral_fee, } = self.pool_fees.get_fee_on_amount( output_amount, trade_fee_numerator, fee_mode.has_referral, - self.has_partner(), )?; + actual_claiming_fee = claiming_fee; + actual_compounding_fee = compounding_fee; actual_protocol_fee = protocol_fee; - actual_trading_fee = trading_fee; actual_referral_fee = referral_fee; - actual_partner_fee = partner_fee; amount }; @@ -756,263 +775,94 @@ impl Pool { excluded_fee_input_amount: actual_amount_in, output_amount: actual_amount_out, next_sqrt_price, - trading_fee: actual_trading_fee, + claiming_fee: actual_claiming_fee, + compounding_fee: actual_compounding_fee, protocol_fee: actual_protocol_fee, - partner_fee: actual_partner_fee, referral_fee: actual_referral_fee, }) } - pub fn calculate_b_to_a_from_amount_out( - &self, - amount_out: u64, - ) -> Result { - let next_sqrt_price = - get_next_sqrt_price_from_output(self.sqrt_price, self.liquidity, amount_out, false)?; - - if next_sqrt_price > self.sqrt_max_price { - return Err(PoolError::PriceRangeViolation.into()); - } - - let in_amount = get_delta_amount_b_unsigned( - self.sqrt_price, - next_sqrt_price, - self.liquidity, - Rounding::Up, - )?; - - Ok(SwapAmountFromOutput { - input_amount: in_amount, - next_sqrt_price, - }) - } - - pub fn calculate_a_to_b_from_amount_out( - &self, - amount_out: u64, - ) -> Result { - let next_sqrt_price = - get_next_sqrt_price_from_output(self.sqrt_price, self.liquidity, amount_out, true)?; - - if next_sqrt_price < self.sqrt_min_price { - return Err(PoolError::PriceRangeViolation.into()); - } - - let in_amount = get_delta_amount_a_unsigned( - next_sqrt_price, - self.sqrt_price, - self.liquidity, - Rounding::Up, - )?; - - Ok(SwapAmountFromOutput { - input_amount: in_amount, - next_sqrt_price, - }) - } - - pub fn calculate_b_to_a_from_partial_amount_in( - &self, - amount_in: u64, - ) -> Result { - let max_amount_in = get_delta_amount_b_unsigned_unchecked( - self.sqrt_price, - self.sqrt_max_price, - self.liquidity, - Rounding::Up, - )?; - - let (consumed_in_amount, next_sqrt_price) = if U256::from(amount_in) >= max_amount_in { - ( - max_amount_in - .try_into() - .map_err(|_| PoolError::TypeCastFailed)?, - self.sqrt_max_price, - ) - } else { - let next_sqrt_price = - get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, false)?; - (amount_in, next_sqrt_price) - }; - - let output_amount = get_delta_amount_a_unsigned( - self.sqrt_price, - next_sqrt_price, - self.liquidity, - Rounding::Down, - )?; - - let amount_left = amount_in.safe_sub(consumed_in_amount)?; - - Ok(SwapAmountFromInput { - output_amount, - next_sqrt_price, - amount_left, - }) - } - - pub fn calculate_a_to_b_from_partial_amount_in( - &self, - amount_in: u64, - ) -> Result { - let max_amount_in = get_delta_amount_a_unsigned_unchecked( - self.sqrt_min_price, - self.sqrt_price, - self.liquidity, - Rounding::Up, - )?; - - let (consumed_in_amount, next_sqrt_price) = if U256::from(amount_in) >= max_amount_in { - ( - max_amount_in - .try_into() - .map_err(|_| PoolError::TypeCastFailed)?, - self.sqrt_min_price, - ) - } else { - let next_sqrt_price = - get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, true)?; - (amount_in, next_sqrt_price) - }; - - let output_amount = get_delta_amount_b_unsigned( - next_sqrt_price, - self.sqrt_price, - self.liquidity, - Rounding::Down, - )?; - - let amount_left = amount_in.safe_sub(consumed_in_amount)?; - - Ok(SwapAmountFromInput { - output_amount, - next_sqrt_price, - amount_left, - }) - } - - fn calculate_a_to_b_from_amount_in(&self, amount_in: u64) -> Result { - // finding new target price - let next_sqrt_price = - get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, true)?; - - if next_sqrt_price < self.sqrt_min_price { - return Err(PoolError::PriceRangeViolation.into()); - } - - // finding output amount - let output_amount = get_delta_amount_b_unsigned( - next_sqrt_price, - self.sqrt_price, - self.liquidity, - Rounding::Down, - )?; - - Ok(SwapAmountFromInput { - output_amount, - next_sqrt_price, - amount_left: 0, - }) - } - - fn calculate_b_to_a_from_amount_in(&self, amount_in: u64) -> Result { - // finding new target price - let next_sqrt_price = - get_next_sqrt_price_from_input(self.sqrt_price, self.liquidity, amount_in, false)?; - - if next_sqrt_price > self.sqrt_max_price { - return Err(PoolError::PriceRangeViolation.into()); - } - // finding output amount - let output_amount = get_delta_amount_a_unsigned( - self.sqrt_price, - next_sqrt_price, - self.liquidity, - Rounding::Down, - )?; - - Ok(SwapAmountFromInput { - output_amount, - next_sqrt_price, - amount_left: 0, - }) - } - pub fn apply_swap_result( &mut self, swap_result: &SwapResult2, fee_mode: &FeeMode, + trade_direction: TradeDirection, current_timestamp: u64, ) -> Result<()> { let &SwapResult2 { - trading_fee: lp_fee, + excluded_fee_input_amount, + output_amount: excluded_fee_output_amount, + compounding_fee, + claiming_fee, next_sqrt_price, protocol_fee, - partner_fee, + referral_fee, .. } = swap_result; let old_sqrt_price = self.sqrt_price; - self.sqrt_price = next_sqrt_price; - let fee_per_token_stored = shl_div_256(lp_fee.into(), self.liquidity, LIQUIDITY_SCALE) - .ok_or_else(|| PoolError::MathOverflow)?; + let fee_per_token_stored = + shl_div_256(claiming_fee.into(), self.liquidity, LIQUIDITY_SCALE) + .ok_or_else(|| PoolError::MathOverflow)?; + + let trading_fee = claiming_fee.safe_add(compounding_fee)?; if fee_mode.fees_on_token_a { - self.partner_a_fee = self.partner_a_fee.safe_add(partner_fee)?; self.protocol_a_fee = self.protocol_a_fee.safe_add(protocol_fee)?; self.fee_a_per_liquidity = self .fee_a_per_liquidity() .safe_add(fee_per_token_stored)? .to_le_bytes(); + // TODO should metrics store trading fee or claiming fee? self.metrics - .accumulate_fee(lp_fee, protocol_fee, partner_fee, true)?; + .accumulate_fee(trading_fee, protocol_fee, true)?; } else { - self.partner_b_fee = self.partner_b_fee.safe_add(partner_fee)?; self.protocol_b_fee = self.protocol_b_fee.safe_add(protocol_fee)?; self.fee_b_per_liquidity = self .fee_b_per_liquidity() .safe_add(fee_per_token_stored)? .to_le_bytes(); + // TODO should metrics store trading fee or claiming fee? self.metrics - .accumulate_fee(lp_fee, protocol_fee, partner_fee, false)?; + .accumulate_fee(trading_fee, protocol_fee, false)?; } - self.update_post_swap(old_sqrt_price, current_timestamp)?; + let included_fee_output_amount = if fee_mode.fees_on_input { + excluded_fee_output_amount + } else { + excluded_fee_output_amount + .safe_add(trading_fee)? + .safe_add(protocol_fee)? + .safe_add(referral_fee)? + }; - Ok(()) - } + if trade_direction == TradeDirection::AtoB { + self.token_a_amount = self.token_a_amount.safe_add(excluded_fee_input_amount)?; + self.token_b_amount = self.token_b_amount.safe_sub(included_fee_output_amount)?; + } else { + self.token_b_amount = self.token_b_amount.safe_add(excluded_fee_input_amount)?; + self.token_a_amount = self.token_a_amount.safe_sub(included_fee_output_amount)?; + } - pub fn get_amounts_for_modify_liquidity( - &self, - liquidity_delta: u128, - round: Rounding, - ) -> Result { - // finding output amount - let token_a_amount = get_delta_amount_a_unsigned( - self.sqrt_price, - self.sqrt_max_price, - liquidity_delta, - round, - )?; + // compounding fees are always accumulated in token b + self.token_b_amount = self.token_b_amount.safe_add(compounding_fee)?; - let token_b_amount = get_delta_amount_b_unsigned( - self.sqrt_min_price, - self.sqrt_price, - liquidity_delta, - round, - )?; + let liquidity_handler = self.get_liquidity_handler()?; + let next_sqrt_price = liquidity_handler.get_next_sqrt_price(next_sqrt_price)?; + self.sqrt_price = next_sqrt_price; - Ok(ModifyLiquidityResult { - token_a_amount, - token_b_amount, - }) + self.update_post_swap(old_sqrt_price, current_timestamp)?; + + Ok(()) } pub fn apply_add_liquidity( &mut self, position: &mut Position, liquidity_delta: u128, + token_a_amount: u64, + token_b_amount: u64, ) -> Result<()> { // update current fee for position position.update_fee(self.fee_a_per_liquidity(), self.fee_b_per_liquidity())?; @@ -1020,7 +870,10 @@ impl Pool { // add liquidity position.add_liquidity(liquidity_delta)?; + // update liquidity and reserve self.liquidity = self.liquidity.safe_add(liquidity_delta)?; + self.token_a_amount = self.token_a_amount.safe_add(token_a_amount)?; + self.token_b_amount = self.token_b_amount.safe_add(token_b_amount)?; Ok(()) } @@ -1029,6 +882,8 @@ impl Pool { &mut self, position: &mut Position, liquidity_delta: u128, + token_a_amount: u64, + token_b_amount: u64, ) -> Result<()> { // update current fee for position position.update_fee(self.fee_a_per_liquidity(), self.fee_b_per_liquidity())?; @@ -1036,7 +891,10 @@ impl Pool { // remove liquidity position.remove_unlocked_liquidity(liquidity_delta)?; + // update liquidity and reserve self.liquidity = self.liquidity.safe_sub(liquidity_delta)?; + self.token_a_amount = self.token_a_amount.safe_sub(token_a_amount)?; + self.token_b_amount = self.token_b_amount.safe_sub(token_b_amount)?; Ok(()) } @@ -1153,29 +1011,6 @@ impl Pool { }) } - #[cfg(test)] - pub fn get_max_amount_in(&self, trade_direction: TradeDirection) -> Result { - let amount = match trade_direction { - TradeDirection::AtoB => get_delta_amount_a_unsigned_unchecked( - self.sqrt_min_price, - self.sqrt_price, - self.liquidity, - Rounding::Down, - )?, - TradeDirection::BtoA => get_delta_amount_b_unsigned_unchecked( - self.sqrt_price, - self.sqrt_max_price, - self.liquidity, - Rounding::Down, - )?, - }; - if amount > U256::from(u64::MAX) { - Ok(u64::MAX) - } else { - Ok(amount.try_into().unwrap()) - } - } - pub fn update_pre_swap(&mut self, current_timestamp: u64) -> Result<()> { if self.pool_fees.dynamic_fee.is_dynamic_fee_enable() { self.pool_fees @@ -1227,18 +1062,6 @@ impl Pool { Ok((token_a_amount, token_b_amount)) } - pub fn claim_partner_fee( - &mut self, - max_amount_a: u64, - max_amount_b: u64, - ) -> Result<(u64, u64)> { - let token_a_amount = self.partner_a_fee.min(max_amount_a); - let token_b_amount = self.partner_b_fee.min(max_amount_b); - self.partner_a_fee = self.partner_a_fee.safe_sub(token_a_amount)?; - self.partner_b_fee = self.partner_b_fee.safe_sub(token_b_amount)?; - Ok((token_a_amount, token_b_amount)) - } - /// Update the rewards per token stored. pub fn update_rewards(&mut self, current_time: u64) -> Result<()> { for reward_idx in 0..NUM_REWARDS { @@ -1277,26 +1100,16 @@ impl Pool { signer == self.creator && reward_index == 0 } - pub fn has_partner(&self) -> bool { - self.partner != Pubkey::default() - } - - pub fn get_reserves_amount(&self) -> Result<(u64, u64)> { - let reserve_b_amount = get_delta_amount_b_unsigned( - self.sqrt_min_price, - self.sqrt_price, - self.liquidity, - Rounding::Down, - )?; - - let reserve_a_amount = get_delta_amount_a_unsigned( - self.sqrt_price, - self.sqrt_max_price, - self.liquidity, - Rounding::Down, - )?; - - Ok((reserve_a_amount, reserve_b_amount)) + pub fn update_layout_version_if_needed(&mut self) -> Result<()> { + let layout_version: LayoutVersion = self.layout_version.safe_cast()?; + if layout_version == LayoutVersion::V0 { + let liquidity_handler = self.get_liquidity_handler()?; + let (token_a_amount, token_b_amount) = liquidity_handler.get_reserves_amount()?; + self.token_a_amount = token_a_amount; + self.token_b_amount = token_b_amount; + self.layout_version = LayoutVersion::V1.into(); + } + Ok(()) } pub fn validate_and_update_pool_fees( @@ -1372,28 +1185,22 @@ impl Pool { } Ok(()) } -} - -/// Encodes all results of swapping -#[derive(Debug, PartialEq, AnchorDeserialize, AnchorSerialize)] -pub struct SwapResult { - pub output_amount: u64, - pub next_sqrt_price: u128, - pub lp_fee: u64, - pub protocol_fee: u64, - pub partner_fee: u64, - pub referral_fee: u64, -} -impl From for SwapResult { - fn from(value: SwapResult2) -> Self { - Self { - output_amount: value.output_amount, - next_sqrt_price: value.next_sqrt_price, - lp_fee: value.trading_fee, - protocol_fee: value.protocol_fee, - partner_fee: value.partner_fee, - referral_fee: value.referral_fee, + pub fn get_liquidity_handler(&self) -> Result> { + let collect_fee_mode: CollectFeeMode = self.collect_fee_mode.safe_cast()?; + if collect_fee_mode == CollectFeeMode::Compounding { + Ok(Box::new(CompoundingLiquidity { + token_a_amount: self.token_a_amount, + token_b_amount: self.token_b_amount, + liquidity: self.liquidity, + })) + } else { + Ok(Box::new(ConcentratedLiquidity { + sqrt_max_price: self.sqrt_max_price, + sqrt_min_price: self.sqrt_min_price, + liquidity: self.liquidity, + sqrt_price: self.sqrt_price, + })) } } } @@ -1406,27 +1213,21 @@ pub struct SwapResult2 { pub amount_left: u64, pub output_amount: u64, pub next_sqrt_price: u128, - pub trading_fee: u64, + pub claiming_fee: u64, pub protocol_fee: u64, - pub partner_fee: u64, + pub compounding_fee: u64, // previous is partner_fee, now will be reused for compounding_fee pub referral_fee: u64, } pub struct SwapAmountFromInput { - output_amount: u64, - next_sqrt_price: u128, - amount_left: u64, + pub output_amount: u64, + pub next_sqrt_price: u128, + pub amount_left: u64, } pub struct SwapAmountFromOutput { - input_amount: u64, - next_sqrt_price: u128, -} - -#[derive(Debug, PartialEq)] -pub struct ModifyLiquidityResult { - pub token_a_amount: u64, - pub token_b_amount: u64, + pub input_amount: u64, + pub next_sqrt_price: u128, } #[derive(AnchorSerialize, AnchorDeserialize, Debug, PartialEq)] diff --git a/programs/cp-amm/src/tests/mod.rs b/programs/cp-amm/src/tests/mod.rs index ac494a8c..8459e00e 100644 --- a/programs/cp-amm/src/tests/mod.rs +++ b/programs/cp-amm/src/tests/mod.rs @@ -42,3 +42,9 @@ mod test_base_fee_serde; #[cfg(test)] mod test_split_inner_vesting; + +#[cfg(test)] +mod test_collect_fee_mode; + +#[cfg(test)] +mod test_liquidity_compounding; diff --git a/programs/cp-amm/src/tests/test_collect_fee_mode.rs b/programs/cp-amm/src/tests/test_collect_fee_mode.rs new file mode 100644 index 00000000..d9dddc51 --- /dev/null +++ b/programs/cp-amm/src/tests/test_collect_fee_mode.rs @@ -0,0 +1,61 @@ +use crate::{ + params::swap::TradeDirection, + state::{fee::FeeMode, CollectFeeMode}, +}; + +#[test] +fn test_fee_mode_output_token_a_to_b() { + let fee_mode = FeeMode::get_fee_mode(CollectFeeMode::BothToken, TradeDirection::AtoB, false); + + assert_eq!(fee_mode.fees_on_input, false); + assert_eq!(fee_mode.fees_on_token_a, false); + assert_eq!(fee_mode.has_referral, false); +} + +#[test] +fn test_fee_mode_output_token_b_to_a() { + let fee_mode = FeeMode::get_fee_mode(CollectFeeMode::BothToken, TradeDirection::BtoA, true); + + assert_eq!(fee_mode.fees_on_input, false); + assert_eq!(fee_mode.fees_on_token_a, true); + assert_eq!(fee_mode.has_referral, true); +} + +#[test] +fn test_fee_mode_quote_token_a_to_b() { + let fee_mode = FeeMode::get_fee_mode(CollectFeeMode::OnlyB, TradeDirection::AtoB, false); + + assert_eq!(fee_mode.fees_on_input, false); + assert_eq!(fee_mode.fees_on_token_a, false); + assert_eq!(fee_mode.has_referral, false); +} + +#[test] +fn test_fee_mode_quote_token_b_to_a() { + let fee_mode = FeeMode::get_fee_mode(CollectFeeMode::OnlyB, TradeDirection::BtoA, true); + + assert_eq!(fee_mode.fees_on_input, true); + assert_eq!(fee_mode.fees_on_token_a, false); + assert_eq!(fee_mode.has_referral, true); +} + +#[test] +fn test_fee_mode_default() { + let fee_mode = FeeMode::default(); + + assert_eq!(fee_mode.fees_on_input, false); + assert_eq!(fee_mode.fees_on_token_a, false); + assert_eq!(fee_mode.has_referral, false); +} + +// Property-based test to ensure consistent behavior +#[test] +fn test_fee_mode_properties() { + // When trading BaseToQuote, fees should never be on input + let fee_mode = FeeMode::get_fee_mode(CollectFeeMode::OnlyB, TradeDirection::AtoB, true); + assert_eq!(fee_mode.fees_on_input, false); + + // When using QuoteToken mode, base_token should always be false + let fee_mode = FeeMode::get_fee_mode(CollectFeeMode::OnlyB, TradeDirection::BtoA, false); + assert_eq!(fee_mode.fees_on_token_a, false); +} diff --git a/programs/cp-amm/src/tests/test_integration.rs b/programs/cp-amm/src/tests/test_integration.rs index 2db28799..4a3baa90 100644 --- a/programs/cp-amm/src/tests/test_integration.rs +++ b/programs/cp-amm/src/tests/test_integration.rs @@ -1,13 +1,16 @@ use crate::{ base_fee::fee_time_scheduler::PodAlignedFeeTimeScheduler, constants::{MAX_SQRT_PRICE, MIN_SQRT_PRICE}, + get_initial_pool_information, params::swap::TradeDirection, + safe_math::SafeCast, state::{ fee::{BaseFeeStruct, FeeMode, PoolFeesStruct}, - Pool, Position, + CollectFeeMode, Pool, Position, }, - tests::LIQUIDITY_MAX, + tests::{test_liquidity_compounding::get_sqrt_price_and_liquidity_from_amounts, LIQUIDITY_MAX}, u128x128_math::Rounding, + InitialPoolInformation, DEAD_LIQUIDITY, }; use proptest::{bool::ANY, prelude::*}; @@ -35,7 +38,6 @@ proptest! { let pool_fees = PoolFeesStruct { base_fee, //1% protocol_fee_percent: 20, - partner_fee_percent: 50, referral_fee_percent: 20, ..Default::default() }; @@ -78,10 +80,100 @@ proptest! { assert!(position.fee_a_pending <= reserve.amount_a); println!("{:?}", reserve); - println!("{:?}", position); - println!("{:?}", pool); + // println!("{:?}", position); + // println!("{:?}", pool); println!("swap_count {}", swap_count); } + + + #[test] + fn test_compounding_reserve_wont_loss( + a in 1..u32::MAX, + b in 1..u32::MAX, + has_referral in ANY, + amount_in_a in 1..=u32::MAX as u64, + amount_in_b in 1..=u32::MAX as u64, + ) { + let fee_scheduler = PodAlignedFeeTimeScheduler { + cliff_fee_numerator: 1_000_000, + ..Default::default() + }; + + let mut base_fee = BaseFeeStruct::default(); + let data = bytemuck::bytes_of(&fee_scheduler); + base_fee.base_fee_info.data.copy_from_slice(data); + + let pool_fees = PoolFeesStruct { + base_fee, //1% + protocol_fee_percent: 20, + referral_fee_percent: 20, + compounding_fee_bps: 5000, // 50% + ..Default::default() + }; + + let result = get_sqrt_price_and_liquidity_from_amounts(a.into(), b.into()); + if result.is_err() { + return Ok(()); + } + let (sqrt_price, liquidity) = result.unwrap(); + + let InitialPoolInformation { + token_a_amount, + token_b_amount, + initial_liquidity, + sqrt_price: _, + sqrt_min_price: _, + sqrt_max_price: _, + } = get_initial_pool_information( + CollectFeeMode::Compounding, + 0, + 0, + sqrt_price, + liquidity, + ).unwrap(); + + let mut pool = Pool { + pool_fees, + collect_fee_mode: CollectFeeMode::Compounding.into(), + token_a_amount, + token_b_amount, + liquidity, + ..Default::default() + }; + + let mut reserve = PoolReserve { amount_a: token_a_amount, amount_b: token_b_amount}; + + let mut position = Position{unlocked_liquidity: initial_liquidity, ..Default::default()}; + + let mut swap_count = 0; + for _i in 0..100 { + //random action + execute_add_liquidity(&mut reserve, &mut pool, &mut position, liquidity); + + if execute_swap_liquidity(&mut reserve, &mut pool, amount_in_a, has_referral, TradeDirection::AtoB){ + swap_count += 1; + } + + if execute_swap_liquidity(&mut reserve, &mut pool, amount_in_b, has_referral, TradeDirection::BtoA) { + swap_count += 1; + } + + + execute_remove_liquidity(&mut reserve, &mut pool, &mut position, liquidity/2); + } + + let total_liquidity = position.unlocked_liquidity; + execute_remove_liquidity(&mut reserve, &mut pool, &mut position, total_liquidity); + + assert!(pool.liquidity == DEAD_LIQUIDITY); + assert!(position.unlocked_liquidity == 0); + assert!(position.fee_b_pending <= reserve.amount_b); + assert!(position.fee_a_pending <= reserve.amount_a); + + println!("swap_count {} {:?}", swap_count, reserve); + // println!("{:?}", position); + // println!("{:?}", pool); + } } #[test] @@ -104,7 +196,6 @@ fn test_reserve_wont_lost_single() { let pool_fees = PoolFeesStruct { base_fee, //1% protocol_fee_percent: 20, - partner_fee_percent: 50, referral_fee_percent: 20, ..Default::default() }; @@ -182,14 +273,16 @@ fn execute_add_liquidity( position: &mut Position, liquidity_delta: u128, ) { - let result = pool + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let (token_a_amount, token_b_amount) = liquidity_handler .get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Up) .unwrap(); - pool.apply_add_liquidity(position, liquidity_delta).unwrap(); + pool.apply_add_liquidity(position, liquidity_delta, token_a_amount, token_b_amount) + .unwrap(); - reserve.amount_a = reserve.amount_a.checked_add(result.token_a_amount).unwrap(); - reserve.amount_b = reserve.amount_b.checked_add(result.token_b_amount).unwrap(); + reserve.amount_a = reserve.amount_a.checked_add(token_a_amount).unwrap(); + reserve.amount_b = reserve.amount_b.checked_add(token_b_amount).unwrap(); } fn execute_remove_liquidity( @@ -198,15 +291,16 @@ fn execute_remove_liquidity( position: &mut Position, liquidity_delta: u128, ) { - let result = pool + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let (token_a_amount, token_b_amount) = liquidity_handler .get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Down) .unwrap(); - pool.apply_remove_liquidity(position, liquidity_delta) + pool.apply_remove_liquidity(position, liquidity_delta, token_a_amount, token_b_amount) .unwrap(); - reserve.amount_a = reserve.amount_a.checked_sub(result.token_a_amount).unwrap(); - reserve.amount_b = reserve.amount_b.checked_sub(result.token_b_amount).unwrap(); + reserve.amount_a = reserve.amount_a.checked_sub(token_a_amount).unwrap(); + reserve.amount_b = reserve.amount_b.checked_sub(token_b_amount).unwrap(); } fn execute_swap_liquidity( @@ -216,17 +310,22 @@ fn execute_swap_liquidity( has_referral: bool, trade_direction: TradeDirection, ) -> bool { - let max_amount_in = pool.get_max_amount_in(trade_direction).unwrap(); + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let max_amount_in = liquidity_handler + .get_max_amount_in(trade_direction) + .unwrap(); if amount_in > max_amount_in { return false; } - let fee_mode = - &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, has_referral).unwrap(); + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, has_referral); + let swap_result = pool - .get_swap_result_from_exact_input(amount_in, fee_mode, trade_direction, 0) + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) .unwrap(); - pool.apply_swap_result(&swap_result, fee_mode, 0).unwrap(); + pool.apply_swap_result(&swap_result, &fee_mode, trade_direction, 0) + .unwrap(); match trade_direction { TradeDirection::AtoB => { diff --git a/programs/cp-amm/src/tests/test_liquidity_compounding.rs b/programs/cp-amm/src/tests/test_liquidity_compounding.rs new file mode 100644 index 00000000..89a5b06a --- /dev/null +++ b/programs/cp-amm/src/tests/test_liquidity_compounding.rs @@ -0,0 +1,307 @@ +use crate::math::safe_math::SafeMath; +use crate::params::swap::TradeDirection; +use crate::safe_math::SafeCast; +use crate::state::fee::FeeMode; +use crate::utils_math::sqrt_u256; +use crate::PoolError; +use crate::{ + constants::{MAX_SQRT_PRICE, MIN_SQRT_PRICE}, + get_initial_pool_information, + state::{CollectFeeMode, Pool, Position}, + u128x128_math::Rounding, + InitialPoolInformation, DEAD_LIQUIDITY, +}; +use crate::{CompoundingLiquidity, LiquidityHandler}; +use anchor_lang::prelude::*; +use proptest::prelude::*; +use ruint::aliases::U256; +use std::u64; + +pub fn get_sqrt_price_and_liquidity_from_amounts( + initial_amount_a: u64, + initial_amount_b: u64, +) -> Result<(u128, u128)> { + let sqrt_price = sqrt_u256( + U256::from(initial_amount_b) + .safe_shl(128) + .unwrap() + .safe_div(U256::from(initial_amount_a)) + .unwrap(), + ) + .unwrap(); + let sqrt_price: u128 = sqrt_price.try_into().unwrap(); + require!( + sqrt_price >= MIN_SQRT_PRICE && sqrt_price <= MAX_SQRT_PRICE, + PoolError::InvalidPriceRange + ); + let liquidity = sqrt_u256( + U256::from(initial_amount_b) + .safe_mul(U256::from(initial_amount_a)) + .unwrap() + .safe_shl(128) + .unwrap(), + ) + .unwrap(); + let liquidity: u128 = liquidity.try_into().unwrap(); + require!( + liquidity >= DEAD_LIQUIDITY, + PoolError::InvalidMinimumLiquidity + ); + Ok((sqrt_price, liquidity)) +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 10000, .. ProptestConfig::default() + })] + #[test] + fn test_compounding_liquidity_initialization( + a in 1..u64::MAX, + b in 1..u64::MAX, + ) { + let result = get_sqrt_price_and_liquidity_from_amounts(a, b); + if result.is_err() { + return Ok(()); + } + let (sqrt_price, liquidity) = result.unwrap(); + let InitialPoolInformation { + token_a_amount, + token_b_amount, + initial_liquidity, + sqrt_price: _, + sqrt_min_price: _, + sqrt_max_price: _, + } = get_initial_pool_information( + CollectFeeMode::Compounding, + 0, + 0, + sqrt_price, + liquidity, + ).unwrap(); + + let mut pool = Pool { + collect_fee_mode: CollectFeeMode::Compounding.into(), + token_a_amount, + token_b_amount, + liquidity, + ..Default::default() + }; + + // println!("amount {} {} {} {}",a,b, token_a_amount, token_b_amount); + + let mut position = Position{ + unlocked_liquidity: initial_liquidity, + ..Default::default() + }; + let unlocked_liquidity = position.unlocked_liquidity; + + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let (removed_token_a_amount, removed_token_b_amount) = liquidity_handler.get_amounts_for_modify_liquidity(unlocked_liquidity, Rounding::Down).unwrap(); + pool.apply_remove_liquidity(&mut position, unlocked_liquidity, removed_token_a_amount, removed_token_b_amount).unwrap(); + assert!(pool.liquidity > 0); // there is a deadshare in pool + assert_eq!(position.unlocked_liquidity, 0); + } +} + +#[test] +fn test_compounding_liquidity_next_sqrt_price() { + let liquidity_handler = CompoundingLiquidity { + token_a_amount: 1, + token_b_amount: u64::MAX, + liquidity: 0, + }; + let next_sqrt_price = liquidity_handler.get_next_sqrt_price(0).unwrap(); + + println!("{}", next_sqrt_price); + + let liquidity_handler = CompoundingLiquidity { + token_a_amount: u64::MAX, + token_b_amount: 1, + liquidity: 0, + }; + let next_sqrt_price = liquidity_handler.get_next_sqrt_price(0).unwrap(); + + println!("{}", next_sqrt_price); +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 10000, .. ProptestConfig::default() + })] + #[test] + fn test_compounding_liquidity_reserve_wont_lost_when_swap_from_a_to_b( + amount_in in 1..=u64::MAX, + a in 1..u64::MAX, + b in 1..u64::MAX, + ) { + let result = get_sqrt_price_and_liquidity_from_amounts(a, b); + if result.is_err() { + return Ok(()); + } + let (sqrt_price, liquidity) = result.unwrap(); + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity:_, + sqrt_min_price: _, + sqrt_max_price: _, + } = get_initial_pool_information( + CollectFeeMode::Compounding, + 0, + 0, + sqrt_price, + liquidity, + ).unwrap(); + + let mut pool = Pool { + collect_fee_mode: CollectFeeMode::Compounding.into(), + token_a_amount, + token_b_amount, + liquidity, + ..Default::default() + }; + + if u128::from(token_a_amount).safe_add(u128::from(amount_in)).unwrap() > u128::from(u64::MAX) { + return Ok(()); + } + + let trade_direction = TradeDirection::AtoB; + + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, false); + + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let max_amount_in = liquidity_handler.get_max_amount_in(trade_direction).unwrap(); + if amount_in <= max_amount_in { + let swap_result_0 = pool + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) + .unwrap(); + + pool.apply_swap_result(&swap_result_0, &fee_mode, trade_direction, 0).unwrap(); + // swap back + + let swap_result_1 = pool + .get_swap_result_from_exact_input(swap_result_0.output_amount, &fee_mode, TradeDirection::BtoA, 0) + .unwrap(); + + assert!(swap_result_1.output_amount < amount_in); + } + + } + + #[test] + fn test_compounding_liquidity_reserve_wont_lost_when_swap_from_b_to_a( + amount_in in 1..=u64::MAX, + a in 1..u64::MAX, + b in 1..u64::MAX, + ) { + let result = get_sqrt_price_and_liquidity_from_amounts(a, b); + if result.is_err() { + return Ok(()); + } + let (sqrt_price, liquidity) = result.unwrap(); + + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity:_, + sqrt_min_price: _, + sqrt_max_price: _, + } = get_initial_pool_information( + CollectFeeMode::Compounding, + 0, + 0, + sqrt_price, + liquidity, + ).unwrap(); + + let mut pool = Pool { + collect_fee_mode: CollectFeeMode::Compounding.into(), + token_a_amount, + token_b_amount, + liquidity, + ..Default::default() + }; + + if u128::from(token_b_amount).safe_add(u128::from(amount_in)).unwrap() > u128::from(u64::MAX) { + return Ok(()); + } + + let trade_direction = TradeDirection::BtoA; + + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, false); + + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let max_amount_in = liquidity_handler.get_max_amount_in(trade_direction).unwrap(); + if amount_in <= max_amount_in { + let swap_result_0 = pool + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) + .unwrap(); + + pool.apply_swap_result(&swap_result_0, &fee_mode, trade_direction, 0).unwrap(); + // swap back + + let swap_result_1 = pool + .get_swap_result_from_exact_input(swap_result_0.output_amount, &fee_mode, TradeDirection::AtoB, 0) + .unwrap(); + + assert!(swap_result_1.output_amount < amount_in); + } + } + +} + +#[test] +fn test_compounding_swap_basic() { + let a = 100_000_000; + let b = 100_000_000_000; + let (sqrt_price, liquidity) = get_sqrt_price_and_liquidity_from_amounts(a, b).unwrap(); + + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity: _, + sqrt_min_price: _, + sqrt_max_price: _, + } = get_initial_pool_information(CollectFeeMode::Compounding, 0, 0, sqrt_price, liquidity) + .unwrap(); + + let mut pool = Pool { + collect_fee_mode: CollectFeeMode::Compounding.into(), + token_a_amount, + token_b_amount, + liquidity, + ..Default::default() + }; + + let amount_in = 100_000_000; + let trade_direction = TradeDirection::AtoB; + + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, false); + + let swap_result = pool + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) + .unwrap(); + + println!("result {:?}", swap_result); + + pool.apply_swap_result(&swap_result, &fee_mode, trade_direction, 0) + .unwrap(); + + let swap_result_referse = pool + .get_swap_result_from_exact_input( + swap_result.output_amount, + &fee_mode, + TradeDirection::BtoA, + 0, + ) + .unwrap(); + + println!("reverse {:?}", swap_result_referse); + assert!(swap_result_referse.output_amount <= amount_in); +} diff --git a/programs/cp-amm/src/tests/test_modify_liquidity.rs b/programs/cp-amm/src/tests/test_modify_liquidity.rs index 10f63742..64f6214b 100644 --- a/programs/cp-amm/src/tests/test_modify_liquidity.rs +++ b/programs/cp-amm/src/tests/test_modify_liquidity.rs @@ -24,23 +24,25 @@ proptest! { let mut position = Position::default(); - let result_0 = pool + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let result_0 = liquidity_handler .get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Up) .unwrap(); println!("result_0 {:?}", result_0); - pool.apply_add_liquidity(&mut position, liquidity_delta).unwrap(); + pool.apply_add_liquidity(&mut position, liquidity_delta, result_0.0, result_0.1).unwrap(); - let result_1 = pool.get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Down).unwrap(); - println!("result_1 {:?}", result_0); + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let result_1 = liquidity_handler.get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Down).unwrap(); + println!("result_1 {:?}", result_1); - pool.apply_remove_liquidity(&mut position, liquidity_delta).unwrap(); + pool.apply_remove_liquidity(&mut position, liquidity_delta, result_1.0, result_1.1).unwrap(); assert_eq!(pool.liquidity, 0); assert_eq!(position.unlocked_liquidity, 0); - assert!(result_0.token_a_amount >= result_1.token_a_amount); - assert!(result_0.token_b_amount >= result_1.token_b_amount); + assert!(result_0.0 >= result_1.0); + assert!(result_0.1 >= result_1.1); } } diff --git a/programs/cp-amm/src/tests/test_operator_permission.rs b/programs/cp-amm/src/tests/test_operator_permission.rs index d07843c6..69b4345b 100644 --- a/programs/cp-amm/src/tests/test_operator_permission.rs +++ b/programs/cp-amm/src/tests/test_operator_permission.rs @@ -5,7 +5,7 @@ use crate::{ #[test] fn test_initialize_with_full_permission() { - let permission: u128 = 0b11111111111; + let permission: u128 = 0b111111111111; assert!(permission > 1 << (MAX_OPERATION - 1) && permission < 1 << MAX_OPERATION); let operator = Operator { @@ -57,6 +57,10 @@ fn test_initialize_with_full_permission() { operator.is_permission_allow(OperatorPermission::ZapProtocolFee), true ); + assert_eq!( + operator.is_permission_allow(OperatorPermission::FixPool), + true + ); } #[test] diff --git a/programs/cp-amm/src/tests/test_overflow.rs b/programs/cp-amm/src/tests/test_overflow.rs index 38869ed1..50afc5f5 100644 --- a/programs/cp-amm/src/tests/test_overflow.rs +++ b/programs/cp-amm/src/tests/test_overflow.rs @@ -5,8 +5,9 @@ use ruint::aliases::U256; use crate::{ constants::{MAX_SQRT_PRICE, MIN_SQRT_PRICE}, - curve::{get_initialize_amounts, get_next_sqrt_price_from_input, RESOLUTION}, + get_next_sqrt_price_from_input, tests::LIQUIDITY_MAX, + ConcentratedLiquidity, }; use proptest::prelude::*; @@ -21,7 +22,7 @@ fn test_get_initialize_amounts( ) { let sqrt_min_price = MIN_SQRT_PRICE; let sqrt_max_price = MAX_SQRT_PRICE; - get_initialize_amounts(sqrt_min_price, sqrt_max_price, sqrt_price, liquidity).unwrap(); + ConcentratedLiquidity::get_initial_pool_information(sqrt_min_price, sqrt_max_price, sqrt_price, liquidity).unwrap(); } #[test] @@ -48,9 +49,14 @@ fn test_get_initialize_amounts_single_case() { let sqrt_max_price = MAX_SQRT_PRICE; let sqrt_price = 29079168020; let liquidity = 13729854716085099837338887321; - let (a, b) = - get_initialize_amounts(sqrt_min_price, sqrt_max_price, sqrt_price, liquidity).unwrap(); - println!("{} {}", a, b); + let result = ConcentratedLiquidity::get_initial_pool_information( + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + ) + .unwrap(); + println!("{:?}", result); } #[test] @@ -73,6 +79,6 @@ pub fn _to_decimal(sqrt_price: u128) -> u128 { let precision = U256::from(1_000_000u128); let scaled_value = value.checked_mul(precision).unwrap(); // ruint checked math is different with the rust std u128. If there's bit with 1 value being shifted out, it will return None. Therefore, we use overflowing_shr - let (scaled_down_value, _) = scaled_value.overflowing_shr(2 * RESOLUTION as usize); + let (scaled_down_value, _) = scaled_value.overflowing_shr(128); scaled_down_value.try_into().unwrap() } diff --git a/programs/cp-amm/src/tests/test_swap.rs b/programs/cp-amm/src/tests/test_swap.rs index ef1dc7f1..2f2ce5e2 100644 --- a/programs/cp-amm/src/tests/test_swap.rs +++ b/programs/cp-amm/src/tests/test_swap.rs @@ -2,11 +2,11 @@ use std::{u128, u64}; use crate::{ constants::{MAX_SQRT_PRICE, MIN_SQRT_PRICE}, - curve::get_initialize_amounts, params::swap::TradeDirection, - safe_math::SafeMath, - state::{fee::FeeMode, Pool}, + safe_math::{SafeCast, SafeMath}, + state::{fee::FeeMode, CollectFeeMode, Pool}, tests::LIQUIDITY_MAX, + ConcentratedLiquidity, InitialPoolInformation, }; use proptest::prelude::*; @@ -20,28 +20,51 @@ proptest! { amount_in in 1..=u64::MAX, liquidity in 1..=LIQUIDITY_MAX, ) { + + let sqrt_min_price = MIN_SQRT_PRICE; + let sqrt_max_price = MAX_SQRT_PRICE; + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity: _, + sqrt_min_price, + sqrt_max_price, + } = ConcentratedLiquidity::get_initial_pool_information( + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + ) + .unwrap(); + let mut pool = Pool { liquidity, + sqrt_max_price, + sqrt_min_price, sqrt_price, - sqrt_min_price: MIN_SQRT_PRICE, - sqrt_max_price: MAX_SQRT_PRICE, + token_a_amount, + token_b_amount, ..Default::default() }; let trade_direction = TradeDirection::AtoB; - let fee_mode = &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, false).unwrap(); - let max_amount_in = pool.get_max_amount_in(trade_direction).unwrap(); + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, false); + + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let max_amount_in = liquidity_handler.get_max_amount_in(trade_direction).unwrap(); if amount_in <= max_amount_in { let swap_result_0 = pool - .get_swap_result_from_exact_input(amount_in, fee_mode, trade_direction, 0) + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) .unwrap(); - pool.apply_swap_result(&swap_result_0, fee_mode, 0).unwrap(); + pool.apply_swap_result(&swap_result_0, &fee_mode, trade_direction, 0).unwrap(); // swap back let swap_result_1 = pool - .get_swap_result_from_exact_input(swap_result_0.output_amount, fee_mode, TradeDirection::BtoA, 0) + .get_swap_result_from_exact_input(swap_result_0.output_amount, &fee_mode, TradeDirection::BtoA, 0) .unwrap(); assert!(swap_result_1.output_amount < amount_in); @@ -56,28 +79,50 @@ proptest! { amount_in in 1..=u64::MAX, liquidity in 1..=LIQUIDITY_MAX, ) { + let sqrt_min_price = MIN_SQRT_PRICE; + let sqrt_max_price = MAX_SQRT_PRICE; + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity: _, + sqrt_min_price, + sqrt_max_price, + } = ConcentratedLiquidity::get_initial_pool_information( + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + ) + .unwrap(); + let mut pool = Pool { liquidity, + sqrt_max_price, + sqrt_min_price, sqrt_price, - sqrt_min_price: MIN_SQRT_PRICE, - sqrt_max_price: MAX_SQRT_PRICE, + token_a_amount, + token_b_amount, ..Default::default() }; let trade_direction = TradeDirection::BtoA; - let fee_mode = &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, false).unwrap(); - let max_amount_in = pool.get_max_amount_in(trade_direction).unwrap(); + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, false); + + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let max_amount_in = liquidity_handler.get_max_amount_in(trade_direction).unwrap(); if amount_in <= max_amount_in { let swap_result_0 = pool - .get_swap_result_from_exact_input(amount_in, fee_mode, trade_direction, 0) + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) .unwrap(); - pool.apply_swap_result(&swap_result_0, fee_mode, 0).unwrap(); + pool.apply_swap_result(&swap_result_0, &fee_mode, trade_direction, 0).unwrap(); // swap back let swap_result_1 = pool - .get_swap_result_from_exact_input(swap_result_0.output_amount, fee_mode, TradeDirection::AtoB, 0) + .get_swap_result_from_exact_input(swap_result_0.output_amount, &fee_mode, TradeDirection::AtoB, 0) .unwrap(); assert!(swap_result_1.output_amount < amount_in); @@ -86,64 +131,56 @@ proptest! { } -// #[test] -// fn test_reserve_wont_lost_when_swap_from_a_to_b_single() { -// let liquidity = 1; -// let sqrt_price = 4295048016; -// let amount_in = 35; -// let trade_direction = TradeDirection::AtoB; -// let mut pool = Pool { -// liquidity, -// sqrt_price, -// sqrt_min_price: MIN_SQRT_PRICE, -// sqrt_max_price: MAX_SQRT_PRICE, -// ..Default::default() -// }; - -// let swap_result_0 = pool -// .get_swap_result(amount_in, false, trade_direction) -// .unwrap(); - -// println!("{:?}", swap_result_0); - -// pool.apply_swap_result(&swap_result_0, trade_direction) -// .unwrap(); - -// let swap_result_1 = pool -// .get_swap_result(swap_result_0.output_amount, false, TradeDirection::BtoA) -// .unwrap(); - -// println!("{:?}", swap_result_1); - -// assert!(swap_result_1.output_amount < amount_in); -// } - #[test] fn test_reserve_wont_lost_when_swap_from_b_to_a_single() { let liquidity = LIQUIDITY_MAX; let sqrt_price = 19163436944492510497018124036; let amount_in = 1_000_0000; let trade_direction = TradeDirection::BtoA; + + let sqrt_min_price = MIN_SQRT_PRICE; + let sqrt_max_price = MAX_SQRT_PRICE; + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity: _, + sqrt_min_price, + sqrt_max_price, + } = ConcentratedLiquidity::get_initial_pool_information( + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + ) + .unwrap(); + let mut pool = Pool { liquidity, + sqrt_max_price, + sqrt_min_price, sqrt_price, - sqrt_min_price: MIN_SQRT_PRICE, - sqrt_max_price: MAX_SQRT_PRICE, + token_a_amount, + token_b_amount, ..Default::default() }; - let fee_mode = &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, false).unwrap(); + + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, false); + let swap_result_0 = pool - .get_swap_result_from_exact_input(amount_in, fee_mode, trade_direction, 0) + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) .unwrap(); println!("{:?}", swap_result_0); - pool.apply_swap_result(&swap_result_0, fee_mode, 0).unwrap(); + pool.apply_swap_result(&swap_result_0, &fee_mode, trade_direction, 0) + .unwrap(); let swap_result_1 = pool .get_swap_result_from_exact_input( swap_result_0.output_amount, - fee_mode, + &fee_mode, TradeDirection::AtoB, 0, ) @@ -156,56 +193,58 @@ fn test_reserve_wont_lost_when_swap_from_b_to_a_single() { #[test] fn test_swap_basic() { - // let pool_fees = PoolFeesStruct { - // trade_fee_numerator: 1_000_000, //1% - // protocol_fee_percent: 20, - // partner_fee_percent: 50, - // referral_fee_percent: 20, - // ..Default::default() - // }; let sqrt_min_price = MIN_SQRT_PRICE; let sqrt_max_price = MAX_SQRT_PRICE; let sqrt_price = u64::MAX as u128; let liquidity = LIQUIDITY_MAX; + + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity: _, + sqrt_min_price, + sqrt_max_price, + } = ConcentratedLiquidity::get_initial_pool_information( + sqrt_min_price, + sqrt_max_price, + sqrt_price, + liquidity, + ) + .unwrap(); + let mut pool = Pool { - // pool_fees, + liquidity, + sqrt_max_price, + sqrt_min_price, + sqrt_price, + token_a_amount, + token_b_amount, ..Default::default() }; - let (_token_a_amount, _token_b_amount) = - get_initialize_amounts(sqrt_min_price, sqrt_max_price, sqrt_price, liquidity).unwrap(); - // println!("amount {} {}", _token_a_amount, _token_b_amount); - pool.liquidity = liquidity; - pool.sqrt_max_price = sqrt_max_price; - pool.sqrt_min_price = sqrt_min_price; - pool.sqrt_price = sqrt_price; - - // let next_sqrt_price = - // get_next_sqrt_price_from_input(sqrt_price, liquidity, 100_000_000, true).unwrap(); - - // println!( - // "price {} {} {}", - // to_decimal(sqrt_price), - // to_decimal(next_sqrt_price), - // liquidity.safe_shr(64).unwrap(), - // ); - let amount_in = 100_000_000; let trade_direction = TradeDirection::AtoB; - let fee_mode = &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, false).unwrap(); + + let collect_fee_mode: CollectFeeMode = pool.collect_fee_mode.safe_cast().unwrap(); + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, false); let swap_result = pool - .get_swap_result_from_exact_input(amount_in, fee_mode, trade_direction, 0) + .get_swap_result_from_exact_input(amount_in, &fee_mode, trade_direction, 0) .unwrap(); println!("result {:?}", swap_result); - // return; - - pool.apply_swap_result(&swap_result, fee_mode, 0).unwrap(); + pool.apply_swap_result(&swap_result, &fee_mode, trade_direction, 0) + .unwrap(); let swap_result_referse = pool - .get_swap_result_from_exact_input(swap_result.output_amount, fee_mode, TradeDirection::BtoA, 0) + .get_swap_result_from_exact_input( + swap_result.output_amount, + &fee_mode, + TradeDirection::BtoA, + 0, + ) .unwrap(); println!("reverse {:?}", swap_result_referse); diff --git a/rust-sdk/Cargo.toml b/rust-sdk/Cargo.toml index 46c07af1..c7663077 100644 --- a/rust-sdk/Cargo.toml +++ b/rust-sdk/Cargo.toml @@ -1,9 +1,8 @@ [package] name = "rust-sdk" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = "cp-amm sdk" -authors = ["minh "] [dependencies] anyhow = "1.0.71" @@ -11,4 +10,5 @@ cp-amm = { path = "../programs/cp-amm", features=["no-entrypoint", "no-custom-en ruint = "1.3.0" [dev-dependencies] -bytemuck = { workspace = true} \ No newline at end of file +bytemuck = { workspace = true} +proptest = { workspace = true} \ No newline at end of file diff --git a/rust-sdk/src/calculate_init_sqrt_price.rs b/rust-sdk/src/calculate_init_sqrt_price.rs deleted file mode 100644 index a8e6f6d4..00000000 --- a/rust-sdk/src/calculate_init_sqrt_price.rs +++ /dev/null @@ -1,45 +0,0 @@ -use anyhow::{ensure, Ok, Result}; -use cp_amm::{safe_math::SafeMath, utils_math::sqrt_u256}; -use ruint::aliases::U256; - -// a = L * (1/s - 1/pb) -// b = L * (s - pa) -// b/a = (s - pa) / (1/s - 1/pb) -// With: x = 1 / pb and y = b/a -// => s ^ 2 + s * (-pa + x * y) - y = 0 -// s = [(pa - xy) + √((xy - pa)² + 4y)]/2, // pa: min_sqrt_price, pb: max_sqrt_price -// s = [(pa - b << 128 / a / pb) + sqrt((b << 128 / a / pb - pa)² + 4 * b << 128 / a)] / 2 -pub fn calculate_init_price( - token_a_amount: u64, - token_b_amount: u64, - min_sqrt_price: u128, - max_sqrt_price: u128, -) -> Result { - ensure!( - token_a_amount != 0 && token_b_amount != 0, - "Token amounts must be non-zero" - ); - - let a = U256::from(token_a_amount); - let b = U256::from(token_b_amount) - .safe_shl(128) - .map_err(|_| anyhow::anyhow!("Math overflow"))?; - let pa = U256::from(min_sqrt_price); - let pb = U256::from(max_sqrt_price); - - let four = U256::from(4); - let two = U256::from(2); - - let s = if b / a > pa * pb { - let delta = b / a / pb - pa; - let sqrt_value = sqrt_u256(delta * delta + four * b / a) - .ok_or_else(|| anyhow::anyhow!("Type cast failed"))?; - (sqrt_value - delta) / two - } else { - let delta = pa - b / a / pb; - let sqrt_value = sqrt_u256(delta * delta + four * b / a) - .ok_or_else(|| anyhow::anyhow!("Type cast failed"))?; - (sqrt_value + delta) / two - }; - Ok(u128::try_from(s).map_err(|_| anyhow::anyhow!("Type cast failed"))?) -} diff --git a/rust-sdk/src/calculate_initial_sqrt_price.rs b/rust-sdk/src/calculate_initial_sqrt_price.rs new file mode 100644 index 00000000..dbfa6793 --- /dev/null +++ b/rust-sdk/src/calculate_initial_sqrt_price.rs @@ -0,0 +1,71 @@ +use cp_amm::{ + constants::{MAX_SQRT_PRICE, MIN_SQRT_PRICE}, + utils_math::sqrt_u256, +}; +use ruint::aliases::U256; + +// a = L * (1/s - 1/pb) +// b = L * (s - pa) +// b/a = (s - pa) / (1/s - 1/pb) +// With: x = 1 / pb and y = b/a +// => s ^ 2 + s * (-pa + x * y) - y = 0 +// s = [(pa - xy) + √((xy - pa)² + 4y)]/2, // pa: min_sqrt_price, pb: max_sqrt_price +// s = [(pa - b << 128 / a / pb) + sqrt((b << 128 / a / pb - pa)² + 4 * b << 128 / a)] / 2 +pub fn calculate_concentrated_initial_sqrt_price( + token_a_amount: u64, + token_b_amount: u64, + min_sqrt_price: u128, + max_sqrt_price: u128, +) -> Option { + if token_a_amount == 0 || token_b_amount == 0 { + return None; + } + + let a = U256::from(token_a_amount); + let b = U256::from(token_b_amount).checked_shl(128)?; + let pa = U256::from(min_sqrt_price); + let pb = U256::from(max_sqrt_price); + + let four = U256::from(4); + let two = U256::from(2); + + let s = if b / a > pa * pb { + let delta = b / a / pb - pa; + let sqrt_value = sqrt_u256(delta * delta + four * b / a)?; + (sqrt_value - delta) / two + } else { + let delta = pa - b / a / pb; + let sqrt_value = sqrt_u256(delta * delta + four * b / a)?; + (sqrt_value + delta) / two + }; + u128::try_from(s).ok() +} + +pub fn calculate_compounding_initial_sqrt_price_and_liquidity( + token_a_amount: u64, + token_b_amount: u64, +) -> Option<(u128, u128)> { + // a = l/s and b = l * s + // s1: sqrt_price round up + // s2: sqrt_price round down + // return (s1, a * s2) + let sqrt_price_1 = sqrt_u256( + U256::from(token_b_amount) + .checked_shl(128)? + .div_ceil(U256::from(token_a_amount)), + )?; + let sqrt_price_1 = u128::try_from(sqrt_price_1).ok()?; + if sqrt_price_1 < MIN_SQRT_PRICE || sqrt_price_1 > MAX_SQRT_PRICE { + return None; + } + + let sqrt_price_2 = sqrt_u256( + U256::from(token_b_amount) + .checked_shl(128)? + .checked_div(U256::from(token_a_amount))?, + )?; + let sqrt_price_2 = u128::try_from(sqrt_price_2).ok()?; + let liquidity = sqrt_price_2.checked_mul(u128::from(token_a_amount))?; + + Some((sqrt_price_1, liquidity)) +} diff --git a/rust-sdk/src/lib.rs b/rust-sdk/src/lib.rs index 3a307f87..b3b77fce 100644 --- a/rust-sdk/src/lib.rs +++ b/rust-sdk/src/lib.rs @@ -1,4 +1,4 @@ -pub mod calculate_init_sqrt_price; +pub mod calculate_initial_sqrt_price; pub mod quote_exact_in; pub mod quote_exact_out; pub mod quote_partial_fill_in; diff --git a/rust-sdk/src/quote_exact_in.rs b/rust-sdk/src/quote_exact_in.rs index c697350f..227c8922 100644 --- a/rust-sdk/src/quote_exact_in.rs +++ b/rust-sdk/src/quote_exact_in.rs @@ -1,8 +1,8 @@ use crate::utils::*; -use anyhow::{ensure, Ok, Result}; +use anyhow::{ensure, Error, Ok, Result}; use cp_amm::{ params::swap::TradeDirection, - state::{fee::FeeMode, Pool, SwapResult2}, + state::{fee::FeeMode, CollectFeeMode, Pool, SwapResult2}, }; pub fn get_quote( @@ -25,11 +25,13 @@ pub fn get_quote( TradeDirection::BtoA }; - let fee_mode = &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, has_referral)?; + let collect_fee_mode = CollectFeeMode::try_from(pool.collect_fee_mode) + .map_err(|_| Error::msg("Invalid collect fee mode"))?; + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, has_referral); Ok(pool.get_swap_result_from_exact_input( actual_amount_in, - fee_mode, + &fee_mode, trade_direction, current_point, )?) diff --git a/rust-sdk/src/quote_exact_out.rs b/rust-sdk/src/quote_exact_out.rs index 1096651d..7d2d9502 100644 --- a/rust-sdk/src/quote_exact_out.rs +++ b/rust-sdk/src/quote_exact_out.rs @@ -1,8 +1,8 @@ use crate::utils::*; -use anyhow::{ensure, Ok, Result}; +use anyhow::{ensure, Error, Ok, Result}; use cp_amm::{ params::swap::TradeDirection, - state::{fee::FeeMode, Pool, SwapResult2}, + state::{fee::FeeMode, CollectFeeMode, Pool, SwapResult2}, }; pub fn get_quote( @@ -23,12 +23,13 @@ pub fn get_quote( } else { TradeDirection::BtoA }; - - let fee_mode = &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, has_referral)?; + let collect_fee_mode = CollectFeeMode::try_from(pool.collect_fee_mode) + .map_err(|_| Error::msg("Invalid collect fee mode"))?; + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, has_referral); let swap_result = pool.get_swap_result_from_exact_output( actual_amount_out, - fee_mode, + &fee_mode, trade_direction, current_point, )?; diff --git a/rust-sdk/src/quote_partial_fill_in.rs b/rust-sdk/src/quote_partial_fill_in.rs index d2035b91..7c3db247 100644 --- a/rust-sdk/src/quote_partial_fill_in.rs +++ b/rust-sdk/src/quote_partial_fill_in.rs @@ -1,8 +1,8 @@ use crate::utils::*; -use anyhow::{ensure, Ok, Result}; +use anyhow::{ensure, Error, Ok, Result}; use cp_amm::{ params::swap::TradeDirection, - state::{fee::FeeMode, Pool, SwapResult2}, + state::{fee::FeeMode, CollectFeeMode, Pool, SwapResult2}, }; pub fn get_quote( @@ -25,11 +25,13 @@ pub fn get_quote( TradeDirection::BtoA }; - let fee_mode = &FeeMode::get_fee_mode(pool.collect_fee_mode, trade_direction, has_referral)?; + let collect_fee_mode = CollectFeeMode::try_from(pool.collect_fee_mode) + .map_err(|_| Error::msg("Invalid collect fee mode"))?; + let fee_mode = FeeMode::get_fee_mode(collect_fee_mode, trade_direction, has_referral); let swap_result = pool.get_swap_result_from_partial_input( actual_amount_in, - fee_mode, + &fee_mode, trade_direction, current_point, )?; diff --git a/rust-sdk/src/tests/mod.rs b/rust-sdk/src/tests/mod.rs index 28f850bb..43f493ea 100644 --- a/rust-sdk/src/tests/mod.rs +++ b/rust-sdk/src/tests/mod.rs @@ -1,4 +1,5 @@ -pub mod test_calculate_init_sqrt_price; +pub mod test_calculate_compounding_liquidity; +pub mod test_calculate_concentrated_initial_sqrt_price; pub mod test_quote_exact_in; pub mod test_quote_exact_out; pub mod test_quote_partial_fill_in; diff --git a/rust-sdk/src/tests/test_calculate_compounding_liquidity.rs b/rust-sdk/src/tests/test_calculate_compounding_liquidity.rs new file mode 100644 index 00000000..3ccc6197 --- /dev/null +++ b/rust-sdk/src/tests/test_calculate_compounding_liquidity.rs @@ -0,0 +1,40 @@ +use crate::calculate_initial_sqrt_price::calculate_compounding_initial_sqrt_price_and_liquidity; +use cp_amm::get_initial_pool_information; +use cp_amm::state::CollectFeeMode; +use cp_amm::InitialPoolInformation; +use proptest::prelude::*; +proptest! { + #![proptest_config(ProptestConfig { + cases: 10000, .. ProptestConfig::default() + })] + #[test] + fn test_compounding_liquidity_initialization( + a in 1..u64::MAX, + b in 1..u64::MAX, + ) { + let result = calculate_compounding_initial_sqrt_price_and_liquidity(a, b); + if result.is_none() { + return Ok(()); + } + let (sqrt_price, liquidity) = result.unwrap(); + let InitialPoolInformation { + token_a_amount, + token_b_amount, + sqrt_price: _, + initial_liquidity: _, + sqrt_min_price: _, + sqrt_max_price: _, + } = get_initial_pool_information( + CollectFeeMode::Compounding, + 0, + 0, + sqrt_price, + liquidity, + ).unwrap(); + + println!("a {} {}", token_a_amount, a); + assert!(token_a_amount <= a); + println!("b {} {}", token_b_amount, b); + assert!(token_b_amount <= b); + } +} diff --git a/rust-sdk/src/tests/test_calculate_init_sqrt_price.rs b/rust-sdk/src/tests/test_calculate_concentrated_initial_sqrt_price.rs similarity index 86% rename from rust-sdk/src/tests/test_calculate_init_sqrt_price.rs rename to rust-sdk/src/tests/test_calculate_concentrated_initial_sqrt_price.rs index c6732b7a..e9f15d1e 100644 --- a/rust-sdk/src/tests/test_calculate_init_sqrt_price.rs +++ b/rust-sdk/src/tests/test_calculate_concentrated_initial_sqrt_price.rs @@ -3,11 +3,12 @@ use std::u64; use cp_amm::{ constants::{MAX_SQRT_PRICE, MIN_SQRT_PRICE}, math::safe_math::SafeMath, - state::{ModifyLiquidityResult, Pool}, + state::Pool, + u128x128_math::Rounding, }; use ruint::aliases::{U256, U512}; -use crate::calculate_init_sqrt_price::calculate_init_price; +use crate::calculate_initial_sqrt_price::calculate_concentrated_initial_sqrt_price; use anyhow::{Ok, Result}; // Δa = L * (1 / √P_lower - 1 / √P_upper) => L = Δa / (1 / √P_lower - 1 / √P_upper) @@ -74,7 +75,8 @@ fn test_b_div_a_larger_than_pa_mul_pb() { .safe_add(1) .unwrap(); - let init_sqrt_price = calculate_init_price(a, b, MIN_SQRT_PRICE, MAX_SQRT_PRICE).unwrap(); + let init_sqrt_price = + calculate_concentrated_initial_sqrt_price(a, b, MIN_SQRT_PRICE, MAX_SQRT_PRICE).unwrap(); println!("init_sqrt_price: {:?}", init_sqrt_price); @@ -94,11 +96,9 @@ fn test_b_div_a_larger_than_pa_mul_pb() { ) .unwrap(); - let ModifyLiquidityResult { - token_a_amount, - token_b_amount, - } = pool - .get_amounts_for_modify_liquidity(liquidity_delta, cp_amm::u128x128_math::Rounding::Up) + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let (token_a_amount, token_b_amount) = liquidity_handler + .get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Up) .unwrap(); // The small difference in token_a is expected due to rounding in liquidity calculations @@ -136,7 +136,8 @@ fn test_b_div_a_less_than_pa_mul_pb() { .safe_sub(1) .unwrap(); - let init_sqrt_price = calculate_init_price(a, b, MIN_SQRT_PRICE, MAX_SQRT_PRICE).unwrap(); + let init_sqrt_price = + calculate_concentrated_initial_sqrt_price(a, b, MIN_SQRT_PRICE, MAX_SQRT_PRICE).unwrap(); println!("init_sqrt_price: {:?}", init_sqrt_price); @@ -156,11 +157,9 @@ fn test_b_div_a_less_than_pa_mul_pb() { ) .unwrap(); - let ModifyLiquidityResult { - token_a_amount, - token_b_amount, - } = pool - .get_amounts_for_modify_liquidity(liquidity_delta, cp_amm::u128x128_math::Rounding::Up) + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let (token_a_amount, token_b_amount) = liquidity_handler + .get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Up) .unwrap(); // The small difference in token_a is expected due to rounding in liquidity calculations @@ -193,7 +192,8 @@ fn test_b_div_a_equal_pa_mul_pb() { .unwrap(); let a = u64::try_from(token_a_in_amount).unwrap(); - let init_sqrt_price = calculate_init_price(a, b, MIN_SQRT_PRICE, MAX_SQRT_PRICE).unwrap(); + let init_sqrt_price = + calculate_concentrated_initial_sqrt_price(a, b, MIN_SQRT_PRICE, MAX_SQRT_PRICE).unwrap(); println!("init_sqrt_price: {:?}", init_sqrt_price); @@ -213,13 +213,10 @@ fn test_b_div_a_equal_pa_mul_pb() { ) .unwrap(); - let ModifyLiquidityResult { - token_a_amount, - token_b_amount, - } = pool - .get_amounts_for_modify_liquidity(liquidity_delta, cp_amm::u128x128_math::Rounding::Up) + let liquidity_handler = pool.get_liquidity_handler().unwrap(); + let (token_a_amount, token_b_amount) = liquidity_handler + .get_amounts_for_modify_liquidity(liquidity_delta, Rounding::Up) .unwrap(); - // The small difference in token_a is expected due to rounding in liquidity calculations let token_a_diff = (token_a_amount as i128 - a as i128).abs(); assert!( diff --git a/tests/addLiquidity.test.ts b/tests/addLiquidity.test.ts index 192a222a..d1f5f6f8 100644 --- a/tests/addLiquidity.test.ts +++ b/tests/addLiquidity.test.ts @@ -80,7 +80,8 @@ describe("Add liquidity", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -255,7 +256,8 @@ describe("Add liquidity", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/alphaVaultWithSniperTax.test.ts b/tests/alphaVaultWithSniperTax.test.ts index 39eb4c6c..101b8003 100644 --- a/tests/alphaVaultWithSniperTax.test.ts +++ b/tests/alphaVaultWithSniperTax.test.ts @@ -219,7 +219,8 @@ const alphaVaultWithSniperTaxFullFlow = async ( activationPoint, poolFees: { baseFee, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, activationType: 0, // slot diff --git a/tests/claimFee.test.ts b/tests/claimFee.test.ts index d8123823..bd091be3 100644 --- a/tests/claimFee.test.ts +++ b/tests/claimFee.test.ts @@ -3,7 +3,6 @@ import BN from "bn.js"; import { addLiquidity, AddLiquidityParams, - claimPartnerFee, claimProtocolFee, createConfigIx, CreateConfigParams, @@ -45,16 +44,14 @@ describe("Claim fee", () => { let position: PublicKey; let inputTokenMint: PublicKey; let outputTokenMint: PublicKey; - let claimFeeOperator: Keypair; - let partner: Keypair; + let creator: Keypair; beforeEach(async () => { svm = startSvm(); user = generateKpAndFund(svm); admin = generateKpAndFund(svm); - partner = generateKpAndFund(svm); - claimFeeOperator = generateKpAndFund(svm); + creator = generateKpAndFund(svm); whitelistedAccount = generateKpAndFund(svm); inputTokenMint = createToken(svm, admin.publicKey, admin.publicKey); @@ -64,9 +61,9 @@ describe("Claim fee", () => { mintSplTokenTo(svm, outputTokenMint, admin, user.publicKey); - mintSplTokenTo(svm, inputTokenMint, admin, partner.publicKey); + mintSplTokenTo(svm, inputTokenMint, admin, creator.publicKey); - mintSplTokenTo(svm, outputTokenMint, admin, partner.publicKey); + mintSplTokenTo(svm, outputTokenMint, admin, creator.publicKey); const cliffFeeNumerator = new BN(2_500_000); const numberOfPeriod = new BN(0); @@ -87,13 +84,14 @@ describe("Claim fee", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), sqrtMaxPrice: new BN(MAX_SQRT_PRICE), vaultConfigKey: PublicKey.default, - poolCreatorAuthority: partner.publicKey, + poolCreatorAuthority: creator.publicKey, activationType: 0, collectFeeMode: 0, }; @@ -120,8 +118,8 @@ describe("Claim fee", () => { sqrtPrice = new BN(MIN_SQRT_PRICE.muln(2)); const initPoolParams: InitializePoolParams = { - payer: partner, - creator: partner.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: inputTokenMint, tokenBMint: outputTokenMint, @@ -164,16 +162,6 @@ describe("Claim fee", () => { pool, treasury: TREASURY, }); - - // claim partner fee - - await claimPartnerFee(svm, { - partner, - pool, - maxAmountA: new BN(100000000000000), - maxAmountB: new BN(100000000000000), - }); - }); }); @@ -190,8 +178,7 @@ describe("Claim fee", () => { let inputTokenMint: PublicKey; let outputTokenMint: PublicKey; - let operator: Keypair; - let partner: Keypair; + let creator: Keypair; beforeEach(async () => { svm = startSvm(); @@ -210,8 +197,7 @@ describe("Claim fee", () => { ]; user = generateKpAndFund(svm); admin = generateKpAndFund(svm); - partner = generateKpAndFund(svm); - operator = generateKpAndFund(svm); + creator = generateKpAndFund(svm); whitelistedAccount = generateKpAndFund(svm); await createToken2022( @@ -231,9 +217,9 @@ describe("Claim fee", () => { await mintToToken2022(svm, outputTokenMint, admin, user.publicKey); - await mintToToken2022(svm, inputTokenMint, admin, partner.publicKey); + await mintToToken2022(svm, inputTokenMint, admin, creator.publicKey); - await mintToToken2022(svm, outputTokenMint, admin, partner.publicKey); + await mintToToken2022(svm, outputTokenMint, admin, creator.publicKey); const cliffFeeNumerator = new BN(2_500_000); const numberOfPeriod = new BN(0); @@ -254,13 +240,14 @@ describe("Claim fee", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), sqrtMaxPrice: new BN(MAX_SQRT_PRICE), vaultConfigKey: PublicKey.default, - poolCreatorAuthority: partner.publicKey, + poolCreatorAuthority: creator.publicKey, activationType: 0, collectFeeMode: 0, }; @@ -287,8 +274,8 @@ describe("Claim fee", () => { sqrtPrice = new BN(MIN_SQRT_PRICE.muln(2)); const initPoolParams: InitializePoolParams = { - payer: partner, - creator: partner.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: inputTokenMint, tokenBMint: outputTokenMint, @@ -331,15 +318,6 @@ describe("Claim fee", () => { pool, treasury: TREASURY, }); - - // claim partner fee - - await claimPartnerFee(svm, { - partner, - pool, - maxAmountA: new BN(100000000000000), - maxAmountB: new BN(100000000000000), - }); }); }); }); diff --git a/tests/claimPositionFee.test.ts b/tests/claimPositionFee.test.ts index 148a2410..1d3984ac 100644 --- a/tests/claimPositionFee.test.ts +++ b/tests/claimPositionFee.test.ts @@ -76,7 +76,8 @@ describe("Claim position fee", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/compoundingLiquidity.test.ts b/tests/compoundingLiquidity.test.ts new file mode 100644 index 00000000..29b23ec1 --- /dev/null +++ b/tests/compoundingLiquidity.test.ts @@ -0,0 +1,166 @@ +import { generateKpAndFund, randomID } from "./helpers/common"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { + addLiquidity, + AddLiquidityParams, + createConfigIx, + CreateConfigParams, + createPosition, + initializePool, + InitializePoolParams, + MIN_SQRT_PRICE, + swapExactIn, + SwapParams, + createToken, + mintSplTokenTo, + encodePermissions, + OperatorPermission, + createOperator, + startSvm, + U128_MAX, + removeAllLiquidity, + LIQUIDITY_MAX, +} from "./helpers"; +import BN from "bn.js"; + +import { BaseFeeMode, encodeFeeTimeSchedulerParams } from "./helpers/feeCodec"; +import { LiteSVM } from "litesvm"; + +describe("Compounding liquidity", () => { + let svm: LiteSVM; + let admin: Keypair; + let user: Keypair; + let creator: Keypair; + let whitelistedAccount: Keypair; + let config: PublicKey; + let liquidity: BN; + let sqrtPrice: BN; + let pool: PublicKey; + let position: PublicKey; + let inputTokenMint: PublicKey; + let outputTokenMint: PublicKey; + + beforeEach(async () => { + svm = startSvm(); + + user = generateKpAndFund(svm); + admin = generateKpAndFund(svm); + creator = generateKpAndFund(svm); + whitelistedAccount = generateKpAndFund(svm); + + inputTokenMint = createToken(svm, admin.publicKey); + outputTokenMint = createToken(svm, admin.publicKey); + + mintSplTokenTo(svm, inputTokenMint, admin, user.publicKey); + + mintSplTokenTo(svm, outputTokenMint, admin, user.publicKey); + + mintSplTokenTo(svm, inputTokenMint, admin, creator.publicKey); + + mintSplTokenTo(svm, outputTokenMint, admin, creator.publicKey); + + const cliffFeeNumerator = new BN(2_500_000); + const numberOfPeriod = new BN(0); + const periodFrequency = new BN(0); + const reductionFactor = new BN(0); + + const data = encodeFeeTimeSchedulerParams( + BigInt(cliffFeeNumerator.toString()), + numberOfPeriod.toNumber(), + BigInt(periodFrequency.toString()), + BigInt(reductionFactor.toString()), + BaseFeeMode.FeeTimeSchedulerLinear + ); + + // create compounding config + const createConfigParams: CreateConfigParams = { + poolFees: { + baseFee: { + data: Array.from(data), + }, + compoundingFeeBps: 5000, + padding: 0, + dynamicFee: null, + }, + sqrtMinPrice: new BN(0), + sqrtMaxPrice: U128_MAX, + vaultConfigKey: PublicKey.default, + poolCreatorAuthority: PublicKey.default, + activationType: 0, + collectFeeMode: 2, + }; + + let permission = encodePermissions([OperatorPermission.CreateConfigKey]); + + await createOperator(svm, { + admin, + whitelistAddress: whitelistedAccount.publicKey, + permission, + }); + + config = await createConfigIx( + svm, + whitelistedAccount, + new BN(randomID()), + createConfigParams + ); + + liquidity = new BN(LIQUIDITY_MAX); + sqrtPrice = new BN(MIN_SQRT_PRICE.muln(2)); + + + }); + + it("Full flow", async () => { + const initPoolParams: InitializePoolParams = { + payer: creator, + creator: creator.publicKey, + config, + tokenAMint: inputTokenMint, + tokenBMint: outputTokenMint, + liquidity, + sqrtPrice, + activationPoint: null, + }; + + // create pool + const result = await initializePool(svm, initPoolParams); + pool = result.pool; + position = await createPosition(svm, user, user.publicKey, pool); + + // add more liquidity + const addLiquidityParams: AddLiquidityParams = { + owner: user, + pool, + position, + liquidityDelta: new BN(MIN_SQRT_PRICE.muln(30)), + tokenAAmountThreshold: new BN(200), + tokenBAmountThreshold: new BN(200), + }; + await addLiquidity(svm, addLiquidityParams); + + // swap exact in + const swapParams: SwapParams = { + payer: user, + pool, + inputTokenMint, + outputTokenMint, + amountIn: new BN(10), + minimumAmountOut: new BN(0), + referralTokenAccount: null, + }; + + await swapExactIn(svm, swapParams); + + // remove liquidity + const removeAllLiquidityParams = { + owner: user, + pool, + position, + tokenAAmountThreshold: new BN(0), + tokenBAmountThreshold: new BN(0), + }; + await removeAllLiquidity(svm, removeAllLiquidityParams); + + }); +}); diff --git a/tests/createConfig.test.ts b/tests/createConfig.test.ts index b8fab053..49b4d4d5 100644 --- a/tests/createConfig.test.ts +++ b/tests/createConfig.test.ts @@ -1,5 +1,4 @@ import { Keypair, PublicKey } from "@solana/web3.js"; -import { BN } from "bn.js"; import { BASIS_POINT_MAX, closeConfigIx, @@ -21,13 +20,14 @@ import { encodeFeeTimeSchedulerParams, } from "./helpers/feeCodec"; import { LiteSVM } from "litesvm"; +import BN from "bn.js"; describe("Admin function: Create config", () => { let svm: LiteSVM; let admin: Keypair; let whitelistedAccount: Keypair; let createConfigParams: CreateConfigParams; - let index; + let index: BN; beforeEach(async () => { svm = startSvm(); @@ -52,7 +52,8 @@ describe("Admin function: Create config", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -118,7 +119,8 @@ describe("Admin function: Create config", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: { binStep: binStep.toNumber(), binStepU128, @@ -169,7 +171,8 @@ describe("Admin function: Create config", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/createCustomizePool.test.ts b/tests/createCustomizePool.test.ts index e2c2a984..9c359242 100644 --- a/tests/createCustomizePool.test.ts +++ b/tests/createCustomizePool.test.ts @@ -3,7 +3,6 @@ import BN from "bn.js"; import { createToken, - getPool, initializeCustomizablePool, InitializeCustomizablePoolParams, MAX_SQRT_PRICE, @@ -71,7 +70,8 @@ describe("Initialize customizable pool", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, activationType: 0, @@ -154,7 +154,8 @@ describe("Initialize customizable pool", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, activationType: 0, diff --git a/tests/createPool.test.ts b/tests/createPool.test.ts index b99d434b..d65f47a7 100644 --- a/tests/createPool.test.ts +++ b/tests/createPool.test.ts @@ -73,7 +73,8 @@ describe("Initialize pool", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -200,7 +201,8 @@ describe("Initialize pool", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/createPosition.test.ts b/tests/createPosition.test.ts index 8181a75a..cf5d0ccb 100644 --- a/tests/createPosition.test.ts +++ b/tests/createPosition.test.ts @@ -80,7 +80,8 @@ describe("Create position", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -193,7 +194,8 @@ describe("Create position", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/dynamicConfig.test.ts b/tests/dynamicConfig.test.ts index 8a3fd454..dda2ec1a 100644 --- a/tests/dynamicConfig.test.ts +++ b/tests/dynamicConfig.test.ts @@ -93,7 +93,8 @@ describe("Dynamic config test", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, activationType: 0, diff --git a/tests/frozenRewardVault.test.ts b/tests/frozenRewardVault.test.ts index a1c87ddd..cd444813 100644 --- a/tests/frozenRewardVault.test.ts +++ b/tests/frozenRewardVault.test.ts @@ -95,7 +95,8 @@ describe("Frozen reward vault", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/helpers/constants.ts b/tests/helpers/constants.ts index f160711d..c09423e1 100644 --- a/tests/helpers/constants.ts +++ b/tests/helpers/constants.ts @@ -38,6 +38,7 @@ export const DECIMALS = 9; export const BASIS_POINT_MAX = 10_000; export const OFFSET = 64; export const U64_MAX = new BN("18446744073709551615"); +export const U128_MAX = new BN("340282366920938463463374607431768211455"); export const MAX_FEE_BPS = 9900; export const MAX_FEE_NUMERATOR = 990_000_000; export const MIN_FEE_NUMERATOR = 100_000; diff --git a/tests/helpers/cpAmm.ts b/tests/helpers/cpAmm.ts index d1fd2304..41b9f184 100644 --- a/tests/helpers/cpAmm.ts +++ b/tests/helpers/cpAmm.ts @@ -133,7 +133,8 @@ export type BaseFee = { export type PoolFees = { baseFee: BaseFee; - padding: number[]; + compoundingFeeBps: number, + padding: number; dynamicFee: DynamicFee | null; }; @@ -321,7 +322,6 @@ export async function createConfigIx( throw new Error("Unreachable"); } expect(configState.poolFees.protocolFeePercent).eq(20); - expect(configState.poolFees.partnerFeePercent).eq(0); expect(configState.poolFees.referralFeePercent).eq(20); expect(configState.configType).eq(0); // ConfigType: Static @@ -346,7 +346,7 @@ export async function closeConfigIx( const result = sendTransaction(svm, transaction, [whitelistedAddress]); expect(result).instanceOf(TransactionMetadata); - const configState = svm.getAccount(config); + const configState = svm.getAccount(config)!; expect(configState.data.length).eq(0); } @@ -406,7 +406,7 @@ export async function closeTokenBadge( const result = sendTransaction(svm, transaction, [whitelistedAddress]); expect(result).instanceOf(TransactionMetadata); - const tokenBadgeAccount = svm.getAccount(tokenBadge); + const tokenBadgeAccount = svm.getAccount(tokenBadge)!; expect(tokenBadgeAccount.data.length).eq(0); } @@ -421,7 +421,8 @@ export enum OperatorPermission { UpdateRewardFunder, // 7 UpdatePoolFees, // 8 ClaimProtocolFee, // 9 - ZapProtocolFee, + ZapProtocolFee, // 10 + FixPool, // 11 } export function encodePermissions(permissions: OperatorPermission[]): BN { @@ -502,8 +503,8 @@ export async function claimProtocolFee( const operator = deriveOperatorAddress(whitelistedKP.publicKey); const poolState = getPool(svm, pool); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; + const tokenAProgram = svm.getAccount(poolState.tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(poolState.tokenBMint)!.owner; const tokenAVaultAccount = svm.getAccount( poolState.tokenAVault @@ -571,59 +572,6 @@ export async function claimProtocolFee( expect(result).instanceOf(TransactionMetadata); } -export type ClaimPartnerFeeParams = { - partner: Keypair; - pool: PublicKey; - maxAmountA: BN; - maxAmountB: BN; -}; -export async function claimPartnerFee( - svm: LiteSVM, - params: ClaimPartnerFeeParams -) { - const program = createCpAmmProgram(); - const { partner, pool, maxAmountA, maxAmountB } = params; - const poolAuthority = derivePoolAuthority(); - const poolState = getPool(svm, pool); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; - const tokenAAccount = getOrCreateAssociatedTokenAccount( - svm, - partner, - poolState.tokenAMint, - partner.publicKey, - tokenAProgram - ); - - const tokenBAccount = getOrCreateAssociatedTokenAccount( - svm, - partner, - poolState.tokenBMint, - partner.publicKey, - tokenBProgram - ); - const transaction = await program.methods - .claimPartnerFee(maxAmountA, maxAmountB) - .accountsPartial({ - poolAuthority, - pool, - tokenAVault: poolState.tokenAVault, - tokenBVault: poolState.tokenBVault, - tokenAMint: poolState.tokenAMint, - tokenBMint: poolState.tokenBMint, - tokenAAccount, - tokenBAccount, - partner: partner.publicKey, - tokenAProgram, - tokenBProgram, - }) - .transaction(); - - const result = sendTransaction(svm, transaction, [partner]); - - expect(result).instanceOf(TransactionMetadata); -} - export type InitializePoolParams = { payer: Keypair; creator: PublicKey; @@ -665,8 +613,8 @@ export async function initializePool( const tokenAVault = deriveTokenVaultAddress(tokenAMint, pool); const tokenBVault = deriveTokenVaultAddress(tokenBMint, pool); - const tokenAProgram = svm.getAccount(tokenAMint).owner; - const tokenBProgram = svm.getAccount(tokenBMint).owner; + const tokenAProgram = svm.getAccount(tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(tokenBMint)!.owner; const payerTokenA = getAssociatedTokenAddressSync( tokenAMint, @@ -716,7 +664,13 @@ export async function initializePool( ); const result = sendTransaction(svm, transaction, [payer, positionNftKP]); + + // if (result instanceof FailedTransactionMetadata) { + // console.log(result.meta().logs()); + // } + if (result instanceof TransactionMetadata) { + // console.log(result.logs()); // validate pool data const poolState = getPool(svm, pool); expect(poolState.tokenAMint.toString()).eq(tokenAMint.toString()); @@ -724,13 +678,16 @@ export async function initializePool( expect(poolState.tokenAVault.toString()).eq(tokenAVault.toString()); expect(poolState.tokenBVault.toString()).eq(tokenBVault.toString()); expect(poolState.liquidity.toString()).eq(liquidity.toString()); - expect(poolState.sqrtPrice.toString()).eq(sqrtPrice.toString()); + + if (poolState.collectFeeMode != 2) { + expect(poolState.sqrtPrice.toString()).eq(sqrtPrice.toString()); + expect(poolState.poolFees.initSqrtPrice.toString()).eq( + sqrtPrice.toString() + ); + } expect(poolState.rewardInfos[0].initialized).eq(0); expect(poolState.rewardInfos[1].initialized).eq(0); - expect(poolState.poolFees.initSqrtPrice.toString()).eq( - sqrtPrice.toString() - ); } return { pool, position: position, result }; @@ -788,8 +745,8 @@ export async function initializePoolWithCustomizeConfig( const position = derivePositionAddress(positionNftKP.publicKey); const positionNftAccount = derivePositionNftAccount(positionNftKP.publicKey); - const tokenAProgram = svm.getAccount(tokenAMint).owner; - const tokenBProgram = svm.getAccount(tokenBMint).owner; + const tokenAProgram = svm.getAccount(tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(tokenBMint)!.owner; const tokenAVault = deriveTokenVaultAddress(tokenAMint, pool); const tokenBVault = deriveTokenVaultAddress(tokenBMint, pool); @@ -897,7 +854,8 @@ export async function setPoolStatus(svm: LiteSVM, params: SetPoolStatusParams) { export type PoolFeesParams = { baseFee: BaseFee; - padding: number[]; + compoundingFeeBps: number, + padding: number; dynamicFee: DynamicFee | null; }; @@ -945,8 +903,8 @@ export async function initializeCustomizablePool( const position = derivePositionAddress(positionNftKP.publicKey); const positionNftAccount = derivePositionNftAccount(positionNftKP.publicKey); - const tokenAProgram = svm.getAccount(tokenAMint).owner; - const tokenBProgram = svm.getAccount(tokenBMint).owner; + const tokenAProgram = svm.getAccount(tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(tokenBMint)!.owner; const tokenAVault = deriveTokenVaultAddress(tokenAMint, pool); const tokenBVault = deriveTokenVaultAddress(tokenBMint, pool); @@ -1143,7 +1101,7 @@ export async function initializeReward( const poolAuthority = derivePoolAuthority(); const rewardVault = deriveRewardVaultAddress(pool, index); - const tokenProgram = svm.getAccount(rewardMint).owner; + const tokenProgram = svm.getAccount(rewardMint)!.owner; const tokenBadge = deriveTokenBadgeAddress(rewardMint); const remainingAccounts: AccountMeta[] = []; @@ -1299,7 +1257,7 @@ export async function fundReward( const poolState = getPool(svm, pool); const rewardVault = poolState.rewardInfos[index].vault; - const tokenProgram = svm.getAccount(poolState.rewardInfos[index].mint).owner; + const tokenProgram = svm.getAccount(poolState.rewardInfos[index].mint)!.owner; const funderTokenAccount = getAssociatedTokenAddressSync( poolState.rewardInfos[index].mint, funder.publicKey, @@ -1344,7 +1302,7 @@ export async function claimReward( const positionNftAccount = derivePositionNftAccount(positionState.nftMint); // TODO should use token flag in pool state to get token program ID - const tokenProgram = svm.getAccount(poolState.rewardInfos[index].mint).owner; + const tokenProgram = svm.getAccount(poolState.rewardInfos[index].mint)!.owner; const userTokenAccount = getOrCreateAssociatedTokenAccount( svm, @@ -1388,7 +1346,7 @@ export async function withdrawIneligibleReward( const poolState = getPool(svm, pool); const poolAuthority = derivePoolAuthority(); - const tokenProgram = svm.getAccount(poolState.rewardInfos[index].mint).owner; + const tokenProgram = svm.getAccount(poolState.rewardInfos[index].mint)!.owner; const funderTokenAccount = getAssociatedTokenAddressSync( poolState.rewardInfos[index].mint, funder.publicKey, @@ -1567,22 +1525,22 @@ export async function createPosition( ); const positionNftData = AccountLayout.decode( - svm.getAccount(positionNftAccount).data + svm.getAccount(positionNftAccount)!.data ); // validate metadata const tlvData = svm - .getAccount(positionState.nftMint) + .getAccount(positionState.nftMint)! .data.slice(ACCOUNT_SIZE + ACCOUNT_TYPE_SIZE); const metadata = unpack( - getExtensionData(ExtensionType.TokenMetadata, Buffer.from(tlvData)) + getExtensionData(ExtensionType.TokenMetadata, Buffer.from(tlvData))! ); expect(metadata.name).eq("Meteora Position NFT"); expect(metadata.symbol).eq("MPN"); // validate metadata pointer const metadataAddress = MetadataPointerLayout.decode( - getExtensionData(ExtensionType.MetadataPointer, Buffer.from(tlvData)) + getExtensionData(ExtensionType.MetadataPointer, Buffer.from(tlvData))! ).metadataAddress; expect(metadataAddress.toString()).eq(positionState.nftMint.toString()); @@ -1620,8 +1578,8 @@ export async function addLiquidity(svm: LiteSVM, params: AddLiquidityParams) { const positionState = getPosition(svm, position); const positionNftAccount = derivePositionNftAccount(positionState.nftMint); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; + const tokenAProgram = svm.getAccount(poolState.tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(poolState.tokenBMint)!.owner; const tokenAAccount = getAssociatedTokenAddressSync( poolState.tokenAMint, @@ -1688,8 +1646,8 @@ export async function removeLiquidity( const positionNftAccount = derivePositionNftAccount(positionState.nftMint); const poolAuthority = derivePoolAuthority(); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; + const tokenAProgram = svm.getAccount(poolState.tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(poolState.tokenBMint)!.owner; const tokenAAccount = getAssociatedTokenAddressSync( poolState.tokenAMint, @@ -1761,8 +1719,8 @@ export async function removeAllLiquidity( const positionNftAccount = derivePositionNftAccount(positionState.nftMint); const poolAuthority = derivePoolAuthority(); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; + const tokenAProgram = svm.getAccount(poolState.tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(poolState.tokenBMint)!.owner; const tokenAAccount = getAssociatedTokenAddressSync( poolState.tokenAMint, @@ -1863,9 +1821,9 @@ export async function swapInstruction( const poolState = getPool(svm, pool); const poolAuthority = derivePoolAuthority(); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; + const tokenAProgram = svm.getAccount(poolState.tokenAMint)!.owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; + const tokenBProgram = svm.getAccount(poolState.tokenBMint)!.owner; const inputTokenAccount = getAssociatedTokenAddressSync( inputTokenMint, payer.publicKey, @@ -1950,9 +1908,9 @@ export async function swap2Instruction(svm: LiteSVM, params: Swap2Params) { const poolState = getPool(svm, pool); const poolAuthority = derivePoolAuthority(); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; + const tokenAProgram = svm.getAccount(poolState.tokenAMint)!.owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; + const tokenBProgram = svm.getAccount(poolState.tokenBMint)!.owner; const inputTokenAccount = getAssociatedTokenAddressSync( inputTokenMint, payer.publicKey, @@ -2071,8 +2029,8 @@ export async function claimPositionFee( const positionNftAccount = derivePositionNftAccount(positionState.nftMint); const poolAuthority = derivePoolAuthority(); - const tokenAProgram = svm.getAccount(poolState.tokenAMint).owner; - const tokenBProgram = svm.getAccount(poolState.tokenBMint).owner; + const tokenAProgram = svm.getAccount(poolState.tokenAMint)!.owner; + const tokenBProgram = svm.getAccount(poolState.tokenBMint)!.owner; const tokenAAccount = getAssociatedTokenAddressSync( poolState.tokenAMint, @@ -2158,7 +2116,7 @@ export async function splitPosition(svm: LiteSVM, params: SplitPositionParams) { reward0Percentage, reward1Percentage, innerVestingLiquidityPercentage, - padding: new Array(16).fill(0), + padding: new Array(15).fill(0), }) .accountsPartial({ pool, @@ -2273,31 +2231,31 @@ export async function zapProtocolFee(params: { export function getPool(svm: LiteSVM, pool: PublicKey): Pool { const program = createCpAmmProgram(); - const account = svm.getAccount(pool); + const account = svm.getAccount(pool)!; return program.coder.accounts.decode("pool", Buffer.from(account.data)); } export function getPosition(svm: LiteSVM, position: PublicKey): Position { const program = createCpAmmProgram(); - const account = svm.getAccount(position); + const account = svm.getAccount(position)!; return program.coder.accounts.decode("position", Buffer.from(account.data)); } export function getVesting(svm: LiteSVM, vesting: PublicKey): Vesting { const program = createCpAmmProgram(); - const account = svm.getAccount(vesting); + const account = svm.getAccount(vesting)!; return program.coder.accounts.decode("vesting", Buffer.from(account.data)); } export function getConfig(svm: LiteSVM, config: PublicKey): Config { const program = createCpAmmProgram(); - const account = svm.getAccount(config); + const account = svm.getAccount(config)!; return program.coder.accounts.decode("config", Buffer.from(account.data)); } export function getTokenBadge(svm: LiteSVM, tokenBadge: PublicKey): TokenBadge { const program = createCpAmmProgram(); - const account = svm.getAccount(tokenBadge); + const account = svm.getAccount(tokenBadge)!; return program.coder.accounts.decode("tokenBadge", Buffer.from(account.data)); } diff --git a/tests/helpers/feeCodec.ts b/tests/helpers/feeCodec.ts index 516403a6..99495226 100644 --- a/tests/helpers/feeCodec.ts +++ b/tests/helpers/feeCodec.ts @@ -12,8 +12,6 @@ type BorshFeeTimeScheduler = IdlTypes["borshFeeTimeScheduler"]; type BorshFeeMarketCapScheduler = IdlTypes["borshFeeMarketCapScheduler"]; type BorshRateLimiter = IdlTypes["borshFeeRateLimiter"]; -const PADDING = Array.from(Buffer.alloc(3)); - export enum BaseFeeMode { FeeTimeSchedulerLinear, FeeTimeSchedulerExponential, @@ -37,7 +35,6 @@ export function encodeFeeTimeSchedulerParams( periodFrequency: new BN(periodFrequency.toString()), reductionFactor: new BN(reductionFactor.toString()), baseFeeMode, - padding: PADDING, }; const program = createCpAmmProgram(); @@ -75,7 +72,6 @@ export function encodeFeeMarketCapSchedulerParams( schedulerExpirationDuration, reductionFactor: new BN(reductionFactor.toString()), baseFeeMode, - padding: PADDING, }; const program = createCpAmmProgram(); @@ -112,7 +108,6 @@ export function encodeFeeRateLimiterParams( maxLimiterDuration, maxFeeBps, referenceAmount: new BN(referenceAmount.toString()), - padding: PADDING, baseFeeMode: BaseFeeMode.RateLimiter, }; diff --git a/tests/helpers/token.ts b/tests/helpers/token.ts index 3d3aa8ac..8116fe67 100644 --- a/tests/helpers/token.ts +++ b/tests/helpers/token.ts @@ -23,7 +23,7 @@ import { expect } from "chai"; import { LiteSVM, TransactionMetadata } from "litesvm"; import { DECIMALS, NATIVE_MINT } from "./constants"; import { sendTransaction } from "./svm"; -const rawAmount = 100_000_000 * 10 ** DECIMALS; // 1 millions +const rawAmount = 100_000_000_000 * 10 ** DECIMALS; // 1 billions export function getOrCreateAssociatedTokenAccount( svm: LiteSVM, @@ -77,7 +77,7 @@ export function createToken( mintKeypair.publicKey, DECIMALS, mintAuthority, - freezeAuthority + freezeAuthority ? freezeAuthority : null, ); let transaction = new Transaction(); @@ -165,7 +165,7 @@ export function mintSplTokenTo( } export function getMint(svm: LiteSVM, mint: PublicKey): RawMint { - const account = svm.getAccount(mint); + const account = svm.getAccount(mint)!; const mintState = MintLayout.decode(account.data); return mintState; } diff --git a/tests/lockPosition.test.ts b/tests/lockPosition.test.ts index bbb74b67..7129abb1 100644 --- a/tests/lockPosition.test.ts +++ b/tests/lockPosition.test.ts @@ -95,7 +95,8 @@ describe("Lock position", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -418,7 +419,8 @@ describe("Lock position", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/lockPositionWithoutVestingAccount.test.ts b/tests/lockPositionWithoutVestingAccount.test.ts index 6b627568..07429c3c 100644 --- a/tests/lockPositionWithoutVestingAccount.test.ts +++ b/tests/lockPositionWithoutVestingAccount.test.ts @@ -91,7 +91,8 @@ describe("Lock position inner", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/marketCapFeeScheduler.test.ts b/tests/marketCapFeeScheduler.test.ts index 181fd303..d4d24d09 100644 --- a/tests/marketCapFeeScheduler.test.ts +++ b/tests/marketCapFeeScheduler.test.ts @@ -35,10 +35,8 @@ const schedulerExpirationDuration = new BN(3600); describe("Market cap fee scheduler", () => { let svm: LiteSVM; let admin: Keypair; - let operator: Keypair; - let partner: Keypair; let user: Keypair; - let poolCreator: Keypair; + let creator: Keypair; let tokenA: PublicKey; let tokenB: PublicKey; let whitelistedAccount: Keypair; @@ -46,10 +44,8 @@ describe("Market cap fee scheduler", () => { before(async () => { svm = startSvm(); admin = generateKpAndFund(svm); - operator = generateKpAndFund(svm); - partner = generateKpAndFund(svm); user = generateKpAndFund(svm); - poolCreator = generateKpAndFund(svm); + creator = generateKpAndFund(svm); whitelistedAccount = generateKpAndFund(svm); tokenA = createToken(svm, admin.publicKey, admin.publicKey); tokenB = createToken(svm, admin.publicKey, admin.publicKey); @@ -58,9 +54,9 @@ describe("Market cap fee scheduler", () => { mintSplTokenTo(svm, tokenB, admin, user.publicKey); - mintSplTokenTo(svm, tokenA, admin, poolCreator.publicKey); + mintSplTokenTo(svm, tokenA, admin, creator.publicKey); - mintSplTokenTo(svm, tokenB, admin, poolCreator.publicKey); + mintSplTokenTo(svm, tokenB, admin, creator.publicKey); let permission = encodePermissions([ OperatorPermission.CreateConfigKey, @@ -91,7 +87,8 @@ describe("Market cap fee scheduler", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: MIN_SQRT_PRICE, @@ -102,8 +99,8 @@ describe("Market cap fee scheduler", () => { collectFeeMode: 1, // onlyB activationPoint: null, hasAlphaVault: false, - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, tokenAMint: tokenA, tokenBMint: tokenB, }); @@ -126,7 +123,8 @@ describe("Market cap fee scheduler", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -146,8 +144,8 @@ describe("Market cap fee scheduler", () => { const liquidity = new BN(MIN_LP_AMOUNT); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -160,7 +158,7 @@ describe("Market cap fee scheduler", () => { // Market cap increase await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -175,7 +173,7 @@ describe("Market cap fee scheduler", () => { // Market cap increase await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, diff --git a/tests/permissionLessTransferHook.test.ts b/tests/permissionLessTransferHook.test.ts index ce01b75e..76d533fb 100644 --- a/tests/permissionLessTransferHook.test.ts +++ b/tests/permissionLessTransferHook.test.ts @@ -96,7 +96,8 @@ describe("Permissionless transfer hook", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/rateLimiter.test.ts b/tests/rateLimiter.test.ts index 5a423b61..de3b6f15 100644 --- a/tests/rateLimiter.test.ts +++ b/tests/rateLimiter.test.ts @@ -38,21 +38,17 @@ import { LiteSVM } from "litesvm"; describe("Rate limiter", () => { let svm: LiteSVM; let admin: Keypair; - let operator: Keypair; - let partner: Keypair; let whitelistedAccount: Keypair; let user: Keypair; - let poolCreator: Keypair; + let creator: Keypair; let tokenA: PublicKey; let tokenB: PublicKey; beforeEach(async () => { svm = startSvm(); admin = generateKpAndFund(svm); - operator = generateKpAndFund(svm); - partner = generateKpAndFund(svm); user = generateKpAndFund(svm); - poolCreator = generateKpAndFund(svm); + creator = generateKpAndFund(svm); whitelistedAccount = generateKpAndFund(svm); tokenA = createToken(svm, admin.publicKey); @@ -62,9 +58,9 @@ describe("Rate limiter", () => { mintSplTokenTo(svm, tokenB, admin, user.publicKey); - mintSplTokenTo(svm, tokenA, admin, poolCreator.publicKey); + mintSplTokenTo(svm, tokenA, admin, creator.publicKey); - mintSplTokenTo(svm, tokenB, admin, poolCreator.publicKey); + mintSplTokenTo(svm, tokenB, admin, creator.publicKey); }); it("Rate limiter", async () => { @@ -88,7 +84,8 @@ describe("Rate limiter", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -117,8 +114,8 @@ describe("Rate limiter", () => { const sqrtPrice = new BN(MIN_SQRT_PRICE.muln(2)); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -132,7 +129,7 @@ describe("Rate limiter", () => { // swap with 1 SOL await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -154,7 +151,7 @@ describe("Rate limiter", () => { // swap with 2 SOL await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -180,7 +177,7 @@ describe("Rate limiter", () => { // swap with 2 SOL await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -219,15 +216,16 @@ describe("Rate limiter", () => { ); const initPoolParams: InitializeCustomizablePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, tokenAMint: tokenA, tokenBMint: tokenB, poolFees: { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -243,7 +241,7 @@ describe("Rate limiter", () => { // swap with 1 SOL const swapIx = await swapInstruction(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -260,7 +258,7 @@ describe("Rate limiter", () => { const errorCode = getCpAmmProgramErrorCode( "FailToValidateSingleSwapInstruction" ); - const result = sendTransaction(svm, transaction, [poolCreator]); + const result = sendTransaction(svm, transaction, [creator]); expectThrowsErrorCode(result, errorCode); }); }); diff --git a/tests/removeLiquidity.test.ts b/tests/removeLiquidity.test.ts index 235100e6..0e75ffcb 100644 --- a/tests/removeLiquidity.test.ts +++ b/tests/removeLiquidity.test.ts @@ -79,7 +79,8 @@ describe("Remove liquidity", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -234,7 +235,8 @@ describe("Remove liquidity", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/rewardByAdmin.test.ts b/tests/rewardByAdmin.test.ts index b03a7d3a..1aae508e 100644 --- a/tests/rewardByAdmin.test.ts +++ b/tests/rewardByAdmin.test.ts @@ -101,7 +101,8 @@ describe("Reward by admin", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -409,7 +410,8 @@ describe("Reward by admin", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/rewardByCreator.test.ts b/tests/rewardByCreator.test.ts index a6080558..d9efaeb3 100644 --- a/tests/rewardByCreator.test.ts +++ b/tests/rewardByCreator.test.ts @@ -100,7 +100,8 @@ describe("Reward by creator", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -364,7 +365,8 @@ describe("Reward by creator", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/splitPosition.test.ts b/tests/splitPosition.test.ts index 0248617b..db31937f 100644 --- a/tests/splitPosition.test.ts +++ b/tests/splitPosition.test.ts @@ -84,7 +84,8 @@ describe("Split position", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/splitPosition2.test.ts b/tests/splitPosition2.test.ts index 644c62d3..ba628542 100644 --- a/tests/splitPosition2.test.ts +++ b/tests/splitPosition2.test.ts @@ -85,7 +85,8 @@ describe("Split position 2", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/splitVesting.test.ts b/tests/splitVesting.test.ts index fd878843..4d5a6b71 100644 --- a/tests/splitVesting.test.ts +++ b/tests/splitVesting.test.ts @@ -83,7 +83,8 @@ describe("Split vesting", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/swap.test.ts b/tests/swap.test.ts index 9b1d0d51..ce785b59 100644 --- a/tests/swap.test.ts +++ b/tests/swap.test.ts @@ -93,7 +93,8 @@ describe("Swap token", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -238,7 +239,8 @@ describe("Swap token", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tests/testMaxFee.test.ts b/tests/testMaxFee.test.ts index 3f734aee..c3c97fd8 100644 --- a/tests/testMaxFee.test.ts +++ b/tests/testMaxFee.test.ts @@ -39,10 +39,8 @@ const schedulerExpirationDuration = new BN(3600); describe("Test max fee 99%", () => { let svm: LiteSVM; let admin: Keypair; - let operator: Keypair; - let partner: Keypair; let user: Keypair; - let poolCreator: Keypair; + let creator: Keypair; let tokenA: PublicKey; let tokenB: PublicKey; let whitelistedAccount: Keypair; @@ -51,10 +49,8 @@ describe("Test max fee 99%", () => { beforeEach(async () => { svm = startSvm(); admin = generateKpAndFund(svm); - operator = generateKpAndFund(svm); - partner = generateKpAndFund(svm); user = generateKpAndFund(svm); - poolCreator = generateKpAndFund(svm); + creator = generateKpAndFund(svm); whitelistedAccount = generateKpAndFund(svm); tokenA = createToken(svm, admin.publicKey); tokenB = createToken(svm, admin.publicKey); @@ -63,9 +59,9 @@ describe("Test max fee 99%", () => { mintSplTokenTo(svm, tokenB, admin, user.publicKey); - mintSplTokenTo(svm, tokenA, admin, poolCreator.publicKey); + mintSplTokenTo(svm, tokenA, admin, creator.publicKey); - mintSplTokenTo(svm, tokenB, admin, poolCreator.publicKey); + mintSplTokenTo(svm, tokenB, admin, creator.publicKey); let permission = encodePermissions([ OperatorPermission.CreateConfigKey, @@ -83,7 +79,8 @@ describe("Test max fee 99%", () => { baseFee: { data: Array.from([]), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), @@ -119,8 +116,8 @@ describe("Test max fee 99%", () => { const liquidity = new BN(MIN_LP_AMOUNT); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -130,12 +127,12 @@ describe("Test max fee 99%", () => { }; const { pool } = await initializePool(svm, initPoolParams); let poolState = getPool(svm, pool); - expect(poolState.version.toString()).eq("1"); + expect(poolState.feeVersion.toString()).eq("1"); // Market cap increase const amountIn = new BN(LAMPORTS_PER_SOL); await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -180,8 +177,8 @@ describe("Test max fee 99%", () => { const liquidity = new BN(MIN_LP_AMOUNT); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -191,12 +188,12 @@ describe("Test max fee 99%", () => { }; const { pool } = await initializePool(svm, initPoolParams); let poolState = getPool(svm, pool); - expect(poolState.version.toString()).eq("1"); + expect(poolState.feeVersion.toString()).eq("1"); // Market cap increase const amountIn = new BN(LAMPORTS_PER_SOL); await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -244,8 +241,8 @@ describe("Test max fee 99%", () => { const liquidity = new BN(MIN_LP_AMOUNT); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -255,12 +252,12 @@ describe("Test max fee 99%", () => { }; const { pool } = await initializePool(svm, initPoolParams); let poolState = getPool(svm, pool); - expect(poolState.version.toString()).eq("1"); + expect(poolState.feeVersion.toString()).eq("1"); // Market cap increase const amountIn = new BN(LAMPORTS_PER_SOL); await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -303,8 +300,8 @@ describe("Test max fee 99%", () => { const liquidity = new BN(MIN_LP_AMOUNT); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -315,12 +312,12 @@ describe("Test max fee 99%", () => { const { pool } = await initializePool(svm, initPoolParams); let poolState = getPool(svm, pool); - expect(poolState.version.toString()).eq("1"); + expect(poolState.feeVersion.toString()).eq("1"); // Market cap increase const amountIn = new BN(LAMPORTS_PER_SOL); await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -363,8 +360,8 @@ describe("Test max fee 99%", () => { const liquidity = new BN(MIN_LP_AMOUNT); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -378,7 +375,7 @@ describe("Test max fee 99%", () => { // Market cap increase const amountIn = new BN(LAMPORTS_PER_SOL); await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, @@ -426,8 +423,8 @@ describe("Test max fee 99%", () => { const sqrtPrice = new BN(MIN_SQRT_PRICE.muln(2)); const initPoolParams: InitializePoolParams = { - payer: poolCreator, - creator: poolCreator.publicKey, + payer: creator, + creator: creator.publicKey, config, tokenAMint: tokenA, tokenBMint: tokenB, @@ -441,7 +438,7 @@ describe("Test max fee 99%", () => { // swap with 3 SOL const amountIn = referenceAmount.muln(3); await swapExactIn(svm, { - payer: poolCreator, + payer: creator, pool, inputTokenMint: tokenB, outputTokenMint: tokenA, diff --git a/tests/updatePoolFees.test.ts b/tests/updatePoolFees.test.ts index 4e6ca06d..3e6f3dc9 100644 --- a/tests/updatePoolFees.test.ts +++ b/tests/updatePoolFees.test.ts @@ -615,7 +615,8 @@ async function createPool( baseFee: { data: Array.from(baseFeeData), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee, }, activationType: 0, diff --git a/tests/zapProtocolFees.test.ts b/tests/zapProtocolFees.test.ts index d462d40f..6a2f8fe5 100644 --- a/tests/zapProtocolFees.test.ts +++ b/tests/zapProtocolFees.test.ts @@ -98,7 +98,8 @@ describe("Zap protocol fees", () => { baseFee: { data: Array.from(data), }, - padding: [], + compoundingFeeBps: 0, + padding: 0, dynamicFee: null, }, sqrtMinPrice: new BN(MIN_SQRT_PRICE), diff --git a/tsconfig.json b/tsconfig.json index 247d160a..281ebf8a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,9 @@ "module": "commonjs", "target": "es6", "esModuleInterop": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "noEmit": true, + "skipLibCheck": true, + "strict": false } }