El patrón fallback-extension es una forma sencilla de eludir el límite de tamaño de 24 KB de los contratos inteligentes.
Supongamos que tenemos las funciones foo() y bar() en nuestro contrato principal y deseamos agregar baz() pero no podemos debido a la falta de espacio.
Agregamos una función fallback a nuestro contrato inteligente principal que delega las llamadas a funciones desconocidas a un contrato de extensión de manera similar a cómo funciona un proxy.
Ponemos baz() en el contrato de extensión. Cuando llamamos a baz() en el contrato principal, no coincidirá con ninguno de los selectores de función en el contrato principal, y por lo tanto activará la función fallback. Luego, baz() se ejecutará mediante delegatecall en el contrato de extensión.
Asegurar un layout de almacenamiento idéntico
Para que este patrón funcione, tanto el contrato principal como el contrato de extensión necesitan un layout de almacenamiento idéntico. Una forma sencilla de lograr esto es poner todas las variables de almacenamiento (¡sin excepciones!) en un solo contrato. Luego, tanto el contrato principal como el contrato de extensión heredan de él. Aquí hay un ejemplo:

Cambiar la extensión
En el ejemplo anterior, la dirección del contrato de extensión se almacena en una variable inmutable. Podríamos agregar una variable de almacenamiento adicional al contrato de almacenamiento que contenga la dirección de la extensión, y luego actualizar la dirección de la extensión cuando queramos cambiar parte de la funcionalidad del contrato.
Este enfoque no es recomendable: si se necesita capacidad de actualización (upgradeability), entonces es mejor usar un patrón proxy establecido. Además, leer esa variable de la dirección de la extensión desde el almacenamiento costará 2,100 de gas adicionales.
Se debe tener cuidado para evitar colisiones de selectores de función
Existe una probabilidad de aproximadamente 1 en 4 millones de que dos funciones aleatorias tengan el mismo selector. Sin embargo, debido a la paradoja del cumpleaños, esta probabilidad aumenta rápidamente cuando tenemos n selectores de función y solo se necesita una colisión para causar un comportamiento no deseado. No existen herramientas para esto; el desarrollador debe verificar manualmente los selectores de función.
Consideraciones de Gas
La propia extensión puede seguir este patrón y enviarlo a otro delegado. De hecho, en principio no hay límite en cuántas veces podemos hacer esto.
Sin embargo, cada “salto” agrega 2,600 de gas adicionales (el gas mínimo requerido para emitir un CALL o DELEGATECALL a una nueva dirección), por lo que el costo puede ser sustancial si la cadena es larga.
Debido a que las funciones en la extensión cuestan 2,600 de gas adicionales, queremos colocar las funciones rara vez utilizadas en la extensión, o funciones que están destinadas a ser llamadas principalmente off-chain donde el gas no importa.
Usar EIP 2930 en combinación con este patrón
Usar transacciones con listas de acceso con este patrón ahorrará 100 de gas al llamar funciones en la extensión. En general, si una transacción de Ethereum contiene una llamada entre contratos o un delegatecall, se debe usar una transacción de lista de acceso.
Usar una fallback-extension como contrato de implementación para proxies actualizables
Este patrón se puede usar con proxies regulares. Es decir, un contrato proxy puede delegar a un contrato de implementación que luego delega a una extensión. Ten en cuenta que las herramientas para actualizaciones (como las herramientas de actualización de Openzeppelin) no están diseñadas para funcionar con el patrón extension fallback y podrían no detectar problemas relacionados con la actualización.
Aprende más con RareSkills
Por favor, consulta nuestro Solidity Bootcamp para aprender más.
Publicado originalmente el 28 de diciembre de 2023