Взаимодействие с другими контрактами из Solidity
В предыдущих руководствах мы многое узнали о том, как развернуть свой первый смарт-контракт и добавить в него некоторые функции, такие как управление доступом с помощью модификаторов (opens in a new tab) или обработка ошибок в Solidity (opens in a new tab). В этом руководстве мы узнаем, как развернуть смарт-контракт из существующего контракта и взаимодействовать с ним.
Мы создадим контракт, который позволит любому желающему иметь свой собственный смарт-контракт Counter, создав для него фабрику, которая будет называться CounterFactory. Сначала приведем код нашего исходного смарт-контракта Counter:
pragma solidity 0.5.17;
contract Counter {
uint256 private _count;
address private _owner;
address private _factory;
modifier onlyOwner(address caller) {
require(caller == _owner, "You're not the owner of the contract");
_;
}
modifier onlyFactory() {
require(msg.sender == _factory, "You need to use the factory");
_;
}
constructor(address owner) public {
_owner = owner;
_factory = msg.sender;
}
function getCount() public view returns (uint256) {
return _count;
}
function increment(address caller) public onlyFactory onlyOwner(caller) {
_count++;
}
}
Обратите внимание, что мы немного изменили код контракта, чтобы отслеживать адрес фабрики и адрес владельца контракта. Когда вы вызываете код контракта из другого контракта, msg.sender будет ссылаться на адрес нашей фабрики контрактов. Это очень важный момент для понимания, поскольку использование контракта для взаимодействия с другими контрактами является обычной практикой. Поэтому в сложных случаях следует обращать внимание на то, кто является отправителем.
Для этого мы также добавили модификатор onlyFactory, который гарантирует, что функция, изменяющая состояние, может быть вызвана только фабрикой, которая передаст первоначального вызывающего в качестве параметра.
Внутри нашей новой CounterFactory, которая будет управлять всеми остальными счетчиками, мы добавим сопоставление, которое свяжет владельца с адресом его контракта счетчика:
mapping(address => Counter) _counters;
В Эфириуме сопоставления (mapping) эквивалентны объектам в JavaScript, они позволяют сопоставить ключ типа A со значением типа B. В данном случае мы сопоставляем адрес владельца с экземпляром его счетчика.
Создание экземпляра нового счетчика для кого-либо будет выглядеть следующим образом:
function createCounter() public {
require (_counters[msg.sender] == Counter(0));
_counters[msg.sender] = new Counter(msg.sender);
}
Сначала мы проверяем, владеет ли уже этот человек счетчиком. Если у него нет счетчика, мы создаем новый счетчик, передавая его адрес в конструктор Counter, и присваиваем вновь созданный экземпляр сопоставлению.
Получение значения конкретного счетчика будет выглядеть так:
function getCount(address account) public view returns (uint256) {
require (_counters[account] != Counter(0));
return (_counters[account].getCount());
}
function getMyCount() public view returns (uint256) {
return (getCount(msg.sender));
}
Первая функция проверяет, существует ли контракт счетчика для заданного адреса, а затем вызывает метод getCount из экземпляра. Вторая функция, getMyCount, — это просто сокращение для передачи msg.sender напрямую в функцию getCount.
Функция increment очень похожа, но передает первоначального отправителя транзакции в контракт Counter:
function increment() public {
require (_counters[msg.sender] != Counter(0));
Counter(_counters[msg.sender]).increment(msg.sender);
}
Обратите внимание, что если вызывать его слишком много раз, наш счетчик может стать жертвой переполнения. Вам следует как можно чаще использовать библиотеку SafeMath (opens in a new tab), чтобы защититься от этого возможного случая.
Чтобы развернуть наш контракт, вам нужно будет предоставить код как CounterFactory, так и Counter. При развертывании, например, в Remix, вам нужно будет выбрать CounterFactory.
Вот полный код:
pragma solidity 0.5.17;
contract Counter {
uint256 private _count;
address private _owner;
address private _factory;
modifier onlyOwner(address caller) {
require(caller == _owner, "You're not the owner of the contract");
_;
}
modifier onlyFactory() {
require(msg.sender == _factory, "You need to use the factory");
_;
}
constructor(address owner) public {
_owner = owner;
_factory = msg.sender;
}
function getCount() public view returns (uint256) {
return _count;
}
function increment(address caller) public onlyFactory onlyOwner(caller) {
_count++;
}
}
contract CounterFactory {
mapping(address => Counter) _counters;
function createCounter() public {
require (_counters[msg.sender] == Counter(0));
_counters[msg.sender] = new Counter(msg.sender);
}
function increment() public {
require (_counters[msg.sender] != Counter(0));
Counter(_counters[msg.sender]).increment(msg.sender);
}
function getCount(address account) public view returns (uint256) {
require (_counters[account] != Counter(0));
return (_counters[account].getCount());
}
function getMyCount() public view returns (uint256) {
return (getCount(msg.sender));
}
}
После компиляции в разделе развертывания Remix вы выберете фабрику для развертывания:
Затем вы можете поэкспериментировать с вашей фабрикой контрактов и проверить изменение значения. Если вы хотите вызвать смарт-контракт с другого адреса, вам нужно будет изменить адрес в выборе аккаунта в Remix.
