Interacción entre Smart Contracts

Solidity

En este tutorial vamos a ver cómo puedes lograr que varios Smart Contracts interactúen entre ellos. Por ejemplo, podrías querer ejecutar una función de otro Smart Contract desde tu Smart Contract. La interacción mediante Smart Contracts es necesaria, entre otras muchas cosas, cuando quieres transferir tokens ERC20.

Creación de Smart Contracts

Vamos a suponer que tenemos dos Smart Contracts, a los que llamaremos ContratoA y ContratoB. En el contrato ContratoA crearemos una función llamada ping que llamará a otra función del contrato ContratoB a la que llamaremos pong. Vamos a comenzar creando ambos Smart Contracts en el mismo archivo, aunque también veremos luego cómo crear cada uno de los contratos en un archivo diferente.

Para empezar, definiremos el contrato ContratoB con un método llamado pong, que será el método al que llamaremos desde el contrato ContratoA:

contract ContratoB
{
    function pong() external pure returns(string memory)
    {
        return 'Pong';
    }
}

Tal y como puedes ver, hemos usado el modificador de visibilidad external, ya que esta función solamente será llamada desde fuera del contrato ContratoB. También hemos usando la sentencia view, ya que no modificaremos ningún dato en el contrato ContratoB, sino que solamente devolveremos información.

A continuación vamos a crear el contrato ContratoA. En este contrato declararemos la función ping, que se encargará de llamar a la función pong del contrato ContratoB. Sin embargo, no sabemos de antemano la dirección del contrato ContratoB, por lo que necesitamos crear una variable tipo address para almacenarla, a la que llamaremos dirB, así como una función que nos permita establecer esta dirección, que recibirá el nombre de setDirB:

contract ContratoA
{
    address dirB;

    function setDirB(address _dirB) external
    {
        dirB = _dirB;
    }

    function ping() view external returns(string memory)
    {
        ContratoB contratoB = ContratoB(dirB);
        return contratoB.pong();
    }
}

Hemos usado el modificador de visibilidad external tanto para la función setDirB como la función ping, ya que ambas serán invocadas desde fuera del contrato.

La función setDirB recibe un parámetro tipo address, que será la dirección del contrato ContratoB, que asignaremos a la variable de estado dirB.

En el interior de la función ping definimos la variable contratoB, que será de tipo ContratoB. El contenido de esta variable será un puntero al contrato ContratoB, por lo que hemos asignado la dirección del mismo, almacenada en la variable dirB, mediante esta sentencia:

ContratoB contratoB = ContratoB(dirB);

Finalmente, hemos llamado a la función pong del contrato ContratoB para devolver su resultado mediante la siguiente sentencia:

return contratoB.pong();

Deploy de los Smart Contracts

Una vez creados los Smart Contracts debemos desplegarlos en la blockchain. Para ello, debes seguir estos pasos:

  1. Accede a las sección «deploy & run transactions de remix» mediante el menú de la izquierda y selecciona el contrato ContratoB en el menú desplegable:
  2. Una vez seleccionado, haz clic en la opción «deploy» para desplegarlo en la blockchain. Verás que en la parte inferior de esta sección se muestra la dirección del contrato:
  3. A continuación selecciona el contrato ContratoA en el menú desplegable y luego haz clic en «deploy» para desplegarlo:
  4. Con esto ya habremos desplegado ambos contratos.

Interacción entre Smart Contracts

Una vez desplegados los Smart Contracts, ya podremos interactuar entre ellos. Para ello selecciona la dirección del contrato ContratoB y cópiala. Luego expande el contrato ContratoA haciendo click en él para ver las funciones externas que puede ejecutar.

Pega la dirección del contrato ContratoB en el campo de texto que verás al lado de la función setDirB para usarla como parámetro. Luego haz clic en el botón setDirB para ejecutar la función y que la variable de estado dirB del contrato ContratoA pase a contener la dirección del contrato ContratoB. Verás un mensaje de confirmación en la consola de Remix.

Ahora ya puedes ejecutar la función ping del contrato ContratoA haciendo click en el botón ping. Verás que obtienes la cadena de texto Pong como respuesta.

Crea un archivo para cada contrato

Hemos visto cómo ejecutar funciones de otros contratos. En este caso, ambos contratos estaban en el mismo archivo. Sin embargo, lo más habitual es que los contratos se encuentren en archivos diferentes. A continuación vamos a ver cómo situar cada uno de los contratos que hemos creado en un archivo diferente.

Lo primero que vas a hacer es crear un archivo llamado ContratoA.sol, en el que incluirás únicamente el código del contrato ContratoA:

pragma solidity ^0.8.13;

contract ContratoA
{
    address dirB;

    function setDirB(address _dirB) external
    {
        dirB = _dirB;
    }

    function ping() view external returns(string memory)
    {
        ContratoB contratoB = ContratoB(dirB);
        return contratoB.pong();
    }
}

Luego crea otro archivo llamado ContratoB.sol, en donde incluirás el código del contrato ContratoB:

pragma solidity ^0.8.13;

contract ContratoB
{
    function pong() external pure returns(string memory)
    {
        return 'Pong';
    }
}

Tal y como podrás comprobar, Remix te indicará que existe un error en la siguiente línea del contrato ContratoA:

ContratoB contratoB = ContratoB(dirB);

El motivo es que estás referenciando al contrato ContratoB desde el contrato ContratoA, cuando no tiene ni idea de dónde se encuentra. Para solucionar este problema usaremos la sentencia import, que te permitirá indicar la localización de otro contrato:

import 'ContratoB.sol';

Mediante la sentencia import podrás importar un contrato en otro contrato. Esta sentencia se suele incluir por convención al comienzo de los archivos .sol, tras la sentencia pragma solidity, aunque antes de declarar ningún contrato. Tras importar el contrato, desaparecerá el error. Además, los contratos estarán más organizados.

Para desplegar los contratos, tendrás que seleccionar el archivo ContratoB.sol; luego selecciona el contrato ContratoB y haz clic en deploy. Finalmente selecciona el archivo ContratoA, luego el contrato ContratoA y haz clic en deploy.

Interacción mediante interfaces

Idealmente no deberías referenciar otros contratos directamente, sino interfaces de otros contratos. Para crear una interfaz, tendrás que usar la sentencia interface, definiendo las funciones en el interior de la interfaz tal y como harías en un contrato, aunque incluyendo únicamente la declaración de las funciones y no su implementación. Bastará con que en una interfaz definas las funciones externas.

Vamos a definir una interfaz llamada InterfaceB en el archivo ContratoB, en donde incluiremos la definición del método pong:

interface InterfaceB {
    function pong() external pure returns(string memory);
}

Seguidamente, tendrás que asignar la interfaz InterfaceB al contrato ContratoB, mediante la sentencia is, seguida del nombre de la interfaz:

pragma solidity ^0.8.13;

contract ContratoB is InterfaceB
{
    function pong() external pure  returns(string memory)
    {
        return 'Pong';
    }
}

Luego, en la función ping de contrato ContratoA, tendrás que reemplazar las referencias al contrato ContratoB por referencias a la interfaz InterfaceB:

function ping() view external returns(string memory)
{
    InterfaceB contratoB = InterfaceB(dirB);
    return contratoB.pong();
}

Ahora ya podrás desplegar tanto los contratos como la interfaz que hemos creado y ejecutar las funciones setDirB y ping del contrato ContratoA tal y como hemos hecho antes.

Interacción mediante métodos abstractos

Las interfaces tienen un problema, y es que no soportan la herencia de contratos. Para arreglarlo, puedes definir la interfaz como un contrato abstracto, usando la sentencia abstract contract en lugar de interface:

pragma solidity ^0.8.13;

abstract contract InterfaceB  {
    function pong() virtual external pure returns(string memory);
}

contract ContratoB is InterfaceB
{
    function pong() override external pure  returns(string memory)
    {
        return 'Pong';
    }
}

Tal y como ves, hemos usado el modificador virtual en la declaración del método pong del contrato InterfaceB. Se trata de un requerimiento de los contratos abstractos. Además, las implementaciones de los métodos virtuales deben incluir el modificador override.

Esto ha sido todo.


Avatar de Edu Lazaro

Edu Lázaro: Ingeniero técnico en informática, actualmente trabajo como desarrollador web y programador de videojuegos.

👋 Hola! Soy Edu, me encanta crear cosas y he redactado esta guía. Si te ha resultado útil, el mayor favor que me podrías hacer es el de compatirla en Twitter 😊

Si quieres conocer mis proyectos, sígueme en Twitter.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

“- Hey, Doc. No tenemos suficiente carretera para ir a 140/h km. - ¿Carretera? A donde vamos, no necesitaremos carreteras.”