Tu tesorería en Solidity, bloqueada a las 3 a.m.: cómo evitarlo

Tu tesorería en Solidity, bloqueada a las 3 a.m.: cómo evitarlo

17 Oct 2026 Violetta H. 2 vistas

Cómo evitar que tu primer contrato inteligente en Solidity deje la tesorería del proyecto bloqueada a las 3 a.m.

Publicado el 17 de octubre de 2026 · 8 min de lectura


Son las 3:07 de la mañana. Te despierta el móvil vibrando sin parar. En el grupo del proyecto hay 47 mensajes sin leer y todos dicen lo mismo: "la tesorería no responde". Tú, que hace seis horas estabas orgulloso de tu primer contrato desplegado en mainnet, ahora estás en pijama mirando un explorer que no miente: los fondos están ahí, pero nadie puede moverlos.

Respira. No eres el primero ni serás el último. Y este artículo existe precisamente para que esa escena no te pase a ti.

Porque desplegar tu primer contrato en Solidity es una experiencia casi religiosa: compilas, ves el check verde, pagas el gas y de repente tienes código tuyo viviendo para siempre en una cadena de bloques. Emocionante, sí. Pero también es como dejar a un niño de cinco años al mando de la caja fuerte del banco. Si no le has enseñado las reglas, va a hacer algo creativo. Y lo creativo, en blockchain, significa "fondos bloqueados para siempre".

Vamos a hablar de las trampas clásicas que dejan una tesorería atrapada, y de cómo blindarte contra ellas antes de que tu contrato vea la luz.

La trampa nº1: el onlyOwner que se quedó huérfano

Empiezas con lo básico. Pones un modificador onlyOwner en las funciones críticas. Perfecto, el patrón más viejo del manual. El problema llega cuando el owner es una wallet de testnet que ya no controlas, una cuenta de un compañero que se fue del proyecto, o —mi favorito— una dirección que copiaste mal.

Resultado: el contrato tiene dueño, pero el dueño no existe. Y como no hay función para transferir la propiedad, la tesorería queda secuestrada por un fantasma.

Cómo evitarlo: define desde el minuto uno una función transferOwnership (o usa un patrón de dos pasos, con pendingOwner que acepta el rol). Y sobre todo, prueba el flujo completo en una testnet antes de ir a mainnet. Sí, es aburrido. También es aburrido perder el presupuesto del equipo.

La trampa nº2: el require que nunca se cumple

Aquí va una verdad incómoda: la mayoría de los bloqueos de tesorería no vienen de bugs exóticos. Vienen de condiciones lógicas que parecían razonables en tu cabeza y que en producción son imposibles de satisfacer.

Un ejemplo típico: pones un require(msg.sender == treasury, "solo la tesorería") en la función de retiro, pero la tesorería es un contrato multisig que nunca llama directamente a esa función, sino a través de un módulo intermedio. O exiges que block.timestamp > deadline cuando en realidad querías lo contrario.

Cómo evitarlo: escribe tests que ejerciten el camino real de retiro, no el camino ideal. Y cuando escribas un require, pregúntate en voz alta: "¿qué tiene que pasar exactamente para que esta condición sea verdadera?". Si no puedes responderlo con una frase concreta, reescríbelo.

La trampa nº3: el patrón pull que nadie implementó

La sabiduría popular dice que en Solidity nunca hagas push de fondos: usa el patrón pull, donde cada beneficiario retira lo suyo. Buena práctica, sí. Pero si implementas el withdraw y te olvidas de la función que calcula cuánto le corresponde a cada uno, o esa función depende de un estado que ya no existe, tienes un montón de ETH sentado en el contrato sin manera de salir.

Cómo evitarlo: trata el par deposit/withdraw como una unidad atómica. Si escribes uno, escribes el otro en la misma sesión, y los pruebas juntos. Nada de "luego lo completo".

La trampa nº4: la actualización que rompió todo

Usas proxies para poder actualizar el contrato. Buena idea en teoría. Pero un día despliegas la versión 2 y el storage layout ya no coincide con la v1. Los balances aparecen en posiciones equivocadas. La tesorería "existe" pero apunta a basura.

Cómo evitarlo: si vas a usar proxies, documenta el layout del storage como si fuera sagrado. Y no toques el orden de las variables entre versiones. Nunca. Jamás. Ni un poquito.

La trampa nº5: el selfdestruct que ya no existe

Aquí viene un cambio que muchos aprendices todavía no han interiorizado. En las versiones recientes del lenguaje, el viejo selfdestruct ya no destruye nada de forma fiable: su comportamiento cambió y confiar en él para "rescatar" fondos es una ilusión. Si tu plan de emergencia era "borro el contrato y listo", tienes que replanteártelo.

Cómo evitarlo: diseña funciones de rescate explícitas (emergencyWithdraw, pause, sweep) desde el principio. No esperes a necesitarlas para escribirlas.

La trampa nº6: los permisos que se olvidaron de la tesorería

Tienes un rol TREASURER_ROLE. Lo asignas al despliegue. Todo bien. Pero luego añades un módulo nuevo que necesita mover fondos y no le das el rol. O cambias de multisig y olvidas actualizar los permisos del contrato antiguo. La tesorería sigue viva, pero ningún humano con las llaves correctas puede operar en ella.

Cómo evitarlo: mantén un documento vivo (aunque sea un README en el repo) con el mapa de roles y quién los tiene. Actualízalo cada vez que cambie algo. Sí, es burocracia. También es lo que separa un proyecto serio de un meme.

La trampa nº7: el gas que no alcanza

Parece menor, pero muerde. Diseñas una función de retiro que recorre una lista de beneficiarios. Con 5 va bien. Con 500 revienta el límite de gas del bloque y nadie puede retirar nada.

Cómo evitarlo: nunca hagas bucles sobre arrays de tamaño no acotado. Usa paginación, o mejor, el patrón pull puro y duro. Si el usuario paga su propio gas, no hay límite que te frene.

El ritual de las 3 a.m. que sí quieres tener

Antes de desplegar, haz esta checklist como si fuera un ritual:

  1. Tests de integración completos, no solo unitarios. Que el flujo entero funcione de principio a fin.
  2. Simulación en fork de mainnet con datos reales. Nada de "en local funciona".
  3. Auditoría externa o, como mínimo, una revisión por pares con alguien que no haya escrito el código.
  4. Plan de emergencia documentado: quién tiene qué llave, qué se puede pausar, cómo se rescatan fondos.
  5. Monitorización activa desde el primer bloque. Si algo se rompe, quieres enterarte tú antes que el grupo de Telegram.

Por qué esto importa más allá del código

La tesorería de un proyecto no es un detalle técnico: es la promesa que le haces a tu comunidad. Cuando dices "los fondos están seguros", estás firmando un contrato social, no solo uno inteligente. Y ese contrato social no se puede parchear con un proxy.

Cada vez que un equipo pierde acceso a su tesorería por un require mal puesto, el ecosistema entero pierde un poco de confianza. Los que llevamos tiempo en esto lo hemos visto demasiadas veces. Por eso escribo este artículo: porque quiero que tu primer contrato sea un hito, no una herida de guerra.

Así que antes de pulsar ese deploy a las 2:55 de la madrugada, hazte una pregunta sencilla: ¿si algo falla, tengo un camino de vuelta? Si la respuesta es "no", cierra el portátil, duerme, y mañana lo revisas con la cabeza fría.

Tu yo de las 3 a.m. te lo agradecerá. Y tu tesorería, también.

¿Cuál de estas trampas te ha mordido a ti?

V
Autor del artículo Violetta H.

Comentarios

Deja un comentario