Ir al contenido principal

Reducir el tamaño de los contratos para luchar contra el límite de tamaño del contrato

soliditycontratos Inteligentesalmacenamientotruffle
Intermedio
Markus Waas
soliditydeveloper.com(opens in a new tab)
26 de junio de 2020
6 minuto leído minute read

¿Por qué hay un límite?

El 22 de noviembre de 2016(opens in a new tab) el hard-fork Spurious Dragon introdujo EIP-170(opens in a new tab), que agregó un límite de tamaño del contrato inteligente de 24.576 bytes. Para usted, como desarrollador de Solidity, esto significa que cuando añada más y más funcionalidad a su contrato, en algún momento alcanzará el límite y al realizar la implementación verá el error:

Advertencia: El tamaño del código del contrato excede los 24576 bytes (un límite introducido en el Spurious Dragon). This contract may not be deployable on Mainnet. Considere habilitar el optimizador (con un valor de "ejecución" bajo), desactivar las cadenas de reversión, o utilizar librerías.

Este límite se introdujo para prevenir ataques de denegación de servicio (DOS). Cualquier llamada a un contrato es relativamente barata en términos de gas. Sin embargo, el impacto de una llamada al contrato para nodos Ethereum aumenta desproporcionadamente dependiendo del tamaño del código del contrato llamado (leer el código del disco, preprocesar el código, agregar datos a la prueba Merkle). Cada vez que uno se encuentre en una situación en la que el atacante requiera pocos recursos para causar mucho trabajo a los demás, obtiene el potencial para ataques de DOS.

Originalmente esto era un problema menor porque el tamaño natural de un contrato es el límite de gas de un bloque. Obviamente, un contrato debe implementarse dentro de una transacción que contenga todo el código de bytes del contrato. Si incluye solo esa transacción en un bloque, puede usar todo ese gas, pero no es infinito. Desde la Actualización London, el límite de gas de un bloque ha podido variar entre 15 millones y 30 millones de unidades dependiendo de la demanda de la red.

Asuma el reto

Desafortunadamente, no hay una manera fácil de obtener el tamaño del código de bytes de sus contratos. Una gran herramienta que lo ayudará es el complemento truffle-contract-size(opens in a new tab) si usa Truffle.

  1. npm instalador truffle-contrato-tamaño
  2. Añadir el complemento a truffle-config.js: plugins: ["truffle-contract-size"]
  3. Ejecutar truffle run contract-size

Esto le ayudará a averiguar cómo sus cambios están afectando los tamaños totales del contrato.

A continuación veremos algunos métodos ordenados según su posible impacto. Piénsalo en términos de pérdida de peso. La mejor estrategia para que alguien alcance su peso deseado (en nuestro caso 24kb) es centrarse primero en los métodos de gran impacto. En la mayoría de los casos, basta con corregir la dieta para conseguirlo, pero a veces se necesita un poco más. Luego puedes añadir algo de ejercicio (impacto medio) o incluso suplementos (impacto bajo).

Gran impacto

Separe sus contratos

Este debería ser siempre su primera estrategia. ¿Cómo puede separar el contrato en múltiples contratos más pequeños? Por lo general, esto lo obliga a crear una buena estructura para sus contratos. Desde el punto de vista de la legibilidad del código, siempre se prefieren los contratos más pequeños. A la hora de dividir los contratos, plantéese lo siguiente:

  • ¿Qué funciones deben ir juntas? Cada conjunto de funciones quizás resulte mejor en su propio contrato.
  • ¿Qué funciones no requieren la lectura del estado del contrato o solo un subconjunto específico del estado?
  • ¿Se puede dividir el almacenamiento y la funcionalidad?

Bibliotecas

Una forma sencilla de mover el código de funcionalidad más allá del almacenamiento es usar una biblioteca(opens in a new tab). No declare las funciones de la biblioteca como internas, ya que que se agregarán al contrato(opens in a new tab) directamente durante la compilación. Pero si utiliza funciones públicas, estas estarán en realidad en un contrato de biblioteca separado. Considere el uso de for(opens in a new tab) para que el uso de las bibliotecas sea más conveniente.

Proxies

Una estrategia más avanzada sería un sistema de proxy. Las bibliotecas utilizan DELEGATECALL en la parte trasera, lo que simplemente ejecuta la función de otro contrato con el estado del contrato invocante. Eche un vistazo a esta entrada de blog(opens in a new tab) para aprender más sobre los sistemas de proxy. Le darán mayor funcionalidad, por ejemplo, permiten la actualización, pero también añaden mucha complejidad. No los añadiría solo para reducir el tamaño de los contratos, a menos que sea su única opción por cualquier razón.

Impacto medio

Elimine funciones

Esta debería ser la opción obvia. Las funciones aumentan el tamaño de un contrato.

  • Externas: A menudo añadimos muchas funciones de visualización por motivos de conveniencia. Eso está muy bien hasta que alcance el límite de tamaño. Es así que tal vez quiera considerar eliminar todas excepto las absolutamente esenciales.
  • Internas: También puede eliminar funciones internas/privadas y simplemente insertar el código en la medida en que la función sea invocada solo una vez.

Evite variables adicionales

Un simple cambio como este:

1function get(uint id) returns (address,address) {
2 MyStruct memory myStruct = myStructs[id];
3 return (myStruct.addr1, myStruct.addr2);
4}
Copiar
1function get(uint id) returns (address,address) {
2 return (myStructs[id].addr1, myStructs[id].addr2);
3}
Copiar

implica una diferencia de 0,28 kb. Es posible que pueda encontrar muchas situaciones similares en sus contratos y que las cantidades sean significativas.

Acorte los mensajes de error

Los mensajes largos de revertir y, en particular, muchos mensajes diferentes de revertir pueden inflar el contrato. En su lugar, use códigos de error cortos y decodifíquelos en su contrato. Un mensaje largo puede ser mucho más corto:

1require(msg.sender == owner, "Only the owner of this contract can call this function");
2
Copiar
1require(msg.sender == owner, "OW1");
Copiar

Utilice errores personalizados en lugar de mensajes de error

Los errores personalizados se introdujeron en Solidity 0.8.4(opens in a new tab). Son una excelente manera de reducir el tamaño de sus contratos, porque están codificados con ABI como selectores (al igual que las funciones).

1error Unauthorized();
2
3if (msg.sender != owner) {
4 revert Unauthorized();
5}
Copiar

Considere un valor de ejecución bajo en el optimizador

También puede cambiar la configuración del optimizador. El valor por defecto de 200 significa que intenta optimizar el código de bytes como si una función fuera llamada 200 veces. Si lo cambia a 1, básicamente le indicará al optimizador que actúe de manera optimizada en el caso de ejecutar cada función una sola vez. Una función optimizada para ejecutarse solo una vez significa que está optimizada para la implementación misma. Tenga en cuenta que esto incrementa el costo del gas por ejecutar las funciones, así que tal vez no quiera inclinarse por esta opción.

Pequeño impacto

Evite pasar estructuras a funciones

Si usa el ABIEncoderV2(opens in a new tab), puede ayudar a no pasar estructuras a una función. En vez de pasar el parámetro como una estructura...

1function get(uint id) returns (address,address) {
2 return _get(myStruct);
3}
4
5function _get(MyStruct memory myStruct) private view returns(address,address) {
6 return (myStruct.addr1, myStruct.addr2);
7}
Copiar
1function get(uint id) returns(address,address) {
2 return _get(myStructs[id].addr1, myStructs[id].addr2);
3}
4
5function _get(address addr1, address addr2) private view returns(address,address) {
6 return (addr1, addr2);
7}
Copiar

... pase directamente los parámetros requeridos. En este ejemplo ahorramos 0,1 kb.

Declare la visibilidad correcta de las funciones y variables

  • ¿Funciones o variables que solo sean invocadas desde el exterior? Declárelas como externas en lugar de públicas.
  • ¿Funciones o variables que solo sean invocadas desde dentro del contrato? Declárelas como privadas o internas en lugar de públicas.

Elimine modificadores

Los modificadores, especialmente cuando se utilizan demasiado, podrían tener un impacto significativo en el tamaño del contrato. Considere eliminarlos y utilizar funciones.

1modifier checkStuff() {}
2
3function doSomething() checkStuff {}
Copiar
1function checkStuff() private {}
2
3function doSomething() { checkStuff(); }
Copiar

Estos consejos deberían ayudarlo a reducir significativamente el tamaño de un contrato. Una vez más, no me canso de decirlo, siempre y cuando sea posible, haga hincapié en la división de los contratos para lograr un mayor impacto.

Última edición: @santy95_77(opens in a new tab), 21 de febrero de 2024

¿Le ha resultado útil este tutorial?