El Modelo Mental de Solana vs. EVM
Si vienes de Ethereum, el mayor cambio es dónde reside el estado. Los contratos EVM agrupan código y almacenamiento. Solana separa los programas (código) de las cuentas (datos). Esto impulsa el paralelismo, el tamaño de las cuentas y los patrones de seguridad.
Receta
Tarjeta de referencia rápida, lista para copiar y pegar.
| Concepto EVM | Equivalente en Solana |
|---|---|
| Contrato | Programa (cuenta ejecutable) |
| Almacenamiento del contrato | Cuentas de datos propiedad de un programa |
msg.sender | Cuenta firmante en la estructura Accounts |
| Dirección del contrato | ID del Programa (o PDA para estado derivado) |
mapping | Múltiples cuentas indexadas por semillas |
| Gas | Unidades de cómputo (CU) + tarifas de prioridad |
Cuándo usar esto:
- Al portar una arquitectura de dapp EVM a Solana
- Al explicar Solana a compañeros nativos de Ethereum
- Al diseñar diseños de cuentas en lugar de mapeos de almacenamiento
- Al comprender por qué las transacciones listan explícitamente todas las cuentas
- Al evitar los antipatrones de "un solo contrato lo posee todo"
Ejemplo de Trabajo
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
// Equivalente EVM: contract UserProfile { mapping(address => Profile) profiles; }
// Solana: cada perfil es su propia cuenta, propiedad de este programa
#[account]
pub struct UserProfile {
pub authority: Pubkey,
pub name: String,
pub score: u64,
}
#[derive(Accounts)]
pub struct CreateProfile<'info> {
#[account(
init,
payer = authority,
space = 8 + 32 + (4 + 32) + 8,
seeds = [b"profile", authority.key().as_ref()],
bump,
)]
pub profile: Account<'info, UserProfile>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[program]
pub mod profiles {
use super::*;
pub fn create_profile(ctx: Context<CreateProfile>, name: String) -> Result<()> {
let profile = &mut ctx.accounts.profile;
profile.authority = ctx.accounts.authority.key();
profile.name = name;
profile.score = 0;
Ok(())
}
}Lo que esto demuestra:
- El estado es una cuenta
UserProfileseparada, no almacenamiento dentro del programa - Las semillas PDA (
b"profile", authority) reemplazanmapping(address => Profile) initcrea y financia la cuenta con lamports exentos de alquiler- El programa es sin estado; solo lee/escribe cuentas pasadas en la instrucción
Análisis Profundo
Cómo Funciona
- Programas: son bytecode en cuentas ejecutables. No almacenan datos de aplicación.
- Cuentas de datos: contienen bytes serializados. Un programa solo escribe en las cuentas que posee.
- Transacciones: declaran cada cuenta tocada en todas las instrucciones.
- Sealevel: programa transacciones no conflictivas en paralelo basándose en bloqueos de cuentas.
Comparación EVM vs Solana
| Aspecto | EVM | Solana |
|---|---|---|
| Ubicación del estado | Dentro del contrato | Cuentas separadas |
| Paralelismo | Secuencial por contrato | Paralelo entre cuentas no superpuestas |
| Modelo de cuenta | El contrato tiene saldo + almacenamiento | Cada entidad es una cuenta |
| Actualización | Patrones de proxy | Cargador actualizable (BPF Upgradeable) |
| Identidad | Dirección del contrato | ID del Programa + PDA |
Implicaciones de Diseño
- Dimensiona las cuentas por adelantado (o
realloc) - sin mapeos ilimitados - Pasa todas las cuentas explícitamente - sin
CALLcon contexto implícito - Usa PDAs como direcciones de cuenta deterministas por usuario/entidad
- Los programas validan la propiedad en cada instrucción
Notas de Rust
// EVM: require(msg.sender == owner)
// Solana: restricción en la estructura Accounts
#[account(has_one = authority)]
pub data: Account<'info, MyData>,
pub authority: Signer<'info>,Trampas
- Una cuenta grande para todos los usuarios: alcanza límites de tamaño y previene el paralelismo. Solución: una cuenta por usuario/entidad con semillas PDA.
- Esperar contexto implícito del llamador: Solana no tiene un global
msg.sender. Solución: pasar cuentas firmantes y validar con restricciones. - Llamar a programas sin declarar cuentas: las transacciones fallan en tiempo de ejecución. Solución: listar cada cuenta que el llamado necesita en la transacción.
- Almacenar saldos dentro del binario del programa: imposible; los programas son ejecutables de solo lectura. Solución: usar cuentas de tokens o saldos de lamports en cuentas de datos.
- Asumir estado atómico entre programas: CPI comparte los bloqueos de cuentas de la transacción. Solución: diseñar instrucciones para que todas las cuentas necesarias estén en una sola transacción.
- Ignorar el alquiler: las cuentas necesitan depósitos de lamports. Solución: financiar cuentas hasta el mínimo exento de alquiler en la creación.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| PDA por entidad | Estado específico del usuario | Configuración global (usar un solo PDA) |
| Cuentas Zero-copy | Grandes conjuntos de datos, ahorro de CU | Estructuras pequeñas donde Borsh está bien |
| Compresión de estado | Millones de NFTs/tenedores | Estado "caliente" mutado frecuentemente |
| EVM en Solana (experimentos Alloy/SVM) | Puerto de Solidity heredado | Dapps nativas de Solana desde cero |
Preguntas Frecuentes
¿Dónde almacena mi programa los datos en Solana?
En cuentas separadas que son propiedad de su programa. El programa en sí solo contiene bytecode ejecutable.
¿Qué reemplaza a un mapeo EVM?
PDAs derivados de semillas (por ejemplo, [b"user", user_pubkey]). Cada entrada es su propia cuenta.
¿Existe un equivalente de Solana para msg.sender?
La cuenta firmante pasada a su instrucción. Valídela con restricciones Signer<'info> o has_one.
¿Pueden dos programas escribir en la misma cuenta?
Solo el programa propietario puede escribir datos. Otros programas pueden leer si la cuenta se pasa y se marca como escribible por el programa propietario a través de CPI.
¿Por qué las transacciones deben listar todas las cuentas por adelantado?
Sealevel utiliza la lista de cuentas para programar la ejecución paralela y aplicar las reglas de bloqueo antes de que comience la ejecución.
¿Tienen los programas de Solana saldos?
Las cuentas de programas contienen lamports para el alquiler, pero los saldos de tokens/SOL de la aplicación residen en cuentas de usuario/token, no en el programa.
¿Cómo se compara la actualizabilidad con los proxies EVM?
Solana utiliza el cargador actualizable BPF: un búfer de programa y una cuenta de datos del programa. La autoridad puede desplegar nuevo bytecode.
¿Qué reemplaza a ERC-20 en Solana?
Programa SPL Token con cuentas de emisión y token. No un contrato por token.
¿Son los CUs lo mismo que el gas?
Concepto similar. CU mide la computación en la cadena. Las tarifas incluyen el costo base de la firma más la tarifa de prioridad para el precio de CU.
¿Puedo desplegar un programa por usuario como contratos de fábrica?
Técnicamente sí, pero es un desperdicio. Use un programa con muchas cuentas de datos (PDAs) por usuario en su lugar.
¿Cómo funcionan los eventos en comparación con los logs EVM?
Los programas emiten logs a través de msg! y emit! de Anchor. Los clientes analizan los logs de transacciones; no hay un almacén de logs persistente en la cadena.
¿Tiene Solana constructores de contratos?
No. Use una instrucción init que cree y configure cuentas en su primer uso.
Relacionado
- Fundamentos de Solana - descripción general del modelo de cuentas
- Cómo Fluye una Transacción - diseño centrado en la transacción
- SOL, Lamports y Unidades - manejo de la moneda nativa
Versiones de Stack: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, y LiteSVM 0.6.x.