diff --git a/.github/workflows/ci-pr-main-program.yml b/.github/workflows/ci-pr-main-program.yml index cd0cba1..bb337b0 100644 --- a/.github/workflows/ci-pr-main-program.yml +++ b/.github/workflows/ci-pr-main-program.yml @@ -9,7 +9,7 @@ on: env: SOLANA_CLI_VERSION: 1.18.21 NODE_VERSION: 18.14.2 - ANCHOR_CLI_VERSION: 0.29.0 + ANCHOR_CLI_VERSION: 0.30.1 jobs: program_changed_files: diff --git a/CHANGELOG.md b/CHANGELOG.md index 684d13f..c105f59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,20 +21,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes -## Program [0.3.0] [PR #5](https://github.com/jup-ag/jup-lock/pull/5) [PR #15](https://github.com/jup-ag/jup-lock/pull/15) +## Program [0.3.0] [PR #5](https://github.com/jup-ag/jup-lock/pull/5) [PR #15](https://github.com/jup-ag/jup-lock/pull/15) [PR #18](https://github.com/jup-ag/jup-lock/pull/18) ### Breaking Changes - Endpoint `create_vesting_escrow` add `cancel_mode` to indicates who can cancel the escrow. +### Changed + +- Bump `anchor` version to 0.30.1 + ### Added - escrow state add `token_program_flag` to indicates the token program used within the escrow. - escrow state add `cancelled_at` to indicates the timestamp of the cancellation. -- Add new instruction `cancel_vesting_escrow`, which will cancel the escrow and close the `escrow_token` token account. The claimable amount will be transferred to recipient and the remaining amount will be transferred to creator. The instruction supports both `splToken` and `token2022`. -- Add new v2 instructions to support `token2022` extensions, including: `TransferFeeConfig`, `TokenMetadata`, `MetadataPointer`, `ConfidentialTransferMint`, `ConfidentialTransferFeeConfig`, `PermanentDelegate`, `TransferHook`, `MintCloseAuthority`, `DefaultAccountState` for Token Mint and `MemoTransfer` for Token Account extensions - - `create_vesting_escrow_v2` to create the escrow relevant accounts. - - `claim_v2` to claim from the escrow. +- Add new instruction `cancel_vesting_escrow`, which will cancel the escrow and close the `escrow_token` token account. + The claimable amount will be transferred to recipient and the remaining amount will be transferred to creator. The + instruction supports both `splToken` and `token2022`. +- Add new v2 instructions to support `token2022` extensions, + including: `TransferFeeConfig`, `TokenMetadata`, `MetadataPointer`, `ConfidentialTransferMint`, `ConfidentialTransferFeeConfig`, `PermanentDelegate`, `TransferHook`, `MintCloseAuthority`, `DefaultAccountState`, `GroupPointer`, `GroupMemberPointer` + for Token Mint and `MemoTransfer` for Token Account extensions + - `create_vesting_escrow_v2` to create the escrow relevant accounts. + - `claim_v2` to claim from the escrow. ## Program [0.2.2] [PR #8](https://github.com/jup-ag/jup-lock/pull/8) diff --git a/package.json b/package.json index e3dde02..46243ae 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" }, "dependencies": { - "@coral-xyz/anchor": "^0.29.0", - "@solana/spl-token": "^0.4.1", + "@coral-xyz/anchor": "^0.30.1", + "@solana/spl-token": "^0.4.8", "tiny-invariant": "^1.3.3" }, "devDependencies": { diff --git a/programs/locker/Cargo.toml b/programs/locker/Cargo.toml index 053186f..8e5039f 100644 --- a/programs/locker/Cargo.toml +++ b/programs/locker/Cargo.toml @@ -17,10 +17,11 @@ cpi = ["no-entrypoint"] default = [] localnet = [] staging = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] [dependencies] -anchor-lang = { version = "0.29.0", features = ["event-cpi"] } -anchor-spl = { version = "0.29.0", features = ["memo"] } +anchor-lang = { version = "0.30.1", features = ["event-cpi"] } +anchor-spl = { version = "0.30.1", features = ["memo"] } spl-transfer-hook-interface = "0.5.0" solana-program = "1.18.21" spl-token = { version = "3.5.0", features = ["no-entrypoint"] } @@ -29,5 +30,4 @@ static_assertions = "1.1.0" num_enum = "0.7.1" [dev-dependencies] -proptest = "1.2.0" -spl-pod = "0.1.0" \ No newline at end of file +proptest = "1.2.0" \ No newline at end of file diff --git a/programs/locker/src/util/token2022.rs b/programs/locker/src/util/token2022.rs index 8fd31e2..2c7a9d3 100644 --- a/programs/locker/src/util/token2022.rs +++ b/programs/locker/src/util/token2022.rs @@ -2,15 +2,15 @@ use anchor_lang::prelude::*; use anchor_spl::memo; use anchor_spl::memo::{BuildMemo, Memo}; use anchor_spl::token::Token; -use anchor_spl::token_2022::spl_token_2022::extension::transfer_fee::{ - TransferFee, TransferFeeConfig, MAX_FEE_BASIS_POINTS, -}; use anchor_spl::token_2022::spl_token_2022::{ self, extension::{self, StateWithExtensions}, }; -use anchor_spl::token_interface::spl_token_2022::extension::BaseStateWithExtensions; +use anchor_spl::token_2022::spl_token_2022::extension::transfer_fee::{ + MAX_FEE_BASIS_POINTS, TransferFee, TransferFeeConfig, +}; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; +use anchor_spl::token_interface::spl_token_2022::extension::BaseStateWithExtensions; use crate::{LockerError, VestingEscrow}; @@ -179,6 +179,11 @@ pub fn validate_mint(token_mint: &InterfaceAccount) -> Result<()> { extension::ExtensionType::TransferHook => {} extension::ExtensionType::MintCloseAuthority => {} extension::ExtensionType::DefaultAccountState => {} + extension::ExtensionType::GroupMemberPointer => {} + extension::ExtensionType::GroupPointer => {} + // Not stable yet to support + // extension::ExtensionType::TokenGroup => {} + // extension::ExtensionType::TokenGroupMember => {} // mint has unknown or unsupported extensions _ => { return Err(LockerError::UnsupportedMint.into()); @@ -280,8 +285,8 @@ pub fn calculate_pre_fee_amount(transfer_fee: &TransferFee, post_fee_amount: u64 #[cfg(test)] mod token2022_tests { + use anchor_spl::token_interface::spl_pod::primitives::{PodU16, PodU64}; use proptest::prelude::*; - use spl_pod::primitives::{PodU16, PodU64}; use super::*; diff --git a/tests/locker_utils/index.ts b/tests/locker_utils/index.ts index ba3d8f7..91d7c20 100644 --- a/tests/locker_utils/index.ts +++ b/tests/locker_utils/index.ts @@ -1,5 +1,12 @@ -import { AnchorProvider, BN, Program, Wallet, web3 } from "@coral-xyz/anchor"; -import { IDL as LockerIDL, Locker } from "../../target/types/locker"; +import { + AnchorProvider, + BN, + Program, + Wallet, + web3, + workspace, +} from "@coral-xyz/anchor"; +import { Locker } from "../../target/types/locker"; import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, @@ -31,8 +38,8 @@ export function createLockerProgram(wallet?: Wallet): Program { maxRetries: 3, }); provider.opts.commitment = "confirmed"; - const program = new Program(LockerIDL, LOCKER_PROGRAM_ID, provider); - return program; + + return workspace.Locker as Program; } export function deriveEscrow(base: web3.PublicKey, programId: web3.PublicKey) { @@ -139,7 +146,7 @@ export async function createVestingPlan(params: CreateVestingPlanParams) { ASSOCIATED_TOKEN_PROGRAM_ID ), ]) - .signers([baseKP]) + .signers([baseKP, ownerKeypair]) .rpc(); if (isAssertion) { @@ -197,6 +204,7 @@ export async function claimToken(params: ClaimTokenParams) { recipient: recipient.publicKey, recipientToken, }) + .signers([recipient]) .rpc(); } @@ -236,6 +244,7 @@ export async function createEscrowMetadata(params: CreateEscrowMetadataParams) { creator: creator.publicKey, escrowMetadata, }) + .signers([creator]) .rpc(); if (isAssertion) { @@ -278,6 +287,7 @@ export async function updateRecipient(params: UpdateRecipientParams) { signer: signer.publicKey, systemProgram: web3.SystemProgram.programId, }) + .signers([signer]) .rpc(); if (isAssertion) { @@ -390,7 +400,7 @@ export async function createVestingPlanV2(params: CreateVestingPlanParams) { ASSOCIATED_TOKEN_PROGRAM_ID ), ]) - .signers([baseKP]) + .signers([baseKP, ownerKeypair]) .rpc(); if (isAssertion) { @@ -569,6 +579,7 @@ export async function cancelVestingPlan( }), ]) .remainingAccounts(remainingAccounts ? remainingAccounts : []) + .signers([signer]) .rpc(); if (isAssertion) { diff --git a/tests/locker_utils/token_2022/mint.ts b/tests/locker_utils/token_2022/mint.ts index 85e7518..da14a5f 100644 --- a/tests/locker_utils/token_2022/mint.ts +++ b/tests/locker_utils/token_2022/mint.ts @@ -9,6 +9,8 @@ import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotent, createInitializeDefaultAccountStateInstruction, + createInitializeGroupMemberPointerInstruction, + createInitializeGroupPointerInstruction, createInitializeInterestBearingMintInstruction, createInitializeMintCloseAuthorityInstruction, createInitializeMintInstruction, @@ -71,7 +73,7 @@ export async function createMintTransaction( let transferFeeConfigAuthority = new web3.Keypair(); let withdrawWithheldAuthority = new web3.Keypair(); - let { instructions, postInstructions, additionalLength } = + let { instructions, postInstructions, additionalLength, rentReserveSpace } = createExtensionMintIx( extensions, UserKP, @@ -82,7 +84,9 @@ export async function createMintTransaction( let mintLen = getMintLen(extensions) + additionalLength; const mintLamports = - await provider.connection.getMinimumBalanceForRentExemption(mintLen); + await provider.connection.getMinimumBalanceForRentExemption( + mintLen + rentReserveSpace + ); const mintTransaction = new Transaction().add( SystemProgram.createAccount({ @@ -163,11 +167,13 @@ function createExtensionMintIx( instructions: web3.TransactionInstruction[]; postInstructions: web3.TransactionInstruction[]; additionalLength: number; + rentReserveSpace: number; } { const ix = []; const postIx = []; let confidentialTransferMintSizePatch = 0; let confidentialTransferFeeConfigSizePatch = 0; + let groupPointerSize = 0; if (extensions.includes(ExtensionType.TransferFeeConfig)) { ix.push( @@ -275,12 +281,50 @@ function createExtensionMintIx( ); } + if (extensions.includes(ExtensionType.GroupPointer)) { + ix.push( + createInitializeGroupPointerInstruction( + TOKEN, + UserKP.publicKey, + TOKEN, + TOKEN_2022_PROGRAM_ID + ) + ); + + // This extension is not yet stable + // Trying this https://solana.com/developers/courses/token-extensions/group-member#lab + // However, the instruction always failed with error 0xc. + // groupPointerSize = TOKEN_GROUP_SIZE; + // postIx.push( + // createInitializeGroupInstruction({ + // group: TOKEN, + // maxSize: 10, + // mint: TOKEN, + // mintAuthority: UserKP.publicKey, + // programId: TOKEN_2022_PROGRAM_ID, + // updateAuthority: UserKP.publicKey, + // }) + // ); + } + + if (extensions.includes(ExtensionType.GroupMemberPointer)) { + ix.push( + createInitializeGroupMemberPointerInstruction( + TOKEN, + UserKP.publicKey, + TOKEN, + TOKEN_2022_PROGRAM_ID + ) + ); + } + return { instructions: ix, postInstructions: postIx, additionalLength: confidentialTransferMintSizePatch + confidentialTransferFeeConfigSizePatch, + rentReserveSpace: groupPointerSize, }; } diff --git a/tests/v2/test_extensions.ts b/tests/v2/test_extensions.ts index 06ae384..3e4aaf1 100644 --- a/tests/v2/test_extensions.ts +++ b/tests/v2/test_extensions.ts @@ -9,8 +9,8 @@ import { BN } from "bn.js"; import { createAndFundWallet, getCurrentBlockTime, sleep } from "../common"; import { claimTokenV2, - createVestingPlanV2, createLockerProgram, + createVestingPlanV2, } from "../locker_utils"; import { assert } from "chai"; import { ADMIN, createMintTransaction } from "../locker_utils/token_2022/mint"; @@ -129,6 +129,22 @@ describe("[V2] Test supported/unsupported Token Mint", () => { await check(TOKEN); }); + it("GroupPointer", async () => { + let extensions = [ExtensionType.GroupPointer]; + + TOKEN = await createMintTransaction(provider, UserKP, extensions); + + await check(TOKEN); + }); + + it("GroupMemberPointer", async () => { + let extensions = [ExtensionType.GroupMemberPointer]; + + TOKEN = await createMintTransaction(provider, UserKP, extensions); + + await check(TOKEN); + }); + async function check(TOKEN: web3.PublicKey, errorMsg = "Unsupported mint") { const program = createLockerProgram(new anchor.Wallet(UserKP)); let currentBlockTime = await getCurrentBlockTime( diff --git a/tsconfig.json b/tsconfig.json index 558b83e..a442941 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,19 @@ { - "compilerOptions": { - "types": ["mocha", "chai"], - "typeRoots": ["./node_modules/@types"], - "lib": ["es2015"], - "module": "commonjs", - "target": "es6", - "esModuleInterop": true - } - } + "compilerOptions": { + "types": [ + "mocha", + "chai" + ], + "typeRoots": [ + "./node_modules/@types" + ], + "lib": [ + "es2015" + ], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true, + "resolveJsonModule": true + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f0bf7f9..5200941 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" @@ -205,7 +211,7 @@ "@solana/codecs" "2.0.0-preview.2" "@solana/spl-type-length-value" "0.1.0" -"@solana/spl-token@^0.4.1": +"@solana/spl-token@^0.4.8": version "0.4.8" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.8.tgz#a84e4131af957fa9fbd2727e5fc45dfbf9083586" integrity sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA==