Leer el saldo de una cuenta en Anchor Rust
Para leer el saldo en Solana de una dirección dentro de un programa de Solana, usa el siguiente código:
use anchor_lang::prelude::*;
declare_id!("Gnf6u7S7fGJbqEGH9PuDE5Prq6f6ZrDxHY3jNJ4SYySQ");
#[program]
pub mod balance {
use super::*;
pub fn read_balance(ctx: Context<ReadBalance>) -> Result<()> {
let balance = ctx.accounts.acct.to_account_info().lamports();
msg!("balance in Lamports is {}", balance);
Ok(())
}
}
#[derive(Accounts)]
pub struct ReadBalance<'info> {
/// CHECK: although we read this account's balance, we don't do anything with the information
pub acct: UncheckedAccount<'info>,
}
Y el siguiente es el código en web3 js para ejecutarlo:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Balance } from "../target/types/balance";
describe("balance", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Balance as Program<Balance>;
// the following is the Solana wallet we are using
let pubkey = new anchor.web3.PublicKey("5jmigjgt77kAfKsHri3MHpMMFPo6UuiAMF19VdDfrrTj");
it("Tests the balance", async () => {
const tx = await program.methods.readBalance().accounts({ acct: pubkey }).rpc();
});
});
Algunos elementos de este ejemplo difieren de tutoriales anteriores, particularmente el uso de UncheckedAccount.
¿Qué es UncheckedAccount en Solana Anchor?
El tipo UncheckedAccount le indica a Anchor que no compruebe si la cuenta que se está leyendo es propiedad del programa.
Ten en cuenta que la cuenta que pasamos a través del struct Context no es una cuenta inicializada por este programa, por lo tanto, el programa no es su propietario.
Cuando Anchor lee una cuenta de tipo Account en el #[derive(Accounts)], comprobará (en segundo plano) si esa cuenta es propiedad de ese programa. Si no lo es, la ejecución se detendrá.
Esto sirve como una importante medida de seguridad.
Si un usuario malintencionado crea una cuenta que el programa no creó y luego la pasa al programa de Solana, y el programa de Solana confía ciegamente en los datos de la cuenta, pueden ocurrir errores críticos.
Por ejemplo, si el programa es un banco y la cuenta está almacenando el saldo que tiene un usuario, entonces un hacker podría proporcionar una cuenta diferente con un saldo artificialmente mayor del que realmente tiene.
Sin embargo, para llevar a cabo este hack, el usuario tendría que crear la cuenta falsa en una transacción separada y luego pasarla al programa de Solana. No obstante, el framework de Anchor comprueba en segundo plano si la cuenta no es propiedad del programa y rechaza leerla.
UncheckedAccount omite esta comprobación de seguridad.
Importante: AccountInfo y UncheckedAccount son alias uno del otro y AccountInfo tiene las mismas consideraciones de seguridad.
En nuestro caso, estamos pasando cuentas que definitivamente no son propiedad del programa — queremos comprobar el saldo de una cuenta arbitraria. Por lo tanto, debemos estar seguros de que ninguna lógica crítica puede ser manipulada al eliminar esta comprobación de seguridad.
En nuestro caso, solo estamos registrando el saldo en la consola, pero la mayoría de los casos de uso en el mundo real tendrán una lógica más compleja.
¿Qué es /// CHECK:?
Debido al peligro de usar UncheckedAccount, Anchor te obliga a incluir este comentario para animarte a no ignorar las consideraciones de seguridad.
Ejercicio: elimina el comentario /// Check: y ejecuta anchor build; deberías ver que la compilación se detiene y te pide que vuelvas a agregar el comentario con una explicación de por qué una Unchecked Account es segura. Es decir, leer una cuenta no confiable podría ser peligroso, y Anchor quiere asegurarse de que no estás haciendo nada crítico con los datos de la cuenta.
¿Por qué no hay un struct #[account] en el programa?
El struct #[account] le dice a Anchor cómo deserializar una cuenta que contiene datos. Por ejemplo, un struct de cuenta que se vea de la siguiente manera le informará a Anchor que debe deserializar los datos almacenados en la cuenta en un único u64:
#[account]
pub struct Counter {
counter: u64
}
Sin embargo, en nuestro caso, no estamos leyendo los datos de la cuenta — solo estamos leyendo el saldo. Esto es similar a cómo podemos leer el saldo de una dirección de Ethereum pero no leer nada de su código. Dado que no queremos deserializar los datos, no proporcionamos un struct #[account].
No todos los SOL en una cuenta se pueden gastar
Recuerda de nuestra discusión sobre Solana account rent que la cuenta debe mantener un cierto saldo de SOL para estar “exenta de renta” (rent exempt) o el entorno de ejecución eliminará la cuenta. El simple hecho de que la cuenta tenga “1 SOL” no significa necesariamente que la cuenta pueda gastar el 1 SOL completo.
Por ejemplo, si estás construyendo una aplicación bancaria o de staking donde los SOL depositados de un usuario se mantienen en cuentas separadas, no es preciso simplemente medir el saldo de SOL de esas cuentas, ya que la renta estará incluida en el saldo.
Aprende más con RareSkills
Consulta nuestro Solana developer course para obtener más material sobre Solana.
Publicado originalmente el 29 de febrero de 2024