¡Buenas! Si estás leyendo esto, es porque o eres un enfermo de la seguridad en smart contracts, o estás justo ahora diseñando la arquitectura de un nuevo protocolo DeFi y te tiemblan un poco las piernas de pensar que mañana todo se puede ir a cero por culpa de un bug tonto.
Mira, llevo un huevo de tiempo en esto. He pasado de estar en hackathons picando exploits con código espagueti a base de pizza y monstruos, a sentarme en la silla de CTO de un exchange de los grandes. ¿Y te digo una cosa? La mayoría de los hacks salvajes que he visto e investigado (y algunos que frené a tiempo en fase de auditoría) no pasan porque la criptografía esté rota. Tampoco porque el compilador de Solidity se haya vuelto loco. Pasan por una falta total de comprensión de cómo interactúan los estándares ERC entre sí cuando se mezclan en producción.
Nos hemos malacostumbrado a pensar que los estándares son sagrados y sinónimo de seguridad. Pero el diablo está en los detalles de la implementación y en los efectos colaterales ocultos. Vamos a desgranar dónde suelen cagarla los arquitectos y cómo blindar tu sistema para que puedas dormir por las noches.
1. La amenaza oculta del ERC-20: Un clásico peligroso
Parece que el ERC-20 ya nos lo sabemos de memoria, ¿no? ¿Qué podría salir mal? Pues absolutamente todo si integras tokens de terceros en tu protocolo sin meterles un control hiperestricto.
El dilema del valor de retorno inexistente (The No-Return Dilemma)
Según la especificación, transfer y transferFrom tienen que devolver un bool. Pero la realidad es que un montón de tokens antiguos y con mucho peso (hola, USDT y BNB en algunos de sus contratos viejos) no lo hacen. Simplemente no devuelven una mierda cuando la transferencia se ejecuta correctamente.
Si tu contrato espera un bool usando la interfaz estándar:
// ¡Ni se te ocurra hacer esto si trabajas con tokens genéricos!
IERC20(token).transferFrom(msg.sender, address(this), amount);En cuanto interactúes con USDT, la transacción va a lanzar un revert fulminante porque la EVM buscará el valor de retorno en el stack y no habrá nada. O lo que es todavía peor: si no estás checkeando el resultado (usando token.transfer(...) a secas en lugar de un require(token.transfer(...))), resulta que algunos tokens devuelven false en vez de hacer revert cuando fallan. Tu contrato seguirá ejecutándose como si no pasara nada y, ¿cuál es el resultado?, que el usuario se acaba de inventar saldo de la nada.
La solución: Olvídate para siempre de llamar directamente a transfer y transferFrom. Usa la librería SafeERC20 de OpenZeppelin con sus métodos safeTransfer y safeTransferFrom. Esta librería comprueba por debajo el retorno a bajo nivel y gestiona correctamente estos contratos "mutantes".
Weird ERC-20 Tokens: Cuando el estándar se convierte en calabaza
Aquí tienes una pequeña chuleta de tokens que se comportan de una manera totalmente distinta a lo que dicen los libros de texto. Como arquitecto, estás obligado a meter esto en la lógica de tus liquidity pools.
| Tipo de token (Weird ERC-20) | ¿Dónde está la trampa? | ¿Por qué es peligroso para la arquitectura? |
|---|---|---|
| Deflationary / Fee-on-Transfer (ej. STA, PAXG) | Te cobran una comisión directamente durante la transferencia. | Tú calculas que el contrato ha recibido 100 tokens, pero al balance real solo llegan 99. La contabilidad interna de la pool se rompe y se genera un agujero. |
| Upgradable Proxies (ej. USDC, USDT) | Los admins pueden cambiar la lógica del token en cualquier momento. | Aparición de listas negras (Blacklists). Si banean la dirección de tu contrato, toda la liquidez se queda atrapada dentro. F en el chat. |
| Rebasing Tokens (ej. AMPL) | El balance de las wallets cambia dinámicamente (el supply se ajusta para estabilizar el precio). | El balance de tu contrato puede subir o bajar por la cara, sin que se haya llamado a ninguna función de transferencia. |
2. ERC-721 y ERC-1155: La trampa de onERC721Received y los Reentrancy
Uff, este es mi tema favorito. ¡La de marketplaces de NFTs y protocolos de lending que han quebrado por culpa de las funciones de transferencia "seguras"!
Cuando lanzas un safeTransferFrom en ERC-721 o ERC-1155, el contrato del token comprueba si el receptor es un smart contract. Si lo es, ejecuta un hook en el destino llamado onERC721Received o onERC1155Received.
¿Para qué? Para asegurarse de que el contrato sabe gestionar NFTs y que no se van a quedar ahí bugeados.
¿Dónde está la puñalada? ¡Este maldito hook le cede el control de la ejecución a un código externo totalmente incontrolable en mitad de tu transacción, antes de que hayas actualizado el estado interno de tu propio contrato!
Código de un mint / marketplace vulnerable
Echa un ojo a este fragmento. Lo he escrito así a posta para mostrar el clásico error de arquitectura: pasarse por el forro el patrón Checks-Effects-Interactions.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract VulnerableNFCLending {
// Guardamos los colaterales: Usuario => ID del Token => ¿Colateral activo?
mapping(address => mapping(uint256 => bool)) public hasCollateral;
IERC721 public nftToken;
constructor(address _nft) {
nftToken = IERC721(_nft);
}
// El usuario quiere pedir un crédito depositando un NFT como colateral
function depositCollateral(uint256 tokenId) external {
// 1. Interactions: Transferimos el NFT al contrato
// ¿El safeTransferFrom dispara el hook onERC721Received en el contrato del receptor?
// Espera, no, aquí el receptor somos NOSOTROS. Pero si el contrato llama a safeMint...
// Vamos a cambiar el contexto para una situación donde devolvemos el NFT o donde el atacante intercepta el flujo.
// Reorganizando el escenario: El contrato devuelve el NFT (por ejemplo, al retirar el colateral)
// o es un contrato de minting que primero transfiere y luego actualiza el estado.
}
}Mejor te muestro un ejemplo limpio con una función de mint vulnerable, que se ve mucho más claro. Digamos que tenemos un contrato que permite mintear solo 1 NFT gratis por cabeza (Free Mint).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract VulnerableMint is ERC721 {
mapping(address => bool) public hasMinted;
uint256 public currentTokenId;
constructor() ERC721("DangerNFT", "DNFT") {}
function freeMint() external {
// Checks
require(!hasMinted[msg.sender], "Brother, you already have one!");
// Interactions (¡Dentro de _safeMint hay metida una llamada a un contrato externo!)
_safeMint(msg.sender, currentTokenId);
currentTokenId++;
// Effects (La actualización del estado se hace DEMASIADO TARDE)
hasMinted[msg.sender] = true;
}
}Y aquí está el contrato del atacante. Lo único que hace es interceptar ese hook, ver que hasMinted en el contrato principal sigue estando en false, y volver a llamar a freeMint una y otra vez hasta que vacía el límite o se queda sin gas.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IVulnerableMint {
function freeMint() external;
}
contract Attacker {
IVulnerableMint public target;
uint256 count;
constructor(address _target) {
target = IVulnerableMint(_target);
}
function attack() external {
target.freeMint();
}
// El dichoso hook que dispara el estándar ERC-721
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external returns (bytes4) {
if (count < 5) {
count++;
// ¡Reentrancy de libro! El estado interno de la víctima aún no se ha actualizado
target.freeMint();
}
return this.onERC721Received.selector;
}
}Joder, la de veces que habré visto esto en sesiones de auditoría. Los devs piensan: "Bueno, es un simple transfer de un NFT, no estoy mandando ETH nativo, ¿qué puede pasar?". Lo que pasa es que te defoldean el contrato y te lo dejan limpio en un abrir y cerrar de ojos.
Regla de oro del arquitecto: Primero actualiza el estado (hasMinted[msg.sender] = true;) y solo después lances cualquier método de mint o transfer. Y siempre, pero es que siempre, ponle el modificador nonReentrant de OpenZeppelin a las funciones que muevan NFTs.
Seguimos. Ya que destripamos el tema de Reentrancy vía hooks, vamos a desenterrar un problema más fresco y rebuscado en el que casi nadie piensa a la hora de diseñar la arquitectura, hasta que las papas queman en mainnet.
3. ERC-2612 (Permit): Approvals fantasma y ataques de Front-running
El estándar ERC-2612 trajo un alivio tremendo al ecosistema Web3: la función permit. Esta permite a los usuarios firmar un mensaje offline (EIP-712) para dar un approval de gasto de tokens, delegando el costo del gas a un relayer o al mismísimo protocolo. La mejora en UX fue una locura: en lugar de clavar al usuario con dos transacciones (approve + transferFrom), ahora resuelve todo en un solo viaje.
El problema es que los arquitectos suelen olvidarse de cómo funciona esta firma bajo el capó y meten la pata hasta el fondo en la lógica.
Front-running de firma (Signature Front-running)
Imaginá la clásica función de depósito en un smart contract usando permit:
function depositWithPermit(
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// Primero ejecutamos el permit con la firma que pasó el usuario
IERC20Permit(token).permit(msg.sender, address(this), amount, deadline, v, r, s);
// Después jalamos los tokens
IERC20(token).transferFrom(msg.sender, address(this), amount);
// Minteamos los shares del pool o puntos internos
_mintShares(msg.sender, amount);
}¿Dónde está la vulnerabilidad acá? Cualquier bot de MEV que ande monitoreando el mempool público puede ver esta transacción pendiente. El bot simplemente extrae la firma válida (v, r, s) y los parámetros de tu TX, genera su propia transacción llamando a token.permit(...) directo al contrato del token y le hace un front-run al usuario metiendo un Gas Price más alto.
La transacción del bot se ejecuta primero. La firma pasa joya y el allowance queda seteado. Acto seguido, entra la transacción del usuario honesto. Pero como la firma ya fue gastada, ¡el nonce del usuario en el contrato del token se incrementó! El llamado a permit dentro de depositWithPermit va a tirar un revert cantado porque la firma ya no es válida.
¿El resultado? La transacción del usuario se va al bombo, quema gas al cohete, la UX queda rota y, si esto era un aporte crítico de margen para salvar una posición en long apalancada, la posición termina liquidada por culpa del delay.
¿Cómo blindar tu arquitectura?
Envolvé el llamado de permit en un bloque try/catch. Si la firma ya fue inyectada en la red por un front-runner, el contrato del token ya tiene el allowance que necesitás asignado. Tu contrato simplemente tiene que ignorar el error de firma duplicada y seguir viaje para ejecutar el transferFrom.
try IERC20Permit(token).permit(msg.sender, address(this), amount, deadline, v, r, s) {}
catch {
// Si falló, es muy probable que la firma haya sufrido front-running.
// Validamos si el allowance actual ya alcanza para cubrir la operación.
require(IERC20(token).allowance(msg.sender, address(this)) >= amount, "Permit failed and allowance insufficient");
}El dolor de cabeza del "Phantom Permit"
Esto ya es una avivada de insiders de la que casi no hay documentación dando vueltas. ¿Qué pasa si tu protocolo DeFi acepta tokens arbitrarios y un usuario intenta meter un depositWithPermit usando un token que NO soporta el estándar ERC-2612?
Vas a pensar: "Y, el contrato va a tirar revert, si la función permit ni existe ahí". Ojo, que no siempre funciona así la cosa.
Si el contrato del token tiene una función genérica de fallback() o receive() que no tira revert cuando recibe un selector desconocido (un comportamiento clásico en arquitecturas de proxy o tokens viejos tipo versiones previas de WETH), el llamado a permit va a devolver éxito (success = true), pero en la realidad no se aprobó ningún allowance.
A continuación, tu contrato va a intentar ejecutar el transferFrom, que va a reventar a menos que hubiera un approve viejo dando vueltas por ahí. Pero si cruzás esta debilidad con una lógica donde el allowance se valida por otra vía, te vas a comer un rekt histórico. Verificá siempre que el token objetivo realmente implemente IERC20Permit vía ERC-165, o controlalo cortito con una whitelist de tokens.
4. ERC-3156 (Flash Loans): Peligro de manipulación de balances en la misma transacción
Los Flash Loans son una herramienta bestial, pero rompen todos los esquemas de timing a los que están acostumbrados los arquitectos de software tradicionales. Dentro de una única transacción atómica, un atacante puede pedir prestados millones de dólares, distorsionar los estados del sistema y devolver la plata al final.
El error de arquitectura más bruto acá es usar la función balanceOf(address(this)) para calcular el precio de las shares de un pool o la cotización de un activo.
// ERROR CATASTRÓFICO DE ARQUITECTURA
function getSharePrice() public view returns (uint256) {
// El precio del share depende directamente del balance actual de tokens en el contrato
return token.balanceOf(address(this)) / totalShares;
}Si tu contrato permite pedir Flash Loans de ese mismo token, en el instante en que el borrower retira los fondos, el balance del contrato se desploma casi a cero. Si exactamente en esa fracción de segundo (dentro del callback onFlashLoan) tu protocolo permite ejecutar otras operaciones secundarias —como liquidaciones o cálculo de recompensas—, el precio del share calculado va a estar completamente adulterado.
El hacker pide el flash loan -> el balance del pool se vacía -> el precio del share se va al piso -> el hacker usa otra wallet para comprar shares del pool a precio de liquidación de saldo -> se devuelve el flash loan -> el balance del pool vuelve a la normalidad -> el hacker revienta las shares al precio real. Listo, pool drenado.
Regra de oro: Nunca, bajo ningún concepto, dependas de balanceOf(address(this)) para cálculos matemáticos o económicos críticos si ese balance se puede alterar de forma temporal sin modificar el estado lógico profundo del sistema. Usá contabilidad interna (internal accounting) mediante una state variable como uint256 internalReserve, que se actualice única y exclusivamente durante los flujos controlados de depósitos y retiros oficiales.
Vale, vamos a meternos de lleno con las cosas que a mí, como encargado de la seguridad de la infraestructura de un exchange, de verdad me ponen los pelos de punta. Hablemos de los estándares nuevos y relativamente frescos, donde los devs todavía no han pisado y aplanado todas las minas que hay sueltas.
5. ERC-4337 (Account Abstraction): Trampas a nivel de transacciones por lotes (Batches) y Paymaster
La abstracción de cuentas (Account Abstraction) es una pasada, las cosas como son. Por fin estamos dejando atrás las EOAs (las wallets comunes de toda la vida) para pasar a usar smart contracts como wallets de usuario. Se acabó el drama de las seed phrases, puedes meter social recovery y encima pagar el gas con stablecoins gracias a los Paymasters.
Pero si lo miras con los ojos de un arquitecto de protocolos que se tiene que integrar con ERC-4337, aquí se abre un abismo enorme de vectores de ataque superespecíficos.
Vulnerabilidad de firma en validateUserOp
En ERC-4337, el punto crítico de la validación customizada de la wallet es el método validateUserOp. Su función es comprobar la firma de la transacción y escupir un estado específico.
// Ejemplo ultrasimplificado de la lógica de validación en una smart wallet
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData) {
// Error crítico: ¿Nos estamos fiando de una llamada de CUALQUIER dirección?
// No, el Bundler llama a esto a través del EntryPoint. Pero si nos dejamos el check...
require(msg.sender == entryPoint, "Only EntryPoint can trigger validation");
// Nuestra propia validación de firma
if (_verifySignature(userOp, userOpHash)) {
// Devolvemos 0 si la validación es correcta
return 0;
}
// Devolvemos SIG_VALIDATION_FAILED (normalmente 1) si falla
return 1;
}Espérate un momento, ¿ves dónde está la jugada? Según la especificación del ERC-4337, si la validación de la firma falla, la función NO debe meter un revert. Lo que tiene que hacer es devolver un valor empaquetado especial (una constante de error) para que el EntryPoint (el contrato director de orquesta) entienda que la transacción no es válida. Así, el EntryPoint no le cobra gas a la wallet y descarta la operación directamente a nivel de Bundler.
Si tú como arquitecto, por pura inercia o costumbre, le metes ahí un require(isValid, "Invalid signature");, la has liado pardísima. Cuando se mande un paquete masivo de transacciones (batch), un solo revert duro por una transacción caída se va a cargar el lote entero del Bundler. ¿El resultado? Los bundlers van a meter tu wallet o tu paymaster en la lista negra (ban), dejando a tus usuarios completamente tirados sin poder mandar ni una sola transacción. La lógica de validación tiene que ser atómica y seguir a rajatabla la matemática de valores de retorno de ERC-4337, pasando olímpicamente de los patrones clásicos de Solidity.
Ataque al Paymaster (Gas Drain)
Si tu protocolo DeFi hace las veces de Paymaster (por ejemplo, si subvencionas el gas de tus usuarios para que operen con cero comisiones), estás obligado a blindar y aislar por completo la fase de validación del mundo exterior.
Dentro del contrato del Paymaster está el método validatePaymasterUserOp. En las entrañas de este método está **estrictamente prohibido** usar estados dinámicos que puedan cambiar entre el momento en el que el bundler simula la transacción y el momento en el que esta entra en el bloque. Por ejemplo, no se te ocurrirá llamar a oráculos de precios (como Chainlink) dentro de la validación del paymaster para calcular cuántos tokens le vas a quitar al usuario para pagar el gas.
**¿Por qué?** Un atacante podría lanzar una transacción de forma que, durante la simulación, el oráculo devuelva un precio que haga pasar la validación sin problemas. Pero justo antes de que se incluya en el bloque, el hacker manipula el precio del propio oráculo (mediante un flash loan o un swap rápido). La validación on-chain empezará a fallar, pero el bundler ya habrá procesado la transacción y quemado el gas. Los fondos se van a detraer de tu paymaster mientras el usuario se va de rositas sin pagar un céntimo. Te drenan el balance de gas por completo en cuestión de horas.
6. ERC-4626 (Tokenized Vaults): Frontrunning en el primer depósito (Inflation Attack)
El ERC-4626 es el estándar de oro para las bóvedas tokenizadas (staking, pools de rendimiento, lending). Se encargó de unificar los métodos deposit, mint, withdraw y redeem. Es una genialidad, porque ahora cualquier agregador de rendimientos tipo Yearn te integra cualquier pool nuevo en cuestión de 5 minutos.
Sin embargo, el propio diseño matemático del estándar esconde una bomba de relojería conocida como **Inflation Attack** (Ataque de inflación de activos). Este exploit golpea a las pools justo en el instante en el que se despliegan en la red y su balance todavía es cero.
Mecánica del ataque
La fórmula para calcular la cantidad de participaciones (shares) que se lleva un usuario al depositar activos (assets) suele ser esta:
$$\text{shares} = \frac{\text{assets} \times \text{totalShares}}{\text{totalAssets}}$$
Si la pool está vacía (totalShares == 0), por defecto se aplica que shares == assets. Es decir, una proporción limpia de 1 a 1.
Ahora fíjate bien en la jugada del hacker:
- Un usuario legítimo manda una transacción de
depositpor valor de 1.000 USDC a una pool ERC-4626 nuevecita y completamente vacía. - El hacker detecta esto en la mempool y le mete un front-run de manual (pagando más gas para pasar delante). Deposita en la pool la miseria de 1 wei de USDC. La pool le mina exactamente 1 wei de shares. La foto del momento se queda así:
totalShares = 1,totalAssets = 1. - Justo después, y dentro de la misma transacción atómica, el hacker mete una transferencia directa (usando un
transfernormal de ERC20, saltándose la función dedeposit) de una cantidad salvaje; pongamos, 10.000 USDC, directos a la dirección del contrato de la pool. - ¿Qué ha pasado con las matemáticas de la pool? El
totalSharessigue siendo 1, pero lostotalAssetsacaban de escalar a 10.001 USDC (la transferencia directa ha inflado el balance del contrato, pero sin emitir nuevas shares). El precio de una sola participación de la pool se acaba de ir a la luna. Por fin se ejecuta la transacción del usuario honesto con sus 1.000 USDC. El contrato echa cuentas para darle sus shares con la fórmula:
$$\text{shares} = \frac{1000 \times 1}{10\,001} = 0$$
Debido al redondeo hacia abajo (integer division) que hace Solidity, ¡el usuario se lleva exactamente 0 shares! Eso sí, sus 1.000 USDC se han transferido perfectamente al balance de la pool.
- Al hacker solo le queda hacer un
withdrawde su única participación (su share de 1 wei) para rebañar absolutamente todo lo que hay en la pool: sus 10.000 USDC iniciales, su wei original y los 1.000 USDC que le ha birlado al usuario liquidado.
Solución a nivel de arquitectura: Te puedes proteger de esto principalmente de dos maneras. La primera es obligar a minar "liquidez muerta" (dead shares) a la dirección cero (zero address) nada más crear la pool (bloquear ahí los primeros 1.000 wei de shares, igualito que se hace en Uniswap V2). La segunda es tirar de las librerías actualizadas de OpenZeppelin, que ya traen protección nativa mediante offsets virtuales (virtual assets y virtual shares), impidiendo que el denominador de la fracción se convierta en cero o uno por culpa de este tipo de manipulaciones.
Oye, vamos a subir el nivel un poco. Ya hablamos de tokens, NFTs, permits y vaults. Pero, ¿cómo conectas todo esto en una arquitectura sólida sin volverte loco durante la integración?
Cuando diseñas un sistema grande, tipo un yield aggregator o un bridge cross-chain, te toca lidiar con todos estos estándares a la vez. Y ahí es donde aparece el efecto sinérgico de las vulnerabilidades: dos funciones que por separado son seguras, juntas pueden abrir un agujero fatal.
7. Matriz de riesgos de arquitectura para tus sistemas
Para que lo tengas claro, armé esta tabla. Es básicamente un checklist para tu próximo Architecture Review. Guárdalo en Notion o imprímelo y tenlo a mano.
| Estándar ERC | La gran amenaza oculta | Impacto en la lógica | ¿Cómo arreglarlo en el diseño? |
|---|---|---|---|
| ERC-20 | Falta de retorno / Transferencias no estándar | Revert de la transacción o error que pasa desapercibido | Usa exclusivamente SafeERC20 (OpenZeppelin). |
| ERC-20 (Weird) | Fee-on-Transfer / Cambio de balance (Rebase) | Discrepancia entre tu accounting interno y el saldo real del contrato | Calcula la diferencia balanceAfter - balanceBefore en lugar de confiar ciegamente en el argumento amount. |
| ERC-721 / 1155 | Secuestro de flujo mediante hooks onERC...Received | Reentrancy antes de actualizar el state interno | Sigue a rajatabla el patrón Checks-Effects-Interactions + el modificador nonReentrant. |
| ERC-2612 | Frontrunning de firmas en el mempool | Denial-of-Service (DoS) para usuarios legítimos | Envuelve las llamadas a permit en bloques try/catch. |
| ERC-3156 | Drenaje temporal del pool (Flash Loan) | Manipulación de precios spot basados en balanceOf | Usa variables de reserva internas (internal reserves) en vez del balance directo. |
| ERC-4337 | revert agresivo en la validación por lotes (batch) | Baneo del contrato o wallet en los bundlers | Retorna constantes de error mágicas en lugar de matar la tx con require. |
| ERC-4626 | Inflation Attack (Ataque al primer depósito) | Redondeo de shares a cero y robo de fondos del primer depositante | Minea "shares muertas" a la address(0) en la inicialización o usa offsets virtuales. |
8. Reflexiones y reglas de oro para una arquitectura segura
Mira, tras tres años en la silla de CTO, aprendí algo: el código más seguro es el que no se escribió. Cuanto más compleja sea tu arquitectura, más dependencias ocultas tiene y más probable es que algún genio de un hackathon encuentre una brecha en la que ni pensaste mientras te tomabas tu café de la mañana.
Si tuviera que darte solo tres reglas para salvar tu proyecto de aparecer en las noticias de Rekt News, serían estas:
- Nunca confíes en contratos externos. Ni aunque sea el token más famoso del mundo. Mañana los admins actualizan el proxy, meten blacklists y tu sistema colapsa. Escribe el código asumiendo que el token externo es el actor más malicioso e impredecible de la red.
- Primero el state, luego los transfers. No me cansaré de repetirlo. Es el ABC de cualquier curso decente de smart contracts, pero la gente se empeña en enviar tokens antes de actualizar los números en sus mappings. Primero ajustas los derechos o saldos internamente, lo fijas en la blockchain y, al final de todo, lanzas el
transfer,safeMintocall. - Aísla la matemática del balance externo. El saldo de tu contrato en la EVM es público y fácil de manipular. Cualquiera puede meterte millones con un flash loan o forzar ETH en tu dirección mediante
selfdestruct. Si tu lógica de recompensas o cálculo de precio de share depende de cuántos tokens hay en el contrato, ya perdiste. Tu contabilidad interna tiene que ser hermética, como la cabina de un piloto.
Con esto hemos cubierto los puntos críticos de estos estándares que pueden tirar por la borda el esfuerzo de un equipo crack, si el arquitecto no puso el "parche" (en el buen sentido) donde debía.