#[derive(Accounts)] en Solana Anchor es una macro similar a un atributo para structs que contiene referencias a todas las cuentas a las que la función accederá durante su ejecución.
En Solana, cada cuenta a la que accederá la transacción debe especificarse por adelantado
Una razón por la que Solana es tan rápida es que ejecuta transacciones en paralelo. Es decir, si Alice y Bob quieren hacer una transacción, Solana intentará procesar sus transacciones simultáneamente. Sin embargo, hay un problema si sus transacciones entran en conflicto al acceder al mismo almacenamiento. Por ejemplo, supongamos que tanto Alice como Bob están intentando escribir en la misma cuenta. Claramente, sus transacciones no pueden ejecutarse en paralelo.
Para que Solana sepa que la transacción de Alice y Bob no se puede paralelizar, tanto Alice como Bob deben especificar por adelantado todas las cuentas que su transacción actualizará.
Dado que tanto Alice como Bob especifican una cuenta (de almacenamiento), el runtime de Solana puede inferir que ambas transacciones entran en conflicto. Se debe elegir una (presumiblemente, la que pagó la tarifa de prioridad más alta), y la otra terminará fallando.
Es por esto que cada función tiene su propio struct #[derive(Accounts)] por separado. Cada campo en el struct es una cuenta a la que el programa tiene la intención de (pero no está obligado a) acceder durante la ejecución.
Algunos desarrolladores de Ethereum pueden notar la similitud entre este requisito y las transacciones con lista de acceso de la EIP 2930.
El tipo de cuenta le indica a Anchor cómo tienes la intención de interactuar con dicha cuenta.
Tipos de cuenta que usarás con más frecuencia: Account, Unchecked Account, System Program y Signer
En nuestro código para inicializar el almacenamiento, vimos tres “tipos” diferentes de cuentas:
AccountSignerProgram
Aquí está el código nuevamente:

Y cuando leímos el saldo de una cuenta, vimos un cuarto tipo:
UncheckedAccount
Aquí está el código que usamos:

Cada uno de los elementos que resaltamos con los cuadros verdes se importan a través de anchor_lang::prelude::*; en la parte superior de los archivos.
El propósito de Account, UncheckedAccount, Signer y Program es realizar algún tipo de verificación en la cuenta proporcionada antes de continuar, y también exponer funciones para interactuar con esas cuentas.
Explicaremos más a fondo cada uno de estos cuatro tipos en las siguientes secciones.
Account
El tipo Account verificará que la cuenta que se está cargando sea realmente propiedad del programa. Si el propietario no coincide, entonces no se cargará. Esto sirve como una importante medida de seguridad para no leer accidentalmente datos que el programa no creó.
En el siguiente ejemplo, creamos una cuenta keypair e intentamos pasarla a foo. Debido a que la cuenta no es propiedad del programa, la transacción falla.
Rust:
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");
#[program]
pub mod account_types {
use super::*;
pub fn foo(ctx: Context<Foo>) -> Result<()> {
// we don't do anything with the account SomeAccount
Ok(())
}
}
#[derive(Accounts)]
pub struct Foo<'info> {
some_account: Account<'info, SomeAccount>,
}
#[account]
pub struct SomeAccount {}
Typescript:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor
.getProvider()
.connection.requestAirdrop(
publicKey,
amount * anchor.web3.LAMPORTS_PER_SOL
);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor
.getProvider()
.connection.getLatestBlockhash();
await anchor
.getProvider()
.connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("Wrong owner with Account", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 10);
await program.methods
.foo()
.accounts({someAccount: newKeypair
.publicKey}).rpc();
});
});
Aquí está el resultado de ejecutar las pruebas:

Si agregamos una macro init a Account, intentará transferir la propiedad del programa del sistema a este programa. Sin embargo, el código anterior no tiene una macro init.
Puede encontrar más información sobre el tipo Account en la documentación: https://docs.rs/anchor-lang/latest/anchor_lang/accounts/account/struct.Account.html
UncheckedAccount o AccountInfo
UncheckedAccount es un alias para AccountInfo. Esto no verifica la propiedad, por lo que se debe tener cuidado, ya que aceptará cuentas arbitrarias.
Aquí hay un ejemplo del uso de UncheckedAccount para leer los datos de una cuenta que no es de su propiedad.
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");
#[program]
pub mod account_types {
use super::*;
pub fn foo(ctx: Context<Foo>) -> Result<()> {
let data = &ctx.accounts.some_account.try_borrow_data()?;
msg!("{:?}", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Foo<'info> {
/// CHECK: we are just printing the data
some_account: AccountInfo<'info>,
}
Aquí está nuestro código en Typescript. Ten en cuenta que estamos llamando directamente al programa del sistema para crear la cuenta keypair, de modo que podamos asignar 16 bytes de datos.
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
const wallet = anchor.workspace.AccountTypes.provider.wallet;
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("Load account with accountInfo", async () => {
// CREATE AN ACCOUNT NOT OWNED BY THE PROGRAM
const newKeypair = anchor.web3.Keypair.generate();
const tx = new anchor.web3.Transaction().add(
anchor.web3.SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: newKeypair.publicKey,
space: 16,
lamports: await anchor
.getProvider()
.connection
.getMinimumBalanceForRentExemption(32),
programId: program.programId,
})
);
await anchor.web3.sendAndConfirmTransaction(
anchor.getProvider().connection,
tx,
[wallet.payer, newKeypair]
);
// READ THE DATA IN THE ACCOUNT
await program.methods
.foo()
.accounts({ someAccount: newKeypair.publicKey })
.rpc();
});
});
Después de que se ejecuta el programa, podemos ver que imprimió los datos en la cuenta, la cual contiene 16 bytes en cero:
Transaction executed in slot 14298:
Signature: 64fv6NqYB4tji9UfLpH8PgFDY1QV4vbMovrnnpw3271vStg7J5g1z1bm9YbE8Lobzozkc6y2YzLdgMjGdftCGKqv
Status: Ok
Log Messages:
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs invoke [1]
Program log: Instruction: Foo
Program log: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs consumed 5334 of 200000 compute units
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs success
Necesitábamos usar este tipo de cuenta cuando pasábamos una dirección arbitraria, pero ten mucho cuidado de cómo se utilizan los datos, porque un hacker podría crear datos maliciosos en una cuenta y luego pasarlos al programa de Solana.
Signer
Este tipo verificará que la cuenta Signer haya firmado la transacción; comprueba que la firma coincida con la clave pública de la cuenta.
Debido a que un firmante también es una cuenta, puedes leer el saldo del Signer o los datos (si los hay) almacenados en la cuenta, aunque su propósito principal es validar firmas.
Según la documentación https://docs.rs/anchor-lang/latest/anchor_lang/accounts/signer/struct.Signer.html, Signer es un tipo que valida que la cuenta firmó la transacción. No se realizan otras verificaciones de propiedad o tipo. Si se usa esto, no se debería intentar acceder a los datos subyacentes de la cuenta.
Ejemplo en Rust:
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");#
[program]
pub mod account_types {
use super::*;
pub fn hello(ctx: Context<Hello>) -> Result<()> {
let lamports = ctx.accounts.signer.lamports();
let address = &ctx.accounts
.signer
.signer_key().unwrap();
msg!(
"hello {:?} you have {} lamports",
address,
lamports
);
Ok(())
}
}
#[derive(Accounts)]
pub struct Hello<'info> {
pub signer: Signer<'info>,
}
Typescript:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("Wrong owner with Account", async () => {
await program.methods.hello().rpc();
});
});
Aquí está el resultado del programa:
Transaction executed in slot 11184:
Signature: 4xipobKHHp7a3N4durXN4YPGUesDAJNg7wsatBemdJAm7U1dXYG3gveLwnuY39iCTEZvaj6nnAViVJwDS8124uJJ
Status: Ok
Log Messages:
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs invoke [1]
Program log: Instruction: Hello
Program log: hello 5jmigjgt77kAfKsHri3MHpMMFPo6UuiAMF19VdDfrrTj you have 499999994602666000 lamports
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs consumed 13096 of 200000 compute units
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs success
Program
Esto debería explicarse por sí mismo. Le indica a Anchor que la cuenta es ejecutable, es decir, un programa, y que puedes emitirle una invocación cruzada de programas. El que hemos estado usando es el programa del sistema, aunque más adelante usaremos nuestros propios programas.
Aprende más
Aprende desarrollo en Solana en nuestro curso de Ethereum a Solana.
Publicado originalmente el 6 de abril de 2024