Cos'è CPI Guard?
L'estensione dell'account CpiGuard del Token Extensions Program impedisce
a un altro programma onchain di utilizzare un token account per determinate
istruzioni token attraverso una Cross Program Invocation (CPI).
Quando CPI guard è abilitato, il Token Extensions Program rifiuta queste istruzioni quando un altro programma onchain le invoca tramite CPI:
Transfer,TransferCheckedeTransferCheckedWithFeequando il proprietario del token account viene utilizzato come autorità dell'istruzioneBurneBurnCheckedquando il proprietario del token account viene utilizzato come autorità dell'istruzioneApproveeApproveCheckedCloseAccountquando i lamport verrebbero inviati a un destinatario diverso dal proprietario del token accountSetAuthorityquando si aggiunge o si sostituisce l'autorità di chiusura
Quando CPI guard è abilitato, questi casi sono ancora consentiti:
Transfer,TransferChecked,TransferCheckedWithFee,BurneBurnCheckedtramite CPI quando firma un delegato o un delegato permanente invece del proprietario del token accountCloseAccounttramite CPI quando i lamport vengono inviati al proprietario del token accountSetAuthorityper rimuovere un'autorità di chiusura esistenteRevoke
Quando il proprietario del token account viene utilizzato come autorità
dell'istruzione, le istruzioni del Token Extensions Program possono ancora
essere elaborate se l'istruzione viene aggiunta direttamente alla transazione
invece di essere invocata tramite CPI. Il cambio del proprietario del token
account con SetAuthority rimane bloccato finché CPI guard è abilitato,
anche al di fuori di CPI. Anche EnableCpiGuard e DisableCpiGuard non
possono essere invocati tramite CPI.
Come Abilitare CPI Guard
Per abilitare CPI guard:
- Creare e inizializzare un mint.
- Calcolare la dimensione del token account e il rent necessario per un token
account con
CpiGuard. - Creare e inizializzare un token account per il mint.
- Invocare
EnableCpiGuardsul token account. - Invocare
DisableCpiGuardsul token account per consentire nuovamente tali istruzioni tramite CPI.
Creare e inizializzare il mint
Creare e inizializzare il mint che il token account utilizzerà.
Calcolare la dimensione del token account
Calcolare la dimensione del token account per il token account base più
l'estensione CpiGuard. Questa è la dimensione utilizzata in
CreateAccount.
Calcolare il rent
Calcolare il rent utilizzando la dimensione del token account necessaria per
l'estensione CpiGuard.
Creare e inizializzare il token account
Creare il token account con lo spazio e i lamport calcolati, quindi inizializzarlo per il mint.
Abilitare CPI guard
Abilitare CpiGuard sul token account.
Disabilitare CPI guard
Disabilitare CpiGuard sul token account.
Riferimento Sorgente
| Elemento | Descrizione | Sorgente |
|---|---|---|
CpiGuard | Estensione dell'account che memorizza se la protezione CPI è attualmente abilitata per il token account. | Sorgente |
CpiGuardInstruction::Enable | Istruzione che abilita la protezione CPI su un token account. | Sorgente |
CpiGuardInstruction::Disable | Istruzione che disabilita la protezione CPI su un token account. | Sorgente |
process_toggle_cpi_guard | Logica del processore utilizzata da Enable e Disable che convalida la proprietà dell'account, rifiuta le modifiche CPI alla protezione stessa e inizializza CpiGuard se non è già presente prima di impostare lock_cpi. | Sorgente |
Typescript
L'esempio Kit qui sotto utilizza le istruzioni generate direttamente. Esempi
legacy che utilizzano @solana/web3.js e @solana/spl-token sono inclusi per
riferimento.
Kit
import {lamports,createClient,generateKeyPairSigner,unwrapOption} from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { getCreateAccountInstruction } from "@solana-program/system";import {extension,fetchToken,getDisableCpiGuardInstruction,getEnableCpiGuardInstruction,getInitializeAccountInstruction,getInitializeMintInstruction,getMintSize,getTokenSize,isExtension,TOKEN_2022_PROGRAM_ADDRESS} from "@solana-program/token-2022";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const tokenAccount = await generateKeyPairSigner();const mintSpace = BigInt(getMintSize());const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding account creation.newAccount: mint, // New mint account to create.lamports: mintRent, // Lamports funding the mint account rent.space: mintSpace, // Account size in bytes for the mint account.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializeMintInstruction({mint: mint.address, // Mint account to initialize.decimals: 0, // Number of decimals for the token.mintAuthority: client.payer.address, // Authority allowed to mint new tokens.freezeAuthority: client.payer.address // Authority allowed to freeze token accounts.})]);const cpiGuardExtension = extension("CpiGuard", {lockCpi: false});const tokenSpace = BigInt(getTokenSize([cpiGuardExtension]));const tokenRent = await client.rpc.getMinimumBalanceForRentExemption(tokenSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding account creation.newAccount: tokenAccount, // New token account to create.lamports: tokenRent, // Lamports funding the token account rent.space: tokenSpace, // Account size in bytes for the token account plus CpiGuard.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the token account.}),getInitializeAccountInstruction({account: tokenAccount.address, // Token account to initialize.mint: mint.address, // Mint for the token account.owner: client.payer.address // Owner allowed to control the token account.})]);await client.sendTransaction([getEnableCpiGuardInstruction({token: tokenAccount.address, // Token account that stores the CpiGuard extension.owner: client.payer // Token account owner authorized to enable CPI guard.})]);const enabledTokenAccount = await fetchToken(client.rpc, tokenAccount.address);const enabledCpiGuard = (unwrapOption(enabledTokenAccount.data.extensions) ?? []).find((item) => isExtension("CpiGuard", item));await client.sendTransaction([getDisableCpiGuardInstruction({token: tokenAccount.address, // Token account that stores the CpiGuard extension.owner: client.payer // Token account owner authorized to disable CPI guard.})]);const disabledTokenAccount = await fetchToken(client.rpc, tokenAccount.address);const disabledCpiGuard = (unwrapOption(disabledTokenAccount.data.extensions) ?? []).find((item) => isExtension("CpiGuard", item));console.log("\nMint Address:", mint.address);console.log("\nToken Account:", tokenAccount.address);console.log("\nEnabled CPI Guard:", enabledCpiGuard);console.log("\nDisabled CPI Guard:", disabledCpiGuard);
Web3.js
import {Connection,Keypair,LAMPORTS_PER_SOL,sendAndConfirmTransaction,SystemProgram,Transaction} from "@solana/web3.js";import {createDisableCpiGuardInstruction,createEnableCpiGuardInstruction,createInitializeAccountInstruction,createInitializeMintInstruction,ExtensionType,getAccountLen,getAccount,getCpiGuard,getMintLen,TOKEN_2022_PROGRAM_ID} from "@solana/spl-token";const connection = new Connection("http://localhost:8899", "confirmed");const latestBlockhash = await connection.getLatestBlockhash();const feePayer = Keypair.generate();const mint = Keypair.generate();const tokenAccount = Keypair.generate();const airdropSignature = await connection.requestAirdrop(feePayer.publicKey,5 * LAMPORTS_PER_SOL);await connection.confirmTransaction({blockhash: latestBlockhash.blockhash,lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,signature: airdropSignature});const mintSpace = getMintLen([]);const mintRent = await connection.getMinimumBalanceForRentExemption(mintSpace);await sendAndConfirmTransaction(connection,new Transaction().add(SystemProgram.createAccount({fromPubkey: feePayer.publicKey, // Account funding account creation.newAccountPubkey: mint.publicKey, // New mint account to create.space: mintSpace, // Account size in bytes for the mint account.lamports: mintRent, // Lamports funding the mint account rent.programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.}),createInitializeMintInstruction(mint.publicKey, // Mint account to initialize.0, // Number of decimals for the token.feePayer.publicKey, // Authority allowed to mint new tokens.feePayer.publicKey, // Authority allowed to freeze token accounts.TOKEN_2022_PROGRAM_ID // Program that owns the mint account.)),[feePayer, mint],{ commitment: "confirmed" });const tokenAccountSpace = getAccountLen([ExtensionType.CpiGuard]);const tokenAccountRent =await connection.getMinimumBalanceForRentExemption(tokenAccountSpace);await sendAndConfirmTransaction(connection,new Transaction().add(SystemProgram.createAccount({fromPubkey: feePayer.publicKey, // Account funding account creation.newAccountPubkey: tokenAccount.publicKey, // New token account to create.space: tokenAccountSpace, // Account size in bytes for the token account plus CpiGuard.lamports: tokenAccountRent, // Lamports funding the token account rent.programId: TOKEN_2022_PROGRAM_ID // Program that owns the token account.}),createInitializeAccountInstruction(tokenAccount.publicKey, // Token account to initialize.mint.publicKey, // Mint for the token account.feePayer.publicKey, // Owner allowed to control the token account.TOKEN_2022_PROGRAM_ID // Program that owns the token account.)),[feePayer, tokenAccount],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createEnableCpiGuardInstruction(tokenAccount.publicKey, // Token account that stores the CpiGuard extension.feePayer.publicKey, // Token account owner authorized to enable CPI guard.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the token account.)),[feePayer],{ commitment: "confirmed" });const enabledTokenAccount = await getAccount(connection,tokenAccount.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);const enabledCpiGuard = getCpiGuard(enabledTokenAccount);await sendAndConfirmTransaction(connection,new Transaction().add(createDisableCpiGuardInstruction(tokenAccount.publicKey, // Token account that stores the CpiGuard extension.feePayer.publicKey, // Token account owner authorized to disable CPI guard.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the token account.)),[feePayer],{ commitment: "confirmed" });const disabledTokenAccount = await getAccount(connection,tokenAccount.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);const disabledCpiGuard = getCpiGuard(disabledTokenAccount);console.log("\nMint Address:", mint.publicKey.toBase58());console.log("\nToken Account:", tokenAccount.publicKey.toBase58());console.log("\nEnabled CPI Guard:", enabledCpiGuard);console.log("\nDisabled CPI Guard:", disabledCpiGuard);
Rust
use anyhow::Result;use solana_client::nonblocking::rpc_client::RpcClient;use solana_commitment_config::CommitmentConfig;use solana_sdk::{program_pack::Pack,signature::{Keypair, Signer},transaction::Transaction,};use solana_system_interface::instruction::create_account;use spl_token_2022_interface::{extension::{cpi_guard::{instruction::{disable_cpi_guard, enable_cpi_guard},CpiGuard,},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{initialize_account, initialize_mint},state::{Account, Mint},ID as TOKEN_2022_PROGRAM_ID,};#[tokio::main]async fn main() -> Result<()> {let client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let fee_payer = Keypair::new();let airdrop_signature = client.request_airdrop(&fee_payer.pubkey(), 5_000_000_000).await?;loop {let confirmed = client.confirm_transaction(&airdrop_signature).await?;if confirmed {break;}}let mint = Keypair::new();let token_account = Keypair::new();let mint_space = Mint::LEN;let mint_rent = client.get_minimum_balance_for_rent_exemption(mint_space).await?;let create_mint_transaction = Transaction::new_signed_with_payer(&[create_account(&fee_payer.pubkey(), // Account funding account creation.&mint.pubkey(), // New mint account to create.mint_rent, // Lamports funding the mint account rent.mint_space as u64, // Account size in bytes for the mint account.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.),initialize_mint(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&mint.pubkey(), // Mint account to initialize.&fee_payer.pubkey(), // Authority allowed to mint new tokens.Some(&fee_payer.pubkey()), // Authority allowed to freeze token accounts.0, // Number of decimals for the token.)?,],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&create_mint_transaction).await?;let token_account_space =ExtensionType::try_calculate_account_len::<Account>(&[ExtensionType::CpiGuard])?;let token_account_rent = client.get_minimum_balance_for_rent_exemption(token_account_space).await?;let create_token_account_transaction = Transaction::new_signed_with_payer(&[create_account(&fee_payer.pubkey(), // Account funding account creation.&token_account.pubkey(), // New token account to create.token_account_rent, // Lamports funding the token account rent.token_account_space as u64, // Account size in bytes for the token account plus CpiGuard.&TOKEN_2022_PROGRAM_ID, // Program that owns the token account.),initialize_account(&TOKEN_2022_PROGRAM_ID, // Program that owns the token account.&token_account.pubkey(), // Token account to initialize.&mint.pubkey(), // Mint for the token account.&fee_payer.pubkey(), // Owner allowed to control the token account.)?,],Some(&fee_payer.pubkey()),&[&fee_payer, &token_account],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&create_token_account_transaction).await?;let enable_cpi_guard_instruction = enable_cpi_guard(&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.&token_account.pubkey(), // Token account that stores the CpiGuard extension.&fee_payer.pubkey(), // Token account owner authorized to enable CPI guard.&[], // Additional multisig signers.)?;let enable_transaction = Transaction::new_signed_with_payer(&[enable_cpi_guard_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&enable_transaction).await?;let enabled_token_account_data = client.get_account(&token_account.pubkey()).await?;let enabled_token_account_state =StateWithExtensions::<Account>::unpack(&enabled_token_account_data.data)?;let enabled_cpi_guard = enabled_token_account_state.get_extension::<CpiGuard>()?;let disable_cpi_guard_instruction = disable_cpi_guard(&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.&token_account.pubkey(), // Token account that stores the CpiGuard extension.&fee_payer.pubkey(), // Token account owner authorized to disable CPI guard.&[], // Additional multisig signers.)?;let disable_transaction = Transaction::new_signed_with_payer(&[disable_cpi_guard_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&disable_transaction).await?;let disabled_token_account_data = client.get_account(&token_account.pubkey()).await?;let disabled_token_account_state =StateWithExtensions::<Account>::unpack(&disabled_token_account_data.data)?;let disabled_cpi_guard = disabled_token_account_state.get_extension::<CpiGuard>()?;println!("\nMint Address: {}", mint.pubkey());println!("\nToken Account: {}", token_account.pubkey());println!("\nEnabled CPI Guard: {:#?}", enabled_cpi_guard);println!("\nDisabled CPI Guard: {:#?}", disabled_cpi_guard);Ok(())}
Is this page helpful?