Vai al contenuto principale

Interazione con altri contratti da Solidity

smart contractSolidityremixdistribuzionecomponibilità
Argomenti avanzati
jdourlens
EthereumDev(opens in a new tab)
5 aprile 2020
4 minuti letti minute read
comp-tutorial-metadata-tip-author 0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE

Nei tutorial precedenti abbiamo imparato molto su come distribuire il primo Smart Contract e aggiungervi alcune funzionalità come il controllo degli accessi con modificatori(opens in a new tab) o la gestione degli errori in Solidity(opens in a new tab). In questo tutorial impareremo come distribuire uno Smart Contract da uno esistente e interagirvi.

Creeremo un contratto che permetta a chiunque ad avere uno Smart Contract Counter creando una factory associata, il cui nome sarà CounterFactory. Prima di tutto, ecco il codice del nostro Smart Contract iniziale Counter:

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, "You're not the owner of the contract");
12 _;
13 }
14
15 modifier onlyFactory() {
16 require(msg.sender == _factory, "You need to use the factory");
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}
Mostra tutto
Copia

Nota: abbiamo leggermente modificato il codice del contratto per tenere traccia dell'indirizzo della factory e dell'indirizzo del proprietario del contratto. Quando si chiama il codice di un contratto da un altro contratto, msg.sender fa riferimento all'indirizzo della factory del contratto. È molto importante comprendere questo concetto, perché usare un contratto per interagire con altri contratti è una prassi comune. Dovresti quindi saper gestire il mittente nei casi complessi.

Per questo motivo abbiamo aggiunto anche un modificatore onlyFactory che fa in modo che la funzione di cambiamento dello stato sia chiamabile solo dalla factory che passerà il chiamante originale come parametro.

Nella nostra nuova CounterFactory che gestirà tutti gli altri Counter aggiungeremo un mapping che assocerà un proprietario all'indirizzo del relativo contratto Counter:

1mapping(address => Counter) _counters;
Copia

In Ethereum, i mapping equivalgono agli oggetti di Javascript, che permettono di mappare una chiave di tipo A a un valore di tipo B. In questo caso mappiamo l'indirizzo di un proprietario all'istanza del suo Counter.

Istanziare un nuovo Counter per un utente sarà più o meno:

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

Prima controlliamo se la persona possiede già un Counter. Se non lo possiede, istanziamo un nuovo Counter passandone l'indirizzo al costruttore del Counter e assegniamo l'istanza appena creata al mapping.

Ottenere il conteggio di un Counter specifico significa:

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}
Copia

La prima funzione controlla se il contratto Counter esiste per un determinato indirizzo e poi chiama il metodo getCount dall'istanza. La seconda funzione: getMyCount è passa direttamente msg.sender alla funzione getCount.

La funzione increment è abbastanza simile ma passa il mittente della transazione originale al contratto Counter:

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

Nota: se chiamato troppe volte, il Counter potrebbe rimanere vittima di overflow. È consigliabile usare la libreria di SafeMath(opens in a new tab) il più possibile per evitare questa eventualità.

Per distribuire il contratto, dovrai fornire sia il codice della CounterFactory che il Counter. Quando si esegue la distribuzione ad esempio in Remix, è necessario selezionare CounterFactory.

Ecco il codice 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, "You're not the owner of the contract");
12 _;
13 }
14
15 modifier onlyFactory() {
16 require(msg.sender == _factory, "You need to use the factory");
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}
Mostra tutto
Copia

Dopo la compilazione, nella sezione di distribuzione di Remix si selezionerà la factory da distribuire:

Selezione della factory da distribuire in Remix

A questo punto puoi fare esperimenti la factory del contratto e controllare il valore che cambia. Se vorresti chiamare il contratto intelligente da un indirizzo differente, dovrai cambiare l'indirizzo nella selezione Conto di Remix.

Questo tutorial è stato utile?