Ir al contenido principal

Interactuar con otros contratos desde Solidity

contratos Inteligentes
Solidity
remix
implementación
composabilidad
Avanzado
jdourlens
5 de abril de 2020
4 minuto leído

En los tutoriales anteriores aprendimos mucho sobre cómo implementar su primer contrato inteligente y añadirle algunas funcionalidades como el control de acceso con modificadoresopens in a new tab o el manejo de errores en Solidityopens in a new tab. En este tutorial aprenderemos cómo implementar un contrato inteligente desde un contrato existente e interactuar con él.

Crearemos un contrato que permita a cualquiera tener su propio contrato inteligente Counter creando una fábrica para este. Se llamará CounterFactory. En primer lugar, aquí está el código de nuestro contrato inteligente Counter inicial:

1pragma solidity 0.5.17;
2
3contract Counter {
4
5 uint256 private _count;
6 address private _owner;
7 address private _factory;
8
9
10 modifier onlyOwner(address caller) {
11 require(caller == _owner, "No es el propietario del contrato");
12 _;
13 }
14
15 modifier onlyFactory() {
16 require(msg.sender == _factory, "Necesita usar la fábrica");
17 _;
18 }
19
20 constructor(address owner) public {
21 _owner = owner;
22 _factory = msg.sender;
23 }
24
25 function getCount() public view returns (uint256) {
26 return _count;
27 }
28
29 function increment(address caller) public onlyFactory onlyOwner(caller) {
30 _count++;
31 }
32
33}
Mostrar todo

Tenga en cuenta que hemos modificado ligeramente el código del contrato para hacer un seguimiento de la dirección de la fábrica y la dirección del propietario del contrato. Cuando se llama al código de un contrato desde otro contrato, msg.sender se referirá a la dirección de nuestra fábrica de contratos. Este es un punto realmente importante que hay que entender, ya que usar un contrato para interactuar con otros contratos es una práctica común. Por lo tanto, debería prestar atención a quién es el emisor en casos complejos.

Por esta razón, también añadimos un modificador onlyFactory que se asegura de que la función de cambio de estado solo pueda ser invocada por la fábrica que pasará el invocador inicial como parámetro.

Dentro de nuestro nuevo CounterFactory, que gestionará todos los demás Counter, añadiremos un mapeo que asociará a un propietario con la dirección de su contrato de contador:

1mapping(address => Counter) _counters;

En Ethereum, los mapeos (mapping) son el equivalente a los objetos en JavaScript; permiten asociar una clave de tipo A a un valor de tipo B. En este caso, asociamos la dirección de un propietario con la instancia de su Counter.

Instanciar un nuevo Counter para alguien se verá así:

1 function createCounter() public {
2 require (_counters[msg.sender] == Counter(0));
3 _counters[msg.sender] = new Counter(msg.sender);
4 }

Primero comprobamos si la persona ya posee un contador. Si no posee un contador, instanciamos uno nuevo pasando su dirección al constructor de Counter y asignamos la instancia recién creada al mapeo.

Para obtener la cuenta de un Counter específico, se verá así:

1function getCount(address account) public view returns (uint256) {
2 require (_counters[account] != Counter(0));
3 return (_counters[account].getCount());
4}
5
6function getMyCount() public view returns (uint256) {
7 return (getCount(msg.sender));
8}

La primera función comprueba si el contrato Counter existe para una dirección determinada y, a continuación, llama al método getCount desde la instancia. La segunda función, getMyCount, es solo un atajo para pasar msg.sender directamente a la función getCount.

La función increment es bastante similar, pero pasa el emisor de la transacción original al contrato Counter:

1function increment() public {
2 require (_counters[msg.sender] != Counter(0));
3 Counter(_counters[msg.sender]).increment(msg.sender);
4 }

Tenga en cuenta que, si se llama demasiadas veces, nuestro contador podría ser víctima de un desbordamiento. Debería usar la librería SafeMathopens in a new tab tanto como sea posible para protegerse de este posible caso.

Para implementar nuestro contrato, necesitará proporcionar el código de CounterFactory y de Counter. Al implementar, por ejemplo en Remix, deberá seleccionar CounterFactory.

Este es el código completo:

1pragma solidity 0.5.17;
2
3contract Counter {
4
5 uint256 private _count;
6 address private _owner;
7 address private _factory;
8
9
10 modifier onlyOwner(address caller) {
11 require(caller == _owner, "No es el propietario del contrato");
12 _;
13 }
14
15 modifier onlyFactory() {
16 require(msg.sender == _factory, "Necesita usar la fábrica");
17 _;
18 }
19
20 constructor(address owner) public {
21 _owner = owner;
22 _factory = msg.sender;
23 }
24
25 function getCount() public view returns (uint256) {
26 return _count;
27 }
28
29 function increment(address caller) public onlyFactory onlyOwner(caller) {
30 _count++;
31 }
32
33}
34
35contract CounterFactory {
36
37 mapping(address => Counter) _counters;
38
39 function createCounter() public {
40 require (_counters[msg.sender] == Counter(0));
41 _counters[msg.sender] = new Counter(msg.sender);
42 }
43
44 function increment() public {
45 require (_counters[msg.sender] != Counter(0));
46 Counter(_counters[msg.sender]).increment(msg.sender);
47 }
48
49 function getCount(address account) public view returns (uint256) {
50 require (_counters[account] != Counter(0));
51 return (_counters[account].getCount());
52 }
53
54 function getMyCount() public view returns (uint256) {
55 return (getCount(msg.sender));
56 }
57
58}
Mostrar todo

Luego de la compilación, en la sección de implementación de Remix, debe seleccionar la fábrica a implementar:

Selección de la fábrica a implementar en Remix

Luego puede experimentar con la fábrica de su contrato y comprobar cómo cambia el valor. Si desea llamar al contrato inteligente desde una dirección diferente, tendrá que cambiar la dirección en el selector de Cuentas de Remix.

Última actualización de la página: 15 de agosto de 2023

¿Le ha resultado útil este tutorial?