WEB3DEV Español

Cover image for Programación en Solidity: Transfiriendo valor entre dos Contratos Inteligentes
Hector
Hector

Posted on

Programación en Solidity: Transfiriendo valor entre dos Contratos Inteligentes

Este artículo es una traducción de Sam Vishwas, hecha por Héctor Botero. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_eshttps://twitter.com/web3dev_es en Twitter.

La red de Ethereum permite que las transferencias sucedan entre diferentes tipos de cuentas, incluyendo dos usuarios, dos contratos, un contrato y un usuario o un usuario y un contrato. En este artículo, exploraremos en detalle el escenario contrato a contrato y examinaremos los diferentes métodos disponibles en Solidity para transferir valor entre ellos.

Image description

Transferir valor en la red de Ethereum requiere cuidadosas consideraciones y muchos pasos importantes. Esta guía cubre todo lo que necesitas saber sobre transferir ether (ETH), la criptomoneda nativa de la red, entre cuentas usando el lenguaje de programación Solidity.

Cuando iniciamos una transferencia, una transacción es creada y enviada a la red con la información, incluyendo la dirección del emisor y receptor, la cantidad de ether a ser transferida, el límite de gas y el precio. En Solidity, wei es la unidad de ether usada para transferencias, siendo 1⁰¹⁸ wei igual a 1 ether.

Luego que la transacción sea transmitida en la red, es validada y ejecutada por nodos, con cualquier contrato inteligente asociado que será ejecutado. Una vez validado, la transacción es añadida a la piscina de transacciones de la red, esperando a ser minada por un minero.

Cuando un minero mina exitosamente el bloque, la transacción se ejecuta y el ether es transferido desde la cuenta del emisor hacia la cuenta del receptor. El minero recibe la tarifa del gas asociada con la transacción como recompensa por incluirlo en el bloque.

En resumen, la transferencia de ether en la red de Ethereum involucra crear y emitir una transacción, validarla, ejecutarla y pagar una tarifa de gas a los mineros por procesar la transacción. Es importante considerar todos los factores envueltos en la transferencia como los precios de gas y límites para asegurar que una transferencia sea exitosa y rentable entre las cuentas deseadas.

Para aprender más sobre emitir y recibir ether en Solidity, visita la documentación oficial de Solidity para información más detallada.

Transferencia de valor entre dos Contratos Inteligentes:

La transferencia de valor entre los contratos inteligentes es una labor común en Solidity. De todos modos, es importante estar consciente de los diferentes métodos disponibles y los potenciales riesgos de seguridad que hay para poder escribir códigos seguros.

En este artículo, veremos cómo transferir valor entre dos contratos inteligentes usando Solidity, incluyendo ejemplos de uso de transfer, send y call. También hablaremos sobre las mejores prácticas para el manejo de errores y la administración de seguridad para ayudarnos a prevenir vulnerabilidades como los DDoS y los ataques de reentrada.

Los Contratos de Muestra

Para demostrar el valor de la transferencia entre dos contratos inteligentes, usaremos dos contratos simples: Sender y Recipient.

transfer (2300 gas, arroja error):

El contrato Sender tendrá una función que permitirá la transferencia de ether en el contrato Recipient. En el contrato Sender, la función transferToRecipient toma dos parámetros: la dirección payable del contrato Recipient y la cantidad de ether a transferir.

El método transfer es usado para transferir ether desde el contrato emisor hacia el contrato receptor. Si la transferencia falla por cualquier razón, incluyendo recipientAddress inexistentes, un error es arrojado y la transferencia de ether no será ejecutada y cualquier cambio de estado que fueron realizados antes de la excepción será revertido. Mucho mejor, un límite de gas de 2300 es colocado en el método de transferencia el cual, no solo asegura que la operación de la transferencia sea completada dentro del límite de gas, pero también actúa como un salvaguardas en caso de ataques de reentrada.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// El contrato emisor para enviar ether
contract Sender {
function transferToRecipient(address payable recipientAddress, uint amount) public {
require(balance >= amount, "Insufficient balance.");
// Transfiere ether al contrato receptor usando el método de transferencia
recipientAddress.transfer(amount);
}
}
Enter fullscreen mode Exit fullscreen mode

Payable: las funciones y direcciones declaradas payable pueden recibir ether en el contrato.

El contrato Recipient tendrá la funcionalidad requerida para recibir Ether y una función que permite retirar el ether transferido.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// El contrato receptor que recibirá ether
contract Recipient {

   //--- Eventos opcionales que ayudarán en nuestros esfuerzos para debugear.
   event ReceivedWithData(address sender, uint256 amount, bytes data);
   event ReceivedWithoutData(address sender, uint256 amount);
   address payable public owner;
   // Configura el dueño al creador del contrato
   constructor() {
       owner = payable(msg.sender);
   }
   // Esta función está marcada como pagable para que el contrato pueda recibir ether.
   // También es llamada"receive" porque esta es la función que es invocada
   // cuando el ether es enviado al contrato sin ninguna función de datos.
   receive() external payable {
       //--- Evita añadir cualquier código aquí pero, para propósitos de debugger, vamos a emitir el evento:
       emit ReceivedWithoutData(msg.sender, msg.value);
   }
   fallback() external payable {
     //--- Evita añadir cualquier código aquí pero, para propósitos de debugger, vamos a emitir el evento:
     emit Received(msg.sender, msg.value, msg.data);
   }
   // Esta función siempre permitirá al dueño del contrato a 
   // transferir ether a su propia cuenta.
   function withdraw() external {
       // Asegúrate que solo el dueño pueda llamar esta función
       require(msg.sender == owner, "Only the owner can withdraw.");
       // Transfer the entire balance of this contract to the owner
       uint256 balance = address(this).balance;
       // Asegúrate que haya un balance que no sea cero para transferir
       require(0 != balance, "There is no Ether to withdraw.");
       owner.transfer(balance);
   }
}

Enter fullscreen mode Exit fullscreen mode

Un contrato que reciba Ether debe tener, al menos, una de las funciones de abajo
receive() external payable
fallback() external payable
receive() es invocado si msg.data está vacío, sino fallback() es invocado.

Image description

Cuando desarrollas contratos inteligentes, es importante incluir las funcionalidades necesarias para prevenir que los fondos sean bloqueados en el contrato de forma indefinida. En caso del contrato del receptor, hemos añadido la función de “retirar” para permitir que el receptor pueda acceder y usar los fondos transferidos a él. Esto asegura que los fondos no sean permanentementes inaccesibles y provee flexibilidad al receptor para usarlos, como debería ser.

Cuando la función transferToRecipient es invocada, primero revisará que el contrato receptor tenga una función pagable que pueda recibir el ether. Si el contrato receptor no tiene ninguna función pagable, la transferencia fallará.

Por lo tanto, es esencial asegurarse que el contrato receptor tenga una función pagable, como las funciones receive o fallback, para recibir ether enviados desde el contrato emisor. Sino, la transferencia fallará y la transacción será revertida.

send (2300 gas, retorna el bool)

El mismo contrato emisor puede ser reescrito usando el método send:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Sender {
   function transferToRecipient(address payable recipient, uint256 amount) public {
       require(address(this).balance >= amount, "Insufficient balance");
       (bool success, ) = recipient.send(amount);
       require(success, "Transfer failed");
   }
}

Enter fullscreen mode Exit fullscreen mode

En esta implementación, la función transferToRecipient usa el método de transferencia send para transferir el ether a la dirección del receptor. A diferencia del método transfer, el método send retorna el valor boolean indicando si la transferencia fue exitosa o no. El método send también limita la cantidad de gas que puede ser usada a 2300, similar al método de transfer.

Finalmente, la función revisa que la transferencia fue exitosa usando el valor boolean, retornado por el método send y arrojará un error si no lo fue.

La diferencia entre los métodos transfer y send es que el método transfer arroja un error si la transferencia falla mientras que el método send, retorna un valor boolean indicando si la transferencia fue exitosa o no. Sin importar qué valor regresa, cualquier cambio de estado que fue realizado antes del método send que fue llamado, aún será salvado por el blockchain, pero la transferencia de ether no será ejecutada si regresa con false.

El método send puede ser usado en situaciones donde las consecuencias de una transferencia fallida no son graves y donde sea más deseable tener más control sobre la fluidez de la transacción. Aun así, el método transfer es preferible en situaciones donde el flujo de la transacción es mucho más simple y donde es importante asegurarse que la transferencia sea exitosa o falle, atómicamente.

call devolver todo el gas o configurarlo, retorna el bool):

El mismo contrato emisor puede ser reescrito usando el método call:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Sender {
   function transferToRecipient(address payable recipient, uint256 amount) public {
       require(address(this).balance >= amount, "Insufficient balance");
       (bool success, bytes memory data) = recipient.call{value: amount}("");
       require(success, "Transfer failed");
   }
}

Enter fullscreen mode Exit fullscreen mode

En esta implementación, la función transferToRecipient usa el método de transferencia call para transferir el ether a la dirección del receptor. El método call permite mucha más personalización de la transacción, incluyendo la especificación del límite de gas y pasando argumentos al receptor del contrato. En este caso, hemos pasado un array de byte vacío como el parámetro de datos para indicar que no vamos a pasar cualquier argumento al contrato receptor.

Como el método send, el método call retorna el valor boolean indicando si la transacción fue exitosa o no. Además, retorna un array de bytes conteniendo cualquier dato que fue retornado por el receptor del contrato.

Finalmente, la función revisa que la transferencia haya sido exitosa usando el valor boolean retornado con el método call y arroja un error si no fue así.

Comparado con el método send, el método call permite más personalización de la transacción, incluyendo especificar un límite de gas y pasar argumentos al receptor del contrato. No obstante, también es más complejo de usar y puede ser propenso a los errores si no es usado cuidadosamente. Además, el costo de gas de la transacción call puede ser superior que el de la transacción send por las opciones adicionales de personalización disponibles.

Para especificar los límites de gas en el método call, podemos usar la siguiente implementación:

(bool success, bytes memory data) = recipient.call{value: amount, gas: 2300}("");
Enter fullscreen mode Exit fullscreen mode

En esta implementación, hemos añadido parámetros de gas al método call, especificando un límite de gas de 2300. Este es el mismo límite de gas que es impuesto en el método transfer en Ethereum.

Colocando un límite de gas de 2300, estamos limitando la cantidad de gas que puede ser usado por el receptor del contrato cuando recibe la transferencia de ether. Esto es una medida de seguridad para prevenir ataques de reentrada, donde contratos malignos pueden intentar llamar al contrato emisor reiteradamente, antes que la transferencia original sea completada, causando robos potenciales de los fondos o causando otros comportamientos inesperados.

Es importante tomar en cuenta que configurar un límite de gas de 2300 puede que no sea apropiado para todos los contratos y que el límite de gas debe ser cuidadosamente elegido, basado en los requisitos específicos del contrato y de los riesgos potenciales involucrados. Adicionalmente, es siempre importante probar a fondo los contratos para encontrar vulnerabilidades potenciales de la seguridad, incluyendo ataques de reentrada, antes de desplegarlos a la red de Ethereum.

En general, la elección de los métodos call y send dependen de los requisitos específicos del contrato y del nivel de personalización y control que son necesarios para el flujo de la transacción.

Gracias por interesarte en este tópico, si tienes cualquier pregunta específica de mis artículos futuros, entonces, siéntete libre de dejarme un mensaje.

_ Continúa tu aprendizaje, visitandos los siguientes enlaces_
https://docs.soliditylang.org/en/v0.8.19/
https://medium.com/coinmonks/solidity-transfer-vs-send-vs-call-function-64c92cfc878a
https://solidity-by-example.org/

Gracias a ChatGPT por ayudarme en escribir este artículo

Discussion (0)