
Transient storage: Las nuevas variables fantasmas
La última actualización de Solidity (0.8.28) trae una mejora notable con el soporte completo para las variables de estado de tipo transient storage
para uint
y bool
.
Esta funcionalidad fue parcialmente introducida en la versión anterior, pero ahora está completamente operativa, lo que ofrece nuevas posibilidades para nuestros smart contracts.
¿Qué es el transient storage?
El transient storage
es una nueva ubicación de datos en la Ethereum Virtual Machine (EVM), introducida por el EIP-1153.
Es similar al almacenamiento permanente, pero con una gran diferencia: los datos en el transient storage
solo persisten durante la transacción actual.
Es decir al finalizar la transacción, los datos son restablecidos a su valor predeterminado.
A diferencia del almacenamiento permanente, que es costoso en términos de gas y se almacena indefinidamente en la blockchain,
el transient storage
es mucho más barato y está diseñado para usos temporales, como por ejemplo, implementaciones para evitar los reentrancy attacks.
¿Cuándo utilizar transient storage?
El caso más fácil de identificar es para evitar los reentrancy attacks donde el transient storage permite establecer una flag para bloquear durante la ejecución de una función crítica y luego restablecerla al final, garantizando que no se pueda llamar la función varias veces en la misma transacción, todo esto sin incurrir en costos elevados de almacenamiento permanente.
Veamos un ejemplo:
pragma solidity ^0.8.28;
contract Generosity {
mapping(address => bool) sentGifts;
bool transient locked;
modifier nonReentrant {
require(!locked, "Reentrancy attempt");
locked = true;
_;
// Unlocks the guard, making the pattern composable.
// After the function exits, it can be called again,
// even in the same transaction.
locked = false;
}
function claimGift() nonReentrant public {
require(address(this).balance >= 1 ether);
require(!sentGifts[msg.sender]);
(bool success, ) = msg.sender.call{value: 1 ether}("");
require(success);
// In a reentrant function,
//doing this last would open up the vulnerability
sentGifts[msg.sender] = true;
}
}
En este contrato, utilizamos una variable transient locked
para implementar un patrón nonReentrant, evitando así el reentrancy attack.
Este enfoque reduce significativamente el costo de gas comparado con el uso de almacenamiento permanente para la variable de bloqueo.
Limitaciones y Consideraciones
Aunque el transient storage
tiene beneficios, también presenta ciertas limitaciones y posibles trampas.
1. Visibilidad limitada y tipos soportados
Por el momento, el transient storage
solo soporta uint
y boolean
. Tampoco es posible declarar variables locales o parámetros como transient
.
Además, las variables transient
no pueden ser inicializadas en su declaración, ya que su valor se reinicia al finalizar la transacción.
Se inicializan automáticamente a su valor por defecto según su tipo (por ejemplo, 0
para uint
o false
para bool
).
2. Composabilidad
Una de las advertencias más importantes es que el transient storage
puede romper la composabilidad de los contratos inteligentes.
Imaginemos un caso donde se almacena un multiplicador en transient storage
y se realizan varias llamadas para multiplicar diferentes valores:
contract MulService {
uint transient multiplier;
function setMultiplier(uint mul) external {
multiplier = mul;
}
function multiply(uint value) external view returns (uint) {
return value * multiplier;
}
}
y seguimos la siguiente secuencia de llamadas desde fuera de la DLT:
setMultiplier(42);
multiply(1);
multiply(2);
Si estas funciones se llaman en transacciones separadas, el valor de multiplier
se restablecería a su valor por defecto (0)
al final de la transacción, lo que llevaría a resultados inesperados. Esto rompe la capacidad de componer funciones en diferentes contratos
y transacciones, un principio fundamental en el diseño de contratos inteligentes.
3. Reentrancia
El uso de transient storage
para prevenir ataques de reentrancia es seguro, pero solo si se restablecen los valores correctamente.
Si se omite esta etapa, el contrato quedaría bloqueado para su uso en transacciones más complejas que involucren múltiples llamadas.
Es recomendable siempre limpiar el transient storage al finalizar una llamada.
Conclusiones
La versión 0.8.28 de Solidity marca un avance importante con la introducción del transient storage. Esta funcionalidad ofrece beneficios claros, como una reducción en los costos de gas para ciertos patrones de diseño, pero también requiere una atención especial a las limitaciones que impone, como la composabilidad y la necesidad de limpiar correctamente los datos al finalizar una transacción.
Es fundamental que nos familiaricemos con estos conceptos y comprendamos cuándo y cómo usar el transient storage
para evitar errores difíciles de detectar.