Solidity
September 10, 2023

Solidity | Экземпляры контрактов

Всем примет. Сегодня говорим об экземплярах контрактов и их особенностях. Покажу как работать с ними и их подводные камни.

Для чего вообще нам нужны экземпляры контрактов?

Взаимодействия с контрактом:

Самое очевидное, это для взаимодействия с контрактом.
Например: у нас есть контракт А, который реализует методы, и мы хотим их использовать у себя в контракте.

Раздельная функциональность:

Иногда наш код удобно разделить на части, но как это было у меня в прошлом проекте, наследование использовать не получиться, потому что код слишком большой и в следствии смарт-контракт не может быть развернут. В таком случае нам приходит на помощь экземпляры контрактов.

Управление доступом:

Также удобно создавать ролевую систему, тоже было показано в прошлом проекте, где вызов функций смарт-контракта A мог только смарт-контракт B

В общем это очень сильный инструмент для разработки, без которого нельзя. Важно понимать, что мы можем брать экземпляр только развернутого контракта. Поэтому мы передаем адрес контракта для его инициализации.

Пример использования:

Самый очевидный и популярный пример:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract A{    
    IERC20 token;    
    constructor(address _token){        
        token = IERC20(_token);    
    }
}

В данном примере я объявляю переменную типа интерфейс IERC20

token = IERC20(_token); - эта строка кода создает переменную token и присваивает ей значение, являющееся экземпляром смарт-контракта, реализующего интерфейс IERC20

address _token - это адрес любого токена ERC20.

Таким образом мы получаем доступ ко всем методам токена стандарта ERC20, адрес которого мы передали.

Более сложный пример:

 // SPDX-License-Identifier: MITpragma solidity ^0.8.9;
contract FirstContract{    
    struct human{        
        string name;        
        uint age;    
    }    
    address public owner;    
    mapping(address=>human) public humans;
    function setName(string calldata _name) public{        
        humans[msg.sender].name = _name;    
    }    
    function setAge(uint _age) public{        
        humans[msg.sender].age = _age;    
    }
}

 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.9;
 import "./test.sol";
contract B{    
    FirstContract public firstContract;    
        constructor(address _A){        
        firstContract = FirstContract(_A);    
    }
    function check() public view returns(string memory, uint){        
        (string memory _name, uint _age) = firstContract.humans(msg.sender);        
        return (_name, _age);    
    }    
    function checkOwner() public view returns(address){        
        return firstContract.owner();    
    }
}

Если у нас есть структура, маппинг или переменная, то обращаться мы можем к ней только под видом функции.

firstContract.owner() - для того, чтоб узнать содержимое переменной нам нужно обратиться к ней, как к функции.

firstContract.owner - так это выглядело, если бы у нас firstContract вызывался через наследование.

Для того, чтоб вытащить поля из структуры, которая находиться в маппинге, нужно создавать кортеж переменных и в данном случае мы обращаемся к маппингу как к функции, таким образом доставая нужные нам поля.

Если мы хотим достать только определенное поле структуры, то можно сделать так: (string memory _name, ) = firstContract.humans(msg.sender);

Тут я достаю только имя из нашей структуры.

Важно!

Мы не можем изменять переменные у экземпляра контракта в своем контракте, если нет функций, разрешающих это сделать.

Как видно у меня в примере, в FirstContract я создал 2 функции устанавливающие значения полям структуры. И теперь вызвав эти функции у себя в смарт-контракте, мы можем изменить эти поля.

Функции контракта B:

 function setName_(string memory _name) public{        
     firstContract.humans(msg.sender).name = _name;    
 }

Такая запись нам не подойдет.

 function setName_(string memory _name) public{        
     firstContract.setName(_name);    
  }

Такая запись подойдет.

Нужно понимать, что сам контракт B не видит эти функции, как при наследовании, поэтому нельзя взять экземпляр и таким образом использовать все его функции. Нужно явно объявлять их у себя.

Еще нельзя забывать, что мы должны импортировать интерфейс или сам смарт-контракт, чтоб наш контракт понимал с чем взаимодействовать.

В общем это основные особенности, при использовании экземпляра контракта. Мы должны рассматривать каждую переменную как функцию, а все остальное такое же как и в наследовании.

Это была небольшая статься, потому что я для себя подметил ряд необычных механик, которые думаю можно было рассмотреть. Всем удачи.

Мой тг канал.