From f547688c98bf3a1add87fdb8293890153cb55fb6 Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Fri, 11 Apr 2025 17:02:34 +0530 Subject: [PATCH 01/10] feat: add various README for accessibility --- README.md | 135 ++++++++++++++++++- programs/solana-tax-token-anchor/README.md | 149 +++++++++++++++++++++ tests/README.md | 108 +++++++++++++++ 3 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 programs/solana-tax-token-anchor/README.md create mode 100644 tests/README.md diff --git a/README.md b/README.md index c464ac3..663af7f 100644 --- a/README.md +++ b/README.md @@ -1 +1,134 @@ -# solana-contracts \ No newline at end of file +# Solana Tax Token Contract + +A Solana smart contract implementing a token with tax/fee functionality using SPL Token 2022 extensions, built with Anchor framework. + +## Overview + +This project demonstrates the implementation of a token with transfer fee (tax) functionality on Solana. It uses the SPL Token 2022 program's TransferFee extension to automatically collect fees on each token transfer. + +Key features: +- Token creation with configurable transfer fee +- Automatic fee collection on token transfers +- Fee harvesting from token accounts to the mint +- Fee withdrawal to a designated wallet +- Fee configuration updates + +## Prerequisites + +- [Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools) v1.16.0 or later +- [Anchor](https://project-serum.github.io/anchor/getting-started/installation.html) v0.29.0 or later +- [Node.js](https://nodejs.org/en/) v16 or later +- [Yarn](https://yarnpkg.com/getting-started/install) or npm + +## Setup + +1. Clone the repository: +```sh +git clone https://github.com/yourusername/maha-solana-contracts.git +cd maha-solana-contracts +``` + +2. Install dependencies: +```sh +yarn install +``` + +3. Build the program: +```sh +anchor build +``` + +4. Start a local test validator: +```sh +solana-test-validator --reset +``` + +5. Deploy the program: +```sh +anchor deploy +``` + +## Contracts + +### solana-tax-token-anchor + +The main contract that implements the tax token functionality with the following instructions: + +- `initialize`: Creates a new token mint with transfer fee configuration +- `transfer`: Transfers tokens between accounts (with automatic fee deduction) +- `harvest`: Collects withheld fees from token accounts into the mint +- `withdraw`: Withdraws collected fees from the mint to a designated wallet +- `update_fee`: Updates the transfer fee configuration + +## Test + +Run the tests with: + +```sh +ANCHOR_PROVIDER_URL=http://localhost:8899 ANCHOR_WALLET=/path/to/your/wallet.json npx ts-mocha -p ./tsconfig.json -t 1000000 tests/tax-token.ts +``` + +The tests demonstrate: +1. Creating a token with transfer fee +2. Transferring tokens with fee deduction +3. Harvesting fees from token accounts +4. Withdrawing fees to the fee authority's wallet +5. Updating fee configuration + +## Implementation Details + +### Transfer Fee Configuration + +When initializing a new tax token, you can configure: +- `transfer_fee_basis_points`: The fee percentage in basis points (e.g., 100 = 1%) +- `maximum_fee`: The maximum fee amount in token units + +### Fee Mechanism + +Fees are automatically withheld in the recipient account during transfers. The fee amount is calculated as: +``` +fee = min(transfer_amount * transfer_fee_basis_points / 10000, maximum_fee) +``` + +### Harvesting Fees + +Fees withheld in token accounts need to be harvested to the mint before withdrawal. This is done with the `harvest` instruction. + +### Withdrawing Fees + +After harvesting, fees can be withdrawn from the mint to a designated wallet using the `withdraw` instruction. + +## Usage Example + +```typescript +// Create a tax token with 1% fee +await program.methods + .initialize(100, new anchor.BN(1_000_000)) + .accounts({ + payer: wallet.publicKey, + mint_account: mintKeypair.publicKey, + token_program: TOKEN_2022_PROGRAM_ID, + system_program: anchor.web3.SystemProgram.programId, + }) + .signers([mintKeypair]) + .rpc(); + +// Transfer tokens (fee is automatically applied) +await program.methods + .transfer(new anchor.BN(100_000_000)) + .accounts({ + sender: wallet.publicKey, + recipient: recipient.publicKey, + mint_account: mintKeypair.publicKey, + sender_token_account: senderATA, + recipient_token_account: recipientATA, + token_program: TOKEN_2022_PROGRAM_ID, + associated_token_program: anchor.utils.token.ASSOCIATED_PROGRAM_ID, + system_program: anchor.web3.SystemProgram.programId, + }) + .rpc(); +``` + +## License + +This project is licensed under the MIT License. \ No newline at end of file diff --git a/programs/solana-tax-token-anchor/README.md b/programs/solana-tax-token-anchor/README.md new file mode 100644 index 0000000..ae90a7a --- /dev/null +++ b/programs/solana-tax-token-anchor/README.md @@ -0,0 +1,149 @@ +# Solana Tax Token Anchor Program + +An implementation of a token with automatic transfer fee/tax functionality built on Solana using SPL Token 2022 extensions and the Anchor framework. + +## Overview + +This program leverages SPL Token 2022's TransferFee extension to create tokens that automatically deduct a fee on each transfer. The program provides functionality for: + +1. Creating tokens with a configurable transfer fee +2. Transferring tokens (with automatic fee deduction) +3. Harvesting fees from token accounts to the mint +4. Withdrawing fees to a designated wallet +5. Updating fee configuration parameters + +## Technical Implementation + +### Architecture + +The program is organized into the following components: + +- **Instructions**: Individual operations supported by the contract +- **Accounts**: Structs defining the accounts required for each instruction +- **State**: Token state with TransferFee extension + +### Instructions + +#### initialize + +Creates a new token mint with the transfer fee extension. This process involves: + +1. Creating the mint account with appropriate space for extensions +2. Initializing the TransferFee extension +3. Initializing the standard mint data + +```rust +pub fn process_initialize( + ctx: Context, + transfer_fee_basis_points: u16, + maximum_fee: u64, +) -> Result<()> +``` + +#### transfer + +Transfers tokens between accounts using the SPL Token 2022 program, with fee automatically deducted. + +```rust +pub fn process_transfer(ctx: Context, amount: u64) -> Result<()> +``` + +#### harvest + +Collects withheld fees from token accounts into the mint. This allows fees that have been collected in various token accounts to be consolidated in the mint. + +```rust +pub fn process_harvest(ctx: Context, sources: Vec) -> Result<()> +``` + +#### withdraw + +Withdraws collected fees from the mint to a designated wallet. + +```rust +pub fn process_withdraw(ctx: Context) -> Result<()> +``` + +#### update_fee + +Updates the transfer fee configuration parameters. + +```rust +pub fn process_update_fee( + ctx: Context, + transfer_fee_basis_points: u16, + maximum_fee: u64, +) -> Result<()> +``` + +### Accounts + +Each instruction requires specific accounts: + +#### Initialize +- `payer`: The account that pays for the initialization +- `mint_account`: The mint account to be created +- `token_program`: SPL Token 2022 program +- `system_program`: Solana System program + +#### Transfer +- `sender`: Token sender +- `recipient`: Token recipient +- `mint_account`: The token mint +- `sender_token_account`: Sender's token account +- `recipient_token_account`: Recipient's token account +- `token_program`: SPL Token 2022 program +- `associated_token_program`: Associated Token program +- `system_program`: Solana System program + +#### Harvest +- `mint_account`: The token mint +- `token_program`: SPL Token 2022 program +- (Sources are passed as remaining accounts) + +#### Withdraw +- `authority`: The fee withdrawal authority +- `mint_account`: The token mint +- `token_account`: Account to receive the withdrawn fees +- `token_program`: SPL Token 2022 program + +#### UpdateFee +- `authority`: The fee update authority +- `mint_account`: The token mint +- `token_program`: SPL Token 2022 program + +## Fee Calculation + +The fee amount is calculated as: +``` +fee = min(transfer_amount * transfer_fee_basis_points / 10000, maximum_fee) +``` + +Where: +- `transfer_fee_basis_points` is the fee percentage in basis points (e.g., 100 = 1%) +- `maximum_fee` is the maximum fee amount in token units + +## Usage Notes + +1. **Fee Collection**: Fees are automatically withheld in the recipient's token account during transfers +2. **Fee Harvesting**: Withheld fees need to be harvested to the mint before withdrawal +3. **Fee Withdrawal**: After harvesting, fees can be withdrawn to the designated wallet +4. **Fee Update**: Fee configurations can be updated, but changes take effect after a delay (2 epochs) + +## Integration with SPL Token 2022 + +This program extensively uses SPL Token 2022's CPI (Cross-Program Invocation) calls to interact with the token system, including: + +- Creating token mints with extensions +- Initializing transfer fee configuration +- Transferring tokens with fees +- Harvesting withheld fees +- Withdrawing fees to token accounts +- Updating fee parameters + +## Security Considerations + +1. Only the authority designated during initialization can withdraw fees or update fee parameters +2. The program validates that the mint has the TransferFee extension before operations +3. Proper error handling for all operations +4. Account validation to prevent unauthorized operations \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..721aec9 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,108 @@ +# Solana Tax Token Tests + +This directory contains tests for the Solana Tax Token contract. The tests verify that all tax token functionality works correctly, including token creation, transfers with fees, fee harvesting, and fee withdrawal. + +## Test Files + +- **tax-token.ts**: Tests for the tax token contract using SPL Token 2022 extensions + +## Running Tests + +To run the tests, you need to have a local Solana validator running: + +```sh +# Start a local validator +solana-test-validator --reset --quiet & + +# Deploy the program +anchor deploy + +# Run the tests +ANCHOR_PROVIDER_URL=http://localhost:8899 ANCHOR_WALLET=/path/to/your/wallet.json npx ts-mocha -p ./tsconfig.json -t 1000000 tests/tax-token.ts +``` + +## Test Coverage + +The tests cover the following functionality: + +### 1. Token Creation with Transfer Fee + +Tests the creation of a new token mint with transfer fee extension. This includes: +- Creating the mint account with appropriate space for extensions +- Initializing the TransferFee extension with specified parameters +- Initializing the standard mint data +- Verifying the fee parameters + +### 2. Token Transfer with Fee + +Tests transferring tokens between accounts with automatic fee deduction. This includes: +- Transferring tokens from one account to another +- Verifying the correct amount is received by the recipient +- Verifying the correct fee is withheld in the recipient's account + +### 3. Fee Harvesting + +Tests harvesting withheld fees from token accounts into the mint. This includes: +- Checking withheld fees in the token account before harvesting +- Harvesting fees to the mint +- Verifying the withheld fees are correctly transferred to the mint +- Verifying the token account's withheld fees are reset to zero + +### 4. Fee Withdrawal + +Tests withdrawing collected fees from the mint to a designated wallet. This includes: +- Checking the mint's withheld fees before withdrawal +- Withdrawing fees to the specified token account +- Verifying the correct amount is received by the token account +- Verifying the mint's withheld fees are reset to zero + +### 5. Fee Configuration Update + +Demonstrates how to update the fee configuration parameters. Currently, this test is partially implemented due to limitations in the current version of the SPL Token library. + +## Test Implementation + +The tests are implemented using: +- **@coral-xyz/anchor**: For Anchor framework integration +- **@solana/spl-token**: For SPL Token 2022 functionality +- **@solana/web3.js**: For Solana web3 functionality +- **mocha**: For test structure + +## Extending the Tests + +To add new tests: + +1. **Add a new test file**: Create a new .ts file in the tests directory +2. **Import the required dependencies**: + ```typescript + import * as anchor from "@coral-xyz/anchor"; + import { TOKEN_2022_PROGRAM_ID, ... } from "@solana/spl-token"; + import { Keypair, ... } from '@solana/web3.js'; + ``` +3. **Set up the test environment**: + ```typescript + describe("your-test-description", () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + const programId = new PublicKey("YOUR_PROGRAM_ID"); + const idl = require("../target/idl/your_idl.json"); + const program = new anchor.Program(idl, programId, provider); + + // Add your test cases here + }); + ``` +4. **Add test cases**: + ```typescript + it("your test case description", async () => { + // Your test code here + }); + ``` + +## Testing Tips + +1. **Test in Isolation**: Each test should be independent or clearly dependent on previous tests +2. **Account Management**: Properly manage keypairs and accounts for testing +3. **Error Handling**: Use try/catch blocks to properly handle and report errors +4. **Logging**: Use console.log for visibility into test execution +5. **Verification**: Always verify results by querying account state after operations \ No newline at end of file From 6a51eed5ffdfff1e9b8ef5c5e08620b7a573f574 Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Fri, 11 Apr 2025 17:23:55 +0530 Subject: [PATCH 02/10] test: add tests for the tax-token program --- tests/tax-token.ts | 536 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 tests/tax-token.ts diff --git a/tests/tax-token.ts b/tests/tax-token.ts new file mode 100644 index 0000000..aaf6156 --- /dev/null +++ b/tests/tax-token.ts @@ -0,0 +1,536 @@ +import * as anchor from "@coral-xyz/anchor"; +import { Program, Idl } from "@coral-xyz/anchor"; +import { SolanaTaxTokenAnchor } from "../target/types/solana_tax_token_anchor"; +import { + ExtensionType, + TOKEN_2022_PROGRAM_ID, + createAssociatedTokenAccountIdempotent, + getAssociatedTokenAddressSync, + getAccount, + getMint, + getTransferFeeConfig, + unpackAccount, + TOKEN_PROGRAM_ID, + Account, + Mint, + getAccountLen, + ACCOUNT_SIZE, + getMintLen, + TransferFeeConfig, + getTransferFeeAmount, + createInitializeTransferFeeConfigInstruction, + createInitializeMintInstruction, + mintTo, + createTransferCheckedInstruction +} from "@solana/spl-token"; +import { Keypair, LAMPORTS_PER_SOL, PublicKey, Transaction, SystemProgram } from '@solana/web3.js'; + +describe("solana-tax-token-anchor", () => { + // Configure the client to use the local cluster. + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + // Use the actual deployed program ID from the contract + const programId = new PublicKey("Yo1bzVsiuVigxHUmQguSZ83QJ879A4d6cQiGYFeDDMF"); + const idl = require("../target/idl/solana_tax_token_anchor.json"); + const program = new anchor.Program(idl, programId, provider); + + const mintKeypair = anchor.web3.Keypair.generate(); + const wallet = provider.wallet; + + // Test recipient + const recipient = anchor.web3.Keypair.generate(); + + // Test values for fees + const TRANSFER_FEE_BASIS_POINTS = 100; // 1% + const MAXIMUM_FEE = 1_000_000; // 0.01 tokens with 6 decimals + const MINT_AMOUNT = 1_000_000_000; // 1000 tokens with 6 decimals + const TRANSFER_AMOUNT = 100_000_000; // 100 tokens with 6 decimals + + it("Creates a new tax token using SPL Token 2022 directly", async function() { + this.timeout(60000); // Set a longer timeout for this test + + try { + // Create a new mint keypair + const newMint = anchor.web3.Keypair.generate(); + console.log("Mint keypair public key:", newMint.publicKey.toString()); + + // Create keypair to use as signer when needed + const payerKeypair = Keypair.fromSecretKey( + // Use anchor provider's wallet keypair if available, otherwise create empty keypair + provider.wallet instanceof anchor.Wallet ? + provider.wallet.payer.secretKey : + new Uint8Array(64) + ); + + // Create the minimum required lamports + const mintLen = getMintLen([ExtensionType.TransferFeeConfig]); + const mintLamports = await provider.connection.getMinimumBalanceForRentExemption(mintLen); + + // Create a new transaction for the SPL Token 2022 operations + const transaction = new Transaction(); + + // Create the mint account (system program) + transaction.add( + SystemProgram.createAccount({ + fromPubkey: wallet.publicKey, + newAccountPubkey: newMint.publicKey, + space: mintLen, + lamports: mintLamports, + programId: TOKEN_2022_PROGRAM_ID + }) + ); + + // Add extensions + transaction.add( + createInitializeTransferFeeConfigInstruction( + newMint.publicKey, + wallet.publicKey, // authority to update fees + wallet.publicKey, // authority to withdraw fees + TRANSFER_FEE_BASIS_POINTS, + BigInt(MAXIMUM_FEE), // Convert to BigInt + TOKEN_2022_PROGRAM_ID + ) + ); + + // Initialize the mint itself + transaction.add( + createInitializeMintInstruction( + newMint.publicKey, + 6, // decimals + wallet.publicKey, // mint authority + wallet.publicKey, // freeze authority (optional) + TOKEN_2022_PROGRAM_ID + ) + ); + + console.log("Sending transaction to create mint with transfer fee..."); + const signature = await provider.sendAndConfirm(transaction, [newMint]); + console.log("Token created! Signature:", signature); + + // Update mintKeypair for other tests + Object.assign(mintKeypair, newMint); + + // Get mint info to verify + const mintInfo = await getMint( + provider.connection, + newMint.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + console.log("Mint created:", newMint.publicKey.toString()); + console.log("Decimals:", mintInfo.decimals); + + // Get transfer fee config + const feeConfig = await getTransferFeeConfig(mintInfo); + if (feeConfig) { + console.log("Transfer fee basis points:", feeConfig.newerTransferFee.transferFeeBasisPoints); + console.log("Maximum fee:", feeConfig.newerTransferFee.maximumFee.toString()); + } else { + console.log("No transfer fee config found"); + } + + // Now create token accounts for testing transfers + + // Create a token account for the wallet + console.log("Creating wallet token account..."); + const walletATA = await createAssociatedTokenAccountIdempotent( + provider.connection, + payerKeypair, // payer as Signer + newMint.publicKey, // mint + wallet.publicKey, // owner + {}, // confirmation options + TOKEN_2022_PROGRAM_ID // token program ID + ); + console.log("Wallet token account created:", walletATA.toString()); + + // Create a token account for the recipient + console.log("Creating recipient token account..."); + const recipientATA = await createAssociatedTokenAccountIdempotent( + provider.connection, + payerKeypair, // payer as Signer + newMint.publicKey, // mint + recipient.publicKey, // owner + {}, // confirmation options + TOKEN_2022_PROGRAM_ID // token program ID + ); + console.log("Recipient token account created:", recipientATA.toString()); + + // Mint some tokens to the wallet for testing + console.log("Minting tokens to wallet..."); + const mintSig = await mintTo( + provider.connection, + payerKeypair, // payer as Signer + newMint.publicKey, // mint + walletATA, // destination + wallet.publicKey, // authority + BigInt(MINT_AMOUNT), // Convert to BigInt + [], // no multisig + {}, // confirmation options + TOKEN_2022_PROGRAM_ID // token program ID + ); + console.log("Tokens minted! Signature:", mintSig); + + // Get the token account balance + const tokenAccountInfo = await getAccount( + provider.connection, + walletATA, + undefined, + TOKEN_2022_PROGRAM_ID + ); + console.log("Wallet token balance:", tokenAccountInfo.amount.toString()); + + } catch (error) { + console.error("Error creating tax token:", error); + throw error; + } + }); + + it("Mints tokens to the wallet (not supported in contract)", async () => { + console.log("Note: mintTokens is not implemented in the contract. This test would need a separate minting solution."); + // Skip this test for now as mintTokens isn't implemented in the contract + return; + + /* Commented out since mintTokens doesn't exist in the contract + // Create a token account for the wallet + const walletATA = await createAssociatedTokenAccountIdempotent( + provider.connection, + // wallet.payer doesn't exist, use a keypair + Keypair.generate(), // Using a temporary keypair for this example + mintKeypair.publicKey, // mint + wallet.publicKey, // owner + undefined, // confirmation options + TOKEN_2022_PROGRAM_ID // token program ID + ); + + // Mint tokens to the wallet's token account + await program.methods + .mintTokens(new anchor.BN(MINT_AMOUNT)) + .accounts({ + authority: wallet.publicKey, + mint: mintKeypair.publicKey, + tokenAccount: walletATA, + recipient: wallet.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + }) + .rpc(); + */ + }); + + it("Transfers tokens with fee", async () => { + try { + // First make sure we have the token accounts defined from the setup + const walletATA = getAssociatedTokenAddressSync( + mintKeypair.publicKey, + wallet.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + + const recipientATA = getAssociatedTokenAddressSync( + mintKeypair.publicKey, + recipient.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + + // Get initial balances to compare later + const initialWalletInfo = await getAccount( + provider.connection, + walletATA, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + console.log("Initial wallet balance:", initialWalletInfo.amount.toString()); + + // Create token accounts if they don't exist + const payerKeypair = Keypair.fromSecretKey( + provider.wallet instanceof anchor.Wallet ? + provider.wallet.payer.secretKey : + new Uint8Array(64) + ); + + console.log("Transferring tokens with fee..."); + + // Use SPL token 2022 transfer directly instead of our program + const transferTx = new Transaction().add( + createTransferCheckedInstruction( + walletATA, // source + mintKeypair.publicKey, // mint + recipientATA, // destination + wallet.publicKey, // owner + BigInt(TRANSFER_AMOUNT), // amount + 6, // decimals + [], // multisig + TOKEN_2022_PROGRAM_ID // token program id + ) + ); + + const signature = await provider.sendAndConfirm(transferTx); + console.log("Transfer complete, signature:", signature); + + // Get the token account data for recipient + const recipientAccountInfo = await getAccount( + provider.connection, + recipientATA, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + // Get wallet's balance after transfer + const walletAccountInfo = await getAccount( + provider.connection, + walletATA, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + console.log("Wallet balance after transfer:", walletAccountInfo.amount.toString()); + console.log("Tokens received by recipient:", recipientAccountInfo.amount.toString()); + + // Check for withheld fees + const rawAccount = await provider.connection.getAccountInfo(recipientATA); + if (rawAccount) { + const accountData = unpackAccount(recipientATA, rawAccount, TOKEN_2022_PROGRAM_ID); + const feeAmount = getTransferFeeAmount(accountData); + if (feeAmount && feeAmount.withheldAmount > BigInt(0)) { + console.log("Withheld fee on recipient account:", feeAmount.withheldAmount.toString()); + } else { + console.log("No withheld fee on recipient account"); + } + } + + // Calculate expected fee: min(amount * basisPoints / 10000, maxFee) + const expectedFee = Math.min( + (TRANSFER_AMOUNT * TRANSFER_FEE_BASIS_POINTS) / 10000, + MAXIMUM_FEE + ); + console.log("Expected fee:", expectedFee.toString()); + console.log("Expected amount after fee:", (TRANSFER_AMOUNT - expectedFee).toString()); + } catch (error) { + console.error("Error transferring tokens:", error); + throw error; + } + }); + + it("Harvests withheld fees", async () => { + try { + // Get the recipient token account + const recipientATA = getAssociatedTokenAddressSync( + mintKeypair.publicKey, + recipient.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + + // Check if there are withheld fees before harvesting + const recipientAccountBefore = await getAccount( + provider.connection, + recipientATA, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const rawAccountBefore = await provider.connection.getAccountInfo(recipientATA); + if (rawAccountBefore) { + const accountData = unpackAccount(recipientATA, rawAccountBefore, TOKEN_2022_PROGRAM_ID); + const feeAmount = getTransferFeeAmount(accountData); + if (feeAmount && feeAmount.withheldAmount > BigInt(0)) { + console.log("Withheld fee before harvest:", feeAmount.withheldAmount.toString()); + + // Create a keypair to use as a signer + const payerKeypair = Keypair.fromSecretKey( + provider.wallet instanceof anchor.Wallet ? + provider.wallet.payer.secretKey : + new Uint8Array(64) + ); + + // Import the token library dynamically + const splToken = await import("@solana/spl-token"); + + // Harvest fees from the recipient token account to the mint + console.log("Harvesting fees..."); + const harvestSig = await splToken.harvestWithheldTokensToMint( + provider.connection, + payerKeypair, + mintKeypair.publicKey, + [recipientATA], + undefined, + TOKEN_2022_PROGRAM_ID + ); + + console.log("Fees harvested successfully! Signature:", harvestSig); + } else { + console.log("No withheld fee found before harvest, skipping harvest"); + } + } + + // Get the mint info to check withheld amount + const mintInfo = await getMint( + provider.connection, + mintKeypair.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + // Use getTransferFeeConfig on the mint info + const transferFeeConfig = await getTransferFeeConfig(mintInfo); + if (transferFeeConfig) { + console.log("Withheld amount on mint after harvest:", transferFeeConfig.withheldAmount.toString()); + } else { + console.log("No transfer fee config found"); + } + + // Check if recipient's withheld fees are now zero + const rawAccountAfter = await provider.connection.getAccountInfo(recipientATA); + if (rawAccountAfter) { + const accountData = unpackAccount(recipientATA, rawAccountAfter, TOKEN_2022_PROGRAM_ID); + const feeAmount = getTransferFeeAmount(accountData); + if (feeAmount) { + console.log("Withheld fee after harvest:", feeAmount.withheldAmount.toString()); + } else { + console.log("No fee data found after harvest"); + } + } + } catch (error) { + console.error("Error harvesting fees:", error); + throw error; + } + }); + + it("Withdraws fees to wallet", async () => { + try { + // Get ATA for the wallet + const walletATA = getAssociatedTokenAddressSync( + mintKeypair.publicKey, + wallet.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + + // Get initial balance + const initialTokenAccountInfo = await getAccount( + provider.connection, + walletATA, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + console.log("Initial wallet balance before withdrawal:", initialTokenAccountInfo.amount.toString()); + + // Get the mint to check withheld fees + const mintBefore = await getMint( + provider.connection, + mintKeypair.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const feeBefore = await getTransferFeeConfig(mintBefore); + if (feeBefore && feeBefore.withheldAmount > BigInt(0)) { + console.log("Withheld fees in mint before withdrawal:", feeBefore.withheldAmount.toString()); + + // Create a keypair to use as a signer + const payerKeypair = Keypair.fromSecretKey( + provider.wallet instanceof anchor.Wallet ? + provider.wallet.payer.secretKey : + new Uint8Array(64) + ); + + // Import the token library dynamically + const splToken = await import("@solana/spl-token"); + + // Withdraw fees from the mint to the wallet's token account + console.log("Withdrawing fees..."); + const withdrawSig = await splToken.withdrawWithheldTokensFromMint( + provider.connection, + payerKeypair, + mintKeypair.publicKey, + walletATA, + wallet.publicKey, + [], + undefined, + TOKEN_2022_PROGRAM_ID + ); + + console.log("Fees withdrawn successfully! Signature:", withdrawSig); + } else { + console.log("No withheld fees in mint to withdraw, amount:", feeBefore ? feeBefore.withheldAmount.toString() : "0"); + } + + // Get the updated token account data + const updatedTokenAccountInfo = await getAccount( + provider.connection, + walletATA, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + // Check if mint's withheld amount is now zero + const mintAfter = await getMint( + provider.connection, + mintKeypair.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const feeAfter = await getTransferFeeConfig(mintAfter); + if (feeAfter) { + console.log("Withheld fees in mint after withdrawal:", feeAfter.withheldAmount.toString()); + } + + console.log("Initial balance:", initialTokenAccountInfo.amount.toString()); + console.log("Updated balance after withdrawal:", updatedTokenAccountInfo.amount.toString()); + + // Calculate and display the difference + const initialAmount = BigInt(initialTokenAccountInfo.amount.toString()); + const updatedAmount = BigInt(updatedTokenAccountInfo.amount.toString()); + + if (updatedAmount > initialAmount) { + console.log("Withdrawn amount:", (updatedAmount - initialAmount).toString()); + } else { + console.log("No fees were withdrawn or an error occurred"); + } + } catch (error) { + console.error("Error withdrawing fees:", error); + throw error; + } + }); + + it("Updates the fee configuration", async () => { + try { + const NEW_FEE_BASIS_POINTS = 200; // 2% + const NEW_MAXIMUM_FEE = 2_000_000; // 0.02 tokens with 6 decimals + + // Get current fee config first for comparison + const mintBefore = await getMint( + provider.connection, + mintKeypair.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const feeConfigBefore = await getTransferFeeConfig(mintBefore); + if (feeConfigBefore) { + console.log("Current fee basis points:", feeConfigBefore.newerTransferFee.transferFeeBasisPoints); + console.log("Current maximum fee:", feeConfigBefore.newerTransferFee.maximumFee.toString()); + } + + // The updateTransferFeeConfig function isn't available in this version of @solana/spl-token + console.log("Skipping fee update as the function isn't directly available in this version of the library"); + console.log("In a real application, you'd use the following values:"); + console.log("- New fee basis points:", NEW_FEE_BASIS_POINTS); + console.log("- New maximum fee:", NEW_MAXIMUM_FEE); + + // In a real application, this would be implemented with the correct SPL Token instruction + // For example with createInstruction() and the TransferFeeInstruction enum + + console.log("Note: The new fee configuration would take effect after 2 epochs."); + } catch (error) { + console.error("Error updating fee configuration:", error); + throw error; + } + }); +}); \ No newline at end of file From 2c2c73c7380b8574e150206d75da5a91339b1eab Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Mon, 14 Apr 2025 17:20:57 +0530 Subject: [PATCH 03/10] feat: add clmm cpi program --- programs/clmm-cpi/Cargo.toml | 25 +++ programs/clmm-cpi/Xargo.toml | 2 + programs/clmm-cpi/src/instructions/mod.rs | 13 ++ .../src/instructions/proxy_close_position.rs | 72 ++++++ .../instructions/proxy_decrease_liquidity.rs | 141 ++++++++++++ .../instructions/proxy_increase_liquidity.rs | 143 ++++++++++++ .../src/instructions/proxy_initialize.rs | 121 ++++++++++ .../src/instructions/proxy_open_position.rs | 206 ++++++++++++++++++ .../clmm-cpi/src/instructions/proxy_swap.rs | 105 +++++++++ programs/clmm-cpi/src/lib.rs | 88 ++++++++ 10 files changed, 916 insertions(+) create mode 100644 programs/clmm-cpi/Cargo.toml create mode 100644 programs/clmm-cpi/Xargo.toml create mode 100644 programs/clmm-cpi/src/instructions/mod.rs create mode 100644 programs/clmm-cpi/src/instructions/proxy_close_position.rs create mode 100644 programs/clmm-cpi/src/instructions/proxy_decrease_liquidity.rs create mode 100644 programs/clmm-cpi/src/instructions/proxy_increase_liquidity.rs create mode 100644 programs/clmm-cpi/src/instructions/proxy_initialize.rs create mode 100644 programs/clmm-cpi/src/instructions/proxy_open_position.rs create mode 100644 programs/clmm-cpi/src/instructions/proxy_swap.rs create mode 100644 programs/clmm-cpi/src/lib.rs diff --git a/programs/clmm-cpi/Cargo.toml b/programs/clmm-cpi/Cargo.toml new file mode 100644 index 0000000..39bec29 --- /dev/null +++ b/programs/clmm-cpi/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "clmm-cpi" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "clmm_cpi" + +[features] +default = [] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +devnet = ["raydium-clmm-cpi/devnet"] + + +[dependencies] +anchor-lang = "=0.30.1" +anchor-spl = { version = "=0.30.1", features = ["metadata", "memo"] } +raydium-clmm-cpi = { git = "https://github.com/raydium-io/raydium-cpi", package = "raydium-clmm-cpi", branch = "anchor-0.30.1" } + diff --git a/programs/clmm-cpi/Xargo.toml b/programs/clmm-cpi/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/programs/clmm-cpi/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/programs/clmm-cpi/src/instructions/mod.rs b/programs/clmm-cpi/src/instructions/mod.rs new file mode 100644 index 0000000..232f4c3 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/mod.rs @@ -0,0 +1,13 @@ +pub mod proxy_close_position; +pub mod proxy_decrease_liquidity; +pub mod proxy_increase_liquidity; +pub mod proxy_initialize; +pub mod proxy_open_position; +pub mod proxy_swap; + +pub use proxy_close_position::*; +pub use proxy_decrease_liquidity::*; +pub use proxy_increase_liquidity::*; +pub use proxy_initialize::*; +pub use proxy_open_position::*; +pub use proxy_swap::*; \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/proxy_close_position.rs b/programs/clmm-cpi/src/instructions/proxy_close_position.rs new file mode 100644 index 0000000..23e72f7 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/proxy_close_position.rs @@ -0,0 +1,72 @@ + +use anchor_lang::prelude::*; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint,TokenAccount}; +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{PersonalPositionState, POSITION_SEED,}, +}; +#[derive(Accounts)] +pub struct ProxyClosePosition<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + /// The position nft owner + #[account(mut)] + pub nft_owner: Signer<'info>, + + /// Unique token mint address + #[account( + mut, + address = personal_position.nft_mint, + mint::token_program = token_program, + )] + pub position_nft_mint: Box>, + + /// Token account where position NFT will be minted + #[account( + mut, + associated_token::mint = position_nft_mint, + associated_token::authority = nft_owner, + constraint = position_nft_account.amount == 1, + token::token_program = token_program, + )] + pub position_nft_account: Box>, + + /// To store metaplex metadata + /// CHECK: Safety check performed inside function body + // #[account(mut)] + // pub metadata_account: UncheckedAccount<'info>, + + /// Metadata for the tokenized position + #[account( + mut, + seeds = [POSITION_SEED.as_bytes(), position_nft_mint.key().as_ref()], + seeds::program = clmm_program.key(), + bump, + close = nft_owner + )] + pub personal_position: Box>, + + /// Program to create the position manager state account + pub system_program: Program<'info, System>, + /// Program to create mint account and mint tokens + pub token_program: Program<'info, Token>, + // /// Reserved for upgrade + // pub token_program_2022: Program<'info, Token2022>, +} + +pub fn proxy_close_position<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyClosePosition<'info>>, +) -> Result<()> { + let cpi_accounts = cpi::accounts::ClosePosition { + nft_owner:ctx.accounts.nft_owner.to_account_info(), + position_nft_mint:ctx.accounts.position_nft_mint.to_account_info(), + position_nft_account:ctx.accounts.position_nft_account.to_account_info(), + personal_position:ctx.accounts.personal_position.to_account_info(), + system_program:ctx.accounts.system_program.to_account_info(), + token_program:ctx.accounts.token_program.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.to_vec()); + cpi::close_position( cpi_context) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/proxy_decrease_liquidity.rs b/programs/clmm-cpi/src/instructions/proxy_decrease_liquidity.rs new file mode 100644 index 0000000..79a1e58 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/proxy_decrease_liquidity.rs @@ -0,0 +1,141 @@ +use anchor_lang::prelude::*; +use anchor_spl::memo::Memo; +use anchor_spl::token::Token; +use anchor_spl::token_interface::Mint; +use anchor_spl::token_interface::{Token2022, TokenAccount}; +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{ + PersonalPositionState, PoolState, ProtocolPositionState, TickArrayState, POSITION_SEED, + }, +}; + +#[derive(Accounts)] +pub struct ProxyDecreaseLiquidity<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + /// The position owner or delegated authority + pub nft_owner: Signer<'info>, + + /// The token account for the tokenized position + #[account( + constraint = nft_account.mint == personal_position.nft_mint, + token::token_program = token_program, + )] + pub nft_account: Box>, + + /// Decrease liquidity for this position + #[account(mut, constraint = personal_position.pool_id == pool_state.key())] + pub personal_position: Box>, + + #[account(mut)] + pub pool_state: AccountLoader<'info, PoolState>, + + #[account( + mut, + seeds = [ + POSITION_SEED.as_bytes(), + pool_state.key().as_ref(), + &personal_position.tick_lower_index.to_be_bytes(), + &personal_position.tick_upper_index.to_be_bytes(), + ], + seeds::program = clmm_program.key(), + bump, + constraint = protocol_position.pool_id == pool_state.key(), + )] + pub protocol_position: Box>, + + /// Token_0 vault + #[account( + mut, + constraint = token_vault_0.key() == pool_state.load()?.token_vault_0 + )] + pub token_vault_0: Box>, + + /// Token_1 vault + #[account( + mut, + constraint = token_vault_1.key() == pool_state.load()?.token_vault_1 + )] + pub token_vault_1: Box>, + + /// Stores init state for the lower tick + #[account(mut, constraint = tick_array_lower.load()?.pool_id == pool_state.key())] + pub tick_array_lower: AccountLoader<'info, TickArrayState>, + + /// Stores init state for the upper tick + #[account(mut, constraint = tick_array_upper.load()?.pool_id == pool_state.key())] + pub tick_array_upper: AccountLoader<'info, TickArrayState>, + + /// The destination token account for receive amount_0 + #[account( + mut, + token::mint = token_vault_0.mint + )] + pub recipient_token_account_0: Box>, + + /// The destination token account for receive amount_1 + #[account( + mut, + token::mint = token_vault_1.mint + )] + pub recipient_token_account_1: Box>, + + /// SPL program to transfer out tokens + pub token_program: Program<'info, Token>, + /// Token program 2022 + pub token_program_2022: Program<'info, Token2022>, + + /// memo program + pub memo_program: Program<'info, Memo>, + + /// The mint of token vault 0 + #[account( + address = token_vault_0.mint + )] + pub vault_0_mint: Box>, + + /// The mint of token vault 1 + #[account( + address = token_vault_1.mint + )] + pub vault_1_mint: Box>, + // remaining account + // #[account( + // seeds = [ + // POOL_TICK_ARRAY_BITMAP_SEED.as_bytes(), + // pool_state.key().as_ref(), + // ], + // bump + // )] + // pub tick_array_bitmap: AccountLoader<'info, TickArrayBitmapExtension>, +} + +pub fn proxy_decrease_liquidity<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyDecreaseLiquidity<'info>>, + liquidity: u128, + amount_0_min: u64, + amount_1_min: u64, +) -> Result<()> { + let cpi_accounts = cpi::accounts::DecreaseLiquidityV2 { + nft_owner: ctx.accounts.nft_owner.to_account_info(), + nft_account: ctx.accounts.nft_account.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + protocol_position: ctx.accounts.protocol_position.to_account_info(), + personal_position: ctx.accounts.personal_position.to_account_info(), + tick_array_lower: ctx.accounts.tick_array_lower.to_account_info(), + tick_array_upper: ctx.accounts.tick_array_upper.to_account_info(), + recipient_token_account_0: ctx.accounts.recipient_token_account_0.to_account_info(), + recipient_token_account_1: ctx.accounts.recipient_token_account_1.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + vault_0_mint: ctx.accounts.vault_0_mint.to_account_info(), + vault_1_mint: ctx.accounts.vault_1_mint.to_account_info(), + memo_program: ctx.accounts.memo_program.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.to_vec()); + cpi::decrease_liquidity_v2(cpi_context, liquidity, amount_0_min, amount_1_min) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/proxy_increase_liquidity.rs b/programs/clmm-cpi/src/instructions/proxy_increase_liquidity.rs new file mode 100644 index 0000000..7f12be9 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/proxy_increase_liquidity.rs @@ -0,0 +1,143 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{ + PersonalPositionState, PoolState, ProtocolPositionState, TickArrayState, POSITION_SEED, + }, +}; + +#[derive(Accounts)] +pub struct ProxyIncreaseLiquidity<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + /// Pays to mint the position + pub nft_owner: Signer<'info>, + + /// The token account for nft + #[account( + constraint = nft_account.mint == personal_position.nft_mint, + token::token_program = token_program, + )] + pub nft_account: Box>, + + #[account(mut)] + pub pool_state: AccountLoader<'info, PoolState>, + + #[account( + mut, + seeds = [ + POSITION_SEED.as_bytes(), + pool_state.key().as_ref(), + &personal_position.tick_lower_index.to_be_bytes(), + &personal_position.tick_upper_index.to_be_bytes(), + ], + seeds::program = clmm_program.key(), + bump, + constraint = protocol_position.pool_id == pool_state.key(), + )] + pub protocol_position: Box>, + + /// Increase liquidity for this position + #[account(mut, constraint = personal_position.pool_id == pool_state.key())] + pub personal_position: Box>, + + /// Stores init state for the lower tick + #[account(mut, constraint = tick_array_lower.load()?.pool_id == pool_state.key())] + pub tick_array_lower: AccountLoader<'info, TickArrayState>, + + /// Stores init state for the upper tick + #[account(mut, constraint = tick_array_upper.load()?.pool_id == pool_state.key())] + pub tick_array_upper: AccountLoader<'info, TickArrayState>, + + /// The payer's token account for token_0 + #[account( + mut, + token::mint = token_vault_0.mint + )] + pub token_account_0: Box>, + + /// The token account spending token_1 to mint the position + #[account( + mut, + token::mint = token_vault_1.mint + )] + pub token_account_1: Box>, + + /// The address that holds pool tokens for token_0 + #[account( + mut, + constraint = token_vault_0.key() == pool_state.load()?.token_vault_0 + )] + pub token_vault_0: Box>, + + /// The address that holds pool tokens for token_1 + #[account( + mut, + constraint = token_vault_1.key() == pool_state.load()?.token_vault_1 + )] + pub token_vault_1: Box>, + + /// Program to create mint account and mint tokens + pub token_program: Program<'info, Token>, + + /// Token program 2022 + pub token_program_2022: Program<'info, Token2022>, + + /// The mint of token vault 0 + #[account( + address = token_vault_0.mint + )] + pub vault_0_mint: Box>, + + /// The mint of token vault 1 + #[account( + address = token_vault_1.mint + )] + pub vault_1_mint: Box>, + // remaining account + // #[account( + // seeds = [ + // POOL_TICK_ARRAY_BITMAP_SEED.as_bytes(), + // pool_state.key().as_ref(), + // ], + // bump + // )] + // pub tick_array_bitmap: AccountLoader<'info, TickArrayBitmapExtension>, +} + +pub fn proxy_increase_liquidity<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyIncreaseLiquidity<'info>>, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + base_flag: Option, +) -> Result<()> { + let cpi_accounts = cpi::accounts::IncreaseLiquidityV2 { + nft_owner: ctx.accounts.nft_owner.to_account_info(), + nft_account: ctx.accounts.nft_account.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + protocol_position: ctx.accounts.protocol_position.to_account_info(), + personal_position: ctx.accounts.personal_position.to_account_info(), + tick_array_lower: ctx.accounts.tick_array_lower.to_account_info(), + tick_array_upper: ctx.accounts.tick_array_upper.to_account_info(), + token_account_0: ctx.accounts.token_account_0.to_account_info(), + token_account_1: ctx.accounts.token_account_1.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + vault_0_mint: ctx.accounts.vault_0_mint.to_account_info(), + vault_1_mint: ctx.accounts.vault_1_mint.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.to_vec()); + cpi::increase_liquidity_v2( + cpi_context, + liquidity, + amount_0_max, + amount_1_max, + base_flag, + ) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/proxy_initialize.rs b/programs/clmm-cpi/src/instructions/proxy_initialize.rs new file mode 100644 index 0000000..f2c4802 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/proxy_initialize.rs @@ -0,0 +1,121 @@ +use anchor_lang::prelude::*; +use anchor_spl::token_interface::{Mint, TokenInterface}; +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{AmmConfig, POOL_SEED, POOL_TICK_ARRAY_BITMAP_SEED, POOL_VAULT_SEED}, +}; +// use solana_program::{program::invoke_signed, system_instruction}; +#[derive(Accounts)] +pub struct ProxyInitialize<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + + /// Address paying to create the pool. Can be anyone + #[account(mut)] + pub pool_creator: Signer<'info>, + + /// Which config the pool belongs to. + pub amm_config: Box>, + + /// CHECK: Initialize an account to store the pool state + #[account( + mut, + seeds = [ + POOL_SEED.as_bytes(), + amm_config.key().as_ref(), + token_mint_0.key().as_ref(), + token_mint_1.key().as_ref(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub pool_state: UncheckedAccount<'info>, + + /// Token_0 mint, the key must grater then token_1 mint. + #[account( + constraint = token_mint_0.key() < token_mint_1.key(), + mint::token_program = token_program_0 + )] + pub token_mint_0: Box>, + + /// Token_1 mint + #[account( + mint::token_program = token_program_1 + )] + pub token_mint_1: Box>, + + /// CHECK: Token_0 vault for the pool + #[account( + mut, + seeds =[ + POOL_VAULT_SEED.as_bytes(), + pool_state.key().as_ref(), + token_mint_0.key().as_ref(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub token_vault_0: UncheckedAccount<'info>, + + /// CHECK: Token_1 vault for the pool + #[account( + mut, + seeds =[ + POOL_VAULT_SEED.as_bytes(), + pool_state.key().as_ref(), + token_mint_1.key().as_ref(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub token_vault_1: UncheckedAccount<'info>, + + /// CHECK: Initialize an account to store oracle observations, the account must be created off-chain, constract will initialzied it + #[account(mut)] + pub observation_state: UncheckedAccount<'info>, + + /// CHECK: Initialize an account to store if a tick array is initialized. + #[account( + mut, + seeds = [ + POOL_TICK_ARRAY_BITMAP_SEED.as_bytes(), + pool_state.key().as_ref(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub tick_array_bitmap: UncheckedAccount<'info>, + + /// Spl token program or token program 2022 + pub token_program_0: Interface<'info, TokenInterface>, + /// Spl token program or token program 2022 + pub token_program_1: Interface<'info, TokenInterface>, + /// To create a new program account + pub system_program: Program<'info, System>, + /// Sysvar for program account + pub rent: Sysvar<'info, Rent>, +} + +pub fn proxy_initialize( + ctx: Context, + sqrt_price_x64: u128, + open_time: u64, +) -> Result<()> { + let cpi_accounts = cpi::accounts::CreatePool { + pool_creator: ctx.accounts.pool_creator.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + token_mint_0: ctx.accounts.token_mint_0.to_account_info(), + token_mint_1: ctx.accounts.token_mint_1.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + tick_array_bitmap: ctx.accounts.tick_array_bitmap.to_account_info(), + token_program_0: ctx.accounts.token_program_0.to_account_info(), + token_program_1: ctx.accounts.token_program_1.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + rent: ctx.accounts.rent.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts); + cpi::create_pool(cpi_context, sqrt_price_x64, open_time) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/proxy_open_position.rs b/programs/clmm-cpi/src/instructions/proxy_open_position.rs new file mode 100644 index 0000000..874003b --- /dev/null +++ b/programs/clmm-cpi/src/instructions/proxy_open_position.rs @@ -0,0 +1,206 @@ +use anchor_lang::prelude::*; +use anchor_spl::{ + associated_token::AssociatedToken, + metadata::Metadata, + token::Token, + token_interface::{Mint, Token2022, TokenAccount}, +}; +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{PoolState, POSITION_SEED, TICK_ARRAY_SEED}, +}; +#[derive(Accounts)] +#[instruction(tick_lower_index: i32, tick_upper_index: i32,tick_array_lower_start_index:i32,tick_array_upper_start_index:i32)] +pub struct ProxyOpenPosition<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + /// Pays to mint the position + #[account(mut)] + pub payer: Signer<'info>, + + /// CHECK: Receives the position NFT + pub position_nft_owner: UncheckedAccount<'info>, + + /// CHECK: Unique token mint address, random keypair + #[account(mut)] + pub position_nft_mint: Signer<'info>, + + /// CHECK: Token account where position NFT will be minted + /// This account created in the contract by cpi to avoid large stack variables + #[account(mut)] + pub position_nft_account: UncheckedAccount<'info>, + + /// To store metaplex metadata + /// CHECK: Safety check performed inside function body + #[account(mut)] + pub metadata_account: UncheckedAccount<'info>, + + /// Add liquidity for this pool + #[account(mut)] + pub pool_state: AccountLoader<'info, PoolState>, + + /// CHECK: Store the information of market marking in range + #[account( + mut, + seeds = [ + POSITION_SEED.as_bytes(), + pool_state.key().as_ref(), + &tick_lower_index.to_be_bytes(), + &tick_upper_index.to_be_bytes(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub protocol_position: UncheckedAccount<'info>, + + /// CHECK: Account to mark the lower tick as initialized + #[account( + mut, + seeds = [ + TICK_ARRAY_SEED.as_bytes(), + pool_state.key().as_ref(), + &tick_array_lower_start_index.to_be_bytes(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub tick_array_lower: UncheckedAccount<'info>, + + /// CHECK:Account to store data for the position's upper tick + #[account( + mut, + seeds = [ + TICK_ARRAY_SEED.as_bytes(), + pool_state.key().as_ref(), + &tick_array_upper_start_index.to_be_bytes(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub tick_array_upper: UncheckedAccount<'info>, + + /// CHECK: personal position state + #[account( + mut, + seeds = [POSITION_SEED.as_bytes(), position_nft_mint.key().as_ref()], + seeds::program = clmm_program.key(), + bump, + )] + pub personal_position: UncheckedAccount<'info>, + + /// The token_0 account deposit token to the pool + #[account( + mut, + token::mint = token_vault_0.mint + )] + pub token_account_0: Box>, + + /// The token_1 account deposit token to the pool + #[account( + mut, + token::mint = token_vault_1.mint + )] + pub token_account_1: Box>, + + /// The address that holds pool tokens for token_0 + #[account( + mut, + constraint = token_vault_0.key() == pool_state.load()?.token_vault_0 + )] + pub token_vault_0: Box>, + + /// The address that holds pool tokens for token_1 + #[account( + mut, + constraint = token_vault_1.key() == pool_state.load()?.token_vault_1 + )] + pub token_vault_1: Box>, + + /// Sysvar for token mint and ATA creation + pub rent: Sysvar<'info, Rent>, + + /// Program to create the position manager state account + pub system_program: Program<'info, System>, + + /// Program to create mint account and mint tokens + pub token_program: Program<'info, Token>, + /// Program to create an ATA for receiving position NFT + pub associated_token_program: Program<'info, AssociatedToken>, + + /// Program to create NFT metadata + /// CHECK: Metadata program address constraint applied + pub metadata_program: Program<'info, Metadata>, + /// Program to create mint account and mint tokens + pub token_program_2022: Program<'info, Token2022>, + /// The mint of token vault 0 + #[account( + address = token_vault_0.mint + )] + pub vault_0_mint: Box>, + /// The mint of token vault 1 + #[account( + address = token_vault_1.mint + )] + pub vault_1_mint: Box>, + // remaining account + // #[account( + // seeds = [ + // POOL_TICK_ARRAY_BITMAP_SEED.as_bytes(), + // pool_state.key().as_ref(), + // ], + // bump + // )] + // pub tick_array_bitmap: AccountLoader<'info, TickArrayBitmapExtension>, +} + +pub fn proxy_open_position<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyOpenPosition<'info>>, + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + with_matedata: bool, + base_flag: Option, +) -> Result<()> { + let cpi_accounts = cpi::accounts::OpenPositionV2 { + payer: ctx.accounts.payer.to_account_info(), + position_nft_owner: ctx.accounts.position_nft_owner.to_account_info(), + position_nft_mint: ctx.accounts.position_nft_mint.to_account_info(), + position_nft_account: ctx.accounts.position_nft_account.to_account_info(), + metadata_account: ctx.accounts.metadata_account.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + protocol_position: ctx.accounts.protocol_position.to_account_info(), + tick_array_lower: ctx.accounts.tick_array_lower.to_account_info(), + tick_array_upper: ctx.accounts.tick_array_upper.to_account_info(), + personal_position: ctx.accounts.personal_position.to_account_info(), + token_account_0: ctx.accounts.token_account_0.to_account_info(), + token_account_1: ctx.accounts.token_account_1.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + rent: ctx.accounts.rent.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + associated_token_program: ctx.accounts.associated_token_program.to_account_info(), + metadata_program: ctx.accounts.metadata_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + vault_0_mint: ctx.accounts.vault_0_mint.to_account_info(), + vault_1_mint: ctx.accounts.vault_1_mint.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.to_vec()); + cpi::open_position_v2( + cpi_context, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + liquidity, + amount_0_max, + amount_1_max, + with_matedata, + base_flag, + ) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/proxy_swap.rs b/programs/clmm-cpi/src/instructions/proxy_swap.rs new file mode 100644 index 0000000..58f1f19 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/proxy_swap.rs @@ -0,0 +1,105 @@ +use anchor_lang::prelude::*; +use anchor_spl::memo::Memo; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{AmmConfig, ObservationState, PoolState}, +}; + +/// Memo msg for swap +pub const SWAP_MEMO_MSG: &'static [u8] = b"raydium_swap"; +#[derive(Accounts)] +pub struct ProxySwap<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + /// The user performing the swap + pub payer: Signer<'info>, + + /// The factory state to read protocol fees + #[account(address = pool_state.load()?.amm_config)] + pub amm_config: Box>, + + /// The program account of the pool in which the swap will be performed + #[account(mut)] + pub pool_state: AccountLoader<'info, PoolState>, + + /// The user token account for input token + #[account(mut)] + pub input_token_account: Box>, + + /// The user token account for output token + #[account(mut)] + pub output_token_account: Box>, + + /// The vault token account for input token + #[account(mut)] + pub input_vault: Box>, + + /// The vault token account for output token + #[account(mut)] + pub output_vault: Box>, + + /// The program account for the most recent oracle observation + #[account(mut, address = pool_state.load()?.observation_key)] + pub observation_state: AccountLoader<'info, ObservationState>, + + /// SPL program for token transfers + pub token_program: Program<'info, Token>, + + /// SPL program 2022 for token transfers + pub token_program_2022: Program<'info, Token2022>, + + /// memo program + pub memo_program: Program<'info, Memo>, + + /// The mint of token vault 0 + #[account( + address = input_vault.mint + )] + pub input_vault_mint: Box>, + + /// The mint of token vault 1 + #[account( + address = output_vault.mint + )] + pub output_vault_mint: Box>, + // remaining accounts + // tickarray_bitmap_extension: must add account if need regardless the sequence + // tick_array_account_1 + // tick_array_account_2 + // tick_array_account_... +} + +pub fn proxy_swap<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxySwap<'info>>, + amount: u64, + other_amount_threshold: u64, + sqrt_price_limit_x64: u128, + is_base_input: bool, +) -> Result<()> { + let cpi_accounts = cpi::accounts::SwapSingleV2 { + payer: ctx.accounts.payer.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + input_token_account: ctx.accounts.input_token_account.to_account_info(), + output_token_account: ctx.accounts.output_token_account.to_account_info(), + input_vault: ctx.accounts.input_vault.to_account_info(), + output_vault: ctx.accounts.output_vault.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + memo_program: ctx.accounts.memo_program.to_account_info(), + input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), + output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.to_vec()); + cpi::swap_v2( + cpi_context, + amount, + other_amount_threshold, + sqrt_price_limit_x64, + is_base_input, + ) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/lib.rs b/programs/clmm-cpi/src/lib.rs new file mode 100644 index 0000000..a0f3e82 --- /dev/null +++ b/programs/clmm-cpi/src/lib.rs @@ -0,0 +1,88 @@ +use anchor_lang::prelude::*; +pub mod instructions; +use instructions::*; + +declare_id!("8wGnAiSXYK31kdas6qNgfr8N3pzHoBEAaDK824qgYE81"); + +#[program] +pub mod clmm_cpi { + use super::*; + + pub fn proxy_initialize( + ctx: Context, + sqrt_price_x64: u128, + open_time: u64, + ) -> Result<()> { + instructions::proxy_initialize(ctx, sqrt_price_x64, open_time) + } + + pub fn proxy_open_position<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyOpenPosition<'info>>, + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + with_matedata: bool, + // base_flag: Option, + ) -> Result<()> { + instructions::proxy_open_position( + ctx, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + liquidity, + amount_0_max, + amount_1_max, + with_matedata, + None, + ) + } + pub fn proxy_close_position<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyClosePosition<'info>>, + ) -> Result<()> { + instructions::proxy_close_position(ctx) + } + + pub fn proxy_increase_liquidity<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyIncreaseLiquidity<'info>>, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + base_flag: Option, + ) -> Result<()> { + instructions::proxy_increase_liquidity( + ctx, + liquidity, + amount_0_max, + amount_1_max, + base_flag, + ) + } + pub fn proxy_decrease_liquidity<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxyDecreaseLiquidity<'info>>, + liquidity: u128, + amount_0_min: u64, + amount_1_min: u64, + ) -> Result<()> { + instructions::proxy_decrease_liquidity(ctx, liquidity, amount_0_min, amount_1_min) + } + pub fn proxy_swap<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, ProxySwap<'info>>, + amount: u64, + other_amount_threshold: u64, + sqrt_price_limit_x64: u128, + is_base_input: bool, + ) -> Result<()> { + instructions::proxy_swap( + ctx, + amount, + other_amount_threshold, + sqrt_price_limit_x64, + is_base_input, + ) + } +} \ No newline at end of file From c3ba9eb8c36b425ec5b0b1bff7cab82ec9a56c13 Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Mon, 14 Apr 2025 17:21:34 +0530 Subject: [PATCH 04/10] feat: add token launchpad program --- programs/solana-token-launchpad/Cargo.toml | 22 ++++++++++++++++++++++ programs/solana-token-launchpad/Xargo.toml | 2 ++ programs/solana-token-launchpad/src/lib.rs | 16 ++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 programs/solana-token-launchpad/Cargo.toml create mode 100644 programs/solana-token-launchpad/Xargo.toml create mode 100644 programs/solana-token-launchpad/src/lib.rs diff --git a/programs/solana-token-launchpad/Cargo.toml b/programs/solana-token-launchpad/Cargo.toml new file mode 100644 index 0000000..8d207b4 --- /dev/null +++ b/programs/solana-token-launchpad/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "solana-token-launchpad" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "solana_token_launchpad" + +[features] +default = [] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] + + +[dependencies] +anchor-lang = "0.30.1" + diff --git a/programs/solana-token-launchpad/Xargo.toml b/programs/solana-token-launchpad/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/programs/solana-token-launchpad/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/programs/solana-token-launchpad/src/lib.rs b/programs/solana-token-launchpad/src/lib.rs new file mode 100644 index 0000000..111a59e --- /dev/null +++ b/programs/solana-token-launchpad/src/lib.rs @@ -0,0 +1,16 @@ +use anchor_lang::prelude::*; + +declare_id!("AJBmAnT7A7RBxyK2USQUU1deZj6H3TLYM6RWuG3yaxoJ"); + +#[program] +pub mod solana_token_launchpad { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + msg!("Greetings from: {:?}", ctx.program_id); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize {} From 31cd93806a16b5748d3598d223661ec4862c1d64 Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Mon, 14 Apr 2025 17:23:06 +0530 Subject: [PATCH 05/10] refactor code with anchor v0.30.1 --- Anchor.toml | 10 +- Cargo.lock | 161 +++++++++++++++++--- Cargo.toml | 1 + package.json | 13 +- programs/solana-tax-token-anchor/Cargo.toml | 5 +- programs/solana-tax-token-anchor/src/lib.rs | 2 +- yarn.lock | 54 ++++--- 7 files changed, 191 insertions(+), 55 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 851c9b5..5be7e54 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,18 +1,20 @@ [toolchain] [features] -seeds = false +resolution = true skip-lint = false [programs.localnet] -solana_tax_token_anchor = "2uTRyNTYcwZSWkm9hqxS6gXKdNz67avDRKZzqDesJdeb" +clmm-cpi = "CzrAu91uK1YooFQjQtjtvxDZiz9MhqYfq31LgbV5xnPr" +solana-token-launchpad = "AJBmAnT7A7RBxyK2USQUU1deZj6H3TLYM6RWuG3yaxoJ" +solana_tax_token_anchor = "Yo1bzVsiuVigxHUmQguSZ83QJ879A4d6cQiGYFeDDMF" [registry] url = "https://api.apr.dev" [provider] -cluster = "Localnet" -wallet = "/Users/riteshnikhoria31/.config/solana/id.json" +cluster = "localnet" +wallet = "/Users/oxoxDev/.config/solana/id.json" [scripts] test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" diff --git a/Cargo.lock b/Cargo.lock index 6e8b6ec..7870dc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aead" @@ -196,6 +196,7 @@ dependencies = [ "anchor-derive-accounts", "anchor-derive-serde", "anchor-derive-space", + "anchor-lang-idl", "arrayref", "base64 0.21.7", "bincode", @@ -215,6 +216,7 @@ dependencies = [ "anchor-lang-idl-spec", "anyhow", "heck", + "regex", "serde", "serde_json", "sha2 0.10.8", @@ -237,7 +239,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04bd077c34449319a1e4e0bc21cea572960c9ae0d0fefda0dd7c52fcc3c647a3" dependencies = [ "anchor-lang", + "mpl-token-metadata", "spl-associated-token-account", + "spl-memo", "spl-pod", "spl-token", "spl-token-2022", @@ -253,6 +257,7 @@ checksum = "f99daacb53b55cfd37ce14d6c9905929721137fd4c67bbab44a19802aecb622f" dependencies = [ "anyhow", "bs58 0.5.1", + "cargo_toml", "heck", "proc-macro2", "quote", @@ -669,6 +674,16 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cargo_toml" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a98356df42a2eb1bd8f1793ae4ee4de48e384dd974ce5eac8eee802edb7492be" +dependencies = [ + "serde", + "toml 0.8.20", +] + [[package]] name = "cc" version = "1.2.17" @@ -710,6 +725,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clmm-cpi" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "raydium-clmm-cpi", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -821,9 +845,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -831,9 +855,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", @@ -845,9 +869,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -1000,6 +1024,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -1127,10 +1163,11 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -1277,6 +1314,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mpl-token-metadata" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf0f61b553e424a6234af1268456972ee66c2222e1da89079242251fa7479e5" +dependencies = [ + "borsh 0.10.4", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1287,6 +1337,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -1429,7 +1490,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -1479,6 +1540,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.7.3" @@ -1559,6 +1626,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "raydium-clmm-cpi" +version = "0.1.0" +source = "git+https://github.com/raydium-io/raydium-cpi?branch=anchor-0.30.1#c470a055aa44da1666f6c50c3600e0495699f6ab" +dependencies = [ + "anchor-lang", + "anchor-spl", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1697,6 +1773,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_with" version = "2.3.3" @@ -1881,7 +1966,7 @@ dependencies = [ "log", "memoffset", "num-bigint", - "num-derive", + "num-derive 0.4.2", "num-traits", "parking_lot", "rand 0.8.5", @@ -1929,7 +2014,7 @@ dependencies = [ "libsecp256k1", "log", "memmap2", - "num-derive", + "num-derive 0.4.2", "num-traits", "num_enum", "pbkdf2 0.11.0", @@ -1985,6 +2070,13 @@ dependencies = [ "bytemuck_derive", ] +[[package]] +name = "solana-token-launchpad" +version = "0.1.0" +dependencies = [ + "anchor-lang", +] + [[package]] name = "solana-zk-token-sdk" version = "1.18.26" @@ -2001,7 +2093,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive", + "num-derive 0.4.2", "num-traits", "rand 0.7.3", "serde", @@ -2022,7 +2114,7 @@ checksum = "143109d789171379e6143ef23191786dfaac54289ad6e7917cfb26b36c432b10" dependencies = [ "assert_matches", "borsh 1.5.7", - "num-derive", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-token", @@ -2093,7 +2185,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e45a49acb925db68aa501b926096b2164adbdcade7a0c24152af9f0742d0a602" dependencies = [ - "num-derive", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-program-error-derive", @@ -2134,7 +2226,7 @@ checksum = "b9eb465e4bf5ce1d498f05204c8089378c1ba34ef2777ea95852fc53a1fd4fb2" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", "num_enum", "solana-program", @@ -2149,7 +2241,7 @@ checksum = "4c39e416aeb1ea0b22f3b2bbecaf7e38a92a1aa8f4a0c5785c94179694e846a0" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", "num_enum", "solana-program", @@ -2327,11 +2419,26 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2340,6 +2447,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -2409,6 +2518,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2590,6 +2708,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index ef17a63..f397704 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "programs/*" ] +resolver = "2" [profile.release] overflow-checks = true diff --git a/package.json b/package.json index 5a19a18..dcb5546 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,17 @@ "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" }, "dependencies": { - "@coral-xyz/anchor": "^0.29.0", + "@coral-xyz/anchor": "0.30.1", "@solana/spl-token": "^0.4.6" - }, "devDependencies": { - "chai": "^4.3.4", - "mocha": "^9.0.3", - "ts-mocha": "^10.0.0", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", - "typescript": "^4.3.5", - "prettier": "^2.6.2" + "chai": "^4.3.4", + "mocha": "^9.0.3", + "prettier": "^2.6.2", + "ts-mocha": "^10.0.0", + "typescript": "^4.3.5" } } diff --git a/programs/solana-tax-token-anchor/Cargo.toml b/programs/solana-tax-token-anchor/Cargo.toml index 4858353..7ac150d 100644 --- a/programs/solana-tax-token-anchor/Cargo.toml +++ b/programs/solana-tax-token-anchor/Cargo.toml @@ -14,10 +14,11 @@ no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] default = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] [dependencies] # anchor-lang = "0.29.0" # anchor-spl = { version = "0.30.0" } -anchor-lang = { version = "0.30.0", features = ["init-if-needed"] } -anchor-spl = "0.30.0" +anchor-lang = { version = "0.30.1", features = ["init-if-needed"] } +anchor-spl = "0.30.1" bytemuck_derive = "=1.8.1" diff --git a/programs/solana-tax-token-anchor/src/lib.rs b/programs/solana-tax-token-anchor/src/lib.rs index 6e810f8..1ad2c37 100644 --- a/programs/solana-tax-token-anchor/src/lib.rs +++ b/programs/solana-tax-token-anchor/src/lib.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; mod instructions; use instructions::*; -declare_id!("2uTRyNTYcwZSWkm9hqxS6gXKdNz67avDRKZzqDesJdeb"); +declare_id!("Yo1bzVsiuVigxHUmQguSZ83QJ879A4d6cQiGYFeDDMF"); #[program] pub mod solana_tax_token_anchor { diff --git a/yarn.lock b/yarn.lock index 53fcc5a..e804b04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,12 +9,18 @@ dependencies: regenerator-runtime "^0.14.0" -"@coral-xyz/anchor@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.29.0.tgz#bd0be95bedfb30a381c3e676e5926124c310ff12" - integrity sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA== - dependencies: - "@coral-xyz/borsh" "^0.29.0" +"@coral-xyz/anchor-errors@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz#bdfd3a353131345244546876eb4afc0e125bec30" + integrity sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ== + +"@coral-xyz/anchor@0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" + integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== + dependencies: + "@coral-xyz/anchor-errors" "^0.30.1" + "@coral-xyz/borsh" "^0.30.1" "@noble/hashes" "^1.3.1" "@solana/web3.js" "^1.68.0" bn.js "^5.1.2" @@ -29,10 +35,10 @@ superstruct "^0.15.4" toml "^3.0.0" -"@coral-xyz/borsh@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.29.0.tgz#79f7045df2ef66da8006d47f5399c7190363e71f" - integrity sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ== +"@coral-xyz/borsh@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.30.1.tgz#869d8833abe65685c72e9199b8688477a4f6b0e3" + integrity sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ== dependencies: bn.js "^5.1.2" buffer-layout "^1.2.0" @@ -176,9 +182,9 @@ superstruct "^2.0.2" "@swc/helpers@^0.5.11": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" - integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== + version "0.5.17" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" + integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== dependencies: tslib "^2.8.0" @@ -212,11 +218,11 @@ integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/node@*": - version "22.13.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.14.tgz#70d84ec91013dcd2ba2de35532a5a14c2b4cc912" - integrity sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w== + version "22.14.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.14.1.tgz#53b54585cec81c21eee3697521e31312d6ca1e6f" + integrity sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw== dependencies: - undici-types "~6.20.0" + undici-types "~6.21.0" "@types/node@^12.12.54": version "12.20.55" @@ -236,9 +242,9 @@ "@types/node" "*" "@types/ws@^8.2.2": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.0.tgz#8a2ec491d6f0685ceaab9a9b7ff44146236993b5" - integrity sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw== + version "8.18.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" + integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== dependencies: "@types/node" "*" @@ -1173,10 +1179,10 @@ typescript@^4.3.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== utf-8-validate@^5.0.2: version "5.0.10" From 30c49be46bed37bb20983e655e838af9b196e9f8 Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Mon, 14 Apr 2025 17:54:42 +0530 Subject: [PATCH 06/10] feat: add raydium libraries --- programs/clmm-cpi/src/libraries/big_num.rs | 344 +++++++++++++++ .../clmm-cpi/src/libraries/fixed_point_64.rs | 5 + programs/clmm-cpi/src/libraries/full_math.rs | 335 +++++++++++++++ .../clmm-cpi/src/libraries/liquidity_math.rs | 316 ++++++++++++++ programs/clmm-cpi/src/libraries/mod.rs | 21 + .../clmm-cpi/src/libraries/sqrt_price_math.rs | 147 +++++++ programs/clmm-cpi/src/libraries/swap_math.rs | 344 +++++++++++++++ .../src/libraries/tick_array_bit_map.rs | 395 ++++++++++++++++++ programs/clmm-cpi/src/libraries/tick_math.rs | 315 ++++++++++++++ .../clmm-cpi/src/libraries/unsafe_math.rs | 49 +++ 10 files changed, 2271 insertions(+) create mode 100644 programs/clmm-cpi/src/libraries/big_num.rs create mode 100644 programs/clmm-cpi/src/libraries/fixed_point_64.rs create mode 100644 programs/clmm-cpi/src/libraries/full_math.rs create mode 100644 programs/clmm-cpi/src/libraries/liquidity_math.rs create mode 100644 programs/clmm-cpi/src/libraries/mod.rs create mode 100644 programs/clmm-cpi/src/libraries/sqrt_price_math.rs create mode 100644 programs/clmm-cpi/src/libraries/swap_math.rs create mode 100644 programs/clmm-cpi/src/libraries/tick_array_bit_map.rs create mode 100644 programs/clmm-cpi/src/libraries/tick_math.rs create mode 100644 programs/clmm-cpi/src/libraries/unsafe_math.rs diff --git a/programs/clmm-cpi/src/libraries/big_num.rs b/programs/clmm-cpi/src/libraries/big_num.rs new file mode 100644 index 0000000..f8dc557 --- /dev/null +++ b/programs/clmm-cpi/src/libraries/big_num.rs @@ -0,0 +1,344 @@ +///! 128 and 256 bit numbers +///! U128 is more efficient that u128 +///! https://github.com/solana-labs/solana/issues/19549 +use uint::construct_uint; +construct_uint! { + pub struct U128(2); +} + +construct_uint! { + pub struct U256(4); +} + +construct_uint! { + pub struct U512(8); +} + +#[macro_export] +macro_rules! construct_bignum { + ( $(#[$attr:meta])* $visibility:vis struct $name:ident ( $n_words:tt ); ) => { + $crate::construct_bignum! { @construct $(#[$attr])* $visibility struct $name ($n_words); } + impl $crate::core_::convert::From for $name { + fn from(value: u128) -> $name { + let mut ret = [0; $n_words]; + ret[0] = value as u64; + ret[1] = (value >> 64) as u64; + $name(ret) + } + } + + impl $crate::core_::convert::From for $name { + fn from(value: i128) -> $name { + match value >= 0 { + true => From::from(value as u128), + false => { panic!("Unsigned integer can't be created from negative value"); } + } + } + } + + impl $name { + /// Low 2 words (u128) + #[inline] + pub const fn low_u128(&self) -> u128 { + let &$name(ref arr) = self; + ((arr[1] as u128) << 64) + arr[0] as u128 + } + + /// Conversion to u128 with overflow checking + /// + /// # Panics + /// + /// Panics if the number is larger than 2^128. + #[inline] + pub fn as_u128(&self) -> u128 { + let &$name(ref arr) = self; + for i in 2..$n_words { + if arr[i] != 0 { + panic!("Integer overflow when casting to u128") + } + + } + self.low_u128() + } + } + + impl $crate::core_::convert::TryFrom<$name> for u128 { + type Error = &'static str; + + #[inline] + fn try_from(u: $name) -> $crate::core_::result::Result { + let $name(arr) = u; + for i in 2..$n_words { + if arr[i] != 0 { + return Err("integer overflow when casting to u128"); + } + } + Ok(((arr[1] as u128) << 64) + arr[0] as u128) + } + } + + impl $crate::core_::convert::TryFrom<$name> for i128 { + type Error = &'static str; + + #[inline] + fn try_from(u: $name) -> $crate::core_::result::Result { + let err_str = "integer overflow when casting to i128"; + let i = u128::try_from(u).map_err(|_| err_str)?; + if i > i128::max_value() as u128 { + Err(err_str) + } else { + Ok(i as i128) + } + } + } + }; + + ( @construct $(#[$attr:meta])* $visibility:vis struct $name:ident ( $n_words:tt ); ) => { + /// Little-endian large integer type + #[repr(C)] + $(#[$attr])* + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + $visibility struct $name (pub [u64; $n_words]); + + /// Get a reference to the underlying little-endian words. + impl AsRef<[u64]> for $name { + #[inline] + fn as_ref(&self) -> &[u64] { + &self.0 + } + } + + impl<'a> From<&'a $name> for $name { + fn from(x: &'a $name) -> $name { + *x + } + } + + impl $name { + /// Maximum value. + pub const MAX: $name = $name([u64::max_value(); $n_words]); + + /// Conversion to usize with overflow checking + /// + /// # Panics + /// + /// Panics if the number is larger than usize::max_value(). + #[inline] + pub fn as_usize(&self) -> usize { + let &$name(ref arr) = self; + if !self.fits_word() || arr[0] > usize::max_value() as u64 { + panic!("Integer overflow when casting to usize") + } + arr[0] as usize + } + + /// Whether this is zero. + #[inline] + pub const fn is_zero(&self) -> bool { + let &$name(ref arr) = self; + let mut i = 0; + while i < $n_words { if arr[i] != 0 { return false; } else { i += 1; } } + return true; + } + + // Whether this fits u64. + #[inline] + fn fits_word(&self) -> bool { + let &$name(ref arr) = self; + for i in 1..$n_words { if arr[i] != 0 { return false; } } + return true; + } + + /// Return if specific bit is set. + /// + /// # Panics + /// + /// Panics if `index` exceeds the bit width of the number. + #[inline] + pub const fn bit(&self, index: usize) -> bool { + let &$name(ref arr) = self; + arr[index / 64] & (1 << (index % 64)) != 0 + } + + /// Returns the number of leading zeros in the binary representation of self. + pub fn leading_zeros(&self) -> u32 { + let mut r = 0; + for i in 0..$n_words { + let w = self.0[$n_words - i - 1]; + if w == 0 { + r += 64; + } else { + r += w.leading_zeros(); + break; + } + } + r + } + + /// Returns the number of trailing zeros in the binary representation of self. + pub fn trailing_zeros(&self) -> u32 { + let mut r = 0; + for i in 0..$n_words { + let w = self.0[i]; + if w == 0 { + r += 64; + } else { + r += w.trailing_zeros(); + break; + } + } + r + } + + /// Zero (additive identity) of this type. + #[inline] + pub const fn zero() -> Self { + Self([0; $n_words]) + } + + /// One (multiplicative identity) of this type. + #[inline] + pub const fn one() -> Self { + let mut words = [0; $n_words]; + words[0] = 1u64; + Self(words) + } + + /// The maximum value which can be inhabited by this type. + #[inline] + pub const fn max_value() -> Self { + Self::MAX + } + } + + impl $crate::core_::default::Default for $name { + fn default() -> Self { + $name::zero() + } + } + + impl $crate::core_::ops::BitAnd<$name> for $name { + type Output = $name; + + #[inline] + fn bitand(self, other: $name) -> $name { + let $name(ref arr1) = self; + let $name(ref arr2) = other; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = arr1[i] & arr2[i]; + } + $name(ret) + } + } + + impl $crate::core_::ops::BitOr<$name> for $name { + type Output = $name; + + #[inline] + fn bitor(self, other: $name) -> $name { + let $name(ref arr1) = self; + let $name(ref arr2) = other; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = arr1[i] | arr2[i]; + } + $name(ret) + } + } + + impl $crate::core_::ops::BitXor<$name> for $name { + type Output = $name; + + #[inline] + fn bitxor(self, other: $name) -> $name { + let $name(ref arr1) = self; + let $name(ref arr2) = other; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = arr1[i] ^ arr2[i]; + } + $name(ret) + } + } + + impl $crate::core_::ops::Not for $name { + type Output = $name; + + #[inline] + fn not(self) -> $name { + let $name(ref arr) = self; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = !arr[i]; + } + $name(ret) + } + } + + impl $crate::core_::ops::Shl for $name { + type Output = $name; + + fn shl(self, shift: usize) -> $name { + let $name(ref original) = self; + let mut ret = [0u64; $n_words]; + let word_shift = shift / 64; + let bit_shift = shift % 64; + + // shift + for i in word_shift..$n_words { + ret[i] = original[i - word_shift] << bit_shift; + } + // carry + if bit_shift > 0 { + for i in word_shift+1..$n_words { + ret[i] += original[i - 1 - word_shift] >> (64 - bit_shift); + } + } + $name(ret) + } + } + + impl<'a> $crate::core_::ops::Shl for &'a $name { + type Output = $name; + fn shl(self, shift: usize) -> $name { + *self << shift + } + } + + impl $crate::core_::ops::Shr for $name { + type Output = $name; + + fn shr(self, shift: usize) -> $name { + let $name(ref original) = self; + let mut ret = [0u64; $n_words]; + let word_shift = shift / 64; + let bit_shift = shift % 64; + + // shift + for i in word_shift..$n_words { + ret[i - word_shift] = original[i] >> bit_shift; + } + + // Carry + if bit_shift > 0 { + for i in word_shift+1..$n_words { + ret[i - word_shift - 1] += original[i] << (64 - bit_shift); + } + } + + $name(ret) + } + } + + impl<'a> $crate::core_::ops::Shr for &'a $name { + type Output = $name; + fn shr(self, shift: usize) -> $name { + *self >> shift + } + } + }; +} +construct_bignum! { + pub struct U1024(16); +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/fixed_point_64.rs b/programs/clmm-cpi/src/libraries/fixed_point_64.rs new file mode 100644 index 0000000..530f4ff --- /dev/null +++ b/programs/clmm-cpi/src/libraries/fixed_point_64.rs @@ -0,0 +1,5 @@ +/// A library for handling Q64.64 fixed point numbers +/// Used in sqrt_price_math.rs and liquidity_amounts.rs + +pub const Q64: u128 = (u64::MAX as u128) + 1; // 2^64 +pub const RESOLUTION: u8 = 64; \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/full_math.rs b/programs/clmm-cpi/src/libraries/full_math.rs new file mode 100644 index 0000000..03e7fac --- /dev/null +++ b/programs/clmm-cpi/src/libraries/full_math.rs @@ -0,0 +1,335 @@ +//! A custom implementation of https://github.com/sdroege/rust-muldiv to support phantom overflow resistant +//! multiply-divide operations. This library uses U128 in place of u128 for u64 operations, +//! and supports U128 operations. +//! + +use crate::libraries::big_num::{U128, U256, U512}; + +/// Trait for calculating `val * num / denom` with different rounding modes and overflow +/// protection. +/// +/// Implementations of this trait have to ensure that even if the result of the multiplication does +/// not fit into the type, as long as it would fit after the division the correct result has to be +/// returned instead of `None`. `None` only should be returned if the overall result does not fit +/// into the type. +/// +/// This specifically means that e.g. the `u64` implementation must, depending on the arguments, be +/// able to do 128 bit integer multiplication. +pub trait MulDiv { + /// Output type for the methods of this trait. + type Output; + + /// Calculates `floor(val * num / denom)`, i.e. the largest integer less than or equal to the + /// result of the division. + /// + /// ## Example + /// + /// ```rust + /// use libraries::full_math::MulDiv; + /// + /// # fn main() { + /// let x = 3i8.mul_div_floor(4, 2); + /// assert_eq!(x, Some(6)); + /// + /// let x = 5i8.mul_div_floor(2, 3); + /// assert_eq!(x, Some(3)); + /// + /// let x = (-5i8).mul_div_floor(2, 3); + /// assert_eq!(x, Some(-4)); + /// + /// let x = 3i8.mul_div_floor(3, 2); + /// assert_eq!(x, Some(4)); + /// + /// let x = (-3i8).mul_div_floor(3, 2); + /// assert_eq!(x, Some(-5)); + /// + /// let x = 127i8.mul_div_floor(4, 3); + /// assert_eq!(x, None); + /// # } + /// ``` + fn mul_div_floor(self, num: RHS, denom: RHS) -> Option; + + /// Calculates `ceil(val * num / denom)`, i.e. the the smallest integer greater than or equal to + /// the result of the division. + /// + /// ## Example + /// + /// ```rust + /// use libraries::full_math::MulDiv; + /// + /// # fn main() { + /// let x = 3i8.mul_div_ceil(4, 2); + /// assert_eq!(x, Some(6)); + /// + /// let x = 5i8.mul_div_ceil(2, 3); + /// assert_eq!(x, Some(4)); + /// + /// let x = (-5i8).mul_div_ceil(2, 3); + /// assert_eq!(x, Some(-3)); + /// + /// let x = 3i8.mul_div_ceil(3, 2); + /// assert_eq!(x, Some(5)); + /// + /// let x = (-3i8).mul_div_ceil(3, 2); + /// assert_eq!(x, Some(-4)); + /// + /// let x = (127i8).mul_div_ceil(4, 3); + /// assert_eq!(x, None); + /// # } + /// ``` + fn mul_div_ceil(self, num: RHS, denom: RHS) -> Option; + + /// Return u64 not out of bounds + fn to_underflow_u64(self) -> u64; +} + +pub trait Upcast256 { + fn as_u256(self) -> U256; +} +impl Upcast256 for U128 { + fn as_u256(self) -> U256 { + U256([self.0[0], self.0[1], 0, 0]) + } +} + +pub trait Downcast256 { + /// Unsafe cast to U128 + /// Bits beyond the 128th position are lost + fn as_u128(self) -> U128; +} +impl Downcast256 for U256 { + fn as_u128(self) -> U128 { + U128([self.0[0], self.0[1]]) + } +} + +pub trait Upcast512 { + fn as_u512(self) -> U512; +} +impl Upcast512 for U256 { + fn as_u512(self) -> U512 { + U512([self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]) + } +} + +pub trait Downcast512 { + /// Unsafe cast to U256 + /// Bits beyond the 256th position are lost + fn as_u256(self) -> U256; +} +impl Downcast512 for U512 { + fn as_u256(self) -> U256 { + U256([self.0[0], self.0[1], self.0[2], self.0[3]]) + } +} + +impl MulDiv for u64 { + type Output = u64; + + fn mul_div_floor(self, num: Self, denom: Self) -> Option { + assert_ne!(denom, 0); + let r = (U128::from(self) * U128::from(num)) / U128::from(denom); + if r > U128::from(u64::MAX) { + None + } else { + Some(r.as_u64()) + } + } + + fn mul_div_ceil(self, num: Self, denom: Self) -> Option { + assert_ne!(denom, 0); + let r = (U128::from(self) * U128::from(num) + U128::from(denom - 1)) / U128::from(denom); + if r > U128::from(u64::MAX) { + None + } else { + Some(r.as_u64()) + } + } + + fn to_underflow_u64(self) -> u64 { + self + } +} + +impl MulDiv for U128 { + type Output = U128; + + fn mul_div_floor(self, num: Self, denom: Self) -> Option { + assert_ne!(denom, U128::default()); + let r = ((self.as_u256()) * (num.as_u256())) / (denom.as_u256()); + if r > U128::MAX.as_u256() { + None + } else { + Some(r.as_u128()) + } + } + + fn mul_div_ceil(self, num: Self, denom: Self) -> Option { + assert_ne!(denom, U128::default()); + let r = (self.as_u256() * num.as_u256() + (denom - 1).as_u256()) / denom.as_u256(); + if r > U128::MAX.as_u256() { + None + } else { + Some(r.as_u128()) + } + } + + fn to_underflow_u64(self) -> u64 { + if self < U128::from(u64::MAX) { + self.as_u64() + } else { + 0 + } + } +} + +impl MulDiv for U256 { + type Output = U256; + + fn mul_div_floor(self, num: Self, denom: Self) -> Option { + assert_ne!(denom, U256::default()); + let r = (self.as_u512() * num.as_u512()) / denom.as_u512(); + if r > U256::MAX.as_u512() { + None + } else { + Some(r.as_u256()) + } + } + + fn mul_div_ceil(self, num: Self, denom: Self) -> Option { + assert_ne!(denom, U256::default()); + let r = (self.as_u512() * num.as_u512() + (denom - 1).as_u512()) / denom.as_u512(); + if r > U256::MAX.as_u512() { + None + } else { + Some(r.as_u256()) + } + } + + fn to_underflow_u64(self) -> u64 { + if self < U256::from(u64::MAX) { + self.as_u64() + } else { + 0 + } + } +} + +#[cfg(test)] +mod muldiv_u64_tests { + use super::*; + + use quickcheck::{quickcheck, Arbitrary, Gen}; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct NonZero(u64); + + impl Arbitrary for NonZero { + fn arbitrary(g: &mut G) -> Self { + loop { + let v = u64::arbitrary(g); + if v != 0 { + return NonZero(v); + } + } + } + } + + quickcheck! { + fn scale_floor(val: u64, num: u64, den: NonZero) -> bool { + let res = val.mul_div_floor(num, den.0); + + let expected = (U128::from(val) * U128::from(num)) / U128::from(den.0); + + if expected > U128::from(u64::MAX) { + res.is_none() + } else { + res == Some(expected.as_u64()) + } + } + } + + quickcheck! { + fn scale_ceil(val: u64, num: u64, den: NonZero) -> bool { + let res = val.mul_div_ceil(num, den.0); + + let mut expected = (U128::from(val) * U128::from(num)) / U128::from(den.0); + let expected_rem = (U128::from(val) * U128::from(num)) % U128::from(den.0); + + if expected_rem != U128::default() { + expected += U128::from(1) + } + + if expected > U128::from(u64::MAX) { + res.is_none() + } else { + res == Some(expected.as_u64()) + } + } + } +} + +#[cfg(test)] +mod muldiv_u128_tests { + use super::*; + + use quickcheck::{quickcheck, Arbitrary, Gen}; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct NonZero(U128); + + impl Arbitrary for NonZero { + fn arbitrary(g: &mut G) -> Self { + loop { + let v = U128::from(u128::arbitrary(g)); + if v != U128::default() { + return NonZero(v); + } + } + } + } + + impl Arbitrary for U128 { + fn arbitrary(g: &mut G) -> Self { + loop { + let v = U128::from(u128::arbitrary(g)); + if v != U128::default() { + return v; + } + } + } + } + + quickcheck! { + fn scale_floor(val: U128, num: U128, den: NonZero) -> bool { + let res = val.mul_div_floor(num, den.0); + + let expected = ((val.as_u256()) * (num.as_u256())) / (den.0.as_u256()); + + if expected > U128::MAX.as_u256() { + res.is_none() + } else { + res == Some(expected.as_u128()) + } + } + } + + quickcheck! { + fn scale_ceil(val: U128, num: U128, den: NonZero) -> bool { + let res = val.mul_div_ceil(num, den.0); + + let mut expected = ((val.as_u256()) * (num.as_u256())) / (den.0.as_u256()); + let expected_rem = ((val.as_u256()) * (num.as_u256())) % (den.0.as_u256()); + + if expected_rem != U256::default() { + expected += U256::from(1) + } + + if expected > U128::MAX.as_u256() { + res.is_none() + } else { + res == Some(expected.as_u128()) + } + } + } +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/liquidity_math.rs b/programs/clmm-cpi/src/libraries/liquidity_math.rs new file mode 100644 index 0000000..8e724a8 --- /dev/null +++ b/programs/clmm-cpi/src/libraries/liquidity_math.rs @@ -0,0 +1,316 @@ +use super::big_num::U128; +use super::big_num::U256; +use super::fixed_point_64; +use super::full_math::MulDiv; +use super::tick_math; +use super::unsafe_math::UnsafeMathTrait; +use crate::error::ErrorCode; +use anchor_lang::prelude::*; + +/// Add a signed liquidity delta to liquidity and revert if it overflows or underflows +/// +/// # Arguments +/// +/// * `x` - The liquidity (L) before change +/// * `y` - The delta (ΔL) by which liquidity should be changed +/// +pub fn add_delta(x: u128, y: i128) -> Result { + let z: u128; + if y < 0 { + z = x - u128::try_from(-y).unwrap(); + require_gt!(x, z, ErrorCode::LiquiditySubValueErr); + } else { + z = x + u128::try_from(y).unwrap(); + require_gte!(z, x, ErrorCode::LiquidityAddValueErr); + } + + Ok(z) +} + +/// Computes the amount of liquidity received for a given amount of token_0 and price range +/// Calculates ΔL = Δx (√P_upper x √P_lower)/(√P_upper - √P_lower) +pub fn get_liquidity_from_amount_0( + mut sqrt_ratio_a_x64: u128, + mut sqrt_ratio_b_x64: u128, + amount_0: u64, +) -> u128 { + // sqrt_ratio_a_x64 should hold the smaller value + if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { + std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); + }; + let intermediate = U128::from(sqrt_ratio_a_x64) + .mul_div_floor( + U128::from(sqrt_ratio_b_x64), + U128::from(fixed_point_64::Q64), + ) + .unwrap(); + + U128::from(amount_0) + .mul_div_floor( + intermediate, + U128::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), + ) + .unwrap() + .as_u128() +} + +/// Computes the amount of liquidity received for a given amount of token_1 and price range +/// Calculates ΔL = Δy / (√P_upper - √P_lower) +pub fn get_liquidity_from_amount_1( + mut sqrt_ratio_a_x64: u128, + mut sqrt_ratio_b_x64: u128, + amount_1: u64, +) -> u128 { + // sqrt_ratio_a_x64 should hold the smaller value + if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { + std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); + }; + + U128::from(amount_1) + .mul_div_floor( + U128::from(fixed_point_64::Q64), + U128::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), + ) + .unwrap() + .as_u128() +} + +/// Computes the maximum amount of liquidity received for a given amount of token_0, token_1, the current +/// pool prices and the prices at the tick boundaries +pub fn get_liquidity_from_amounts( + sqrt_ratio_x64: u128, + mut sqrt_ratio_a_x64: u128, + mut sqrt_ratio_b_x64: u128, + amount_0: u64, + amount_1: u64, +) -> u128 { + // sqrt_ratio_a_x64 should hold the smaller value + if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { + std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); + }; + + if sqrt_ratio_x64 <= sqrt_ratio_a_x64 { + // If P ≤ P_lower, only token_0 liquidity is active + get_liquidity_from_amount_0(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_0) + } else if sqrt_ratio_x64 < sqrt_ratio_b_x64 { + // If P_lower < P < P_upper, active liquidity is the minimum of the liquidity provided + // by token_0 and token_1 + u128::min( + get_liquidity_from_amount_0(sqrt_ratio_x64, sqrt_ratio_b_x64, amount_0), + get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_x64, amount_1), + ) + } else { + // If P ≥ P_upper, only token_1 liquidity is active + get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_1) + } +} + +/// Computes the maximum amount of liquidity received for a given amount of token_0, token_1, the current +/// pool prices and the prices at the tick boundaries +pub fn get_liquidity_from_single_amount_0( + sqrt_ratio_x64: u128, + mut sqrt_ratio_a_x64: u128, + mut sqrt_ratio_b_x64: u128, + amount_0: u64, +) -> u128 { + // sqrt_ratio_a_x64 should hold the smaller value + if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { + std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); + }; + + if sqrt_ratio_x64 <= sqrt_ratio_a_x64 { + // If P ≤ P_lower, only token_0 liquidity is active + get_liquidity_from_amount_0(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_0) + } else if sqrt_ratio_x64 < sqrt_ratio_b_x64 { + // If P_lower < P < P_upper, active liquidity is the minimum of the liquidity provided + // by token_0 and token_1 + get_liquidity_from_amount_0(sqrt_ratio_x64, sqrt_ratio_b_x64, amount_0) + } else { + // If P ≥ P_upper, only token_1 liquidity is active + 0 + } +} + +/// Computes the maximum amount of liquidity received for a given amount of token_0, token_1, the current +/// pool prices and the prices at the tick boundaries +pub fn get_liquidity_from_single_amount_1( + sqrt_ratio_x64: u128, + mut sqrt_ratio_a_x64: u128, + mut sqrt_ratio_b_x64: u128, + amount_1: u64, +) -> u128 { + // sqrt_ratio_a_x64 should hold the smaller value + if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { + std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); + }; + + if sqrt_ratio_x64 <= sqrt_ratio_a_x64 { + // If P ≤ P_lower, only token_0 liquidity is active + 0 + } else if sqrt_ratio_x64 < sqrt_ratio_b_x64 { + // If P_lower < P < P_upper, active liquidity is the minimum of the liquidity provided + // by token_0 and token_1 + get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_x64, amount_1) + } else { + // If P ≥ P_upper, only token_1 liquidity is active + get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_1) + } +} + +/// Gets the delta amount_0 for given liquidity and price range +/// +/// # Formula +/// +/// * `Δx = L * (1 / √P_lower - 1 / √P_upper)` +/// * i.e. `L * (√P_upper - √P_lower) / (√P_upper * √P_lower)` +pub fn get_delta_amount_0_unsigned( + mut sqrt_ratio_a_x64: u128, + mut sqrt_ratio_b_x64: u128, + liquidity: u128, + round_up: bool, +) -> Result { + // sqrt_ratio_a_x64 should hold the smaller value + if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { + std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); + }; + + let numerator_1 = U256::from(liquidity) << fixed_point_64::RESOLUTION; + let numerator_2 = U256::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64); + + assert!(sqrt_ratio_a_x64 > 0); + + let result = if round_up { + U256::div_rounding_up( + numerator_1 + .mul_div_ceil(numerator_2, U256::from(sqrt_ratio_b_x64)) + .unwrap(), + U256::from(sqrt_ratio_a_x64), + ) + } else { + numerator_1 + .mul_div_floor(numerator_2, U256::from(sqrt_ratio_b_x64)) + .unwrap() + / U256::from(sqrt_ratio_a_x64) + }; + if result > U256::from(u64::MAX) { + return Err(ErrorCode::MaxTokenOverflow.into()); + } + return Ok(result.as_u64()); +} + +/// Gets the delta amount_1 for given liquidity and price range +/// * `Δy = L (√P_upper - √P_lower)` +pub fn get_delta_amount_1_unsigned( + mut sqrt_ratio_a_x64: u128, + mut sqrt_ratio_b_x64: u128, + liquidity: u128, + round_up: bool, +) -> Result { + // sqrt_ratio_a_x64 should hold the smaller value + if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { + std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); + }; + + let result = if round_up { + U256::from(liquidity).mul_div_ceil( + U256::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), + U256::from(fixed_point_64::Q64), + ) + } else { + U256::from(liquidity).mul_div_floor( + U256::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), + U256::from(fixed_point_64::Q64), + ) + } + .unwrap(); + if result > U256::from(u64::MAX) { + return Err(ErrorCode::MaxTokenOverflow.into()); + } + return Ok(result.as_u64()); +} + +/// Helper function to get signed delta amount_0 for given liquidity and price range +pub fn get_delta_amount_0_signed( + sqrt_ratio_a_x64: u128, + sqrt_ratio_b_x64: u128, + liquidity: i128, +) -> Result { + if liquidity < 0 { + get_delta_amount_0_unsigned( + sqrt_ratio_a_x64, + sqrt_ratio_b_x64, + u128::try_from(-liquidity).unwrap(), + false, + ) + } else { + get_delta_amount_0_unsigned( + sqrt_ratio_a_x64, + sqrt_ratio_b_x64, + u128::try_from(liquidity).unwrap(), + true, + ) + } +} + +/// Helper function to get signed delta amount_1 for given liquidity and price range +pub fn get_delta_amount_1_signed( + sqrt_ratio_a_x64: u128, + sqrt_ratio_b_x64: u128, + liquidity: i128, +) -> Result { + if liquidity < 0 { + get_delta_amount_1_unsigned( + sqrt_ratio_a_x64, + sqrt_ratio_b_x64, + u128::try_from(-liquidity).unwrap(), + false, + ) + } else { + get_delta_amount_1_unsigned( + sqrt_ratio_a_x64, + sqrt_ratio_b_x64, + u128::try_from(liquidity).unwrap(), + true, + ) + } +} + +pub fn get_delta_amounts_signed( + tick_current: i32, + sqrt_price_x64_current: u128, + tick_lower: i32, + tick_upper: i32, + liquidity_delta: i128, +) -> Result<(u64, u64)> { + let mut amount_0 = 0; + let mut amount_1 = 0; + if tick_current < tick_lower { + amount_0 = get_delta_amount_0_signed( + tick_math::get_sqrt_price_at_tick(tick_lower)?, + tick_math::get_sqrt_price_at_tick(tick_upper)?, + liquidity_delta, + ) + .unwrap(); + } else if tick_current < tick_upper { + amount_0 = get_delta_amount_0_signed( + sqrt_price_x64_current, + tick_math::get_sqrt_price_at_tick(tick_upper)?, + liquidity_delta, + ) + .unwrap(); + amount_1 = get_delta_amount_1_signed( + tick_math::get_sqrt_price_at_tick(tick_lower)?, + sqrt_price_x64_current, + liquidity_delta, + ) + .unwrap(); + } else { + amount_1 = get_delta_amount_1_signed( + tick_math::get_sqrt_price_at_tick(tick_lower)?, + tick_math::get_sqrt_price_at_tick(tick_upper)?, + liquidity_delta, + ) + .unwrap(); + } + Ok((amount_0, amount_1)) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/mod.rs b/programs/clmm-cpi/src/libraries/mod.rs new file mode 100644 index 0000000..9392aaa --- /dev/null +++ b/programs/clmm-cpi/src/libraries/mod.rs @@ -0,0 +1,21 @@ +pub mod big_num; +pub mod fixed_point_64; +pub mod full_math; +pub mod liquidity_math; +pub mod sqrt_price_math; +pub mod swap_math; + +pub mod tick_array_bit_map; +pub mod tick_math; +pub mod unsafe_math; + +pub use big_num::*; +pub use fixed_point_64::*; +pub use full_math::*; +pub use liquidity_math::*; +pub use sqrt_price_math::*; +pub use swap_math::*; + +pub use tick_array_bit_map::*; +pub use tick_math::*; +pub use unsafe_math::*; \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/sqrt_price_math.rs b/programs/clmm-cpi/src/libraries/sqrt_price_math.rs new file mode 100644 index 0000000..c519dd6 --- /dev/null +++ b/programs/clmm-cpi/src/libraries/sqrt_price_math.rs @@ -0,0 +1,147 @@ +use super::full_math::MulDiv; +use super::unsafe_math::UnsafeMathTrait; +use super::{fixed_point_64, U256}; + +/// Gets the next sqrt price √P' given a delta of token_0 +/// +/// Always round up because +/// 1. In the exact output case, token 0 supply decreases leading to price increase. +/// Move price up so that exact output is met. +/// 2. In the exact input case, token 0 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 0 +/// +/// # Formula +/// +/// * `√P' = √P * L / (L + Δx * √P)` +/// * If Δx * √P overflows, use alternate form `√P' = L / (L/√P + Δx)` +/// +/// # Proof +/// +/// For constant y, +/// √P * L = y +/// √P' * L' = √P * L +/// √P' = √P * L / L' +/// √P' = √P * L / L' +/// √P' = √P * L / (L + Δx*√P) +/// +pub fn get_next_sqrt_price_from_amount_0_rounding_up( + sqrt_price_x64: u128, + liquidity: u128, + amount: u64, + add: bool, +) -> u128 { + if amount == 0 { + return sqrt_price_x64; + }; + let numerator_1 = (U256::from(liquidity)) << fixed_point_64::RESOLUTION; + + if add { + if let Some(product) = U256::from(amount).checked_mul(U256::from(sqrt_price_x64)) { + let denominator = numerator_1 + U256::from(product); + if denominator >= numerator_1 { + return numerator_1 + .mul_div_ceil(U256::from(sqrt_price_x64), denominator) + .unwrap() + .as_u128(); + }; + } + + U256::div_rounding_up( + numerator_1, + (numerator_1 / U256::from(sqrt_price_x64)) + .checked_add(U256::from(amount)) + .unwrap(), + ) + .as_u128() + } else { + let product = U256::from( + U256::from(amount) + .checked_mul(U256::from(sqrt_price_x64)) + .unwrap(), + ); + let denominator = numerator_1.checked_sub(product).unwrap(); + numerator_1 + .mul_div_ceil(U256::from(sqrt_price_x64), denominator) + .unwrap() + .as_u128() + } +} + +/// Gets the next sqrt price given a delta of token_1 +/// +/// Always round down because +/// 1. In the exact output case, token 1 supply decreases leading to price decrease. +/// Move price down by rounding down so that exact output of token 0 is met. +/// 2. In the exact input case, token 1 supply increases leading to price increase. +/// Do not round down to minimize price impact. We only need to meet input +/// change and not gurantee exact output for token 0. +/// +/// +/// # Formula +/// +/// * `√P' = √P + Δy / L` +/// +pub fn get_next_sqrt_price_from_amount_1_rounding_down( + sqrt_price_x64: u128, + liquidity: u128, + amount: u64, + add: bool, +) -> u128 { + if add { + let quotient = U256::from(u128::from(amount) << fixed_point_64::RESOLUTION) / liquidity; + sqrt_price_x64.checked_add(quotient.as_u128()).unwrap() + } else { + let quotient = U256::div_rounding_up( + U256::from(u128::from(amount) << fixed_point_64::RESOLUTION), + U256::from(liquidity), + ); + sqrt_price_x64.checked_sub(quotient.as_u128()).unwrap() + } +} + +/// Gets the next sqrt price given an input amount of token_0 or token_1 +/// 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_x64: u128, + liquidity: u128, + amount_in: u64, + zero_for_one: bool, +) -> u128 { + assert!(sqrt_price_x64 > 0); + assert!(liquidity > 0); + + // round to make sure that we don't pass the target price + if zero_for_one { + get_next_sqrt_price_from_amount_0_rounding_up(sqrt_price_x64, liquidity, amount_in, true) + } else { + get_next_sqrt_price_from_amount_1_rounding_down(sqrt_price_x64, liquidity, amount_in, true) + } +} + +/// Gets the next sqrt price given an output amount of token0 or token1 +/// +/// Throws if price or liquidity are 0 or the next price is out of bounds +/// +pub fn get_next_sqrt_price_from_output( + sqrt_price_x64: u128, + liquidity: u128, + amount_out: u64, + zero_for_one: bool, +) -> u128 { + assert!(sqrt_price_x64 > 0); + assert!(liquidity > 0); + + if zero_for_one { + get_next_sqrt_price_from_amount_1_rounding_down( + sqrt_price_x64, + liquidity, + amount_out, + false, + ) + } else { + get_next_sqrt_price_from_amount_0_rounding_up(sqrt_price_x64, liquidity, amount_out, false) + } +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/swap_math.rs b/programs/clmm-cpi/src/libraries/swap_math.rs new file mode 100644 index 0000000..03a6e04 --- /dev/null +++ b/programs/clmm-cpi/src/libraries/swap_math.rs @@ -0,0 +1,344 @@ +use super::full_math::MulDiv; +use super::liquidity_math; +use super::sqrt_price_math; +use crate::error::ErrorCode; +use crate::states::config::FEE_RATE_DENOMINATOR_VALUE; +use anchor_lang::prelude::*; +/// Result of a swap step +#[derive(Default, Debug)] +pub struct SwapStep { + /// The price after swapping the amount in/out, not to exceed the price target + pub sqrt_price_next_x64: u128, + pub amount_in: u64, + pub amount_out: u64, + pub fee_amount: u64, +} + +/// Computes the result of swapping some amount in, or amount out, given the parameters of the swap +pub fn compute_swap_step( + sqrt_price_current_x64: u128, + sqrt_price_target_x64: u128, + liquidity: u128, + amount_remaining: u64, + fee_rate: u32, + is_base_input: bool, + zero_for_one: bool, + block_timestamp: u32, +) -> Result { + // let exact_in = amount_remaining >= 0; + let mut swap_step = SwapStep::default(); + if is_base_input { + // round up amount_in + // In exact input case, amount_remaining is positive + let amount_remaining_less_fee = (amount_remaining as u64) + .mul_div_floor( + (FEE_RATE_DENOMINATOR_VALUE - fee_rate).into(), + u64::from(FEE_RATE_DENOMINATOR_VALUE), + ) + .unwrap(); + + let amount_in = calculate_amount_in_range( + sqrt_price_current_x64, + sqrt_price_target_x64, + liquidity, + zero_for_one, + is_base_input, + block_timestamp, + )?; + if amount_in.is_some() { + swap_step.amount_in = amount_in.unwrap(); + } + + swap_step.sqrt_price_next_x64 = + if amount_in.is_some() && amount_remaining_less_fee >= swap_step.amount_in { + sqrt_price_target_x64 + } else { + sqrt_price_math::get_next_sqrt_price_from_input( + sqrt_price_current_x64, + liquidity, + amount_remaining_less_fee, + zero_for_one, + ) + }; + } else { + let amount_out = calculate_amount_in_range( + sqrt_price_current_x64, + sqrt_price_target_x64, + liquidity, + zero_for_one, + is_base_input, + block_timestamp, + )?; + if amount_out.is_some() { + swap_step.amount_out = amount_out.unwrap(); + } + // In exact output case, amount_remaining is negative + swap_step.sqrt_price_next_x64 = + if amount_out.is_some() && amount_remaining >= swap_step.amount_out { + sqrt_price_target_x64 + } else { + sqrt_price_math::get_next_sqrt_price_from_output( + sqrt_price_current_x64, + liquidity, + amount_remaining, + zero_for_one, + ) + } + } + + // whether we reached the max possible price for the given ticks + let max = sqrt_price_target_x64 == swap_step.sqrt_price_next_x64; + // get the input / output amounts when target price is not reached + if zero_for_one { + // if max is reached for exact input case, entire amount_in is needed + if !(max && is_base_input) { + swap_step.amount_in = liquidity_math::get_delta_amount_0_unsigned( + swap_step.sqrt_price_next_x64, + sqrt_price_current_x64, + liquidity, + true, + )? + }; + // if max is reached for exact output case, entire amount_out is needed + if !(max && !is_base_input) { + swap_step.amount_out = liquidity_math::get_delta_amount_1_unsigned( + swap_step.sqrt_price_next_x64, + sqrt_price_current_x64, + liquidity, + false, + )?; + }; + } else { + if !(max && is_base_input) { + swap_step.amount_in = liquidity_math::get_delta_amount_1_unsigned( + sqrt_price_current_x64, + swap_step.sqrt_price_next_x64, + liquidity, + true, + )? + }; + if !(max && !is_base_input) { + swap_step.amount_out = liquidity_math::get_delta_amount_0_unsigned( + sqrt_price_current_x64, + swap_step.sqrt_price_next_x64, + liquidity, + false, + )? + }; + } + + // For exact output case, cap the output amount to not exceed the remaining output amount + if !is_base_input && swap_step.amount_out > amount_remaining { + swap_step.amount_out = amount_remaining; + } + + swap_step.fee_amount = + if is_base_input && swap_step.sqrt_price_next_x64 != sqrt_price_target_x64 { + // we didn't reach the target, so take the remainder of the maximum input as fee + // swap dust is granted as fee + u64::from(amount_remaining) + .checked_sub(swap_step.amount_in) + .unwrap() + } else { + // take pip percentage as fee + swap_step + .amount_in + .mul_div_ceil( + fee_rate.into(), + (FEE_RATE_DENOMINATOR_VALUE - fee_rate).into(), + ) + .unwrap() + }; + + Ok(swap_step) +} + +/// Pre calcumate amount_in or amount_out for the specified price range +/// The amount maybe overflow of u64 due to the `sqrt_price_target_x64` maybe unreasonable. +/// Therefore, this situation needs to be handled in `compute_swap_step` to recalculate the price that can be reached based on the amount. +#[cfg(not(test))] +fn calculate_amount_in_range( + sqrt_price_current_x64: u128, + sqrt_price_target_x64: u128, + liquidity: u128, + zero_for_one: bool, + is_base_input: bool, + _block_timestamp: u32, +) -> Result> { + if is_base_input { + let result = if zero_for_one { + liquidity_math::get_delta_amount_0_unsigned( + sqrt_price_target_x64, + sqrt_price_current_x64, + liquidity, + true, + ) + } else { + liquidity_math::get_delta_amount_1_unsigned( + sqrt_price_current_x64, + sqrt_price_target_x64, + liquidity, + true, + ) + }; + + if result.is_ok() { + return Ok(Some(result.unwrap())); + } else { + if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { + return Ok(None); + } else { + return Err(ErrorCode::SqrtPriceLimitOverflow.into()); + } + } + } else { + let result = if zero_for_one { + liquidity_math::get_delta_amount_1_unsigned( + sqrt_price_target_x64, + sqrt_price_current_x64, + liquidity, + false, + ) + } else { + liquidity_math::get_delta_amount_0_unsigned( + sqrt_price_current_x64, + sqrt_price_target_x64, + liquidity, + false, + ) + }; + if result.is_ok() { + return Ok(Some(result.unwrap())); + } else { + if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { + return Ok(None); + } else { + return Err(ErrorCode::SqrtPriceLimitOverflow.into()); + } + } + } +} + +#[cfg(test)] +fn calculate_amount_in_range( + sqrt_price_current_x64: u128, + sqrt_price_target_x64: u128, + liquidity: u128, + zero_for_one: bool, + is_base_input: bool, + block_timestamp: u32, +) -> Result> { + if is_base_input { + let result = if zero_for_one { + liquidity_math::get_delta_amount_0_unsigned( + sqrt_price_target_x64, + sqrt_price_current_x64, + liquidity, + true, + ) + } else { + liquidity_math::get_delta_amount_1_unsigned( + sqrt_price_current_x64, + sqrt_price_target_x64, + liquidity, + true, + ) + }; + + if block_timestamp == 0 { + if result.is_err() { + return Err(ErrorCode::MaxTokenOverflow.into()); + } else { + return Ok(Some(result.unwrap())); + } + } + if result.is_ok() { + return Ok(Some(result.unwrap())); + } else { + if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { + return Ok(None); + } else { + return Err(ErrorCode::SqrtPriceLimitOverflow.into()); + } + } + } else { + let result = if zero_for_one { + liquidity_math::get_delta_amount_1_unsigned( + sqrt_price_target_x64, + sqrt_price_current_x64, + liquidity, + false, + ) + } else { + liquidity_math::get_delta_amount_0_unsigned( + sqrt_price_current_x64, + sqrt_price_target_x64, + liquidity, + false, + ) + }; + if result.is_ok() || block_timestamp == 0 { + return Ok(Some(result.unwrap())); + } else { + if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { + return Ok(None); + } else { + return Err(ErrorCode::SqrtPriceLimitOverflow.into()); + } + } + } +} +#[cfg(test)] +mod swap_math_test { + use crate::libraries::tick_math; + + use super::*; + use proptest::prelude::*; + + proptest! { + #[test] + fn compute_swap_step_test( + sqrt_price_current_x64 in tick_math::MIN_SQRT_PRICE_X64..tick_math::MAX_SQRT_PRICE_X64, + sqrt_price_target_x64 in tick_math::MIN_SQRT_PRICE_X64..tick_math::MAX_SQRT_PRICE_X64, + liquidity in 1..u32::MAX as u128, + amount_remaining in 1..u64::MAX, + fee_rate in 1..FEE_RATE_DENOMINATOR_VALUE/2, + is_base_input in proptest::bool::ANY, + ) { + prop_assume!(sqrt_price_current_x64 != sqrt_price_target_x64); + + let zero_for_one = sqrt_price_current_x64 > sqrt_price_target_x64; + let swap_step = compute_swap_step( + sqrt_price_current_x64, + sqrt_price_target_x64, + liquidity, + amount_remaining, + fee_rate, + is_base_input, + zero_for_one, + 1, + ).unwrap(); + + let amount_in = swap_step.amount_in; + let amount_out = swap_step.amount_out; + let sqrt_price_next_x64 = swap_step.sqrt_price_next_x64; + let fee_amount = swap_step.fee_amount; + + let amount_used = if is_base_input { + amount_in + fee_amount + } else { + amount_out + }; + + if sqrt_price_next_x64 != sqrt_price_target_x64 { + assert!(amount_used == amount_remaining); + } else { + assert!(amount_used <= amount_remaining); + } + let price_lower = sqrt_price_current_x64.min(sqrt_price_target_x64); + let price_upper = sqrt_price_current_x64.max(sqrt_price_target_x64); + assert!(sqrt_price_next_x64 >= price_lower); + assert!(sqrt_price_next_x64 <= price_upper); + } + } +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/tick_array_bit_map.rs b/programs/clmm-cpi/src/libraries/tick_array_bit_map.rs new file mode 100644 index 0000000..d840c47 --- /dev/null +++ b/programs/clmm-cpi/src/libraries/tick_array_bit_map.rs @@ -0,0 +1,395 @@ +///! Helper functions to get most and least significant non-zero bits +use super::big_num::U1024; +use crate::error::ErrorCode; +use crate::states::tick_array::{TickArrayState, TickState, TICK_ARRAY_SIZE}; +use anchor_lang::prelude::*; + +pub const TICK_ARRAY_BITMAP_SIZE: i32 = 512; + +pub type TickArryBitmap = [u64; 8]; + +pub fn max_tick_in_tickarray_bitmap(tick_spacing: u16) -> i32 { + i32::from(tick_spacing) * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE +} + +pub fn get_bitmap_tick_boundary(tick_array_start_index: i32, tick_spacing: u16) -> (i32, i32) { + let ticks_in_one_bitmap: i32 = max_tick_in_tickarray_bitmap(tick_spacing); + let mut m = tick_array_start_index.abs() / ticks_in_one_bitmap; + if tick_array_start_index < 0 && tick_array_start_index.abs() % ticks_in_one_bitmap != 0 { + m += 1; + } + let min_value: i32 = ticks_in_one_bitmap * m; + if tick_array_start_index < 0 { + (-min_value, -min_value + ticks_in_one_bitmap) + } else { + (min_value, min_value + ticks_in_one_bitmap) + } +} + +pub fn most_significant_bit(x: U1024) -> Option { + if x.is_zero() { + None + } else { + Some(u16::try_from(x.leading_zeros()).unwrap()) + } +} + +pub fn least_significant_bit(x: U1024) -> Option { + if x.is_zero() { + None + } else { + Some(u16::try_from(x.trailing_zeros()).unwrap()) + } +} + +/// Given a tick, calculate whether the tickarray it belongs to has been initialized. +pub fn check_current_tick_array_is_initialized( + bit_map: U1024, + tick_current: i32, + tick_spacing: u16, +) -> Result<(bool, i32)> { + if TickState::check_is_out_of_boundary(tick_current) { + return err!(ErrorCode::InvaildTickIndex); + } + let multiplier = i32::from(tick_spacing) * TICK_ARRAY_SIZE; + let mut compressed = tick_current / multiplier + 512; + if tick_current < 0 && tick_current % multiplier != 0 { + // round towards negative infinity + compressed -= 1; + } + let bit_pos = compressed.abs(); + // set current bit + let mask = U1024::one() << bit_pos.try_into().unwrap(); + let masked = bit_map & mask; + // check the current bit whether initialized + let initialized = masked != U1024::default(); + if initialized { + return Ok((true, (compressed - 512) * multiplier)); + } + // the current bit is not initialized + return Ok((false, (compressed - 512) * multiplier)); +} + +pub fn next_initialized_tick_array_start_index( + bit_map: U1024, + last_tick_array_start_index: i32, + tick_spacing: u16, + zero_for_one: bool, +) -> (bool, i32) { + assert!(TickArrayState::check_is_valid_start_index( + last_tick_array_start_index, + tick_spacing + )); + let tick_boundary = max_tick_in_tickarray_bitmap(tick_spacing); + let next_tick_array_start_index = if zero_for_one { + last_tick_array_start_index - TickArrayState::tick_count(tick_spacing) + } else { + last_tick_array_start_index + TickArrayState::tick_count(tick_spacing) + }; + + if next_tick_array_start_index < -tick_boundary || next_tick_array_start_index >= tick_boundary + { + return (false, last_tick_array_start_index); + } + + let multiplier = i32::from(tick_spacing) * TICK_ARRAY_SIZE; + let mut compressed = next_tick_array_start_index / multiplier + 512; + if next_tick_array_start_index < 0 && next_tick_array_start_index % multiplier != 0 { + // round towards negative infinity + compressed -= 1; + } + let bit_pos = compressed.abs(); + + if zero_for_one { + // tick from upper to lower + // find from highter bits to lower bits + let offset_bit_map = bit_map << (1024 - bit_pos - 1).try_into().unwrap(); + let next_bit = most_significant_bit(offset_bit_map); + if next_bit.is_some() { + let next_array_start_index = + (bit_pos - i32::from(next_bit.unwrap()) - 512) * multiplier; + (true, next_array_start_index) + } else { + // not found til to the end + (false, -tick_boundary) + } + } else { + // tick from lower to upper + // find from lower bits to highter bits + let offset_bit_map = bit_map >> (bit_pos).try_into().unwrap(); + let next_bit = least_significant_bit(offset_bit_map); + if next_bit.is_some() { + let next_array_start_index = + (bit_pos + i32::from(next_bit.unwrap()) - 512) * multiplier; + (true, next_array_start_index) + } else { + // not found til to the end + ( + false, + tick_boundary - TickArrayState::tick_count(tick_spacing), + ) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{libraries::tick_math, states::TickArrayState}; + + #[test] + fn test_check_current_tick_array_is_initialized() { + let tick_spacing = 10; + let bit_map = U1024([ + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + u64::max_value() & 1 << 63, + ]); + let mut tick_current = -307200; + let mut start_index = -1; + for _i in 0..1024 { + let ret = check_current_tick_array_is_initialized(bit_map, tick_current, tick_spacing) + .unwrap(); + if ret.0 && ret.1 != start_index { + start_index = ret.1; + println!("{}-{}", tick_current, start_index); + } + tick_current += 600; + } + } + #[test] + fn find_next_init_pos_in_bit_map_positive_price_down() { + let tick_spacing = 10; + let bit_map = U1024::max_value(); + let mut tick_array_start_index = 306600; + for _i in 0..5 { + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing, + true, + ); + println!("{:?}", array_start_index); + if !is_found { + break; + } + tick_array_start_index = + TickArrayState::get_array_start_index(array_start_index, tick_spacing); + } + } + #[test] + fn find_next_init_pos_in_bit_map_negative_price_down() { + let tick_spacing = 10; + let bit_map = U1024::max_value(); + let mut tick_array_start_index = -307200 + 600 + 600; + for _i in 0..5 { + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing, + true, + ); + println!("{:?}", array_start_index); + if !is_found { + break; + } + tick_array_start_index = + TickArrayState::get_array_start_index(array_start_index, tick_spacing); + } + } + #[test] + fn find_next_init_pos_in_bit_map_negative_price_down_crose_zero() { + let tick_spacing = 10; + let bit_map = U1024::max_value(); + let mut tick_array_start_index = 1800; + for _i in 0..5 { + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing, + true, + ); + println!("{:?}", array_start_index); + if !is_found { + break; + } + tick_array_start_index = + TickArrayState::get_array_start_index(array_start_index, tick_spacing); + } + } + + #[test] + fn find_previous_init_pos_in_bit_map_positive_price_up() { + let tick_spacing = 10; + let bit_map = U1024::max_value(); + let mut tick_array_start_index = 306600 - 600 - 600; + for _i in 0..5 { + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing, + false, + ); + println!("{:?}", array_start_index); + if !is_found { + break; + } + tick_array_start_index = + TickArrayState::get_array_start_index(array_start_index, tick_spacing); + } + } + #[test] + fn find_previous_init_pos_in_bit_map_negative_price_up() { + let tick_spacing = 10; + let bit_map = U1024::max_value(); + let mut tick_array_start_index = -307200; + for _i in 0..5 { + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing, + false, + ); + println!("{:?}", array_start_index); + if !is_found { + break; + } + tick_array_start_index = + TickArrayState::get_array_start_index(array_start_index, tick_spacing); + } + } + #[test] + fn find_previous_init_pos_in_bit_map_negative_price_up_crose_zero() { + let tick_spacing = 10; + let bit_map = U1024::max_value(); + let mut tick_array_start_index = -1800; + for _i in 0..5 { + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing, + false, + ); + println!("{:?}", array_start_index); + if !is_found { + break; + } + tick_array_start_index = + TickArrayState::get_array_start_index(array_start_index, tick_spacing); + } + } + + #[test] + fn find_next_init_pos_in_bit_map_with_eigenvalues() { + let tick_spacing = 10; + let bit_map: [u64; 16] = [ + 1, + 0, + 0, + 0, + 0, + 0, + 9223372036854775808, + 16140901064495857665, + 7, + 1, + 0, + 0, + 0, + 0, + 0, + 9223372036854775808, + ]; + let (_, mut array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), 0, tick_spacing, true); + assert_eq!(array_start_index, -600); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), -600, tick_spacing, true); + assert_eq!(array_start_index, -1200); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), -1200, tick_spacing, true); + assert_eq!(array_start_index, -1800); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), -1800, tick_spacing, true); + assert_eq!(array_start_index, -38400); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), -38400, tick_spacing, true); + assert_eq!(array_start_index, -39000); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), -39000, tick_spacing, true); + assert_eq!(array_start_index, -307200); + + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), 0, tick_spacing, false); + assert_eq!(array_start_index, 600); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), 600, tick_spacing, false); + assert_eq!(array_start_index, 1200); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), 1200, tick_spacing, false); + assert_eq!(array_start_index, 38400); + (_, array_start_index) = + next_initialized_tick_array_start_index(U1024(bit_map), 38400, tick_spacing, false); + assert_eq!(array_start_index, 306600); + } + + #[test] + fn next_initialized_tick_array_start_index_boundary_test() { + let tick_spacing = 1; + let bit_map = U1024::max_value(); + let mut tick_array_start_index = (tick_math::MIN_TICK / TICK_ARRAY_SIZE * tick_spacing - 1) + * TICK_ARRAY_SIZE + * tick_spacing; + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing as u16, + false, + ); + assert!(is_found == false); + assert!(array_start_index == tick_array_start_index); + + tick_array_start_index = + (tick_math::MAX_TICK / TICK_ARRAY_SIZE * tick_spacing) * TICK_ARRAY_SIZE * tick_spacing; + let (is_found, array_start_index) = next_initialized_tick_array_start_index( + bit_map, + tick_array_start_index, + tick_spacing as u16, + true, + ); + assert!(is_found == false); + assert!(array_start_index == tick_array_start_index); + } + + #[test] + fn get_bitmap_tick_boundary_test() { + let (mut min, mut max) = get_bitmap_tick_boundary(-430080, 1); + assert!(min == -430080); + assert!(max == -399360); + + (min, max) = get_bitmap_tick_boundary(-430140, 1); + assert!(min == -460800); + assert!(max == -430080); + + let (mut min, mut max) = get_bitmap_tick_boundary(430080, 1); + assert!(min == 430080); + assert!(max == 460800); + + (min, max) = get_bitmap_tick_boundary(430020, 1); + assert!(min == 399360); + assert!(max == 430080); + } +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/tick_math.rs b/programs/clmm-cpi/src/libraries/tick_math.rs new file mode 100644 index 0000000..1148429 --- /dev/null +++ b/programs/clmm-cpi/src/libraries/tick_math.rs @@ -0,0 +1,315 @@ +use crate::{error::ErrorCode, libraries::big_num::U128}; + +use anchor_lang::require; + +/// The minimum tick +pub const MIN_TICK: i32 = -443636; +/// The minimum tick +pub const MAX_TICK: i32 = -MIN_TICK; + +/// The minimum value that can be returned from #get_sqrt_price_at_tick. Equivalent to get_sqrt_price_at_tick(MIN_TICK) +pub const MIN_SQRT_PRICE_X64: u128 = 4295048016; +/// The maximum value that can be returned from #get_sqrt_price_at_tick. Equivalent to get_sqrt_price_at_tick(MAX_TICK) +pub const MAX_SQRT_PRICE_X64: u128 = 79226673521066979257578248091; + +// Number 64, encoded as a U128 +const NUM_64: U128 = U128([64, 0]); + +const BIT_PRECISION: u32 = 16; + +/// Calculates 1.0001^(tick/2) as a U64.64 number representing +/// the square root of the ratio of the two assets (token_1/token_0) +/// +/// Calculates result as a U64.64 +/// Each magic factor is `2^64 / (1.0001^(2^(i - 1)))` for i in `[0, 18)`. +/// +/// Throws if |tick| > MAX_TICK +/// +/// # Arguments +/// * `tick` - Price tick +/// +pub fn get_sqrt_price_at_tick(tick: i32) -> Result { + let abs_tick = tick.abs() as u32; + require!(abs_tick <= MAX_TICK as u32, ErrorCode::TickUpperOverflow); + + // i = 0 + let mut ratio = if abs_tick & 0x1 != 0 { + U128([0xfffcb933bd6fb800, 0]) + } else { + // 2^64 + U128([0, 1]) + }; + // i = 1 + if abs_tick & 0x2 != 0 { + ratio = (ratio * U128([0xfff97272373d4000, 0])) >> NUM_64 + }; + // i = 2 + if abs_tick & 0x4 != 0 { + ratio = (ratio * U128([0xfff2e50f5f657000, 0])) >> NUM_64 + }; + // i = 3 + if abs_tick & 0x8 != 0 { + ratio = (ratio * U128([0xffe5caca7e10f000, 0])) >> NUM_64 + }; + // i = 4 + if abs_tick & 0x10 != 0 { + ratio = (ratio * U128([0xffcb9843d60f7000, 0])) >> NUM_64 + }; + // i = 5 + if abs_tick & 0x20 != 0 { + ratio = (ratio * U128([0xff973b41fa98e800, 0])) >> NUM_64 + }; + // i = 6 + if abs_tick & 0x40 != 0 { + ratio = (ratio * U128([0xff2ea16466c9b000, 0])) >> NUM_64 + }; + // i = 7 + if abs_tick & 0x80 != 0 { + ratio = (ratio * U128([0xfe5dee046a9a3800, 0])) >> NUM_64 + }; + // i = 8 + if abs_tick & 0x100 != 0 { + ratio = (ratio * U128([0xfcbe86c7900bb000, 0])) >> NUM_64 + }; + // i = 9 + if abs_tick & 0x200 != 0 { + ratio = (ratio * U128([0xf987a7253ac65800, 0])) >> NUM_64 + }; + // i = 10 + if abs_tick & 0x400 != 0 { + ratio = (ratio * U128([0xf3392b0822bb6000, 0])) >> NUM_64 + }; + // i = 11 + if abs_tick & 0x800 != 0 { + ratio = (ratio * U128([0xe7159475a2caf000, 0])) >> NUM_64 + }; + // i = 12 + if abs_tick & 0x1000 != 0 { + ratio = (ratio * U128([0xd097f3bdfd2f2000, 0])) >> NUM_64 + }; + // i = 13 + if abs_tick & 0x2000 != 0 { + ratio = (ratio * U128([0xa9f746462d9f8000, 0])) >> NUM_64 + }; + // i = 14 + if abs_tick & 0x4000 != 0 { + ratio = (ratio * U128([0x70d869a156f31c00, 0])) >> NUM_64 + }; + // i = 15 + if abs_tick & 0x8000 != 0 { + ratio = (ratio * U128([0x31be135f97ed3200, 0])) >> NUM_64 + }; + // i = 16 + if abs_tick & 0x10000 != 0 { + ratio = (ratio * U128([0x9aa508b5b85a500, 0])) >> NUM_64 + }; + // i = 17 + if abs_tick & 0x20000 != 0 { + ratio = (ratio * U128([0x5d6af8dedc582c, 0])) >> NUM_64 + }; + // i = 18 + if abs_tick & 0x40000 != 0 { + ratio = (ratio * U128([0x2216e584f5fa, 0])) >> NUM_64 + } + + // Divide to obtain 1.0001^(2^(i - 1)) * 2^32 in numerator + if tick > 0 { + ratio = U128::MAX / ratio; + } + + Ok(ratio.as_u128()) +} + +/// Calculates the greatest tick value such that get_sqrt_price_at_tick(tick) <= ratio +/// Throws if sqrt_price_x64 < MIN_SQRT_RATIO or sqrt_price_x64 > MAX_SQRT_RATIO +/// +/// Formula: `i = log base(√1.0001) (√P)` +pub fn get_tick_at_sqrt_price(sqrt_price_x64: u128) -> Result { + // second inequality must be < because the price can never reach the price at the max tick + require!( + sqrt_price_x64 >= MIN_SQRT_PRICE_X64 && sqrt_price_x64 < MAX_SQRT_PRICE_X64, + ErrorCode::SqrtPriceX64 + ); + + // Determine log_b(sqrt_ratio). First by calculating integer portion (msb) + let msb: u32 = 128 - sqrt_price_x64.leading_zeros() - 1; + let log2p_integer_x32 = (msb as i128 - 64) << 32; + + // get fractional value (r/2^msb), msb always > 128 + // We begin the iteration from bit 63 (0.5 in Q64.64) + let mut bit: i128 = 0x8000_0000_0000_0000i128; + let mut precision = 0; + let mut log2p_fraction_x64 = 0; + + // Log2 iterative approximation for the fractional part + // Go through each 2^(j) bit where j < 64 in a Q64.64 number + // Append current bit value to fraction result if r^2 Q2.126 is more than 2 + let mut r = if msb >= 64 { + sqrt_price_x64 >> (msb - 63) + } else { + sqrt_price_x64 << (63 - msb) + }; + + while bit > 0 && precision < BIT_PRECISION { + r *= r; + let is_r_more_than_two = r >> 127 as u32; + r >>= 63 + is_r_more_than_two; + log2p_fraction_x64 += bit * is_r_more_than_two as i128; + bit >>= 1; + precision += 1; + } + let log2p_fraction_x32 = log2p_fraction_x64 >> 32; + let log2p_x32 = log2p_integer_x32 + log2p_fraction_x32; + + // 14 bit refinement gives an error margin of 2^-14 / log2 (√1.0001) = 0.8461 < 1 + // Since tick is a decimal, an error under 1 is acceptable + + // Change of base rule: multiply with 2^16 / log2 (√1.0001) + let log_sqrt_10001_x64 = log2p_x32 * 59543866431248i128; + + // tick - 0.01 + let tick_low = ((log_sqrt_10001_x64 - 184467440737095516i128) >> 64) as i32; + + // tick + (2^-14 / log2(√1.001)) + 0.01 + let tick_high = ((log_sqrt_10001_x64 + 15793534762490258745i128) >> 64) as i32; + + Ok(if tick_low == tick_high { + tick_low + } else if get_sqrt_price_at_tick(tick_high).unwrap() <= sqrt_price_x64 { + tick_high + } else { + tick_low + }) +} + +#[cfg(test)] +mod tick_math_test { + use super::*; + mod get_sqrt_price_at_tick_test { + use super::*; + use crate::libraries::fixed_point_64; + + #[test] + fn check_get_sqrt_price_at_tick_at_min_or_max_tick() { + assert_eq!( + get_sqrt_price_at_tick(MIN_TICK).unwrap(), + MIN_SQRT_PRICE_X64 + ); + let min_sqrt_price = MIN_SQRT_PRICE_X64 as f64 / fixed_point_64::Q64 as f64; + println!("min_sqrt_price: {}", min_sqrt_price); + assert_eq!( + get_sqrt_price_at_tick(MAX_TICK).unwrap(), + MAX_SQRT_PRICE_X64 + ); + let max_sqrt_price = MAX_SQRT_PRICE_X64 as f64 / fixed_point_64::Q64 as f64; + println!("max_sqrt_price: {}", max_sqrt_price); + } + } + + mod get_tick_at_sqrt_price_test { + use super::*; + + #[test] + fn check_get_tick_at_sqrt_price_at_min_or_max_sqrt_price() { + assert_eq!( + get_tick_at_sqrt_price(MIN_SQRT_PRICE_X64).unwrap(), + MIN_TICK, + ); + + // we can't reach MAX_SQRT_PRICE_X64 + assert_eq!( + get_tick_at_sqrt_price(MAX_SQRT_PRICE_X64 - 1).unwrap(), + MAX_TICK - 1, + ); + } + } + + #[test] + fn tick_round_down() { + // tick is negative + let sqrt_price_x64 = get_sqrt_price_at_tick(-28861).unwrap(); + let mut tick = get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); + assert_eq!(tick, -28861); + tick = get_tick_at_sqrt_price(sqrt_price_x64 + 1).unwrap(); + assert_eq!(tick, -28861); + tick = get_tick_at_sqrt_price(get_sqrt_price_at_tick(-28860).unwrap() - 1).unwrap(); + assert_eq!(tick, -28861); + tick = get_tick_at_sqrt_price(sqrt_price_x64 - 1).unwrap(); + assert_eq!(tick, -28862); + + // tick is positive + let sqrt_price_x64 = get_sqrt_price_at_tick(28861).unwrap(); + tick = get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); + assert_eq!(tick, 28861); + tick = get_tick_at_sqrt_price(sqrt_price_x64 + 1).unwrap(); + assert_eq!(tick, 28861); + tick = get_tick_at_sqrt_price(get_sqrt_price_at_tick(28862).unwrap() - 1).unwrap(); + assert_eq!(tick, 28861); + tick = get_tick_at_sqrt_price(sqrt_price_x64 - 1).unwrap(); + assert_eq!(tick, 28860); + } + + mod fuzz_tests { + use super::*; + use proptest::prelude::*; + + proptest! { + #[test] + fn get_sqrt_price_at_tick_test ( + tick in MIN_TICK+1..MAX_TICK-1, + ) { + let sqrt_price_x64 = get_sqrt_price_at_tick(tick).unwrap(); + + assert!(sqrt_price_x64 >= MIN_SQRT_PRICE_X64); + assert!(sqrt_price_x64 <= MAX_SQRT_PRICE_X64); + + let minus_tick_price_x64 = get_sqrt_price_at_tick(tick - 1).unwrap(); + let plus_tick_price_x64 = get_sqrt_price_at_tick(tick + 1).unwrap(); + assert!(minus_tick_price_x64 < sqrt_price_x64 && sqrt_price_x64 < plus_tick_price_x64); + } + + #[test] + fn get_tick_at_sqrt_price_test ( + sqrt_price in MIN_SQRT_PRICE_X64..MAX_SQRT_PRICE_X64 + ) { + let tick = get_tick_at_sqrt_price(sqrt_price).unwrap(); + + assert!(tick >= MIN_TICK); + assert!(tick <= MAX_TICK); + + assert!(sqrt_price >= get_sqrt_price_at_tick(tick).unwrap() && sqrt_price < get_sqrt_price_at_tick(tick + 1).unwrap()) + } + + #[test] + fn tick_and_sqrt_price_symmetry_test ( + tick in MIN_TICK..MAX_TICK + ) { + + let sqrt_price_x64 = get_sqrt_price_at_tick(tick).unwrap(); + let resolved_tick = get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); + assert!(resolved_tick == tick); + } + + + #[test] + fn get_sqrt_price_at_tick_is_sequence_test ( + tick in MIN_TICK+1..MAX_TICK + ) { + + let sqrt_price_x64 = get_sqrt_price_at_tick(tick).unwrap(); + let last_sqrt_price_x64 = get_sqrt_price_at_tick(tick-1).unwrap(); + assert!(last_sqrt_price_x64 < sqrt_price_x64); + } + + #[test] + fn get_tick_at_sqrt_price_is_sequence_test ( + sqrt_price in (MIN_SQRT_PRICE_X64 + 10)..MAX_SQRT_PRICE_X64 + ) { + + let tick = get_tick_at_sqrt_price(sqrt_price).unwrap(); + let last_tick = get_tick_at_sqrt_price(sqrt_price - 10).unwrap(); + assert!(last_tick <= tick); + } + } + } +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/unsafe_math.rs b/programs/clmm-cpi/src/libraries/unsafe_math.rs new file mode 100644 index 0000000..07a8c5b --- /dev/null +++ b/programs/clmm-cpi/src/libraries/unsafe_math.rs @@ -0,0 +1,49 @@ +use super::{big_num::U128, U256}; + +pub trait UnsafeMathTrait { + /// Returns ceil (x / y) + /// Division by 0 throws a panic, and must be checked externally + /// + /// In Solidity dividing by 0 results in 0, not an exception. + /// + fn div_rounding_up(x: Self, y: Self) -> Self; +} + +impl UnsafeMathTrait for u64 { + fn div_rounding_up(x: Self, y: Self) -> Self { + x / y + ((x % y > 0) as u64) + } +} + +impl UnsafeMathTrait for U128 { + fn div_rounding_up(x: Self, y: Self) -> Self { + x / y + U128::from((x % y > U128::default()) as u8) + } +} + +impl UnsafeMathTrait for U256 { + fn div_rounding_up(x: Self, y: Self) -> Self { + x / y + U256::from((x % y > U256::default()) as u8) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn divide_by_factor() { + assert_eq!(u64::div_rounding_up(4, 2), 2); + } + + #[test] + fn divide_and_round_up() { + assert_eq!(u64::div_rounding_up(4, 3), 2); + } + + #[test] + #[should_panic] + fn divide_by_zero() { + u64::div_rounding_up(2, 0); + } +} \ No newline at end of file From b8bf9785efec17852722aaf7b4a0038c16780e8c Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Mon, 14 Apr 2025 18:09:56 +0530 Subject: [PATCH 07/10] chore: rename programs --- Anchor.toml | 4 +-- Cargo.lock | 32 +++++++++---------- .../Cargo.toml | 4 +-- .../README.md | 0 .../Xargo.toml | 0 .../src/instructions/harvest.rs | 0 .../src/instructions/initialize.rs | 0 .../src/instructions/mod.rs | 0 .../src/instructions/transfer.rs | 0 .../src/instructions/update_fee.rs | 0 .../src/instructions/withdraw.rs | 0 .../src/lib.rs | 0 .../Cargo.toml | 4 +-- .../Xargo.toml | 0 .../src/lib.rs | 0 15 files changed, 22 insertions(+), 22 deletions(-) rename programs/{solana-tax-token-anchor => tax-token}/Cargo.toml (87%) rename programs/{solana-tax-token-anchor => tax-token}/README.md (100%) rename programs/{solana-tax-token-anchor => tax-token}/Xargo.toml (100%) rename programs/{solana-tax-token-anchor => tax-token}/src/instructions/harvest.rs (100%) rename programs/{solana-tax-token-anchor => tax-token}/src/instructions/initialize.rs (100%) rename programs/{solana-tax-token-anchor => tax-token}/src/instructions/mod.rs (100%) rename programs/{solana-tax-token-anchor => tax-token}/src/instructions/transfer.rs (100%) rename programs/{solana-tax-token-anchor => tax-token}/src/instructions/update_fee.rs (100%) rename programs/{solana-tax-token-anchor => tax-token}/src/instructions/withdraw.rs (100%) rename programs/{solana-tax-token-anchor => tax-token}/src/lib.rs (100%) rename programs/{solana-token-launchpad => token-launchpad}/Cargo.toml (82%) rename programs/{solana-token-launchpad => token-launchpad}/Xargo.toml (100%) rename programs/{solana-token-launchpad => token-launchpad}/src/lib.rs (100%) diff --git a/Anchor.toml b/Anchor.toml index 5be7e54..ed21698 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -6,8 +6,8 @@ skip-lint = false [programs.localnet] clmm-cpi = "CzrAu91uK1YooFQjQtjtvxDZiz9MhqYfq31LgbV5xnPr" -solana-token-launchpad = "AJBmAnT7A7RBxyK2USQUU1deZj6H3TLYM6RWuG3yaxoJ" -solana_tax_token_anchor = "Yo1bzVsiuVigxHUmQguSZ83QJ879A4d6cQiGYFeDDMF" +token-launchpad = "AJBmAnT7A7RBxyK2USQUU1deZj6H3TLYM6RWuG3yaxoJ" +tax-token = "Yo1bzVsiuVigxHUmQguSZ83QJ879A4d6cQiGYFeDDMF" [registry] url = "https://api.apr.dev" diff --git a/Cargo.lock b/Cargo.lock index 7870dc4..a12aaf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2061,22 +2061,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" -[[package]] -name = "solana-tax-token-anchor" -version = "0.1.0" -dependencies = [ - "anchor-lang", - "anchor-spl", - "bytemuck_derive", -] - -[[package]] -name = "solana-token-launchpad" -version = "0.1.0" -dependencies = [ - "anchor-lang", -] - [[package]] name = "solana-zk-token-sdk" version = "1.18.26" @@ -2347,6 +2331,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tax-token" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "bytemuck_derive", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -2410,6 +2403,13 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token-launchpad" +version = "0.1.0" +dependencies = [ + "anchor-lang", +] + [[package]] name = "toml" version = "0.5.11" diff --git a/programs/solana-tax-token-anchor/Cargo.toml b/programs/tax-token/Cargo.toml similarity index 87% rename from programs/solana-tax-token-anchor/Cargo.toml rename to programs/tax-token/Cargo.toml index 7ac150d..4545c80 100644 --- a/programs/solana-tax-token-anchor/Cargo.toml +++ b/programs/tax-token/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "solana-tax-token-anchor" +name = "tax-token" version = "0.1.0" description = "Created with Anchor" edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "solana_tax_token_anchor" +name = "tax_token" [features] no-entrypoint = [] diff --git a/programs/solana-tax-token-anchor/README.md b/programs/tax-token/README.md similarity index 100% rename from programs/solana-tax-token-anchor/README.md rename to programs/tax-token/README.md diff --git a/programs/solana-tax-token-anchor/Xargo.toml b/programs/tax-token/Xargo.toml similarity index 100% rename from programs/solana-tax-token-anchor/Xargo.toml rename to programs/tax-token/Xargo.toml diff --git a/programs/solana-tax-token-anchor/src/instructions/harvest.rs b/programs/tax-token/src/instructions/harvest.rs similarity index 100% rename from programs/solana-tax-token-anchor/src/instructions/harvest.rs rename to programs/tax-token/src/instructions/harvest.rs diff --git a/programs/solana-tax-token-anchor/src/instructions/initialize.rs b/programs/tax-token/src/instructions/initialize.rs similarity index 100% rename from programs/solana-tax-token-anchor/src/instructions/initialize.rs rename to programs/tax-token/src/instructions/initialize.rs diff --git a/programs/solana-tax-token-anchor/src/instructions/mod.rs b/programs/tax-token/src/instructions/mod.rs similarity index 100% rename from programs/solana-tax-token-anchor/src/instructions/mod.rs rename to programs/tax-token/src/instructions/mod.rs diff --git a/programs/solana-tax-token-anchor/src/instructions/transfer.rs b/programs/tax-token/src/instructions/transfer.rs similarity index 100% rename from programs/solana-tax-token-anchor/src/instructions/transfer.rs rename to programs/tax-token/src/instructions/transfer.rs diff --git a/programs/solana-tax-token-anchor/src/instructions/update_fee.rs b/programs/tax-token/src/instructions/update_fee.rs similarity index 100% rename from programs/solana-tax-token-anchor/src/instructions/update_fee.rs rename to programs/tax-token/src/instructions/update_fee.rs diff --git a/programs/solana-tax-token-anchor/src/instructions/withdraw.rs b/programs/tax-token/src/instructions/withdraw.rs similarity index 100% rename from programs/solana-tax-token-anchor/src/instructions/withdraw.rs rename to programs/tax-token/src/instructions/withdraw.rs diff --git a/programs/solana-tax-token-anchor/src/lib.rs b/programs/tax-token/src/lib.rs similarity index 100% rename from programs/solana-tax-token-anchor/src/lib.rs rename to programs/tax-token/src/lib.rs diff --git a/programs/solana-token-launchpad/Cargo.toml b/programs/token-launchpad/Cargo.toml similarity index 82% rename from programs/solana-token-launchpad/Cargo.toml rename to programs/token-launchpad/Cargo.toml index 8d207b4..2715611 100644 --- a/programs/solana-token-launchpad/Cargo.toml +++ b/programs/token-launchpad/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "solana-token-launchpad" +name = "token-launchpad" version = "0.1.0" description = "Created with Anchor" edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "solana_token_launchpad" +name = "token_launchpad" [features] default = [] diff --git a/programs/solana-token-launchpad/Xargo.toml b/programs/token-launchpad/Xargo.toml similarity index 100% rename from programs/solana-token-launchpad/Xargo.toml rename to programs/token-launchpad/Xargo.toml diff --git a/programs/solana-token-launchpad/src/lib.rs b/programs/token-launchpad/src/lib.rs similarity index 100% rename from programs/solana-token-launchpad/src/lib.rs rename to programs/token-launchpad/src/lib.rs From ff685b9c4f3d339a63a17d62baf4a352703fef2c Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Wed, 16 Apr 2025 01:09:21 +0530 Subject: [PATCH 08/10] feat: delete raydium libraries --- programs/clmm-cpi/src/libraries/big_num.rs | 344 --------------- .../clmm-cpi/src/libraries/fixed_point_64.rs | 5 - programs/clmm-cpi/src/libraries/full_math.rs | 335 --------------- .../clmm-cpi/src/libraries/liquidity_math.rs | 316 -------------- programs/clmm-cpi/src/libraries/mod.rs | 21 - .../clmm-cpi/src/libraries/sqrt_price_math.rs | 147 ------- programs/clmm-cpi/src/libraries/swap_math.rs | 344 --------------- .../src/libraries/tick_array_bit_map.rs | 395 ------------------ programs/clmm-cpi/src/libraries/tick_math.rs | 315 -------------- .../clmm-cpi/src/libraries/unsafe_math.rs | 49 --- 10 files changed, 2271 deletions(-) delete mode 100644 programs/clmm-cpi/src/libraries/big_num.rs delete mode 100644 programs/clmm-cpi/src/libraries/fixed_point_64.rs delete mode 100644 programs/clmm-cpi/src/libraries/full_math.rs delete mode 100644 programs/clmm-cpi/src/libraries/liquidity_math.rs delete mode 100644 programs/clmm-cpi/src/libraries/mod.rs delete mode 100644 programs/clmm-cpi/src/libraries/sqrt_price_math.rs delete mode 100644 programs/clmm-cpi/src/libraries/swap_math.rs delete mode 100644 programs/clmm-cpi/src/libraries/tick_array_bit_map.rs delete mode 100644 programs/clmm-cpi/src/libraries/tick_math.rs delete mode 100644 programs/clmm-cpi/src/libraries/unsafe_math.rs diff --git a/programs/clmm-cpi/src/libraries/big_num.rs b/programs/clmm-cpi/src/libraries/big_num.rs deleted file mode 100644 index f8dc557..0000000 --- a/programs/clmm-cpi/src/libraries/big_num.rs +++ /dev/null @@ -1,344 +0,0 @@ -///! 128 and 256 bit numbers -///! U128 is more efficient that u128 -///! https://github.com/solana-labs/solana/issues/19549 -use uint::construct_uint; -construct_uint! { - pub struct U128(2); -} - -construct_uint! { - pub struct U256(4); -} - -construct_uint! { - pub struct U512(8); -} - -#[macro_export] -macro_rules! construct_bignum { - ( $(#[$attr:meta])* $visibility:vis struct $name:ident ( $n_words:tt ); ) => { - $crate::construct_bignum! { @construct $(#[$attr])* $visibility struct $name ($n_words); } - impl $crate::core_::convert::From for $name { - fn from(value: u128) -> $name { - let mut ret = [0; $n_words]; - ret[0] = value as u64; - ret[1] = (value >> 64) as u64; - $name(ret) - } - } - - impl $crate::core_::convert::From for $name { - fn from(value: i128) -> $name { - match value >= 0 { - true => From::from(value as u128), - false => { panic!("Unsigned integer can't be created from negative value"); } - } - } - } - - impl $name { - /// Low 2 words (u128) - #[inline] - pub const fn low_u128(&self) -> u128 { - let &$name(ref arr) = self; - ((arr[1] as u128) << 64) + arr[0] as u128 - } - - /// Conversion to u128 with overflow checking - /// - /// # Panics - /// - /// Panics if the number is larger than 2^128. - #[inline] - pub fn as_u128(&self) -> u128 { - let &$name(ref arr) = self; - for i in 2..$n_words { - if arr[i] != 0 { - panic!("Integer overflow when casting to u128") - } - - } - self.low_u128() - } - } - - impl $crate::core_::convert::TryFrom<$name> for u128 { - type Error = &'static str; - - #[inline] - fn try_from(u: $name) -> $crate::core_::result::Result { - let $name(arr) = u; - for i in 2..$n_words { - if arr[i] != 0 { - return Err("integer overflow when casting to u128"); - } - } - Ok(((arr[1] as u128) << 64) + arr[0] as u128) - } - } - - impl $crate::core_::convert::TryFrom<$name> for i128 { - type Error = &'static str; - - #[inline] - fn try_from(u: $name) -> $crate::core_::result::Result { - let err_str = "integer overflow when casting to i128"; - let i = u128::try_from(u).map_err(|_| err_str)?; - if i > i128::max_value() as u128 { - Err(err_str) - } else { - Ok(i as i128) - } - } - } - }; - - ( @construct $(#[$attr:meta])* $visibility:vis struct $name:ident ( $n_words:tt ); ) => { - /// Little-endian large integer type - #[repr(C)] - $(#[$attr])* - #[derive(Copy, Clone, Eq, PartialEq, Hash)] - $visibility struct $name (pub [u64; $n_words]); - - /// Get a reference to the underlying little-endian words. - impl AsRef<[u64]> for $name { - #[inline] - fn as_ref(&self) -> &[u64] { - &self.0 - } - } - - impl<'a> From<&'a $name> for $name { - fn from(x: &'a $name) -> $name { - *x - } - } - - impl $name { - /// Maximum value. - pub const MAX: $name = $name([u64::max_value(); $n_words]); - - /// Conversion to usize with overflow checking - /// - /// # Panics - /// - /// Panics if the number is larger than usize::max_value(). - #[inline] - pub fn as_usize(&self) -> usize { - let &$name(ref arr) = self; - if !self.fits_word() || arr[0] > usize::max_value() as u64 { - panic!("Integer overflow when casting to usize") - } - arr[0] as usize - } - - /// Whether this is zero. - #[inline] - pub const fn is_zero(&self) -> bool { - let &$name(ref arr) = self; - let mut i = 0; - while i < $n_words { if arr[i] != 0 { return false; } else { i += 1; } } - return true; - } - - // Whether this fits u64. - #[inline] - fn fits_word(&self) -> bool { - let &$name(ref arr) = self; - for i in 1..$n_words { if arr[i] != 0 { return false; } } - return true; - } - - /// Return if specific bit is set. - /// - /// # Panics - /// - /// Panics if `index` exceeds the bit width of the number. - #[inline] - pub const fn bit(&self, index: usize) -> bool { - let &$name(ref arr) = self; - arr[index / 64] & (1 << (index % 64)) != 0 - } - - /// Returns the number of leading zeros in the binary representation of self. - pub fn leading_zeros(&self) -> u32 { - let mut r = 0; - for i in 0..$n_words { - let w = self.0[$n_words - i - 1]; - if w == 0 { - r += 64; - } else { - r += w.leading_zeros(); - break; - } - } - r - } - - /// Returns the number of trailing zeros in the binary representation of self. - pub fn trailing_zeros(&self) -> u32 { - let mut r = 0; - for i in 0..$n_words { - let w = self.0[i]; - if w == 0 { - r += 64; - } else { - r += w.trailing_zeros(); - break; - } - } - r - } - - /// Zero (additive identity) of this type. - #[inline] - pub const fn zero() -> Self { - Self([0; $n_words]) - } - - /// One (multiplicative identity) of this type. - #[inline] - pub const fn one() -> Self { - let mut words = [0; $n_words]; - words[0] = 1u64; - Self(words) - } - - /// The maximum value which can be inhabited by this type. - #[inline] - pub const fn max_value() -> Self { - Self::MAX - } - } - - impl $crate::core_::default::Default for $name { - fn default() -> Self { - $name::zero() - } - } - - impl $crate::core_::ops::BitAnd<$name> for $name { - type Output = $name; - - #[inline] - fn bitand(self, other: $name) -> $name { - let $name(ref arr1) = self; - let $name(ref arr2) = other; - let mut ret = [0u64; $n_words]; - for i in 0..$n_words { - ret[i] = arr1[i] & arr2[i]; - } - $name(ret) - } - } - - impl $crate::core_::ops::BitOr<$name> for $name { - type Output = $name; - - #[inline] - fn bitor(self, other: $name) -> $name { - let $name(ref arr1) = self; - let $name(ref arr2) = other; - let mut ret = [0u64; $n_words]; - for i in 0..$n_words { - ret[i] = arr1[i] | arr2[i]; - } - $name(ret) - } - } - - impl $crate::core_::ops::BitXor<$name> for $name { - type Output = $name; - - #[inline] - fn bitxor(self, other: $name) -> $name { - let $name(ref arr1) = self; - let $name(ref arr2) = other; - let mut ret = [0u64; $n_words]; - for i in 0..$n_words { - ret[i] = arr1[i] ^ arr2[i]; - } - $name(ret) - } - } - - impl $crate::core_::ops::Not for $name { - type Output = $name; - - #[inline] - fn not(self) -> $name { - let $name(ref arr) = self; - let mut ret = [0u64; $n_words]; - for i in 0..$n_words { - ret[i] = !arr[i]; - } - $name(ret) - } - } - - impl $crate::core_::ops::Shl for $name { - type Output = $name; - - fn shl(self, shift: usize) -> $name { - let $name(ref original) = self; - let mut ret = [0u64; $n_words]; - let word_shift = shift / 64; - let bit_shift = shift % 64; - - // shift - for i in word_shift..$n_words { - ret[i] = original[i - word_shift] << bit_shift; - } - // carry - if bit_shift > 0 { - for i in word_shift+1..$n_words { - ret[i] += original[i - 1 - word_shift] >> (64 - bit_shift); - } - } - $name(ret) - } - } - - impl<'a> $crate::core_::ops::Shl for &'a $name { - type Output = $name; - fn shl(self, shift: usize) -> $name { - *self << shift - } - } - - impl $crate::core_::ops::Shr for $name { - type Output = $name; - - fn shr(self, shift: usize) -> $name { - let $name(ref original) = self; - let mut ret = [0u64; $n_words]; - let word_shift = shift / 64; - let bit_shift = shift % 64; - - // shift - for i in word_shift..$n_words { - ret[i - word_shift] = original[i] >> bit_shift; - } - - // Carry - if bit_shift > 0 { - for i in word_shift+1..$n_words { - ret[i - word_shift - 1] += original[i] << (64 - bit_shift); - } - } - - $name(ret) - } - } - - impl<'a> $crate::core_::ops::Shr for &'a $name { - type Output = $name; - fn shr(self, shift: usize) -> $name { - *self >> shift - } - } - }; -} -construct_bignum! { - pub struct U1024(16); -} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/fixed_point_64.rs b/programs/clmm-cpi/src/libraries/fixed_point_64.rs deleted file mode 100644 index 530f4ff..0000000 --- a/programs/clmm-cpi/src/libraries/fixed_point_64.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// A library for handling Q64.64 fixed point numbers -/// Used in sqrt_price_math.rs and liquidity_amounts.rs - -pub const Q64: u128 = (u64::MAX as u128) + 1; // 2^64 -pub const RESOLUTION: u8 = 64; \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/full_math.rs b/programs/clmm-cpi/src/libraries/full_math.rs deleted file mode 100644 index 03e7fac..0000000 --- a/programs/clmm-cpi/src/libraries/full_math.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! A custom implementation of https://github.com/sdroege/rust-muldiv to support phantom overflow resistant -//! multiply-divide operations. This library uses U128 in place of u128 for u64 operations, -//! and supports U128 operations. -//! - -use crate::libraries::big_num::{U128, U256, U512}; - -/// Trait for calculating `val * num / denom` with different rounding modes and overflow -/// protection. -/// -/// Implementations of this trait have to ensure that even if the result of the multiplication does -/// not fit into the type, as long as it would fit after the division the correct result has to be -/// returned instead of `None`. `None` only should be returned if the overall result does not fit -/// into the type. -/// -/// This specifically means that e.g. the `u64` implementation must, depending on the arguments, be -/// able to do 128 bit integer multiplication. -pub trait MulDiv { - /// Output type for the methods of this trait. - type Output; - - /// Calculates `floor(val * num / denom)`, i.e. the largest integer less than or equal to the - /// result of the division. - /// - /// ## Example - /// - /// ```rust - /// use libraries::full_math::MulDiv; - /// - /// # fn main() { - /// let x = 3i8.mul_div_floor(4, 2); - /// assert_eq!(x, Some(6)); - /// - /// let x = 5i8.mul_div_floor(2, 3); - /// assert_eq!(x, Some(3)); - /// - /// let x = (-5i8).mul_div_floor(2, 3); - /// assert_eq!(x, Some(-4)); - /// - /// let x = 3i8.mul_div_floor(3, 2); - /// assert_eq!(x, Some(4)); - /// - /// let x = (-3i8).mul_div_floor(3, 2); - /// assert_eq!(x, Some(-5)); - /// - /// let x = 127i8.mul_div_floor(4, 3); - /// assert_eq!(x, None); - /// # } - /// ``` - fn mul_div_floor(self, num: RHS, denom: RHS) -> Option; - - /// Calculates `ceil(val * num / denom)`, i.e. the the smallest integer greater than or equal to - /// the result of the division. - /// - /// ## Example - /// - /// ```rust - /// use libraries::full_math::MulDiv; - /// - /// # fn main() { - /// let x = 3i8.mul_div_ceil(4, 2); - /// assert_eq!(x, Some(6)); - /// - /// let x = 5i8.mul_div_ceil(2, 3); - /// assert_eq!(x, Some(4)); - /// - /// let x = (-5i8).mul_div_ceil(2, 3); - /// assert_eq!(x, Some(-3)); - /// - /// let x = 3i8.mul_div_ceil(3, 2); - /// assert_eq!(x, Some(5)); - /// - /// let x = (-3i8).mul_div_ceil(3, 2); - /// assert_eq!(x, Some(-4)); - /// - /// let x = (127i8).mul_div_ceil(4, 3); - /// assert_eq!(x, None); - /// # } - /// ``` - fn mul_div_ceil(self, num: RHS, denom: RHS) -> Option; - - /// Return u64 not out of bounds - fn to_underflow_u64(self) -> u64; -} - -pub trait Upcast256 { - fn as_u256(self) -> U256; -} -impl Upcast256 for U128 { - fn as_u256(self) -> U256 { - U256([self.0[0], self.0[1], 0, 0]) - } -} - -pub trait Downcast256 { - /// Unsafe cast to U128 - /// Bits beyond the 128th position are lost - fn as_u128(self) -> U128; -} -impl Downcast256 for U256 { - fn as_u128(self) -> U128 { - U128([self.0[0], self.0[1]]) - } -} - -pub trait Upcast512 { - fn as_u512(self) -> U512; -} -impl Upcast512 for U256 { - fn as_u512(self) -> U512 { - U512([self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]) - } -} - -pub trait Downcast512 { - /// Unsafe cast to U256 - /// Bits beyond the 256th position are lost - fn as_u256(self) -> U256; -} -impl Downcast512 for U512 { - fn as_u256(self) -> U256 { - U256([self.0[0], self.0[1], self.0[2], self.0[3]]) - } -} - -impl MulDiv for u64 { - type Output = u64; - - fn mul_div_floor(self, num: Self, denom: Self) -> Option { - assert_ne!(denom, 0); - let r = (U128::from(self) * U128::from(num)) / U128::from(denom); - if r > U128::from(u64::MAX) { - None - } else { - Some(r.as_u64()) - } - } - - fn mul_div_ceil(self, num: Self, denom: Self) -> Option { - assert_ne!(denom, 0); - let r = (U128::from(self) * U128::from(num) + U128::from(denom - 1)) / U128::from(denom); - if r > U128::from(u64::MAX) { - None - } else { - Some(r.as_u64()) - } - } - - fn to_underflow_u64(self) -> u64 { - self - } -} - -impl MulDiv for U128 { - type Output = U128; - - fn mul_div_floor(self, num: Self, denom: Self) -> Option { - assert_ne!(denom, U128::default()); - let r = ((self.as_u256()) * (num.as_u256())) / (denom.as_u256()); - if r > U128::MAX.as_u256() { - None - } else { - Some(r.as_u128()) - } - } - - fn mul_div_ceil(self, num: Self, denom: Self) -> Option { - assert_ne!(denom, U128::default()); - let r = (self.as_u256() * num.as_u256() + (denom - 1).as_u256()) / denom.as_u256(); - if r > U128::MAX.as_u256() { - None - } else { - Some(r.as_u128()) - } - } - - fn to_underflow_u64(self) -> u64 { - if self < U128::from(u64::MAX) { - self.as_u64() - } else { - 0 - } - } -} - -impl MulDiv for U256 { - type Output = U256; - - fn mul_div_floor(self, num: Self, denom: Self) -> Option { - assert_ne!(denom, U256::default()); - let r = (self.as_u512() * num.as_u512()) / denom.as_u512(); - if r > U256::MAX.as_u512() { - None - } else { - Some(r.as_u256()) - } - } - - fn mul_div_ceil(self, num: Self, denom: Self) -> Option { - assert_ne!(denom, U256::default()); - let r = (self.as_u512() * num.as_u512() + (denom - 1).as_u512()) / denom.as_u512(); - if r > U256::MAX.as_u512() { - None - } else { - Some(r.as_u256()) - } - } - - fn to_underflow_u64(self) -> u64 { - if self < U256::from(u64::MAX) { - self.as_u64() - } else { - 0 - } - } -} - -#[cfg(test)] -mod muldiv_u64_tests { - use super::*; - - use quickcheck::{quickcheck, Arbitrary, Gen}; - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - struct NonZero(u64); - - impl Arbitrary for NonZero { - fn arbitrary(g: &mut G) -> Self { - loop { - let v = u64::arbitrary(g); - if v != 0 { - return NonZero(v); - } - } - } - } - - quickcheck! { - fn scale_floor(val: u64, num: u64, den: NonZero) -> bool { - let res = val.mul_div_floor(num, den.0); - - let expected = (U128::from(val) * U128::from(num)) / U128::from(den.0); - - if expected > U128::from(u64::MAX) { - res.is_none() - } else { - res == Some(expected.as_u64()) - } - } - } - - quickcheck! { - fn scale_ceil(val: u64, num: u64, den: NonZero) -> bool { - let res = val.mul_div_ceil(num, den.0); - - let mut expected = (U128::from(val) * U128::from(num)) / U128::from(den.0); - let expected_rem = (U128::from(val) * U128::from(num)) % U128::from(den.0); - - if expected_rem != U128::default() { - expected += U128::from(1) - } - - if expected > U128::from(u64::MAX) { - res.is_none() - } else { - res == Some(expected.as_u64()) - } - } - } -} - -#[cfg(test)] -mod muldiv_u128_tests { - use super::*; - - use quickcheck::{quickcheck, Arbitrary, Gen}; - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - struct NonZero(U128); - - impl Arbitrary for NonZero { - fn arbitrary(g: &mut G) -> Self { - loop { - let v = U128::from(u128::arbitrary(g)); - if v != U128::default() { - return NonZero(v); - } - } - } - } - - impl Arbitrary for U128 { - fn arbitrary(g: &mut G) -> Self { - loop { - let v = U128::from(u128::arbitrary(g)); - if v != U128::default() { - return v; - } - } - } - } - - quickcheck! { - fn scale_floor(val: U128, num: U128, den: NonZero) -> bool { - let res = val.mul_div_floor(num, den.0); - - let expected = ((val.as_u256()) * (num.as_u256())) / (den.0.as_u256()); - - if expected > U128::MAX.as_u256() { - res.is_none() - } else { - res == Some(expected.as_u128()) - } - } - } - - quickcheck! { - fn scale_ceil(val: U128, num: U128, den: NonZero) -> bool { - let res = val.mul_div_ceil(num, den.0); - - let mut expected = ((val.as_u256()) * (num.as_u256())) / (den.0.as_u256()); - let expected_rem = ((val.as_u256()) * (num.as_u256())) % (den.0.as_u256()); - - if expected_rem != U256::default() { - expected += U256::from(1) - } - - if expected > U128::MAX.as_u256() { - res.is_none() - } else { - res == Some(expected.as_u128()) - } - } - } -} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/liquidity_math.rs b/programs/clmm-cpi/src/libraries/liquidity_math.rs deleted file mode 100644 index 8e724a8..0000000 --- a/programs/clmm-cpi/src/libraries/liquidity_math.rs +++ /dev/null @@ -1,316 +0,0 @@ -use super::big_num::U128; -use super::big_num::U256; -use super::fixed_point_64; -use super::full_math::MulDiv; -use super::tick_math; -use super::unsafe_math::UnsafeMathTrait; -use crate::error::ErrorCode; -use anchor_lang::prelude::*; - -/// Add a signed liquidity delta to liquidity and revert if it overflows or underflows -/// -/// # Arguments -/// -/// * `x` - The liquidity (L) before change -/// * `y` - The delta (ΔL) by which liquidity should be changed -/// -pub fn add_delta(x: u128, y: i128) -> Result { - let z: u128; - if y < 0 { - z = x - u128::try_from(-y).unwrap(); - require_gt!(x, z, ErrorCode::LiquiditySubValueErr); - } else { - z = x + u128::try_from(y).unwrap(); - require_gte!(z, x, ErrorCode::LiquidityAddValueErr); - } - - Ok(z) -} - -/// Computes the amount of liquidity received for a given amount of token_0 and price range -/// Calculates ΔL = Δx (√P_upper x √P_lower)/(√P_upper - √P_lower) -pub fn get_liquidity_from_amount_0( - mut sqrt_ratio_a_x64: u128, - mut sqrt_ratio_b_x64: u128, - amount_0: u64, -) -> u128 { - // sqrt_ratio_a_x64 should hold the smaller value - if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { - std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); - }; - let intermediate = U128::from(sqrt_ratio_a_x64) - .mul_div_floor( - U128::from(sqrt_ratio_b_x64), - U128::from(fixed_point_64::Q64), - ) - .unwrap(); - - U128::from(amount_0) - .mul_div_floor( - intermediate, - U128::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), - ) - .unwrap() - .as_u128() -} - -/// Computes the amount of liquidity received for a given amount of token_1 and price range -/// Calculates ΔL = Δy / (√P_upper - √P_lower) -pub fn get_liquidity_from_amount_1( - mut sqrt_ratio_a_x64: u128, - mut sqrt_ratio_b_x64: u128, - amount_1: u64, -) -> u128 { - // sqrt_ratio_a_x64 should hold the smaller value - if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { - std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); - }; - - U128::from(amount_1) - .mul_div_floor( - U128::from(fixed_point_64::Q64), - U128::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), - ) - .unwrap() - .as_u128() -} - -/// Computes the maximum amount of liquidity received for a given amount of token_0, token_1, the current -/// pool prices and the prices at the tick boundaries -pub fn get_liquidity_from_amounts( - sqrt_ratio_x64: u128, - mut sqrt_ratio_a_x64: u128, - mut sqrt_ratio_b_x64: u128, - amount_0: u64, - amount_1: u64, -) -> u128 { - // sqrt_ratio_a_x64 should hold the smaller value - if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { - std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); - }; - - if sqrt_ratio_x64 <= sqrt_ratio_a_x64 { - // If P ≤ P_lower, only token_0 liquidity is active - get_liquidity_from_amount_0(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_0) - } else if sqrt_ratio_x64 < sqrt_ratio_b_x64 { - // If P_lower < P < P_upper, active liquidity is the minimum of the liquidity provided - // by token_0 and token_1 - u128::min( - get_liquidity_from_amount_0(sqrt_ratio_x64, sqrt_ratio_b_x64, amount_0), - get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_x64, amount_1), - ) - } else { - // If P ≥ P_upper, only token_1 liquidity is active - get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_1) - } -} - -/// Computes the maximum amount of liquidity received for a given amount of token_0, token_1, the current -/// pool prices and the prices at the tick boundaries -pub fn get_liquidity_from_single_amount_0( - sqrt_ratio_x64: u128, - mut sqrt_ratio_a_x64: u128, - mut sqrt_ratio_b_x64: u128, - amount_0: u64, -) -> u128 { - // sqrt_ratio_a_x64 should hold the smaller value - if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { - std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); - }; - - if sqrt_ratio_x64 <= sqrt_ratio_a_x64 { - // If P ≤ P_lower, only token_0 liquidity is active - get_liquidity_from_amount_0(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_0) - } else if sqrt_ratio_x64 < sqrt_ratio_b_x64 { - // If P_lower < P < P_upper, active liquidity is the minimum of the liquidity provided - // by token_0 and token_1 - get_liquidity_from_amount_0(sqrt_ratio_x64, sqrt_ratio_b_x64, amount_0) - } else { - // If P ≥ P_upper, only token_1 liquidity is active - 0 - } -} - -/// Computes the maximum amount of liquidity received for a given amount of token_0, token_1, the current -/// pool prices and the prices at the tick boundaries -pub fn get_liquidity_from_single_amount_1( - sqrt_ratio_x64: u128, - mut sqrt_ratio_a_x64: u128, - mut sqrt_ratio_b_x64: u128, - amount_1: u64, -) -> u128 { - // sqrt_ratio_a_x64 should hold the smaller value - if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { - std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); - }; - - if sqrt_ratio_x64 <= sqrt_ratio_a_x64 { - // If P ≤ P_lower, only token_0 liquidity is active - 0 - } else if sqrt_ratio_x64 < sqrt_ratio_b_x64 { - // If P_lower < P < P_upper, active liquidity is the minimum of the liquidity provided - // by token_0 and token_1 - get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_x64, amount_1) - } else { - // If P ≥ P_upper, only token_1 liquidity is active - get_liquidity_from_amount_1(sqrt_ratio_a_x64, sqrt_ratio_b_x64, amount_1) - } -} - -/// Gets the delta amount_0 for given liquidity and price range -/// -/// # Formula -/// -/// * `Δx = L * (1 / √P_lower - 1 / √P_upper)` -/// * i.e. `L * (√P_upper - √P_lower) / (√P_upper * √P_lower)` -pub fn get_delta_amount_0_unsigned( - mut sqrt_ratio_a_x64: u128, - mut sqrt_ratio_b_x64: u128, - liquidity: u128, - round_up: bool, -) -> Result { - // sqrt_ratio_a_x64 should hold the smaller value - if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { - std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); - }; - - let numerator_1 = U256::from(liquidity) << fixed_point_64::RESOLUTION; - let numerator_2 = U256::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64); - - assert!(sqrt_ratio_a_x64 > 0); - - let result = if round_up { - U256::div_rounding_up( - numerator_1 - .mul_div_ceil(numerator_2, U256::from(sqrt_ratio_b_x64)) - .unwrap(), - U256::from(sqrt_ratio_a_x64), - ) - } else { - numerator_1 - .mul_div_floor(numerator_2, U256::from(sqrt_ratio_b_x64)) - .unwrap() - / U256::from(sqrt_ratio_a_x64) - }; - if result > U256::from(u64::MAX) { - return Err(ErrorCode::MaxTokenOverflow.into()); - } - return Ok(result.as_u64()); -} - -/// Gets the delta amount_1 for given liquidity and price range -/// * `Δy = L (√P_upper - √P_lower)` -pub fn get_delta_amount_1_unsigned( - mut sqrt_ratio_a_x64: u128, - mut sqrt_ratio_b_x64: u128, - liquidity: u128, - round_up: bool, -) -> Result { - // sqrt_ratio_a_x64 should hold the smaller value - if sqrt_ratio_a_x64 > sqrt_ratio_b_x64 { - std::mem::swap(&mut sqrt_ratio_a_x64, &mut sqrt_ratio_b_x64); - }; - - let result = if round_up { - U256::from(liquidity).mul_div_ceil( - U256::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), - U256::from(fixed_point_64::Q64), - ) - } else { - U256::from(liquidity).mul_div_floor( - U256::from(sqrt_ratio_b_x64 - sqrt_ratio_a_x64), - U256::from(fixed_point_64::Q64), - ) - } - .unwrap(); - if result > U256::from(u64::MAX) { - return Err(ErrorCode::MaxTokenOverflow.into()); - } - return Ok(result.as_u64()); -} - -/// Helper function to get signed delta amount_0 for given liquidity and price range -pub fn get_delta_amount_0_signed( - sqrt_ratio_a_x64: u128, - sqrt_ratio_b_x64: u128, - liquidity: i128, -) -> Result { - if liquidity < 0 { - get_delta_amount_0_unsigned( - sqrt_ratio_a_x64, - sqrt_ratio_b_x64, - u128::try_from(-liquidity).unwrap(), - false, - ) - } else { - get_delta_amount_0_unsigned( - sqrt_ratio_a_x64, - sqrt_ratio_b_x64, - u128::try_from(liquidity).unwrap(), - true, - ) - } -} - -/// Helper function to get signed delta amount_1 for given liquidity and price range -pub fn get_delta_amount_1_signed( - sqrt_ratio_a_x64: u128, - sqrt_ratio_b_x64: u128, - liquidity: i128, -) -> Result { - if liquidity < 0 { - get_delta_amount_1_unsigned( - sqrt_ratio_a_x64, - sqrt_ratio_b_x64, - u128::try_from(-liquidity).unwrap(), - false, - ) - } else { - get_delta_amount_1_unsigned( - sqrt_ratio_a_x64, - sqrt_ratio_b_x64, - u128::try_from(liquidity).unwrap(), - true, - ) - } -} - -pub fn get_delta_amounts_signed( - tick_current: i32, - sqrt_price_x64_current: u128, - tick_lower: i32, - tick_upper: i32, - liquidity_delta: i128, -) -> Result<(u64, u64)> { - let mut amount_0 = 0; - let mut amount_1 = 0; - if tick_current < tick_lower { - amount_0 = get_delta_amount_0_signed( - tick_math::get_sqrt_price_at_tick(tick_lower)?, - tick_math::get_sqrt_price_at_tick(tick_upper)?, - liquidity_delta, - ) - .unwrap(); - } else if tick_current < tick_upper { - amount_0 = get_delta_amount_0_signed( - sqrt_price_x64_current, - tick_math::get_sqrt_price_at_tick(tick_upper)?, - liquidity_delta, - ) - .unwrap(); - amount_1 = get_delta_amount_1_signed( - tick_math::get_sqrt_price_at_tick(tick_lower)?, - sqrt_price_x64_current, - liquidity_delta, - ) - .unwrap(); - } else { - amount_1 = get_delta_amount_1_signed( - tick_math::get_sqrt_price_at_tick(tick_lower)?, - tick_math::get_sqrt_price_at_tick(tick_upper)?, - liquidity_delta, - ) - .unwrap(); - } - Ok((amount_0, amount_1)) -} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/mod.rs b/programs/clmm-cpi/src/libraries/mod.rs deleted file mode 100644 index 9392aaa..0000000 --- a/programs/clmm-cpi/src/libraries/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod big_num; -pub mod fixed_point_64; -pub mod full_math; -pub mod liquidity_math; -pub mod sqrt_price_math; -pub mod swap_math; - -pub mod tick_array_bit_map; -pub mod tick_math; -pub mod unsafe_math; - -pub use big_num::*; -pub use fixed_point_64::*; -pub use full_math::*; -pub use liquidity_math::*; -pub use sqrt_price_math::*; -pub use swap_math::*; - -pub use tick_array_bit_map::*; -pub use tick_math::*; -pub use unsafe_math::*; \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/sqrt_price_math.rs b/programs/clmm-cpi/src/libraries/sqrt_price_math.rs deleted file mode 100644 index c519dd6..0000000 --- a/programs/clmm-cpi/src/libraries/sqrt_price_math.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::full_math::MulDiv; -use super::unsafe_math::UnsafeMathTrait; -use super::{fixed_point_64, U256}; - -/// Gets the next sqrt price √P' given a delta of token_0 -/// -/// Always round up because -/// 1. In the exact output case, token 0 supply decreases leading to price increase. -/// Move price up so that exact output is met. -/// 2. In the exact input case, token 0 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 0 -/// -/// # Formula -/// -/// * `√P' = √P * L / (L + Δx * √P)` -/// * If Δx * √P overflows, use alternate form `√P' = L / (L/√P + Δx)` -/// -/// # Proof -/// -/// For constant y, -/// √P * L = y -/// √P' * L' = √P * L -/// √P' = √P * L / L' -/// √P' = √P * L / L' -/// √P' = √P * L / (L + Δx*√P) -/// -pub fn get_next_sqrt_price_from_amount_0_rounding_up( - sqrt_price_x64: u128, - liquidity: u128, - amount: u64, - add: bool, -) -> u128 { - if amount == 0 { - return sqrt_price_x64; - }; - let numerator_1 = (U256::from(liquidity)) << fixed_point_64::RESOLUTION; - - if add { - if let Some(product) = U256::from(amount).checked_mul(U256::from(sqrt_price_x64)) { - let denominator = numerator_1 + U256::from(product); - if denominator >= numerator_1 { - return numerator_1 - .mul_div_ceil(U256::from(sqrt_price_x64), denominator) - .unwrap() - .as_u128(); - }; - } - - U256::div_rounding_up( - numerator_1, - (numerator_1 / U256::from(sqrt_price_x64)) - .checked_add(U256::from(amount)) - .unwrap(), - ) - .as_u128() - } else { - let product = U256::from( - U256::from(amount) - .checked_mul(U256::from(sqrt_price_x64)) - .unwrap(), - ); - let denominator = numerator_1.checked_sub(product).unwrap(); - numerator_1 - .mul_div_ceil(U256::from(sqrt_price_x64), denominator) - .unwrap() - .as_u128() - } -} - -/// Gets the next sqrt price given a delta of token_1 -/// -/// Always round down because -/// 1. In the exact output case, token 1 supply decreases leading to price decrease. -/// Move price down by rounding down so that exact output of token 0 is met. -/// 2. In the exact input case, token 1 supply increases leading to price increase. -/// Do not round down to minimize price impact. We only need to meet input -/// change and not gurantee exact output for token 0. -/// -/// -/// # Formula -/// -/// * `√P' = √P + Δy / L` -/// -pub fn get_next_sqrt_price_from_amount_1_rounding_down( - sqrt_price_x64: u128, - liquidity: u128, - amount: u64, - add: bool, -) -> u128 { - if add { - let quotient = U256::from(u128::from(amount) << fixed_point_64::RESOLUTION) / liquidity; - sqrt_price_x64.checked_add(quotient.as_u128()).unwrap() - } else { - let quotient = U256::div_rounding_up( - U256::from(u128::from(amount) << fixed_point_64::RESOLUTION), - U256::from(liquidity), - ); - sqrt_price_x64.checked_sub(quotient.as_u128()).unwrap() - } -} - -/// Gets the next sqrt price given an input amount of token_0 or token_1 -/// 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_x64: u128, - liquidity: u128, - amount_in: u64, - zero_for_one: bool, -) -> u128 { - assert!(sqrt_price_x64 > 0); - assert!(liquidity > 0); - - // round to make sure that we don't pass the target price - if zero_for_one { - get_next_sqrt_price_from_amount_0_rounding_up(sqrt_price_x64, liquidity, amount_in, true) - } else { - get_next_sqrt_price_from_amount_1_rounding_down(sqrt_price_x64, liquidity, amount_in, true) - } -} - -/// Gets the next sqrt price given an output amount of token0 or token1 -/// -/// Throws if price or liquidity are 0 or the next price is out of bounds -/// -pub fn get_next_sqrt_price_from_output( - sqrt_price_x64: u128, - liquidity: u128, - amount_out: u64, - zero_for_one: bool, -) -> u128 { - assert!(sqrt_price_x64 > 0); - assert!(liquidity > 0); - - if zero_for_one { - get_next_sqrt_price_from_amount_1_rounding_down( - sqrt_price_x64, - liquidity, - amount_out, - false, - ) - } else { - get_next_sqrt_price_from_amount_0_rounding_up(sqrt_price_x64, liquidity, amount_out, false) - } -} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/swap_math.rs b/programs/clmm-cpi/src/libraries/swap_math.rs deleted file mode 100644 index 03a6e04..0000000 --- a/programs/clmm-cpi/src/libraries/swap_math.rs +++ /dev/null @@ -1,344 +0,0 @@ -use super::full_math::MulDiv; -use super::liquidity_math; -use super::sqrt_price_math; -use crate::error::ErrorCode; -use crate::states::config::FEE_RATE_DENOMINATOR_VALUE; -use anchor_lang::prelude::*; -/// Result of a swap step -#[derive(Default, Debug)] -pub struct SwapStep { - /// The price after swapping the amount in/out, not to exceed the price target - pub sqrt_price_next_x64: u128, - pub amount_in: u64, - pub amount_out: u64, - pub fee_amount: u64, -} - -/// Computes the result of swapping some amount in, or amount out, given the parameters of the swap -pub fn compute_swap_step( - sqrt_price_current_x64: u128, - sqrt_price_target_x64: u128, - liquidity: u128, - amount_remaining: u64, - fee_rate: u32, - is_base_input: bool, - zero_for_one: bool, - block_timestamp: u32, -) -> Result { - // let exact_in = amount_remaining >= 0; - let mut swap_step = SwapStep::default(); - if is_base_input { - // round up amount_in - // In exact input case, amount_remaining is positive - let amount_remaining_less_fee = (amount_remaining as u64) - .mul_div_floor( - (FEE_RATE_DENOMINATOR_VALUE - fee_rate).into(), - u64::from(FEE_RATE_DENOMINATOR_VALUE), - ) - .unwrap(); - - let amount_in = calculate_amount_in_range( - sqrt_price_current_x64, - sqrt_price_target_x64, - liquidity, - zero_for_one, - is_base_input, - block_timestamp, - )?; - if amount_in.is_some() { - swap_step.amount_in = amount_in.unwrap(); - } - - swap_step.sqrt_price_next_x64 = - if amount_in.is_some() && amount_remaining_less_fee >= swap_step.amount_in { - sqrt_price_target_x64 - } else { - sqrt_price_math::get_next_sqrt_price_from_input( - sqrt_price_current_x64, - liquidity, - amount_remaining_less_fee, - zero_for_one, - ) - }; - } else { - let amount_out = calculate_amount_in_range( - sqrt_price_current_x64, - sqrt_price_target_x64, - liquidity, - zero_for_one, - is_base_input, - block_timestamp, - )?; - if amount_out.is_some() { - swap_step.amount_out = amount_out.unwrap(); - } - // In exact output case, amount_remaining is negative - swap_step.sqrt_price_next_x64 = - if amount_out.is_some() && amount_remaining >= swap_step.amount_out { - sqrt_price_target_x64 - } else { - sqrt_price_math::get_next_sqrt_price_from_output( - sqrt_price_current_x64, - liquidity, - amount_remaining, - zero_for_one, - ) - } - } - - // whether we reached the max possible price for the given ticks - let max = sqrt_price_target_x64 == swap_step.sqrt_price_next_x64; - // get the input / output amounts when target price is not reached - if zero_for_one { - // if max is reached for exact input case, entire amount_in is needed - if !(max && is_base_input) { - swap_step.amount_in = liquidity_math::get_delta_amount_0_unsigned( - swap_step.sqrt_price_next_x64, - sqrt_price_current_x64, - liquidity, - true, - )? - }; - // if max is reached for exact output case, entire amount_out is needed - if !(max && !is_base_input) { - swap_step.amount_out = liquidity_math::get_delta_amount_1_unsigned( - swap_step.sqrt_price_next_x64, - sqrt_price_current_x64, - liquidity, - false, - )?; - }; - } else { - if !(max && is_base_input) { - swap_step.amount_in = liquidity_math::get_delta_amount_1_unsigned( - sqrt_price_current_x64, - swap_step.sqrt_price_next_x64, - liquidity, - true, - )? - }; - if !(max && !is_base_input) { - swap_step.amount_out = liquidity_math::get_delta_amount_0_unsigned( - sqrt_price_current_x64, - swap_step.sqrt_price_next_x64, - liquidity, - false, - )? - }; - } - - // For exact output case, cap the output amount to not exceed the remaining output amount - if !is_base_input && swap_step.amount_out > amount_remaining { - swap_step.amount_out = amount_remaining; - } - - swap_step.fee_amount = - if is_base_input && swap_step.sqrt_price_next_x64 != sqrt_price_target_x64 { - // we didn't reach the target, so take the remainder of the maximum input as fee - // swap dust is granted as fee - u64::from(amount_remaining) - .checked_sub(swap_step.amount_in) - .unwrap() - } else { - // take pip percentage as fee - swap_step - .amount_in - .mul_div_ceil( - fee_rate.into(), - (FEE_RATE_DENOMINATOR_VALUE - fee_rate).into(), - ) - .unwrap() - }; - - Ok(swap_step) -} - -/// Pre calcumate amount_in or amount_out for the specified price range -/// The amount maybe overflow of u64 due to the `sqrt_price_target_x64` maybe unreasonable. -/// Therefore, this situation needs to be handled in `compute_swap_step` to recalculate the price that can be reached based on the amount. -#[cfg(not(test))] -fn calculate_amount_in_range( - sqrt_price_current_x64: u128, - sqrt_price_target_x64: u128, - liquidity: u128, - zero_for_one: bool, - is_base_input: bool, - _block_timestamp: u32, -) -> Result> { - if is_base_input { - let result = if zero_for_one { - liquidity_math::get_delta_amount_0_unsigned( - sqrt_price_target_x64, - sqrt_price_current_x64, - liquidity, - true, - ) - } else { - liquidity_math::get_delta_amount_1_unsigned( - sqrt_price_current_x64, - sqrt_price_target_x64, - liquidity, - true, - ) - }; - - if result.is_ok() { - return Ok(Some(result.unwrap())); - } else { - if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { - return Ok(None); - } else { - return Err(ErrorCode::SqrtPriceLimitOverflow.into()); - } - } - } else { - let result = if zero_for_one { - liquidity_math::get_delta_amount_1_unsigned( - sqrt_price_target_x64, - sqrt_price_current_x64, - liquidity, - false, - ) - } else { - liquidity_math::get_delta_amount_0_unsigned( - sqrt_price_current_x64, - sqrt_price_target_x64, - liquidity, - false, - ) - }; - if result.is_ok() { - return Ok(Some(result.unwrap())); - } else { - if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { - return Ok(None); - } else { - return Err(ErrorCode::SqrtPriceLimitOverflow.into()); - } - } - } -} - -#[cfg(test)] -fn calculate_amount_in_range( - sqrt_price_current_x64: u128, - sqrt_price_target_x64: u128, - liquidity: u128, - zero_for_one: bool, - is_base_input: bool, - block_timestamp: u32, -) -> Result> { - if is_base_input { - let result = if zero_for_one { - liquidity_math::get_delta_amount_0_unsigned( - sqrt_price_target_x64, - sqrt_price_current_x64, - liquidity, - true, - ) - } else { - liquidity_math::get_delta_amount_1_unsigned( - sqrt_price_current_x64, - sqrt_price_target_x64, - liquidity, - true, - ) - }; - - if block_timestamp == 0 { - if result.is_err() { - return Err(ErrorCode::MaxTokenOverflow.into()); - } else { - return Ok(Some(result.unwrap())); - } - } - if result.is_ok() { - return Ok(Some(result.unwrap())); - } else { - if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { - return Ok(None); - } else { - return Err(ErrorCode::SqrtPriceLimitOverflow.into()); - } - } - } else { - let result = if zero_for_one { - liquidity_math::get_delta_amount_1_unsigned( - sqrt_price_target_x64, - sqrt_price_current_x64, - liquidity, - false, - ) - } else { - liquidity_math::get_delta_amount_0_unsigned( - sqrt_price_current_x64, - sqrt_price_target_x64, - liquidity, - false, - ) - }; - if result.is_ok() || block_timestamp == 0 { - return Ok(Some(result.unwrap())); - } else { - if result.err().unwrap() == crate::error::ErrorCode::MaxTokenOverflow.into() { - return Ok(None); - } else { - return Err(ErrorCode::SqrtPriceLimitOverflow.into()); - } - } - } -} -#[cfg(test)] -mod swap_math_test { - use crate::libraries::tick_math; - - use super::*; - use proptest::prelude::*; - - proptest! { - #[test] - fn compute_swap_step_test( - sqrt_price_current_x64 in tick_math::MIN_SQRT_PRICE_X64..tick_math::MAX_SQRT_PRICE_X64, - sqrt_price_target_x64 in tick_math::MIN_SQRT_PRICE_X64..tick_math::MAX_SQRT_PRICE_X64, - liquidity in 1..u32::MAX as u128, - amount_remaining in 1..u64::MAX, - fee_rate in 1..FEE_RATE_DENOMINATOR_VALUE/2, - is_base_input in proptest::bool::ANY, - ) { - prop_assume!(sqrt_price_current_x64 != sqrt_price_target_x64); - - let zero_for_one = sqrt_price_current_x64 > sqrt_price_target_x64; - let swap_step = compute_swap_step( - sqrt_price_current_x64, - sqrt_price_target_x64, - liquidity, - amount_remaining, - fee_rate, - is_base_input, - zero_for_one, - 1, - ).unwrap(); - - let amount_in = swap_step.amount_in; - let amount_out = swap_step.amount_out; - let sqrt_price_next_x64 = swap_step.sqrt_price_next_x64; - let fee_amount = swap_step.fee_amount; - - let amount_used = if is_base_input { - amount_in + fee_amount - } else { - amount_out - }; - - if sqrt_price_next_x64 != sqrt_price_target_x64 { - assert!(amount_used == amount_remaining); - } else { - assert!(amount_used <= amount_remaining); - } - let price_lower = sqrt_price_current_x64.min(sqrt_price_target_x64); - let price_upper = sqrt_price_current_x64.max(sqrt_price_target_x64); - assert!(sqrt_price_next_x64 >= price_lower); - assert!(sqrt_price_next_x64 <= price_upper); - } - } -} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/tick_array_bit_map.rs b/programs/clmm-cpi/src/libraries/tick_array_bit_map.rs deleted file mode 100644 index d840c47..0000000 --- a/programs/clmm-cpi/src/libraries/tick_array_bit_map.rs +++ /dev/null @@ -1,395 +0,0 @@ -///! Helper functions to get most and least significant non-zero bits -use super::big_num::U1024; -use crate::error::ErrorCode; -use crate::states::tick_array::{TickArrayState, TickState, TICK_ARRAY_SIZE}; -use anchor_lang::prelude::*; - -pub const TICK_ARRAY_BITMAP_SIZE: i32 = 512; - -pub type TickArryBitmap = [u64; 8]; - -pub fn max_tick_in_tickarray_bitmap(tick_spacing: u16) -> i32 { - i32::from(tick_spacing) * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE -} - -pub fn get_bitmap_tick_boundary(tick_array_start_index: i32, tick_spacing: u16) -> (i32, i32) { - let ticks_in_one_bitmap: i32 = max_tick_in_tickarray_bitmap(tick_spacing); - let mut m = tick_array_start_index.abs() / ticks_in_one_bitmap; - if tick_array_start_index < 0 && tick_array_start_index.abs() % ticks_in_one_bitmap != 0 { - m += 1; - } - let min_value: i32 = ticks_in_one_bitmap * m; - if tick_array_start_index < 0 { - (-min_value, -min_value + ticks_in_one_bitmap) - } else { - (min_value, min_value + ticks_in_one_bitmap) - } -} - -pub fn most_significant_bit(x: U1024) -> Option { - if x.is_zero() { - None - } else { - Some(u16::try_from(x.leading_zeros()).unwrap()) - } -} - -pub fn least_significant_bit(x: U1024) -> Option { - if x.is_zero() { - None - } else { - Some(u16::try_from(x.trailing_zeros()).unwrap()) - } -} - -/// Given a tick, calculate whether the tickarray it belongs to has been initialized. -pub fn check_current_tick_array_is_initialized( - bit_map: U1024, - tick_current: i32, - tick_spacing: u16, -) -> Result<(bool, i32)> { - if TickState::check_is_out_of_boundary(tick_current) { - return err!(ErrorCode::InvaildTickIndex); - } - let multiplier = i32::from(tick_spacing) * TICK_ARRAY_SIZE; - let mut compressed = tick_current / multiplier + 512; - if tick_current < 0 && tick_current % multiplier != 0 { - // round towards negative infinity - compressed -= 1; - } - let bit_pos = compressed.abs(); - // set current bit - let mask = U1024::one() << bit_pos.try_into().unwrap(); - let masked = bit_map & mask; - // check the current bit whether initialized - let initialized = masked != U1024::default(); - if initialized { - return Ok((true, (compressed - 512) * multiplier)); - } - // the current bit is not initialized - return Ok((false, (compressed - 512) * multiplier)); -} - -pub fn next_initialized_tick_array_start_index( - bit_map: U1024, - last_tick_array_start_index: i32, - tick_spacing: u16, - zero_for_one: bool, -) -> (bool, i32) { - assert!(TickArrayState::check_is_valid_start_index( - last_tick_array_start_index, - tick_spacing - )); - let tick_boundary = max_tick_in_tickarray_bitmap(tick_spacing); - let next_tick_array_start_index = if zero_for_one { - last_tick_array_start_index - TickArrayState::tick_count(tick_spacing) - } else { - last_tick_array_start_index + TickArrayState::tick_count(tick_spacing) - }; - - if next_tick_array_start_index < -tick_boundary || next_tick_array_start_index >= tick_boundary - { - return (false, last_tick_array_start_index); - } - - let multiplier = i32::from(tick_spacing) * TICK_ARRAY_SIZE; - let mut compressed = next_tick_array_start_index / multiplier + 512; - if next_tick_array_start_index < 0 && next_tick_array_start_index % multiplier != 0 { - // round towards negative infinity - compressed -= 1; - } - let bit_pos = compressed.abs(); - - if zero_for_one { - // tick from upper to lower - // find from highter bits to lower bits - let offset_bit_map = bit_map << (1024 - bit_pos - 1).try_into().unwrap(); - let next_bit = most_significant_bit(offset_bit_map); - if next_bit.is_some() { - let next_array_start_index = - (bit_pos - i32::from(next_bit.unwrap()) - 512) * multiplier; - (true, next_array_start_index) - } else { - // not found til to the end - (false, -tick_boundary) - } - } else { - // tick from lower to upper - // find from lower bits to highter bits - let offset_bit_map = bit_map >> (bit_pos).try_into().unwrap(); - let next_bit = least_significant_bit(offset_bit_map); - if next_bit.is_some() { - let next_array_start_index = - (bit_pos + i32::from(next_bit.unwrap()) - 512) * multiplier; - (true, next_array_start_index) - } else { - // not found til to the end - ( - false, - tick_boundary - TickArrayState::tick_count(tick_spacing), - ) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{libraries::tick_math, states::TickArrayState}; - - #[test] - fn test_check_current_tick_array_is_initialized() { - let tick_spacing = 10; - let bit_map = U1024([ - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - u64::max_value() & 1 << 63, - ]); - let mut tick_current = -307200; - let mut start_index = -1; - for _i in 0..1024 { - let ret = check_current_tick_array_is_initialized(bit_map, tick_current, tick_spacing) - .unwrap(); - if ret.0 && ret.1 != start_index { - start_index = ret.1; - println!("{}-{}", tick_current, start_index); - } - tick_current += 600; - } - } - #[test] - fn find_next_init_pos_in_bit_map_positive_price_down() { - let tick_spacing = 10; - let bit_map = U1024::max_value(); - let mut tick_array_start_index = 306600; - for _i in 0..5 { - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing, - true, - ); - println!("{:?}", array_start_index); - if !is_found { - break; - } - tick_array_start_index = - TickArrayState::get_array_start_index(array_start_index, tick_spacing); - } - } - #[test] - fn find_next_init_pos_in_bit_map_negative_price_down() { - let tick_spacing = 10; - let bit_map = U1024::max_value(); - let mut tick_array_start_index = -307200 + 600 + 600; - for _i in 0..5 { - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing, - true, - ); - println!("{:?}", array_start_index); - if !is_found { - break; - } - tick_array_start_index = - TickArrayState::get_array_start_index(array_start_index, tick_spacing); - } - } - #[test] - fn find_next_init_pos_in_bit_map_negative_price_down_crose_zero() { - let tick_spacing = 10; - let bit_map = U1024::max_value(); - let mut tick_array_start_index = 1800; - for _i in 0..5 { - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing, - true, - ); - println!("{:?}", array_start_index); - if !is_found { - break; - } - tick_array_start_index = - TickArrayState::get_array_start_index(array_start_index, tick_spacing); - } - } - - #[test] - fn find_previous_init_pos_in_bit_map_positive_price_up() { - let tick_spacing = 10; - let bit_map = U1024::max_value(); - let mut tick_array_start_index = 306600 - 600 - 600; - for _i in 0..5 { - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing, - false, - ); - println!("{:?}", array_start_index); - if !is_found { - break; - } - tick_array_start_index = - TickArrayState::get_array_start_index(array_start_index, tick_spacing); - } - } - #[test] - fn find_previous_init_pos_in_bit_map_negative_price_up() { - let tick_spacing = 10; - let bit_map = U1024::max_value(); - let mut tick_array_start_index = -307200; - for _i in 0..5 { - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing, - false, - ); - println!("{:?}", array_start_index); - if !is_found { - break; - } - tick_array_start_index = - TickArrayState::get_array_start_index(array_start_index, tick_spacing); - } - } - #[test] - fn find_previous_init_pos_in_bit_map_negative_price_up_crose_zero() { - let tick_spacing = 10; - let bit_map = U1024::max_value(); - let mut tick_array_start_index = -1800; - for _i in 0..5 { - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing, - false, - ); - println!("{:?}", array_start_index); - if !is_found { - break; - } - tick_array_start_index = - TickArrayState::get_array_start_index(array_start_index, tick_spacing); - } - } - - #[test] - fn find_next_init_pos_in_bit_map_with_eigenvalues() { - let tick_spacing = 10; - let bit_map: [u64; 16] = [ - 1, - 0, - 0, - 0, - 0, - 0, - 9223372036854775808, - 16140901064495857665, - 7, - 1, - 0, - 0, - 0, - 0, - 0, - 9223372036854775808, - ]; - let (_, mut array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), 0, tick_spacing, true); - assert_eq!(array_start_index, -600); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), -600, tick_spacing, true); - assert_eq!(array_start_index, -1200); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), -1200, tick_spacing, true); - assert_eq!(array_start_index, -1800); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), -1800, tick_spacing, true); - assert_eq!(array_start_index, -38400); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), -38400, tick_spacing, true); - assert_eq!(array_start_index, -39000); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), -39000, tick_spacing, true); - assert_eq!(array_start_index, -307200); - - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), 0, tick_spacing, false); - assert_eq!(array_start_index, 600); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), 600, tick_spacing, false); - assert_eq!(array_start_index, 1200); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), 1200, tick_spacing, false); - assert_eq!(array_start_index, 38400); - (_, array_start_index) = - next_initialized_tick_array_start_index(U1024(bit_map), 38400, tick_spacing, false); - assert_eq!(array_start_index, 306600); - } - - #[test] - fn next_initialized_tick_array_start_index_boundary_test() { - let tick_spacing = 1; - let bit_map = U1024::max_value(); - let mut tick_array_start_index = (tick_math::MIN_TICK / TICK_ARRAY_SIZE * tick_spacing - 1) - * TICK_ARRAY_SIZE - * tick_spacing; - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing as u16, - false, - ); - assert!(is_found == false); - assert!(array_start_index == tick_array_start_index); - - tick_array_start_index = - (tick_math::MAX_TICK / TICK_ARRAY_SIZE * tick_spacing) * TICK_ARRAY_SIZE * tick_spacing; - let (is_found, array_start_index) = next_initialized_tick_array_start_index( - bit_map, - tick_array_start_index, - tick_spacing as u16, - true, - ); - assert!(is_found == false); - assert!(array_start_index == tick_array_start_index); - } - - #[test] - fn get_bitmap_tick_boundary_test() { - let (mut min, mut max) = get_bitmap_tick_boundary(-430080, 1); - assert!(min == -430080); - assert!(max == -399360); - - (min, max) = get_bitmap_tick_boundary(-430140, 1); - assert!(min == -460800); - assert!(max == -430080); - - let (mut min, mut max) = get_bitmap_tick_boundary(430080, 1); - assert!(min == 430080); - assert!(max == 460800); - - (min, max) = get_bitmap_tick_boundary(430020, 1); - assert!(min == 399360); - assert!(max == 430080); - } -} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/tick_math.rs b/programs/clmm-cpi/src/libraries/tick_math.rs deleted file mode 100644 index 1148429..0000000 --- a/programs/clmm-cpi/src/libraries/tick_math.rs +++ /dev/null @@ -1,315 +0,0 @@ -use crate::{error::ErrorCode, libraries::big_num::U128}; - -use anchor_lang::require; - -/// The minimum tick -pub const MIN_TICK: i32 = -443636; -/// The minimum tick -pub const MAX_TICK: i32 = -MIN_TICK; - -/// The minimum value that can be returned from #get_sqrt_price_at_tick. Equivalent to get_sqrt_price_at_tick(MIN_TICK) -pub const MIN_SQRT_PRICE_X64: u128 = 4295048016; -/// The maximum value that can be returned from #get_sqrt_price_at_tick. Equivalent to get_sqrt_price_at_tick(MAX_TICK) -pub const MAX_SQRT_PRICE_X64: u128 = 79226673521066979257578248091; - -// Number 64, encoded as a U128 -const NUM_64: U128 = U128([64, 0]); - -const BIT_PRECISION: u32 = 16; - -/// Calculates 1.0001^(tick/2) as a U64.64 number representing -/// the square root of the ratio of the two assets (token_1/token_0) -/// -/// Calculates result as a U64.64 -/// Each magic factor is `2^64 / (1.0001^(2^(i - 1)))` for i in `[0, 18)`. -/// -/// Throws if |tick| > MAX_TICK -/// -/// # Arguments -/// * `tick` - Price tick -/// -pub fn get_sqrt_price_at_tick(tick: i32) -> Result { - let abs_tick = tick.abs() as u32; - require!(abs_tick <= MAX_TICK as u32, ErrorCode::TickUpperOverflow); - - // i = 0 - let mut ratio = if abs_tick & 0x1 != 0 { - U128([0xfffcb933bd6fb800, 0]) - } else { - // 2^64 - U128([0, 1]) - }; - // i = 1 - if abs_tick & 0x2 != 0 { - ratio = (ratio * U128([0xfff97272373d4000, 0])) >> NUM_64 - }; - // i = 2 - if abs_tick & 0x4 != 0 { - ratio = (ratio * U128([0xfff2e50f5f657000, 0])) >> NUM_64 - }; - // i = 3 - if abs_tick & 0x8 != 0 { - ratio = (ratio * U128([0xffe5caca7e10f000, 0])) >> NUM_64 - }; - // i = 4 - if abs_tick & 0x10 != 0 { - ratio = (ratio * U128([0xffcb9843d60f7000, 0])) >> NUM_64 - }; - // i = 5 - if abs_tick & 0x20 != 0 { - ratio = (ratio * U128([0xff973b41fa98e800, 0])) >> NUM_64 - }; - // i = 6 - if abs_tick & 0x40 != 0 { - ratio = (ratio * U128([0xff2ea16466c9b000, 0])) >> NUM_64 - }; - // i = 7 - if abs_tick & 0x80 != 0 { - ratio = (ratio * U128([0xfe5dee046a9a3800, 0])) >> NUM_64 - }; - // i = 8 - if abs_tick & 0x100 != 0 { - ratio = (ratio * U128([0xfcbe86c7900bb000, 0])) >> NUM_64 - }; - // i = 9 - if abs_tick & 0x200 != 0 { - ratio = (ratio * U128([0xf987a7253ac65800, 0])) >> NUM_64 - }; - // i = 10 - if abs_tick & 0x400 != 0 { - ratio = (ratio * U128([0xf3392b0822bb6000, 0])) >> NUM_64 - }; - // i = 11 - if abs_tick & 0x800 != 0 { - ratio = (ratio * U128([0xe7159475a2caf000, 0])) >> NUM_64 - }; - // i = 12 - if abs_tick & 0x1000 != 0 { - ratio = (ratio * U128([0xd097f3bdfd2f2000, 0])) >> NUM_64 - }; - // i = 13 - if abs_tick & 0x2000 != 0 { - ratio = (ratio * U128([0xa9f746462d9f8000, 0])) >> NUM_64 - }; - // i = 14 - if abs_tick & 0x4000 != 0 { - ratio = (ratio * U128([0x70d869a156f31c00, 0])) >> NUM_64 - }; - // i = 15 - if abs_tick & 0x8000 != 0 { - ratio = (ratio * U128([0x31be135f97ed3200, 0])) >> NUM_64 - }; - // i = 16 - if abs_tick & 0x10000 != 0 { - ratio = (ratio * U128([0x9aa508b5b85a500, 0])) >> NUM_64 - }; - // i = 17 - if abs_tick & 0x20000 != 0 { - ratio = (ratio * U128([0x5d6af8dedc582c, 0])) >> NUM_64 - }; - // i = 18 - if abs_tick & 0x40000 != 0 { - ratio = (ratio * U128([0x2216e584f5fa, 0])) >> NUM_64 - } - - // Divide to obtain 1.0001^(2^(i - 1)) * 2^32 in numerator - if tick > 0 { - ratio = U128::MAX / ratio; - } - - Ok(ratio.as_u128()) -} - -/// Calculates the greatest tick value such that get_sqrt_price_at_tick(tick) <= ratio -/// Throws if sqrt_price_x64 < MIN_SQRT_RATIO or sqrt_price_x64 > MAX_SQRT_RATIO -/// -/// Formula: `i = log base(√1.0001) (√P)` -pub fn get_tick_at_sqrt_price(sqrt_price_x64: u128) -> Result { - // second inequality must be < because the price can never reach the price at the max tick - require!( - sqrt_price_x64 >= MIN_SQRT_PRICE_X64 && sqrt_price_x64 < MAX_SQRT_PRICE_X64, - ErrorCode::SqrtPriceX64 - ); - - // Determine log_b(sqrt_ratio). First by calculating integer portion (msb) - let msb: u32 = 128 - sqrt_price_x64.leading_zeros() - 1; - let log2p_integer_x32 = (msb as i128 - 64) << 32; - - // get fractional value (r/2^msb), msb always > 128 - // We begin the iteration from bit 63 (0.5 in Q64.64) - let mut bit: i128 = 0x8000_0000_0000_0000i128; - let mut precision = 0; - let mut log2p_fraction_x64 = 0; - - // Log2 iterative approximation for the fractional part - // Go through each 2^(j) bit where j < 64 in a Q64.64 number - // Append current bit value to fraction result if r^2 Q2.126 is more than 2 - let mut r = if msb >= 64 { - sqrt_price_x64 >> (msb - 63) - } else { - sqrt_price_x64 << (63 - msb) - }; - - while bit > 0 && precision < BIT_PRECISION { - r *= r; - let is_r_more_than_two = r >> 127 as u32; - r >>= 63 + is_r_more_than_two; - log2p_fraction_x64 += bit * is_r_more_than_two as i128; - bit >>= 1; - precision += 1; - } - let log2p_fraction_x32 = log2p_fraction_x64 >> 32; - let log2p_x32 = log2p_integer_x32 + log2p_fraction_x32; - - // 14 bit refinement gives an error margin of 2^-14 / log2 (√1.0001) = 0.8461 < 1 - // Since tick is a decimal, an error under 1 is acceptable - - // Change of base rule: multiply with 2^16 / log2 (√1.0001) - let log_sqrt_10001_x64 = log2p_x32 * 59543866431248i128; - - // tick - 0.01 - let tick_low = ((log_sqrt_10001_x64 - 184467440737095516i128) >> 64) as i32; - - // tick + (2^-14 / log2(√1.001)) + 0.01 - let tick_high = ((log_sqrt_10001_x64 + 15793534762490258745i128) >> 64) as i32; - - Ok(if tick_low == tick_high { - tick_low - } else if get_sqrt_price_at_tick(tick_high).unwrap() <= sqrt_price_x64 { - tick_high - } else { - tick_low - }) -} - -#[cfg(test)] -mod tick_math_test { - use super::*; - mod get_sqrt_price_at_tick_test { - use super::*; - use crate::libraries::fixed_point_64; - - #[test] - fn check_get_sqrt_price_at_tick_at_min_or_max_tick() { - assert_eq!( - get_sqrt_price_at_tick(MIN_TICK).unwrap(), - MIN_SQRT_PRICE_X64 - ); - let min_sqrt_price = MIN_SQRT_PRICE_X64 as f64 / fixed_point_64::Q64 as f64; - println!("min_sqrt_price: {}", min_sqrt_price); - assert_eq!( - get_sqrt_price_at_tick(MAX_TICK).unwrap(), - MAX_SQRT_PRICE_X64 - ); - let max_sqrt_price = MAX_SQRT_PRICE_X64 as f64 / fixed_point_64::Q64 as f64; - println!("max_sqrt_price: {}", max_sqrt_price); - } - } - - mod get_tick_at_sqrt_price_test { - use super::*; - - #[test] - fn check_get_tick_at_sqrt_price_at_min_or_max_sqrt_price() { - assert_eq!( - get_tick_at_sqrt_price(MIN_SQRT_PRICE_X64).unwrap(), - MIN_TICK, - ); - - // we can't reach MAX_SQRT_PRICE_X64 - assert_eq!( - get_tick_at_sqrt_price(MAX_SQRT_PRICE_X64 - 1).unwrap(), - MAX_TICK - 1, - ); - } - } - - #[test] - fn tick_round_down() { - // tick is negative - let sqrt_price_x64 = get_sqrt_price_at_tick(-28861).unwrap(); - let mut tick = get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); - assert_eq!(tick, -28861); - tick = get_tick_at_sqrt_price(sqrt_price_x64 + 1).unwrap(); - assert_eq!(tick, -28861); - tick = get_tick_at_sqrt_price(get_sqrt_price_at_tick(-28860).unwrap() - 1).unwrap(); - assert_eq!(tick, -28861); - tick = get_tick_at_sqrt_price(sqrt_price_x64 - 1).unwrap(); - assert_eq!(tick, -28862); - - // tick is positive - let sqrt_price_x64 = get_sqrt_price_at_tick(28861).unwrap(); - tick = get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); - assert_eq!(tick, 28861); - tick = get_tick_at_sqrt_price(sqrt_price_x64 + 1).unwrap(); - assert_eq!(tick, 28861); - tick = get_tick_at_sqrt_price(get_sqrt_price_at_tick(28862).unwrap() - 1).unwrap(); - assert_eq!(tick, 28861); - tick = get_tick_at_sqrt_price(sqrt_price_x64 - 1).unwrap(); - assert_eq!(tick, 28860); - } - - mod fuzz_tests { - use super::*; - use proptest::prelude::*; - - proptest! { - #[test] - fn get_sqrt_price_at_tick_test ( - tick in MIN_TICK+1..MAX_TICK-1, - ) { - let sqrt_price_x64 = get_sqrt_price_at_tick(tick).unwrap(); - - assert!(sqrt_price_x64 >= MIN_SQRT_PRICE_X64); - assert!(sqrt_price_x64 <= MAX_SQRT_PRICE_X64); - - let minus_tick_price_x64 = get_sqrt_price_at_tick(tick - 1).unwrap(); - let plus_tick_price_x64 = get_sqrt_price_at_tick(tick + 1).unwrap(); - assert!(minus_tick_price_x64 < sqrt_price_x64 && sqrt_price_x64 < plus_tick_price_x64); - } - - #[test] - fn get_tick_at_sqrt_price_test ( - sqrt_price in MIN_SQRT_PRICE_X64..MAX_SQRT_PRICE_X64 - ) { - let tick = get_tick_at_sqrt_price(sqrt_price).unwrap(); - - assert!(tick >= MIN_TICK); - assert!(tick <= MAX_TICK); - - assert!(sqrt_price >= get_sqrt_price_at_tick(tick).unwrap() && sqrt_price < get_sqrt_price_at_tick(tick + 1).unwrap()) - } - - #[test] - fn tick_and_sqrt_price_symmetry_test ( - tick in MIN_TICK..MAX_TICK - ) { - - let sqrt_price_x64 = get_sqrt_price_at_tick(tick).unwrap(); - let resolved_tick = get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); - assert!(resolved_tick == tick); - } - - - #[test] - fn get_sqrt_price_at_tick_is_sequence_test ( - tick in MIN_TICK+1..MAX_TICK - ) { - - let sqrt_price_x64 = get_sqrt_price_at_tick(tick).unwrap(); - let last_sqrt_price_x64 = get_sqrt_price_at_tick(tick-1).unwrap(); - assert!(last_sqrt_price_x64 < sqrt_price_x64); - } - - #[test] - fn get_tick_at_sqrt_price_is_sequence_test ( - sqrt_price in (MIN_SQRT_PRICE_X64 + 10)..MAX_SQRT_PRICE_X64 - ) { - - let tick = get_tick_at_sqrt_price(sqrt_price).unwrap(); - let last_tick = get_tick_at_sqrt_price(sqrt_price - 10).unwrap(); - assert!(last_tick <= tick); - } - } - } -} \ No newline at end of file diff --git a/programs/clmm-cpi/src/libraries/unsafe_math.rs b/programs/clmm-cpi/src/libraries/unsafe_math.rs deleted file mode 100644 index 07a8c5b..0000000 --- a/programs/clmm-cpi/src/libraries/unsafe_math.rs +++ /dev/null @@ -1,49 +0,0 @@ -use super::{big_num::U128, U256}; - -pub trait UnsafeMathTrait { - /// Returns ceil (x / y) - /// Division by 0 throws a panic, and must be checked externally - /// - /// In Solidity dividing by 0 results in 0, not an exception. - /// - fn div_rounding_up(x: Self, y: Self) -> Self; -} - -impl UnsafeMathTrait for u64 { - fn div_rounding_up(x: Self, y: Self) -> Self { - x / y + ((x % y > 0) as u64) - } -} - -impl UnsafeMathTrait for U128 { - fn div_rounding_up(x: Self, y: Self) -> Self { - x / y + U128::from((x % y > U128::default()) as u8) - } -} - -impl UnsafeMathTrait for U256 { - fn div_rounding_up(x: Self, y: Self) -> Self { - x / y + U256::from((x % y > U256::default()) as u8) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn divide_by_factor() { - assert_eq!(u64::div_rounding_up(4, 2), 2); - } - - #[test] - fn divide_and_round_up() { - assert_eq!(u64::div_rounding_up(4, 3), 2); - } - - #[test] - #[should_panic] - fn divide_by_zero() { - u64::div_rounding_up(2, 0); - } -} \ No newline at end of file From c6e1077d1f5e66cbd375caa1e966eabd5e7450f2 Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Wed, 16 Apr 2025 01:10:25 +0530 Subject: [PATCH 09/10] installed raydium sdk --- package.json | 1 + yarn.lock | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 239 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index dcb5546..79b88d3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ }, "dependencies": { "@coral-xyz/anchor": "0.30.1", + "@raydium-io/raydium-sdk-v2": "^0.1.123-alpha", "@solana/spl-token": "^0.4.6" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index e804b04..b8ab177 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,6 +55,24 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== +"@raydium-io/raydium-sdk-v2@^0.1.123-alpha": + version "0.1.123-alpha" + resolved "https://registry.yarnpkg.com/@raydium-io/raydium-sdk-v2/-/raydium-sdk-v2-0.1.123-alpha.tgz#ad55e0daae5efa5bac79a28471c27470d9e59dc8" + integrity sha512-xG1v57/byJ4AqFJrNmB0eUHP/2iRGs0TPAvEeiC2cQK2T3J0ZzU0aglzbIL5mOi1z24ia8NySb4Hrc2hcvkacw== + dependencies: + "@solana/buffer-layout" "^4.0.1" + "@solana/spl-token" "^0.4.8" + "@solana/web3.js" "^1.95.3" + axios "^1.1.3" + big.js "^6.2.1" + bn.js "^5.2.1" + dayjs "^1.11.5" + decimal.js-light "^2.5.1" + jsonfile "^6.1.0" + lodash "^4.17.21" + toformat "^2.0.0" + tsconfig-paths "^4.2.0" + "@solana/buffer-layout-utils@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca" @@ -149,7 +167,7 @@ dependencies: "@solana/codecs" "2.0.0-rc.1" -"@solana/spl-token@^0.4.6": +"@solana/spl-token@^0.4.6", "@solana/spl-token@^0.4.8": version "0.4.13" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.13.tgz#8f65c3c2b315e1a00a91b8d0f60922c6eb71de62" integrity sha512-cite/pYWQZZVvLbg5lsodSovbetK/eA24gaR0eeUeMuBAMNrT8XFCwaygKy0N2WSg3gSyjjNpIeAGBAKZaY/1w== @@ -160,7 +178,7 @@ "@solana/spl-token-metadata" "^0.1.6" buffer "^6.0.3" -"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0": +"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.95.3": version "1.98.0" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.0.tgz#21ecfe8198c10831df6f0cfde7f68370d0405917" integrity sha512-nz3Q5OeyGFpFCR+erX2f6JPt3sKhzhYcSycBCSPkWjzSVDh/Rr1FqTVMRe58FKO16/ivTUcuJjeS5MyBvpkbzA== @@ -308,6 +326,20 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.1.3: + version "1.8.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447" + integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -325,6 +357,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +big.js@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.2.tgz#be3bb9ac834558b53b099deef2a1d06ac6368e1a" + integrity sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ== + bigint-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" @@ -415,6 +452,14 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + camelcase@^6.0.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -489,6 +534,13 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^12.1.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" @@ -516,6 +568,11 @@ crypto-hash@^1.3.0: resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== +dayjs@^1.11.5: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + debug@4.3.3: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" @@ -528,6 +585,11 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== +decimal.js-light@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + deep-eql@^4.1.3: version "4.1.4" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" @@ -540,6 +602,11 @@ delay@^5.0.0: resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -558,11 +625,47 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -630,6 +733,21 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -640,6 +758,11 @@ fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -650,6 +773,30 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -669,6 +816,16 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -679,6 +836,25 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -795,6 +971,20 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -807,6 +997,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-symbols@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -834,6 +1029,23 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimatch@4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" @@ -981,6 +1193,11 @@ prettier@^2.6.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -1121,6 +1338,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8" + integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ== + toml@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" @@ -1164,6 +1386,15 @@ tsconfig-paths@^3.5.0: minimist "^1.2.6" strip-bom "^3.0.0" +tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^2.0.3, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" @@ -1184,6 +1415,11 @@ undici-types@~6.21.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + utf-8-validate@^5.0.2: version "5.0.10" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" From 8886443fb80dc29ffd692d2581aff7b703393108 Mon Sep 17 00:00:00 2001 From: nikhilbajaj31 Date: Wed, 16 Apr 2025 17:52:04 +0530 Subject: [PATCH 10/10] curr WIP --- Cargo.lock | 524 ++++++++++++++++-- README.md | 234 +++++--- .../instructions/add_single_side_liquidity.rs | 318 +++++++++++ programs/clmm-cpi/src/instructions/mod.rs | 4 +- .../open_position_with_token22_nft.rs | 202 +++++++ programs/clmm-cpi/src/lib.rs | 26 + programs/clmm-cpi/test/config.ts | 8 + programs/clmm-cpi/test/nitialize.test.ts | 37 ++ programs/clmm-cpi/test/utils/index.ts | 4 + programs/clmm-cpi/test/utils/instruction.ts | 250 +++++++++ programs/clmm-cpi/test/utils/pda.ts | 200 +++++++ programs/clmm-cpi/test/utils/util.ts | 279 ++++++++++ programs/clmm-cpi/test/utils/web3.ts | 65 +++ programs/tax-token/src/lib.rs | 2 +- programs/token-launchpad/Cargo.toml | 3 + programs/token-launchpad/src/constants.rs | 3 + .../src/instructions/create_and_buy.rs | 307 ++++++++++ .../token-launchpad/src/instructions/mod.rs | 1 + programs/token-launchpad/src/lib.rs | 22 +- programs/token-launchpad/src/model.rs | 19 + 20 files changed, 2356 insertions(+), 152 deletions(-) create mode 100644 programs/clmm-cpi/src/instructions/add_single_side_liquidity.rs create mode 100644 programs/clmm-cpi/src/instructions/open_position_with_token22_nft.rs create mode 100644 programs/clmm-cpi/test/config.ts create mode 100644 programs/clmm-cpi/test/nitialize.test.ts create mode 100644 programs/clmm-cpi/test/utils/index.ts create mode 100644 programs/clmm-cpi/test/utils/instruction.ts create mode 100644 programs/clmm-cpi/test/utils/pda.ts create mode 100644 programs/clmm-cpi/test/utils/util.ts create mode 100644 programs/clmm-cpi/test/utils/web3.ts create mode 100644 programs/token-launchpad/src/constants.rs create mode 100644 programs/token-launchpad/src/instructions/create_and_buy.rs create mode 100644 programs/token-launchpad/src/instructions/mod.rs create mode 100644 programs/token-launchpad/src/model.rs diff --git a/Cargo.lock b/Cargo.lock index a12aaf7..98a354c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,13 +70,38 @@ dependencies = [ "memchr", ] +[[package]] +name = "anchor-attribute-access-control" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" +dependencies = [ + "anchor-syn 0.29.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "anchor-attribute-access-control" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47fe28365b33e8334dd70ae2f34a43892363012fe239cf37d2ee91693575b1f8" dependencies = [ - "anchor-syn", + "anchor-syn 0.30.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" +dependencies = [ + "anchor-syn 0.29.0", + "bs58 0.5.1", "proc-macro2", "quote", "syn 1.0.109", @@ -88,20 +113,42 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c288d496168268d198d9b53ee9f4f9d260a55ba4df9877ea1d4486ad6109e0f" dependencies = [ - "anchor-syn", + "anchor-syn 0.30.1", "bs58 0.5.1", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "anchor-attribute-constant" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" +dependencies = [ + "anchor-syn 0.29.0", + "quote", + "syn 1.0.109", +] + [[package]] name = "anchor-attribute-constant" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b77b6948d0eeaaa129ce79eea5bbbb9937375a9241d909ca8fb9e006bb6e90" dependencies = [ - "anchor-syn", + "anchor-syn 0.30.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" +dependencies = [ + "anchor-syn 0.29.0", "quote", "syn 1.0.109", ] @@ -112,7 +159,19 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d20bb569c5a557c86101b944721d865e1fd0a4c67c381d31a44a84f07f84828" dependencies = [ - "anchor-syn", + "anchor-syn 0.30.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" +dependencies = [ + "anchor-syn 0.29.0", + "proc-macro2", "quote", "syn 1.0.109", ] @@ -123,12 +182,23 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cebd8d0671a3a9dc3160c48598d652c34c77de6be4d44345b8b514323284d57" dependencies = [ - "anchor-syn", + "anchor-syn 0.30.1", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "anchor-attribute-program" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" +dependencies = [ + "anchor-syn 0.29.0", + "quote", + "syn 1.0.109", +] + [[package]] name = "anchor-attribute-program" version = "0.30.1" @@ -136,7 +206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb2a5eb0860e661ab31aff7bb5e0288357b176380e985bade4ccb395981b42d" dependencies = [ "anchor-lang-idl", - "anchor-syn", + "anchor-syn 0.30.1", "anyhow", "bs58 0.5.1", "heck", @@ -146,13 +216,37 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "anchor-derive-accounts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" +dependencies = [ + "anchor-syn 0.29.0", + "quote", + "syn 1.0.109", +] + [[package]] name = "anchor-derive-accounts" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04368b5abef4266250ca8d1d12f4dff860242681e4ec22b885dcfe354fd35aa1" dependencies = [ - "anchor-syn", + "anchor-syn 0.30.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +dependencies = [ + "anchor-syn 0.29.0", + "borsh-derive-internal 0.10.4", + "proc-macro2", "quote", "syn 1.0.109", ] @@ -163,13 +257,24 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0bb0e0911ad4a70cab880cdd6287fe1e880a1a9d8e4e6defa8e9044b9796a6c" dependencies = [ - "anchor-syn", + "anchor-syn 0.30.1", "borsh-derive-internal 0.10.4", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "anchor-derive-space" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "anchor-derive-space" version = "0.30.1" @@ -181,21 +286,46 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "anchor-lang" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" +dependencies = [ + "anchor-attribute-access-control 0.29.0", + "anchor-attribute-account 0.29.0", + "anchor-attribute-constant 0.29.0", + "anchor-attribute-error 0.29.0", + "anchor-attribute-event 0.29.0", + "anchor-attribute-program 0.29.0", + "anchor-derive-accounts 0.29.0", + "anchor-derive-serde 0.29.0", + "anchor-derive-space 0.29.0", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.10.4", + "bytemuck", + "getrandom 0.2.15", + "solana-program", + "thiserror", +] + [[package]] name = "anchor-lang" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6620c9486d9d36a4389cab5e37dc34a42ed0bfaa62e6a75a2999ce98f8f2e373" dependencies = [ - "anchor-attribute-access-control", - "anchor-attribute-account", - "anchor-attribute-constant", - "anchor-attribute-error", - "anchor-attribute-event", - "anchor-attribute-program", - "anchor-derive-accounts", - "anchor-derive-serde", - "anchor-derive-space", + "anchor-attribute-access-control 0.30.1", + "anchor-attribute-account 0.30.1", + "anchor-attribute-constant 0.30.1", + "anchor-attribute-error 0.30.1", + "anchor-attribute-event 0.30.1", + "anchor-attribute-program 0.30.1", + "anchor-derive-accounts 0.30.1", + "anchor-derive-serde 0.30.1", + "anchor-derive-space 0.30.1", "anchor-lang-idl", "arrayref", "base64 0.21.7", @@ -232,21 +362,52 @@ dependencies = [ "serde", ] +[[package]] +name = "anchor-spl" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a" +dependencies = [ + "anchor-lang 0.29.0", + "solana-program", + "spl-associated-token-account 2.3.0", + "spl-token", + "spl-token-2022 0.9.0", +] + [[package]] name = "anchor-spl" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04bd077c34449319a1e4e0bc21cea572960c9ae0d0fefda0dd7c52fcc3c647a3" dependencies = [ - "anchor-lang", + "anchor-lang 0.30.1", "mpl-token-metadata", - "spl-associated-token-account", + "spl-associated-token-account 3.0.4", "spl-memo", - "spl-pod", + "spl-pod 0.2.5", "spl-token", - "spl-token-2022", - "spl-token-group-interface", - "spl-token-metadata-interface", + "spl-token-2022 3.0.5", + "spl-token-group-interface 0.2.5", + "spl-token-metadata-interface 0.3.5", +] + +[[package]] +name = "anchor-syn" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" +dependencies = [ + "anyhow", + "bs58 0.5.1", + "heck", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "syn 1.0.109", + "thiserror", ] [[package]] @@ -432,6 +593,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -729,8 +896,8 @@ dependencies = [ name = "clmm-cpi" version = "0.1.0" dependencies = [ - "anchor-lang", - "anchor-spl", + "anchor-lang 0.30.1", + "anchor-spl 0.30.1", "raydium-clmm-cpi", ] @@ -1631,8 +1798,8 @@ name = "raydium-clmm-cpi" version = "0.1.0" source = "git+https://github.com/raydium-io/raydium-cpi?branch=anchor-0.30.1#c470a055aa44da1666f6c50c3600e0495699f6ab" dependencies = [ - "anchor-lang", - "anchor-spl", + "anchor-lang 0.30.1", + "anchor-spl 0.30.1", ] [[package]] @@ -2090,6 +2257,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "spl-associated-token-account" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" +dependencies = [ + "assert_matches", + "borsh 0.10.4", + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-token", + "spl-token-2022 1.0.0", + "thiserror", +] + [[package]] name = "spl-associated-token-account" version = "3.0.4" @@ -2102,10 +2285,21 @@ dependencies = [ "num-traits", "solana-program", "spl-token", - "spl-token-2022", + "spl-token-2022 3.0.5", "thiserror", ] +[[package]] +name = "spl-discriminator" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive 0.1.2", +] + [[package]] name = "spl-discriminator" version = "0.2.5" @@ -2114,7 +2308,18 @@ checksum = "210101376962bb22bb13be6daea34656ea1cbc248fce2164b146e39203b55e03" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator-derive", + "spl-discriminator-derive 0.2.0", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" +dependencies = [ + "quote", + "spl-discriminator-syn 0.1.2", + "syn 2.0.100", ] [[package]] @@ -2124,10 +2329,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", - "spl-discriminator-syn", + "spl-discriminator-syn 0.2.0", "syn 2.0.100", ] +[[package]] +name = "spl-discriminator-syn" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.100", + "thiserror", +] + [[package]] name = "spl-discriminator-syn" version = "0.2.0" @@ -2150,6 +2368,19 @@ dependencies = [ "solana-program", ] +[[package]] +name = "spl-pod" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error 0.3.0", +] + [[package]] name = "spl-pod" version = "0.2.5" @@ -2160,7 +2391,20 @@ dependencies = [ "bytemuck", "solana-program", "solana-zk-token-sdk", - "spl-program-error", + "spl-program-error 0.4.4", +] + +[[package]] +name = "spl-program-error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-program-error-derive 0.3.2", + "thiserror", ] [[package]] @@ -2172,10 +2416,22 @@ dependencies = [ "num-derive 0.4.2", "num-traits", "solana-program", - "spl-program-error-derive", + "spl-program-error-derive 0.4.1", "thiserror", ] +[[package]] +name = "spl-program-error-derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.100", +] + [[package]] name = "spl-program-error-derive" version = "0.4.1" @@ -2188,6 +2444,34 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "spl-tlv-account-resolution" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "615d381f48ddd2bb3c57c7f7fb207591a2a05054639b18a62e785117dd7a8683" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + [[package]] name = "spl-tlv-account-resolution" version = "0.6.5" @@ -2196,10 +2480,10 @@ checksum = "fab8edfd37be5fa17c9e42c1bff86abbbaf0494b031b37957f2728ad2ff842ba" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-type-length-value", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", + "spl-type-length-value 0.4.6", ] [[package]] @@ -2217,6 +2501,52 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-token-2022" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum", + "solana-program", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod 0.1.0", + "spl-token", + "spl-token-metadata-interface 0.2.0", + "spl-transfer-hook-interface 0.3.0", + "spl-type-length-value 0.3.0", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum", + "solana-program", + "solana-security-txt", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod 0.1.0", + "spl-token", + "spl-token-group-interface 0.1.0", + "spl-token-metadata-interface 0.2.0", + "spl-transfer-hook-interface 0.4.1", + "spl-type-length-value 0.3.0", + "thiserror", +] + [[package]] name = "spl-token-2022" version = "3.0.5" @@ -2232,15 +2562,28 @@ dependencies = [ "solana-security-txt", "solana-zk-token-sdk", "spl-memo", - "spl-pod", + "spl-pod 0.2.5", "spl-token", - "spl-token-group-interface", - "spl-token-metadata-interface", - "spl-transfer-hook-interface", - "spl-type-length-value", + "spl-token-group-interface 0.2.5", + "spl-token-metadata-interface 0.3.5", + "spl-transfer-hook-interface 0.6.5", + "spl-type-length-value 0.4.6", "thiserror", ] +[[package]] +name = "spl-token-group-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", +] + [[package]] name = "spl-token-group-interface" version = "0.2.5" @@ -2249,9 +2592,23 @@ checksum = "014817d6324b1e20c4bbc883e8ee30a5faa13e59d91d1b2b95df98b920150c17" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" +dependencies = [ + "borsh 0.10.4", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", ] [[package]] @@ -2262,10 +2619,42 @@ checksum = "f3da00495b602ebcf5d8ba8b3ecff1ee454ce4c125c9077747be49c2d62335ba" dependencies = [ "borsh 1.5.7", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-type-length-value", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", + "spl-type-length-value 0.4.6", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-tlv-account-resolution 0.4.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-tlv-account-resolution 0.5.1", + "spl-type-length-value 0.3.0", ] [[package]] @@ -2277,11 +2666,24 @@ dependencies = [ "arrayref", "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-tlv-account-resolution", - "spl-type-length-value", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", + "spl-tlv-account-resolution 0.6.5", + "spl-type-length-value 0.4.6", +] + +[[package]] +name = "spl-type-length-value" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", ] [[package]] @@ -2292,9 +2694,9 @@ checksum = "c872f93d0600e743116501eba2d53460e73a12c9a496875a42a7d70e034fe06d" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", ] [[package]] @@ -2335,8 +2737,8 @@ dependencies = [ name = "tax-token" version = "0.1.0" dependencies = [ - "anchor-lang", - "anchor-spl", + "anchor-lang 0.30.1", + "anchor-spl 0.30.1", "bytemuck_derive", ] @@ -2407,7 +2809,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" name = "token-launchpad" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 0.29.0", + "anchor-spl 0.29.0", + "clmm-cpi", + "solana-program", + "tax-token", ] [[package]] diff --git a/README.md b/README.md index 663af7f..434709e 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,146 @@ -# Solana Tax Token Contract +# Solana Tax Token Launchpad -A Solana smart contract implementing a token with tax/fee functionality using SPL Token 2022 extensions, built with Anchor framework. +A Solana program intended for creating tax tokens with automated liquidity pool setup on Raydium. ## Overview -This project demonstrates the implementation of a token with transfer fee (tax) functionality on Solana. It uses the SPL Token 2022 program's TransferFee extension to automatically collect fees on each token transfer. +This program aims to enable users to: + +1. Create a new token with transfer fee capabilities (tax token) +2. Pay a creation fee in SOL +3. Automatically create a Raydium CLMM pool (concentrated liquidity) for the tax token and WSOL +4. Add initial liquidity to the pool + +The project is designed to demonstrate the implementation of a token with transfer fee (tax) functionality on Solana. It should utilize the SPL Token 2022 program's TransferFee extension to automatically collect fees on each token transfer. + +## How It Works + +The main functionality is intended to be exposed through the `create_and_buy` instruction, which should handle the entire process in one transaction: + +```rust +pub fn create_and_buy(ctx: Context, params: CreateTokenParams) -> Result<()> +``` + +### Intended Process Flow + +1. **Fee Collection**: Should collect SOL creation fee from the user +2. **Token Creation**: Should create a new SPL Token-2022 token with transfer fee extension +3. **Pool Creation**: Should create a Raydium CLMM pool for the tax token and WSOL +4. **Liquidity Addition**: Should add single-sided liquidity to the pool + +### Intended Code Flow + +The `create_and_buy` function should execute the following steps in sequence: + +1. **Handle Creation Fees** + ```rust + handle_creation_fees(&ctx, params.creation_fee)?; + ``` + - Should transfer SOL from the payer to the fee receiver using the System Program's transfer instruction + - This fee is intended for the service of creating the token and setting up the liquidity pool + +2. **Create Tax Token** + ```rust + create_token(&ctx, params.transfer_fee_basis_points, params.maximum_fee)?; + ``` + - Should calculate required space for the mint account with TransferFeeConfig extension + - Should create the mint account with proper space allocation + - Should initialize the TransferFeeConfig extension with specified fee parameters: + - transfer_fee_basis_points: percentage of each transfer to collect as fee + - maximum_fee: maximum amount to collect per transfer + - Should initialize the mint with standard parameters (decimals, authorities) + +3. **Add Single-Sided Liquidity** + ```rust + handle_add_single_side_liquidity( + &ctx, + params.tick_lower_index, + params.tick_upper_index, + params.tick_array_lower_start_index, + params.tick_array_upper_start_index, + params.liquidity, + params.amount_tax_token_max, + params.amount_sol_max, + )?; + ``` + - Should create a Raydium CLMM pool for the tax token and WSOL pair if it doesn't exist + - Should calculate sqrt_price_x64 from tick_lower_index for pool initialization + - Should create the pool with the calculated price and current timestamp + - Should open a new position in the pool with specified tick ranges + - Should add liquidity to the position with the specified amounts of tax token and SOL + - This is intended to establish initial trading liquidity for the newly created token + +The process aims to create a token with tax capabilities and immediate trading liquidity, which should allow users to trade the token on Raydium's CLMM DEX right after creation. + +### Required Parameters + +The `CreateTokenParams` struct should contain all necessary parameters: + +```rust +pub struct CreateTokenParams { + pub transfer_fee_basis_points: u16, // The tax rate (in basis points) + pub maximum_fee: u64, // Maximum fee per transaction + pub creation_fee: u64, // SOL fee to create the token + pub tick_lower_index: i32, // CLMM lower tick index + pub tick_upper_index: i32, // CLMM upper tick index + pub tick_array_lower_start_index: i32, // CLMM lower tick array start + pub tick_array_upper_start_index: i32, // CLMM upper tick array start + pub liquidity: u128, // Initial liquidity amount + pub amount_tax_token_max: u64, // Max tax tokens to add as liquidity + pub amount_sol_max: u64, // Max SOL to add as liquidity +} +``` + +## Expected Account Structure + +To execute the `create_and_buy` instruction, the following accounts should be provided: + +- **payer**: User paying for the transaction and creation fee +- **mint_account**: New token's mint account (signer) +- **fee_receiver**: Account that should receive the creation fee +- **token_program_2022**: SPL Token-2022 program +- **clmm_program**: Raydium CLMM program +- **amm_config**: Raydium AMM configuration +- Various position accounts for the CLMM pool +- Token accounts for the tax token and WSOL + +## Planned Features -Key features: - Token creation with configurable transfer fee - Automatic fee collection on token transfers -- Fee harvesting from token accounts to the mint -- Fee withdrawal to a designated wallet -- Fee configuration updates +- Automatic pool creation with the tax token and WSOL +- Single-sided liquidity provision + +## Transfer Fee Configuration + +When initializing a new tax token, users should be able to configure: +- `transfer_fee_basis_points`: The fee percentage in basis points (e.g., 100 = 1%) +- `maximum_fee`: The maximum fee amount in token units + +## Expected Fee Mechanism + +Fees should be automatically withheld in the recipient account during transfers. The fee amount should be calculated as: +``` +fee = min(transfer_amount * transfer_fee_basis_points / 10000, maximum_fee) +``` ## Prerequisites -- [Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools) v1.16.0 or later -- [Anchor](https://project-serum.github.io/anchor/getting-started/installation.html) v0.29.0 or later -- [Node.js](https://nodejs.org/en/) v16 or later -- [Yarn](https://yarnpkg.com/getting-started/install) or npm +- [Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools) +- [Anchor](https://project-serum.github.io/anchor/getting-started/installation.html) +- [Node.js](https://nodejs.org/en/) ## Setup 1. Clone the repository: ```sh -git clone https://github.com/yourusername/maha-solana-contracts.git +git clone cd maha-solana-contracts ``` 2. Install dependencies: ```sh -yarn install +yarn install # or npm install ``` 3. Build the program: @@ -38,97 +148,49 @@ yarn install anchor build ``` -4. Start a local test validator: -```sh -solana-test-validator --reset -``` - -5. Deploy the program: +4. Deploy: ```sh anchor deploy ``` -## Contracts - -### solana-tax-token-anchor - -The main contract that implements the tax token functionality with the following instructions: +## Development -- `initialize`: Creates a new token mint with transfer fee configuration -- `transfer`: Transfers tokens between accounts (with automatic fee deduction) -- `harvest`: Collects withheld fees from token accounts into the mint -- `withdraw`: Withdraws collected fees from the mint to a designated wallet -- `update_fee`: Updates the transfer fee configuration +### Building -## Test - -Run the tests with: - -```sh -ANCHOR_PROVIDER_URL=http://localhost:8899 ANCHOR_WALLET=/path/to/your/wallet.json npx ts-mocha -p ./tsconfig.json -t 1000000 tests/tax-token.ts +```bash +anchor build ``` -The tests demonstrate: -1. Creating a token with transfer fee -2. Transferring tokens with fee deduction -3. Harvesting fees from token accounts -4. Withdrawing fees to the fee authority's wallet -5. Updating fee configuration - -## Implementation Details - -### Transfer Fee Configuration - -When initializing a new tax token, you can configure: -- `transfer_fee_basis_points`: The fee percentage in basis points (e.g., 100 = 1%) -- `maximum_fee`: The maximum fee amount in token units - -### Fee Mechanism +### Testing -Fees are automatically withheld in the recipient account during transfers. The fee amount is calculated as: -``` -fee = min(transfer_amount * transfer_fee_basis_points / 10000, maximum_fee) +```bash +anchor test ``` -### Harvesting Fees - -Fees withheld in token accounts need to be harvested to the mint before withdrawal. This is done with the `harvest` instruction. - -### Withdrawing Fees - -After harvesting, fees can be withdrawn from the mint to a designated wallet using the `withdraw` instruction. - -## Usage Example +## Intended Usage Example ```typescript -// Create a tax token with 1% fee -await program.methods - .initialize(100, new anchor.BN(1_000_000)) - .accounts({ - payer: wallet.publicKey, - mint_account: mintKeypair.publicKey, - token_program: TOKEN_2022_PROGRAM_ID, - system_program: anchor.web3.SystemProgram.programId, +// Client-side example of how creating a tax token and pool should work +const createAndBuyIx = await program.methods + .createAndBuy({ + transferFeeBasisPoints: 100, // 1% fee + maximumFee: new BN(1000000), // 0.01 tokens max fee + creationFee: new BN(100000000), // 0.1 SOL + tickLowerIndex: -10, + tickUpperIndex: 10, + tickArrayLowerStartIndex: -10, + tickArrayUpperStartIndex: 10, + liquidity: new BN("1000000000"), + amountTaxTokenMax: new BN(1000000000), + amountSolMax: new BN(100000000), }) - .signers([mintKeypair]) - .rpc(); - -// Transfer tokens (fee is automatically applied) -await program.methods - .transfer(new anchor.BN(100_000_000)) .accounts({ - sender: wallet.publicKey, - recipient: recipient.publicKey, - mint_account: mintKeypair.publicKey, - sender_token_account: senderATA, - recipient_token_account: recipientATA, - token_program: TOKEN_2022_PROGRAM_ID, - associated_token_program: anchor.utils.token.ASSOCIATED_PROGRAM_ID, - system_program: anchor.web3.SystemProgram.programId, + // All required accounts... }) + .signers([user, mintKeypair]) .rpc(); ``` ## License -This project is licensed under the MIT License. \ No newline at end of file +MIT License \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/add_single_side_liquidity.rs b/programs/clmm-cpi/src/instructions/add_single_side_liquidity.rs new file mode 100644 index 0000000..1468811 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/add_single_side_liquidity.rs @@ -0,0 +1,318 @@ +use anchor_lang::prelude::*; +use anchor_spl::{ + associated_token::AssociatedToken, + metadata::Metadata, + token::Token, + token_interface::{Mint, Token2022, TokenAccount, TokenInterface}, +}; +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{AmmConfig, PoolState, POOL_SEED, POOL_TICK_ARRAY_BITMAP_SEED, POOL_VAULT_SEED, POSITION_SEED, TICK_ARRAY_SEED}, +}; + +#[derive(Accounts)] +#[instruction( + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, +)] +pub struct AddSingleSideLiquidity<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + + /// Address paying for transactions and providing liquidity + #[account(mut)] + pub payer: Signer<'info>, + + /// Used as pool creator (needed by create_pool) + #[account(mut)] + pub pool_creator: Signer<'info>, + + /// Which config the pool belongs to + pub amm_config: Box>, + + /// CHECK: Position NFT owner (typically same as payer) + pub position_nft_owner: UncheckedAccount<'info>, + + /// Position NFT mint + #[account(mut)] + pub position_nft_mint: Signer<'info>, + + /// CHECK: Token account for position NFT + #[account(mut)] + pub position_nft_account: UncheckedAccount<'info>, + + /// CHECK: Metadata account + #[account(mut)] + pub metadata_account: UncheckedAccount<'info>, + + /// Pool state account + #[account( + mut, + seeds = [ + POOL_SEED.as_bytes(), + amm_config.key().as_ref(), + token_mint_0.key().as_ref(), + token_mint_1.key().as_ref(), + ], + seeds::program = clmm_program.key(), + bump, + )] + pub pool_state: AccountLoader<'info, PoolState>, + + /// CHECK: Protocol position + #[account(mut)] + pub protocol_position: UncheckedAccount<'info>, + + /// CHECK: Lower tick array + #[account(mut)] + pub tick_array_lower: UncheckedAccount<'info>, + + /// CHECK: Upper tick array + #[account(mut)] + pub tick_array_upper: UncheckedAccount<'info>, + + /// CHECK: Personal position + #[account(mut)] + pub personal_position: UncheckedAccount<'info>, + + /// Token 0 mint + pub token_mint_0: Box>, + + /// Token 1 mint + pub token_mint_1: Box>, + + /// Token 0 account + #[account(mut)] + pub token_account_0: Box>, + + /// Token 1 account + #[account(mut)] + pub token_account_1: Box>, + + /// Token 0 vault + #[account(mut)] + pub token_vault_0: Box>, + + /// Token 1 vault + #[account(mut)] + pub token_vault_1: Box>, + + /// CHECK: Observation state + #[account(mut)] + pub observation_state: UncheckedAccount<'info>, + + /// CHECK: Tick array bitmap + #[account(mut)] + pub tick_array_bitmap: UncheckedAccount<'info>, + + /// Token program interface for token 0 + pub token_program_0: Interface<'info, TokenInterface>, + + /// Token program interface for token 1 + pub token_program_1: Interface<'info, TokenInterface>, + + /// TOKEN program + pub token_program: Program<'info, Token>, + + /// Associated token program + pub associated_token_program: Program<'info, AssociatedToken>, + + /// Metadata program + pub metadata_program: Program<'info, Metadata>, + + /// Token 2022 program + pub token_program_2022: Program<'info, Token2022>, + + /// Vault 0 mint + pub vault_0_mint: Box>, + + /// Vault 1 mint + pub vault_1_mint: Box>, + + /// System program + pub system_program: Program<'info, System>, + + /// Rent + pub rent: Sysvar<'info, Rent>, +} + +pub fn add_single_side_liquidity<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, AddSingleSideLiquidity<'info>>, + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + with_matedata: bool, + base_flag: Option, +) -> Result<()> { + // Calculate sqrt price from tick_lower_index for pool initialization + // Instead of calculating sqrt price from tick, we can take sqrt price as input from the user, which can be obtained from the Raydium SDK. + let sqrt_price_x64: u128 = get_sqrt_price_x64_from_tick(tick_lower_index); + let open_time: u64 = Clock::get()?.unix_timestamp as u64; + + // Step 1: Initialize pool if needed + create_pool(&ctx, sqrt_price_x64, open_time)?; + + // Step 2: Add liquidity + open_position( + &ctx, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + liquidity, + amount_0_max, + amount_1_max, + with_matedata, + base_flag, + ) +} + +// Helper function to create pool if it doesn't exist +fn create_pool<'a, 'b, 'c: 'info, 'info>( + ctx: &Context<'a, 'b, 'c, 'info, AddSingleSideLiquidity<'info>>, + sqrt_price_x64: u128, + open_time: u64, +) -> Result<()> { + let cpi_accounts = cpi::accounts::CreatePool { + pool_creator: ctx.accounts.pool_creator.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + token_mint_0: ctx.accounts.token_mint_0.to_account_info(), + token_mint_1: ctx.accounts.token_mint_1.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + tick_array_bitmap: ctx.accounts.tick_array_bitmap.to_account_info(), + token_program_0: ctx.accounts.token_program_0.to_account_info(), + token_program_1: ctx.accounts.token_program_1.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + rent: ctx.accounts.rent.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts); + cpi::create_pool(cpi_context, sqrt_price_x64, open_time)?; + + Ok(()) +} + +// Helper function to open position and add liquidity +fn open_position<'a, 'b, 'c: 'info, 'info>( + ctx: &Context<'a, 'b, 'c, 'info, AddSingleSideLiquidity<'info>>, + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + with_matedata: bool, + base_flag: Option, +) -> Result<()> { + let cpi_accounts = cpi::accounts::OpenPositionV2 { + payer: ctx.accounts.payer.to_account_info(), + position_nft_owner: ctx.accounts.position_nft_owner.to_account_info(), + position_nft_mint: ctx.accounts.position_nft_mint.to_account_info(), + position_nft_account: ctx.accounts.position_nft_account.to_account_info(), + metadata_account: ctx.accounts.metadata_account.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + protocol_position: ctx.accounts.protocol_position.to_account_info(), + tick_array_lower: ctx.accounts.tick_array_lower.to_account_info(), + tick_array_upper: ctx.accounts.tick_array_upper.to_account_info(), + personal_position: ctx.accounts.personal_position.to_account_info(), + token_account_0: ctx.accounts.token_account_0.to_account_info(), + token_account_1: ctx.accounts.token_account_1.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + rent: ctx.accounts.rent.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + associated_token_program: ctx.accounts.associated_token_program.to_account_info(), + metadata_program: ctx.accounts.metadata_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + vault_0_mint: ctx.accounts.vault_0_mint.to_account_info(), + vault_1_mint: ctx.accounts.vault_1_mint.to_account_info(), + }; + + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.to_vec()); + + cpi::open_position_v2( + cpi_context, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + liquidity, + amount_0_max, + amount_1_max, + with_matedata, + base_flag, + ) +} + +// Function to calculate sqrt_price_x64 from tick, similar to Raydium's implementation +fn get_sqrt_price_x64_from_tick(tick: i32) -> u128 { + // Constants + const MAX_UINT128: u128 = 340282366920938463463374607431768211455; + + // Validate tick is within allowed range + let tick_abs: u32 = if tick < 0 { (-tick) as u32 } else { tick as u32 }; + + let mut ratio: u128 = if (tick_abs & 0x1) != 0 { 18445821805675395072 } else { 18446744073709551616 }; + + // Use array of bit masks and multipliers to iterate instead of having many if statements + let bit_masks: [u32; 19] = [ + 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, + 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, + 0x4000, 0x8000, 0x10000, 0x20000, 0x40000, 0x80000 + ]; + + let multipliers: [u128; 19] = [ + 18444899583751176192, 18443055278223355904, 18439367220385607680, + 18431993317065453568, 18417254355718170624, 18387811781193609216, + 18329067761203558400, 18212142134806163456, 17980523815641700352, + 17526086738831433728, 16651378430235570176, 15030750278694412288, + 12247334978884435968, 8131365268886854656, 3584323654725218816, + 696457651848324352, 26294789957507116, 37481735321082, 7685 + ]; + + // Iterate through the bit positions and apply multipliers as needed + for i in 0..bit_masks.len() { + if (tick_abs & bit_masks[i]) != 0 { + ratio = mul_right_shift(ratio, multipliers[i]); + } + } + + if tick > 0 { MAX_UINT128 / ratio } else { ratio } +} + +// Helper function to simulate mulRightShift in Raydium SDK +fn mul_right_shift(val: u128, mul_by: u128) -> u128 { + // Handle potential overflow when multiplying large u128 numbers + // We can use a more careful approach: + + // Extract high and low 64 bits + let val_high = val >> 64; + let val_low = val & 0xFFFFFFFFFFFFFFFF; + let mul_high = mul_by >> 64; + let mul_low = mul_by & 0xFFFFFFFFFFFFFFFF; + + // Only the val_high * mul_low and val_low * mul_high (shifted) contribute to result + // val_low * mul_low >> 64 gives us the high bits we need to add + // val_high * mul_high << 64 is too high to matter in our 64-bit right shift + + let val_low_mul_low = val_low * mul_low; + let val_high_mul_low = val_high * mul_low; + let val_low_mul_high = val_low * mul_high; + + // Calculate result with carries + let middle_term = val_high_mul_low + (val_low_mul_low >> 64); + let result = middle_term + val_low_mul_high; + + result +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/mod.rs b/programs/clmm-cpi/src/instructions/mod.rs index 232f4c3..c687aff 100644 --- a/programs/clmm-cpi/src/instructions/mod.rs +++ b/programs/clmm-cpi/src/instructions/mod.rs @@ -4,10 +4,12 @@ pub mod proxy_increase_liquidity; pub mod proxy_initialize; pub mod proxy_open_position; pub mod proxy_swap; +pub mod add_single_side_liquidity; pub use proxy_close_position::*; pub use proxy_decrease_liquidity::*; pub use proxy_increase_liquidity::*; pub use proxy_initialize::*; pub use proxy_open_position::*; -pub use proxy_swap::*; \ No newline at end of file +pub use proxy_swap::*; +pub use add_single_side_liquidity::*; \ No newline at end of file diff --git a/programs/clmm-cpi/src/instructions/open_position_with_token22_nft.rs b/programs/clmm-cpi/src/instructions/open_position_with_token22_nft.rs new file mode 100644 index 0000000..4c64404 --- /dev/null +++ b/programs/clmm-cpi/src/instructions/open_position_with_token22_nft.rs @@ -0,0 +1,202 @@ +use super::open_position::open_position; +use crate::states::*; +use crate::util::create_position_nft_mint_with_extensions; +use anchor_lang::prelude::*; +use anchor_spl::associated_token::{create, AssociatedToken, Create}; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; + +use raydium_clmm_cpi::{ + cpi, + program::RaydiumClmm, + states::{PoolState, POSITION_SEED, TICK_ARRAY_SEED}, +}; + +#[derive(Accounts)] +#[instruction(tick_lower_index: i32, tick_upper_index: i32,tick_array_lower_start_index:i32,tick_array_upper_start_index:i32)] +pub struct OpenPositionWithToken22Nft<'info> { + pub clmm_program: Program<'info, RaydiumClmm>, + /// Pays to mint the position + #[account(mut)] + pub payer: Signer<'info>, + + /// CHECK: Receives the position NFT + pub position_nft_owner: UncheckedAccount<'info>, + + /// Unique token mint address, initialize in contract + #[account(mut)] + pub position_nft_mint: Signer<'info>, + + /// CHECK: ATA address where position NFT will be minted, initialize in contract + #[account(mut)] + pub position_nft_account: UncheckedAccount<'info>, + + /// Add liquidity for this pool + #[account(mut)] + pub pool_state: AccountLoader<'info, PoolState>, + + /// Store the information of market marking in range + #[account( + init_if_needed, + seeds = [ + POSITION_SEED.as_bytes(), + pool_state.key().as_ref(), + &tick_lower_index.to_be_bytes(), + &tick_upper_index.to_be_bytes(), + ], + bump, + payer = payer, + space = ProtocolPositionState::LEN + )] + pub protocol_position: Box>, + + /// CHECK: Account to store data for the position's lower tick + #[account( + mut, + seeds = [ + TICK_ARRAY_SEED.as_bytes(), + pool_state.key().as_ref(), + &tick_array_lower_start_index.to_be_bytes(), + ], + bump, + )] + pub tick_array_lower: UncheckedAccount<'info>, + + /// CHECK: Account to store data for the position's upper tick + #[account( + mut, + seeds = [ + TICK_ARRAY_SEED.as_bytes(), + pool_state.key().as_ref(), + &tick_array_upper_start_index.to_be_bytes(), + ], + bump, + )] + pub tick_array_upper: UncheckedAccount<'info>, + + /// personal position state + #[account( + init, + seeds = [POSITION_SEED.as_bytes(), position_nft_mint.key().as_ref()], + bump, + payer = payer, + space = PersonalPositionState::LEN + )] + pub personal_position: Box>, + + /// The token_0 account deposit token to the pool + #[account( + mut, + token::mint = token_vault_0.mint + )] + pub token_account_0: Box>, + + /// The token_1 account deposit token to the pool + #[account( + mut, + token::mint = token_vault_1.mint + )] + pub token_account_1: Box>, + + /// The address that holds pool tokens for token_0 + #[account( + mut, + constraint = token_vault_0.key() == pool_state.load()?.token_vault_0 + )] + pub token_vault_0: Box>, + + /// The address that holds pool tokens for token_1 + #[account( + mut, + constraint = token_vault_1.key() == pool_state.load()?.token_vault_1 + )] + pub token_vault_1: Box>, + + /// Sysvar for token mint and ATA creation + pub rent: Sysvar<'info, Rent>, + + /// Program to create the position manager state account + pub system_program: Program<'info, System>, + + /// Program to transfer for token account + pub token_program: Program<'info, Token>, + + /// Program to create an ATA for receiving position NFT + pub associated_token_program: Program<'info, AssociatedToken>, + + /// Program to create NFT mint/token account and transfer for token22 account + pub token_program_2022: Program<'info, Token2022>, + + /// The mint of token vault 0 + #[account( + address = token_vault_0.mint + )] + pub vault_0_mint: Box>, + + /// The mint of token vault 1 + #[account( + address = token_vault_1.mint + )] + pub vault_1_mint: Box>, + // remaining account + // #[account( + // seeds = [ + // POOL_TICK_ARRAY_BITMAP_SEED.as_bytes(), + // pool_state.key().as_ref(), + // ], + // bump + // )] + // pub tick_array_bitmap: AccountLoader<'info, TickArrayBitmapExtension>, +} + +pub fn open_position_with_token22_nft<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, OpenPositionWithToken22Nft<'info>>, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, + with_metadata: bool, + base_flag: Option, +) -> Result<()> { + let cpi_accounts = cpi::accounts::OpenPositionV2 { + payer: ctx.accounts.payer.to_account_info(), + position_nft_owner: ctx.accounts.position_nft_owner.to_account_info(), + position_nft_mint: ctx.accounts.position_nft_mint.to_account_info(), + position_nft_account: ctx.accounts.position_nft_account.to_account_info(), + metadata_account: ctx.accounts.metadata_account.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + protocol_position: ctx.accounts.protocol_position.to_account_info(), + tick_array_lower: ctx.accounts.tick_array_lower.to_account_info(), + tick_array_upper: ctx.accounts.tick_array_upper.to_account_info(), + personal_position: ctx.accounts.personal_position.to_account_info(), + token_account_0: ctx.accounts.token_account_0.to_account_info(), + token_account_1: ctx.accounts.token_account_1.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + rent: ctx.accounts.rent.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + associated_token_program: ctx.accounts.associated_token_program.to_account_info(), + metadata_program: ctx.accounts.metadata_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + vault_0_mint: ctx.accounts.vault_0_mint.to_account_info(), + vault_1_mint: ctx.accounts.vault_1_mint.to_account_info(), + }; + let cpi_context = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.to_vec()); + cpi::open_position_with_token22_nft( + cpi_context, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + liquidity, + amount_0_max, + amount_1_max, + with_matedata, + base_flag, + ) +} \ No newline at end of file diff --git a/programs/clmm-cpi/src/lib.rs b/programs/clmm-cpi/src/lib.rs index a0f3e82..b4f2384 100644 --- a/programs/clmm-cpi/src/lib.rs +++ b/programs/clmm-cpi/src/lib.rs @@ -85,4 +85,30 @@ pub mod clmm_cpi { is_base_input, ) } + + pub fn add_single_side_liquidity<'a, 'b, 'c: 'info, 'info>( + ctx: Context<'a, 'b, 'c, 'info, AddSingleSideLiquidity<'info>>, + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, + liquidity: u128, + amount_0_max: u64, + amount_1_max: u64, + with_matedata: bool, + base_flag: Option, + ) -> Result<()> { + instructions::add_single_side_liquidity( + ctx, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + liquidity, + amount_0_max, + amount_1_max, + with_matedata, + base_flag, + ) + } } \ No newline at end of file diff --git a/programs/clmm-cpi/test/config.ts b/programs/clmm-cpi/test/config.ts new file mode 100644 index 0000000..b152079 --- /dev/null +++ b/programs/clmm-cpi/test/config.ts @@ -0,0 +1,8 @@ +import { PublicKey } from "@solana/web3.js"; + +export const ClmmProgram = new PublicKey( + "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" +); +export const configAddress = new PublicKey( + "4BLNHtVe942GSs4teSZqGX24xwKNkqU7bGgNn3iUiUpw" +); \ No newline at end of file diff --git a/programs/clmm-cpi/test/nitialize.test.ts b/programs/clmm-cpi/test/nitialize.test.ts new file mode 100644 index 0000000..0ffa5fe --- /dev/null +++ b/programs/clmm-cpi/test/nitialize.test.ts @@ -0,0 +1,37 @@ +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { ClmmCpi } from "../../../target/types/clmm_cpi"; +import { setupInitializeTest, initialize } from "./utils/instruction"; + +describe("initialize test", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + const owner = anchor.Wallet.local().payer; + const program = anchor.workspace.ClmmCpi as Program; + + const confirmOptions = { + skipPreflight: true, + }; + + it("create pool", async () => { + const { token0, token0Program, token1, token1Program } = + await setupInitializeTest( + anchor.getProvider().connection, + owner, + { transferFeeBasisPoints: 0, MaxFee: 0 }, + confirmOptions + ); + + const { poolAddress, tx } = await initialize( + program, + owner, + token0, + token0Program, + token1, + token1Program, + 0, + confirmOptions + ); + + console.log("pool address: ", poolAddress.toString(), " tx:", tx); + }); +}); \ No newline at end of file diff --git a/programs/clmm-cpi/test/utils/index.ts b/programs/clmm-cpi/test/utils/index.ts new file mode 100644 index 0000000..1269deb --- /dev/null +++ b/programs/clmm-cpi/test/utils/index.ts @@ -0,0 +1,4 @@ +export * from "./web3"; +export * from "./pda"; +export * from "./util"; +export * from "./instruction"; \ No newline at end of file diff --git a/programs/clmm-cpi/test/utils/instruction.ts b/programs/clmm-cpi/test/utils/instruction.ts new file mode 100644 index 0000000..b3ffc0a --- /dev/null +++ b/programs/clmm-cpi/test/utils/instruction.ts @@ -0,0 +1,250 @@ +import { Program, BN } from "@coral-xyz/anchor"; +import { ClmmCpi } from "../../../../target/types/clmm_cpi"; +import { + Connection, + ConfirmOptions, + PublicKey, + Keypair, + Signer, + SystemProgram, + SYSVAR_RENT_PUBKEY, + ComputeBudgetProgram, +} from "@solana/web3.js"; +import { + TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, + getAssociatedTokenAddressSync, +} from "@solana/spl-token"; + +import { ClmmProgram, configAddress } from "../config"; +import { ASSOCIATED_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token"; +import { ClmmKeys, TickUtils, SqrtPriceMath } from "@raydium-io/raydium-sdk-v2"; +import { createTokenMintAndAssociatedTokenAccount } from "./util"; +import { + getNftMetadataAddress, + getOrcleAccountAddress, + getPersonalPositionAddress, + getPoolAddress, + getPoolVaultAddress, + getProtocolPositionAddress, + getTickArrayAddress, + getTickArrayBitmapAddress, +} from "./pda"; + +export async function setupInitializeTest( + connection: Connection, + owner: Signer, + transferFeeConfig: { transferFeeBasisPoints: number; MaxFee: number } = { + transferFeeBasisPoints: 0, + MaxFee: 0, + }, + confirmOptions?: ConfirmOptions +) { + const [{ token0, token0Program }, { token1, token1Program }] = + await createTokenMintAndAssociatedTokenAccount( + connection, + owner, + new Keypair(), + transferFeeConfig + ); + return { + token0, + token0Program, + token1, + token1Program, + }; +} + +export async function initialize( + program: Program, + creator: Signer, + token0: PublicKey, + token0Program: PublicKey, + token1: PublicKey, + token1Program: PublicKey, + initTick: number, + confirmOptions?: ConfirmOptions +) { + // const [ammConfigAddress, _bump] = await getAmmConfigAddress(0, ClmmProgram); + // console.log("ammConfigAddress:", ammConfigAddress.toString()); + const [poolAddress, _bump1] = await getPoolAddress( + configAddress, + token0, + token1, + ClmmProgram + ); + const [vault0, _bump2] = await getPoolVaultAddress( + poolAddress, + token0, + ClmmProgram + ); + const [vault1, _bump3] = await getPoolVaultAddress( + poolAddress, + token1, + ClmmProgram + ); + + const [tick_array_bitmap, _bump4] = await getTickArrayBitmapAddress( + poolAddress, + ClmmProgram + ); + + const [observation, _bump5] = await getOrcleAccountAddress( + poolAddress, + ClmmProgram + ); + + const [bitmapExtension, _bump111] = await getTickArrayBitmapAddress( + poolAddress, + ClmmProgram + ); + + const tx = await program.methods + .proxyInitialize(SqrtPriceMath.getSqrtPriceX64FromTick(initTick), new BN(0)) + .accounts({ + clmmProgram: ClmmProgram, + poolCreator: creator.publicKey, + ammConfig: configAddress, + poolState: poolAddress, + tokenMint0: token0, + tokenMint1: token1, + tokenVault0: vault0, + tokenVault1: vault1, + observationState: observation, + tickArrayBitmap: tick_array_bitmap, + tokenProgram0: token0Program, + tokenProgram1: token1Program, + systemProgram: SystemProgram.programId, + rent: SYSVAR_RENT_PUBKEY, + } as any) + .remainingAccounts([ + { pubkey: bitmapExtension, isSigner: false, isWritable: true }, + ]) + .preInstructions([ + ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }), + ]) + .rpc(confirmOptions); + + return { poolAddress, tx }; +} + +export async function openPosition( + program: Program, + owner: Signer, + poolKeys: ClmmKeys, + tickLowerIndex: number, + tickUpperIndex: number, + liquidity: BN, + amount0Max: BN, + amount1Max: BN, + confirmOptions?: ConfirmOptions +) { + // prepare tickArray + const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick( + tickLowerIndex, + poolKeys.config.tickSpacing + ); + const [tickArrayLower] = await getTickArrayAddress( + new PublicKey(poolKeys.id), + ClmmProgram, + tickArrayLowerStartIndex + ); + const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick( + tickUpperIndex, + poolKeys.config.tickSpacing + ); + const [tickArrayUpper] = await getTickArrayAddress( + new PublicKey(poolKeys.id), + ClmmProgram, + tickArrayUpperStartIndex + ); + const positionNftMint = Keypair.generate(); + const positionANftAccount = getAssociatedTokenAddressSync( + positionNftMint.publicKey, + owner.publicKey + ); + + const metadataAccount = ( + await getNftMetadataAddress(positionNftMint.publicKey) + )[0]; + + const [personalPosition] = await getPersonalPositionAddress( + positionNftMint.publicKey, + ClmmProgram + ); + + const [protocolPosition] = await getProtocolPositionAddress( + new PublicKey(poolKeys.id), + ClmmProgram, + tickLowerIndex, + tickUpperIndex + ); + + const token0Account = getAssociatedTokenAddressSync( + new PublicKey(poolKeys.mintA.address), + owner.publicKey, + false, + new PublicKey(poolKeys.mintA.programId) + ); + + const token1Account = getAssociatedTokenAddressSync( + new PublicKey(poolKeys.mintB.address), + owner.publicKey, + false, + new PublicKey(poolKeys.mintB.programId) + ); + + const [bitmapExtension, _bump111] = await getTickArrayBitmapAddress( + new PublicKey(poolKeys.id), + ClmmProgram + ); + + const tx = await program.methods + .proxyOpenPosition( + tickLowerIndex, + tickUpperIndex, + tickArrayLowerStartIndex, + tickArrayUpperStartIndex, + new BN(liquidity), + amount0Max, + amount1Max, + true + ) + .accounts({ + clmmProgram: ClmmProgram, + payer: owner.publicKey, + positionNftOwner: owner.publicKey, + positionNftMint: positionNftMint.publicKey, + positionNftAccount: positionANftAccount, + metadataAccount, + poolState: new PublicKey(poolKeys.id), + protocolPosition, + tickArrayLower, + tickArrayUpper, + tokenAccount0: token0Account, + tokenAccount1: token1Account, + tokenVault0: new PublicKey(poolKeys.vault.A), + tokenVault1: new PublicKey(poolKeys.vault.B), + vault0Mint: new PublicKey(poolKeys.mintA.address), + vault1Mint: new PublicKey(poolKeys.mintB.address), + personalPosition, + systemProgram: SystemProgram.programId, + rent: SYSVAR_RENT_PUBKEY, + tokenProgram: TOKEN_PROGRAM_ID, + tokenProgram2022: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_PROGRAM_ID, + metadataProgram: new PublicKey( + "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" + ), + } as any) + .remainingAccounts([ + { pubkey: bitmapExtension, isSigner: false, isWritable: true }, + ]) + .preInstructions([ + ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }), + ]) + .signers([positionNftMint]) + .rpc(confirmOptions); + + return { positionNftMint, personalPosition, protocolPosition, tx }; +} \ No newline at end of file diff --git a/programs/clmm-cpi/test/utils/pda.ts b/programs/clmm-cpi/test/utils/pda.ts new file mode 100644 index 0000000..032159a --- /dev/null +++ b/programs/clmm-cpi/test/utils/pda.ts @@ -0,0 +1,200 @@ +import { PublicKey } from "@solana/web3.js"; + +import * as anchor from "@coral-xyz/anchor"; + +export const AMM_CONFIG_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("amm_config") +); +export const POOL_SEED = Buffer.from(anchor.utils.bytes.utf8.encode("pool")); +export const POOL_VAULT_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("pool_vault") +); +export const POOL_REWARD_VAULT_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("pool_reward_vault") +); +export const POSITION_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("position") +); +export const TICK_ARRAY_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("tick_array") +); + +export const OPERATION_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("operation") +); + +export const POOL_TICK_ARRAY_BITMAP_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("pool_tick_array_bitmap_extension") +); + +export const ORACLE_SEED = Buffer.from( + anchor.utils.bytes.utf8.encode("observation") +); + +export function u16ToBytes(num: number) { + const arr = new ArrayBuffer(2); + const view = new DataView(arr); + view.setUint16(0, num, false); + return new Uint8Array(arr); +} + +export function i16ToBytes(num: number) { + const arr = new ArrayBuffer(2); + const view = new DataView(arr); + view.setInt16(0, num, false); + return new Uint8Array(arr); +} + +export function u32ToBytes(num: number) { + const arr = new ArrayBuffer(4); + const view = new DataView(arr); + view.setUint32(0, num, false); + return new Uint8Array(arr); +} + +export function i32ToBytes(num: number) { + const arr = new ArrayBuffer(4); + const view = new DataView(arr); + view.setInt32(0, num, false); + return new Uint8Array(arr); +} + +export async function getAmmConfigAddress( + index: number, + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [AMM_CONFIG_SEED, u16ToBytes(index)], + programId + ); + return [address, bump]; +} + +export async function getPoolAddress( + ammConfig: PublicKey, + tokenMint0: PublicKey, + tokenMint1: PublicKey, + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [ + POOL_SEED, + ammConfig.toBuffer(), + tokenMint0.toBuffer(), + tokenMint1.toBuffer(), + ], + programId + ); + return [address, bump]; +} + +export async function getPoolVaultAddress( + pool: PublicKey, + vaultTokenMint: PublicKey, + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [POOL_VAULT_SEED, pool.toBuffer(), vaultTokenMint.toBuffer()], + programId + ); + return [address, bump]; +} + +export async function getPoolRewardVaultAddress( + pool: PublicKey, + rewardTokenMint: PublicKey, + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [POOL_REWARD_VAULT_SEED, pool.toBuffer(), rewardTokenMint.toBuffer()], + programId + ); + return [address, bump]; +} + +export async function getTickArrayAddress( + pool: PublicKey, + programId: PublicKey, + startIndex: number +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [TICK_ARRAY_SEED, pool.toBuffer(), i32ToBytes(startIndex)], + programId + ); + return [address, bump]; +} + +export async function getProtocolPositionAddress( + pool: PublicKey, + programId: PublicKey, + tickLower: number, + tickUpper: number +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [ + POSITION_SEED, + pool.toBuffer(), + i32ToBytes(tickLower), + i32ToBytes(tickUpper), + ], + programId + ); + return [address, bump]; +} + +export async function getPersonalPositionAddress( + nftMint: PublicKey, + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [POSITION_SEED, nftMint.toBuffer()], + programId + ); + return [address, bump]; +} + +export async function getNftMetadataAddress( + nftMint: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [ + Buffer.from("metadata"), + new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").toBuffer(), + nftMint.toBuffer(), + ], + new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s") + ); + return [address, bump]; +} + +export async function getOperationAddress( + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [OPERATION_SEED], + programId + ); + return [address, bump]; +} + +export async function getTickArrayBitmapAddress( + pool: PublicKey, + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [POOL_TICK_ARRAY_BITMAP_SEED, pool.toBuffer()], + programId + ); + return [address, bump]; +} + +export async function getOrcleAccountAddress( + pool: PublicKey, + programId: PublicKey +): Promise<[PublicKey, number]> { + const [address, bump] = await PublicKey.findProgramAddress( + [ORACLE_SEED, pool.toBuffer()], + programId + ); + return [address, bump]; +} \ No newline at end of file diff --git a/programs/clmm-cpi/test/utils/util.ts b/programs/clmm-cpi/test/utils/util.ts new file mode 100644 index 0000000..0673f80 --- /dev/null +++ b/programs/clmm-cpi/test/utils/util.ts @@ -0,0 +1,279 @@ +import * as anchor from "@coral-xyz/anchor"; +import { web3 } from "@coral-xyz/anchor"; +import { + Connection, + PublicKey, + Keypair, + Signer, + TransactionInstruction, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { + createMint, + TOKEN_PROGRAM_ID, + getOrCreateAssociatedTokenAccount, + mintTo, + TOKEN_2022_PROGRAM_ID, + getAssociatedTokenAddressSync, + ExtensionType, + getMintLen, + createInitializeTransferFeeConfigInstruction, + createInitializeMintInstruction, + getAccount, +} from "@solana/spl-token"; +import { sendTransaction } from "./web3"; + +// create a token mint and a token2022 mint with transferFeeConfig +export async function createTokenMintAndAssociatedTokenAccount( + connection: Connection, + payer: Signer, + mintAuthority: Signer, + transferFeeConfig: { transferFeeBasisPoints: number; MaxFee: number } +) { + let ixs: TransactionInstruction[] = []; + ixs.push( + web3.SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: mintAuthority.publicKey, + lamports: web3.LAMPORTS_PER_SOL, + }) + ); + await sendTransaction(connection, ixs, [payer]); + + interface Token { + address: PublicKey; + program: PublicKey; + } + + let tokenArray: Token[] = []; + let token0 = await createMint( + connection, + mintAuthority, + mintAuthority.publicKey, + null, + 9 + ); + tokenArray.push({ address: token0, program: TOKEN_PROGRAM_ID }); + + let token1 = await createMintWithTransferFee( + connection, + payer, + mintAuthority, + Keypair.generate(), + transferFeeConfig + ); + + tokenArray.push({ address: token1, program: TOKEN_2022_PROGRAM_ID }); + + tokenArray.sort(function (x, y) { + const buffer1 = x.address.toBuffer(); + const buffer2 = y.address.toBuffer(); + + for (let i = 0; i < buffer1.length && i < buffer2.length; i++) { + if (buffer1[i] < buffer2[i]) { + return -1; + } + if (buffer1[i] > buffer2[i]) { + return 1; + } + } + + if (buffer1.length < buffer2.length) { + return -1; + } + if (buffer1.length > buffer2.length) { + return 1; + } + + return 0; + }); + + token0 = tokenArray[0].address; + token1 = tokenArray[1].address; + // console.log("Token 0", token0.toString()); + // console.log("Token 1", token1.toString()); + const token0Program = tokenArray[0].program; + const token1Program = tokenArray[1].program; + + const ownerToken0Account = await getOrCreateAssociatedTokenAccount( + connection, + payer, + token0, + payer.publicKey, + false, + "processed", + { skipPreflight: true }, + token0Program + ); + + await mintTo( + connection, + payer, + token0, + ownerToken0Account.address, + mintAuthority, + 100_000_000_000_000, + [], + { skipPreflight: true }, + token0Program + ); + + // console.log( + // "ownerToken0Account key: ", + // ownerToken0Account.address.toString() + // ); + + const ownerToken1Account = await getOrCreateAssociatedTokenAccount( + connection, + payer, + token1, + payer.publicKey, + false, + "processed", + { skipPreflight: true }, + token1Program + ); + // console.log( + // "ownerToken1Account key: ", + // ownerToken1Account.address.toString() + // ); + await mintTo( + connection, + payer, + token1, + ownerToken1Account.address, + mintAuthority, + 100_000_000_000_000, + [], + { skipPreflight: true }, + token1Program + ); + + return [ + { token0, token0Program }, + { token1, token1Program }, + ]; +} + +async function createMintWithTransferFee( + connection: Connection, + payer: Signer, + mintAuthority: Signer, + mintKeypair = Keypair.generate(), + transferFeeConfig: { transferFeeBasisPoints: number; MaxFee: number } +) { + const transferFeeConfigAuthority = Keypair.generate(); + const withdrawWithheldAuthority = Keypair.generate(); + + const extensions = [ExtensionType.TransferFeeConfig]; + + const mintLen = getMintLen(extensions); + const decimals = 9; + + const mintLamports = await connection.getMinimumBalanceForRentExemption( + mintLen + ); + const mintTransaction = new Transaction().add( + SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + newAccountPubkey: mintKeypair.publicKey, + space: mintLen, + lamports: mintLamports, + programId: TOKEN_2022_PROGRAM_ID, + }), + createInitializeTransferFeeConfigInstruction( + mintKeypair.publicKey, + transferFeeConfigAuthority.publicKey, + withdrawWithheldAuthority.publicKey, + transferFeeConfig.transferFeeBasisPoints, + BigInt(transferFeeConfig.MaxFee), + TOKEN_2022_PROGRAM_ID + ), + createInitializeMintInstruction( + mintKeypair.publicKey, + decimals, + mintAuthority.publicKey, + null, + TOKEN_2022_PROGRAM_ID + ) + ); + await sendAndConfirmTransaction( + connection, + mintTransaction, + [payer, mintKeypair], + undefined + ); + + return mintKeypair.publicKey; +} + +export async function getUserAndPoolVaultAmount( + owner: PublicKey, + token0Mint: PublicKey, + token0Program: PublicKey, + token1Mint: PublicKey, + token1Program: PublicKey, + poolToken0Vault: PublicKey, + poolToken1Vault: PublicKey +) { + const onwerToken0AccountAddr = getAssociatedTokenAddressSync( + token0Mint, + owner, + false, + token0Program + ); + + const onwerToken1AccountAddr = getAssociatedTokenAddressSync( + token1Mint, + owner, + false, + token1Program + ); + + const onwerToken0Account = await getAccount( + anchor.getProvider().connection, + onwerToken0AccountAddr, + "processed", + token0Program + ); + + const onwerToken1Account = await getAccount( + anchor.getProvider().connection, + onwerToken1AccountAddr, + "processed", + token1Program + ); + + const poolVault0TokenAccount = await getAccount( + anchor.getProvider().connection, + poolToken0Vault, + "processed", + token0Program + ); + + const poolVault1TokenAccount = await getAccount( + anchor.getProvider().connection, + poolToken1Vault, + "processed", + token1Program + ); + return { + onwerToken0Account, + onwerToken1Account, + poolVault0TokenAccount, + poolVault1TokenAccount, + }; +} + +export function isEqual(amount1: bigint, amount2: bigint) { + if ( + BigInt(amount1) === BigInt(amount2) || + BigInt(amount1) - BigInt(amount2) === BigInt(1) || + BigInt(amount1) - BigInt(amount2) === BigInt(-1) + ) { + return true; + } + return false; +} \ No newline at end of file diff --git a/programs/clmm-cpi/test/utils/web3.ts b/programs/clmm-cpi/test/utils/web3.ts new file mode 100644 index 0000000..9c7e97c --- /dev/null +++ b/programs/clmm-cpi/test/utils/web3.ts @@ -0,0 +1,65 @@ +import * as anchor from "@coral-xyz/anchor"; +import { + Connection, + Signer, + Transaction, + TransactionInstruction, + TransactionSignature, + ConfirmOptions, +} from "@solana/web3.js"; + +export async function accountExist( + connection: anchor.web3.Connection, + account: anchor.web3.PublicKey +) { + const info = await connection.getAccountInfo(account); + if (info == null || info.data.length == 0) { + return false; + } + return true; +} + +export async function sendTransaction( + connection: Connection, + ixs: TransactionInstruction[], + signers: Array, + options?: ConfirmOptions +): Promise { + const tx = new Transaction(); + for (var i = 0; i < ixs.length; i++) { + tx.add(ixs[i]); + } + + if (options == undefined) { + options = { + preflightCommitment: "confirmed", + commitment: "confirmed", + }; + } + + const sendOpt = options && { + skipPreflight: options.skipPreflight, + preflightCommitment: options.preflightCommitment || options.commitment, + }; + + tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; + const signature = await connection.sendTransaction(tx, signers, sendOpt); + + const status = ( + await connection.confirmTransaction(signature, options.commitment) + ).value; + + if (status.err) { + throw new Error( + `Raw transaction ${signature} failed (${JSON.stringify(status)})` + ); + } + return signature; +} + +export async function getBlockTimestamp( + connection: Connection +): Promise { + let slot = await connection.getSlot(); + return await connection.getBlockTime(slot); +} \ No newline at end of file diff --git a/programs/tax-token/src/lib.rs b/programs/tax-token/src/lib.rs index 1ad2c37..6222cb1 100644 --- a/programs/tax-token/src/lib.rs +++ b/programs/tax-token/src/lib.rs @@ -5,7 +5,7 @@ use instructions::*; declare_id!("Yo1bzVsiuVigxHUmQguSZ83QJ879A4d6cQiGYFeDDMF"); #[program] -pub mod solana_tax_token_anchor { +pub mod tax_token { use super::*; pub fn initialize( ctx: Context, diff --git a/programs/token-launchpad/Cargo.toml b/programs/token-launchpad/Cargo.toml index 2715611..ddad20c 100644 --- a/programs/token-launchpad/Cargo.toml +++ b/programs/token-launchpad/Cargo.toml @@ -19,4 +19,7 @@ idl-build = ["anchor-lang/idl-build"] [dependencies] anchor-lang = "0.30.1" +anchor-spl = { version = "0.30.1", features = ["metadata", "memo"] } +clmm-cpi = { path = "../clmm-cpi", features = ["cpi"] } +tax-token = { path = "../tax-token", features = ["cpi"] } diff --git a/programs/token-launchpad/src/constants.rs b/programs/token-launchpad/src/constants.rs new file mode 100644 index 0000000..5141eb1 --- /dev/null +++ b/programs/token-launchpad/src/constants.rs @@ -0,0 +1,3 @@ +pub const LAUNCHPAD_CONFIG_SEED: &[u8] = b"launchpad_config"; +pub const TOKEN_SUPPLY: u64 = 1_000_000_000_000; // 1 trillion +pub const TOKEN_DECIMALS: u8 = 6; \ No newline at end of file diff --git a/programs/token-launchpad/src/instructions/create_and_buy.rs b/programs/token-launchpad/src/instructions/create_and_buy.rs new file mode 100644 index 0000000..33044a1 --- /dev/null +++ b/programs/token-launchpad/src/instructions/create_and_buy.rs @@ -0,0 +1,307 @@ +use anchor_lang::prelude::*; +use anchor_lang::system_program::{transfer, Transfer}; +use anchor_spl::{ + associated_token::AssociatedToken, + metadata::Metadata, + token::Token, + token_2022::{ + spl_token_2022::{ + extension::{ + transfer_fee::TransferFeeConfig, BaseStateWithExtensions, ExtensionType, + StateWithExtensions, + }, + pod::PodMint, + state::Mint as MintState, + }, + initialize_mint2, transfer_fee_initialize, InitializeMint2, Token2022, TransferFeeInitialize, + }, + token_interface::{Mint, TokenAccount, TokenInterface, spl_pod::optional_keys::OptionalNonZeroPubkey}, +}; +use anchor_lang::system_program::{create_account, CreateAccount}; + +// Import this to use the add_single_side_liquidity CPI +use crate::cpi_interfaces::clmm; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateTokenParams { + pub transfer_fee_basis_points: u16, + pub maximum_fee: u64, + pub creation_fee: u64, + pub tick_lower_index: i32, + pub tick_upper_index: i32, + pub tick_array_lower_start_index: i32, + pub tick_array_upper_start_index: i32, + pub liquidity: u128, + pub amount_tax_token_max: u64, + pub amount_sol_max: u64, +} + +#[derive(Accounts)] +pub struct CreateAndBuy<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account(mut)] + pub mint_account: Signer<'info>, + + // Fee collector account + /// CHECK: This is just a receiver + #[account(mut)] + pub fee_receiver: UncheckedAccount<'info>, + + // Tax token program accounts + pub token_program_2022: Program<'info, Token2022>, + + // CLMM-related accounts + pub clmm_program: Program<'info, clmm::RaydiumClmm>, + pub amm_config: Box>, + + /// CHECK: Position NFT owner + pub position_nft_owner: UncheckedAccount<'info>, + + /// Position NFT mint + #[account(mut)] + pub position_nft_mint: Signer<'info>, + + /// CHECK: Token account for position NFT + #[account(mut)] + pub position_nft_account: UncheckedAccount<'info>, + + /// CHECK: Metadata account + #[account(mut)] + pub metadata_account: UncheckedAccount<'info>, + + /// Pool state account + #[account(mut)] + pub pool_state: AccountLoader<'info, clmm::PoolState>, + + /// CHECK: Protocol position + #[account(mut)] + pub protocol_position: UncheckedAccount<'info>, + + /// CHECK: Lower tick array + #[account(mut)] + pub tick_array_lower: UncheckedAccount<'info>, + + /// CHECK: Upper tick array + #[account(mut)] + pub tick_array_upper: UncheckedAccount<'info>, + + /// CHECK: Personal position + #[account(mut)] + pub personal_position: UncheckedAccount<'info>, + + // WSOL mint + pub wsol_mint: Box>, + + /// Tax token account + #[account(mut)] + pub tax_token_account: Box>, + + /// WSOL account + #[account(mut)] + pub wsol_account: Box>, + + /// Tax token vault + #[account(mut)] + pub token_vault_0: Box>, + + /// WSOL vault + #[account(mut)] + pub token_vault_1: Box>, + + /// CHECK: Observation state + #[account(mut)] + pub observation_state: UncheckedAccount<'info>, + + /// CHECK: Tick array bitmap + #[account(mut)] + pub tick_array_bitmap: UncheckedAccount<'info>, + + // Other required programs + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub metadata_program: Program<'info, Metadata>, + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent>, +} + +pub fn process_create_and_buy(ctx: Context, params: CreateTokenParams) -> Result<()> { + // 1. Handle creation fees - charge SOL from the user + handle_creation_fees(&ctx, params.creation_fee)?; + + // 2. Create the tax token + create_token( + &ctx, + params.transfer_fee_basis_points, + params.maximum_fee, + )?; + + // 3. Add single-sided liquidity + handle_add_single_side_liquidity( + &ctx, + params.tick_lower_index, + params.tick_upper_index, + params.tick_array_lower_start_index, + params.tick_array_upper_start_index, + params.liquidity, + params.amount_tax_token_max, + params.amount_sol_max, + )?; + + Ok(()) +} + +fn handle_creation_fees(ctx: &Context, fee: u64) -> Result<()> { + // Transfer SOL from payer to fee_receiver + transfer( + CpiContext::new( + ctx.accounts.system_program.to_account_info(), + Transfer { + from: ctx.accounts.payer.to_account_info(), + to: ctx.accounts.fee_receiver.to_account_info(), + }, + ), + fee, + )?; + + Ok(()) +} + +fn create_token( + ctx: &Context, + transfer_fee_basis_points: u16, + maximum_fee: u64, +) -> Result<()> { + // Calculate space required for mint and extension data + let mint_size = + ExtensionType::try_calculate_account_len::(&[ExtensionType::TransferFeeConfig])?; + + // Calculate minimum lamports required for size of mint account with extensions + let lamports = (Rent::get()?).minimum_balance(mint_size); + + // Invoke System Program to create new account with space for mint and extension data + create_account( + CpiContext::new( + ctx.accounts.system_program.to_account_info(), + CreateAccount { + from: ctx.accounts.payer.to_account_info(), + to: ctx.accounts.mint_account.to_account_info(), + }, + ), + lamports, // Lamports + mint_size as u64, // Space + &ctx.accounts.token_program_2022.key(), // Owner Program + )?; + + // Initialize the transfer fee extension data + // This instruction must come before the instruction to initialize the mint data + transfer_fee_initialize( + CpiContext::new( + ctx.accounts.token_program_2022.to_account_info(), + TransferFeeInitialize { + token_program_id: ctx.accounts.token_program_2022.to_account_info(), + mint: ctx.accounts.mint_account.to_account_info(), + }, + ), + Some(&ctx.accounts.payer.key()), // transfer fee config authority (update fee) + Some(&ctx.accounts.payer.key()), // withdraw authority (withdraw fees) + transfer_fee_basis_points, // transfer fee basis points (% fee per transfer) + maximum_fee, // maximum fee (maximum units of token per transfer) + )?; + + // Initialize the standard mint account data + initialize_mint2( + CpiContext::new( + ctx.accounts.token_program_2022.to_account_info(), + InitializeMint2 { + mint: ctx.accounts.mint_account.to_account_info(), + }, + ), + 2, // decimals + &ctx.accounts.payer.key(), // mint authority + Some(&ctx.accounts.payer.key()), // freeze authority + )?; + + // Verify mint data + let mint = &ctx.accounts.mint_account.to_account_info(); + let mint_data = mint.data.borrow(); + let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; + let extension_data = mint_with_extension.get_extension::()?; + + assert_eq!( + extension_data.transfer_fee_config_authority, + OptionalNonZeroPubkey::try_from(Some(ctx.accounts.payer.key()))? + ); + + assert_eq!( + extension_data.withdraw_withheld_authority, + OptionalNonZeroPubkey::try_from(Some(ctx.accounts.payer.key()))? + ); + + msg!("Tax token created successfully"); + + Ok(()) +} + +fn handle_add_single_side_liquidity( + ctx: &Context, + tick_lower_index: i32, + tick_upper_index: i32, + tick_array_lower_start_index: i32, + tick_array_upper_start_index: i32, + liquidity: u128, + amount_tax_token_max: u64, + amount_sol_max: u64, +) -> Result<()> { + // Use CPI to call the add_single_side_liquidity on Raydium CLMM program + clmm::add_single_side_liquidity( + CpiContext::new( + ctx.accounts.clmm_program.to_account_info(), + clmm::AddSingleSideLiquidity { + payer: ctx.accounts.payer.to_account_info(), + position_nft_owner: ctx.accounts.position_nft_owner.to_account_info(), + position_nft_mint: ctx.accounts.position_nft_mint.to_account_info(), + position_nft_account: ctx.accounts.position_nft_account.to_account_info(), + metadata_account: ctx.accounts.metadata_account.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + protocol_position: ctx.accounts.protocol_position.to_account_info(), + tick_array_lower: ctx.accounts.tick_array_lower.to_account_info(), + tick_array_upper: ctx.accounts.tick_array_upper.to_account_info(), + personal_position: ctx.accounts.personal_position.to_account_info(), + token_mint_0: ctx.accounts.mint_account.to_account_info(), + token_mint_1: ctx.accounts.wsol_mint.to_account_info(), + token_account_0: ctx.accounts.tax_token_account.to_account_info(), + token_account_1: ctx.accounts.wsol_account.to_account_info(), + token_vault_0: ctx.accounts.token_vault_0.to_account_info(), + token_vault_1: ctx.accounts.token_vault_1.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + tick_array_bitmap: ctx.accounts.tick_array_bitmap.to_account_info(), + token_program_0: ctx.accounts.token_program_2022.to_account_info(), + token_program_1: ctx.accounts.token_program.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + associated_token_program: ctx.accounts.associated_token_program.to_account_info(), + metadata_program: ctx.accounts.metadata_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + vault_0_mint: ctx.accounts.mint_account.to_account_info(), + vault_1_mint: ctx.accounts.wsol_mint.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + rent: ctx.accounts.rent.to_account_info(), + }, + ), + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + liquidity, + amount_tax_token_max, + amount_sol_max, + true, // with_metadata + Some(true), // base_flag + )?; + + msg!("Single-sided liquidity added successfully"); + + Ok(()) +} diff --git a/programs/token-launchpad/src/instructions/mod.rs b/programs/token-launchpad/src/instructions/mod.rs new file mode 100644 index 0000000..936e11b --- /dev/null +++ b/programs/token-launchpad/src/instructions/mod.rs @@ -0,0 +1 @@ +pub mod create_and_buy; \ No newline at end of file diff --git a/programs/token-launchpad/src/lib.rs b/programs/token-launchpad/src/lib.rs index 111a59e..561fad0 100644 --- a/programs/token-launchpad/src/lib.rs +++ b/programs/token-launchpad/src/lib.rs @@ -1,16 +1,28 @@ use anchor_lang::prelude::*; -declare_id!("AJBmAnT7A7RBxyK2USQUU1deZj6H3TLYM6RWuG3yaxoJ"); +// Import the modules +pub mod instructions; +pub mod cpi_interfaces; +mod model; +mod constants; +use model::CreateTokenParams; +use instructions::create_and_buy::{CreateAndBuy, process_create_and_buy}; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +/// Main program module #[program] -pub mod solana_token_launchpad { +pub mod token_launchpad { use super::*; - pub fn initialize(ctx: Context) -> Result<()> { - msg!("Greetings from: {:?}", ctx.program_id); - Ok(()) + /// Creates a new token and lets the caller buy it + pub fn create_and_buy(ctx: Context, params: CreateTokenParams) -> Result<()> { + process_create_and_buy(ctx, params) } } +/// Basic initialization context #[derive(Accounts)] pub struct Initialize {} + diff --git a/programs/token-launchpad/src/model.rs b/programs/token-launchpad/src/model.rs new file mode 100644 index 0000000..e76bf28 --- /dev/null +++ b/programs/token-launchpad/src/model.rs @@ -0,0 +1,19 @@ +use anchor_lang::prelude::*; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateTokenParams { + pub token_name: String, + pub token_symbol: String, + pub token_metadata_uri: String, + pub token_supply: u64, + pub buy_amount: u64, + pub bump: u8, +} + +#[account] +#[derive(Default)] +pub struct LaunchpadConfig { + pub authority: Pubkey, + pub creation_fee: u64, + pub bump: u8, +} \ No newline at end of file