Solidity
February 10, 2023

Solidity | Интерфейсы

Всем привет сегодня пойдет речь об интерфейсах и их применение.

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

Рассмотрим пример:

Напишем контракт, который будет отвечать за все наши функции. Я буду писать в ремиксе.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;


contract Gamer{ 
  
    mapping (address => uint) gamers;
    
    function joinGame(address player, uint amount) public {   
        require(amount > 2000, "Not anougth to join game");        
        gamers[player] = 1;    
    }
    
    function leaveGame(address player) public {    
        require(gamers[player] == 1, "you are not in game");        
        gamers[player] = 0;    
    }
    
    function getEntry(address player) public view returns(uint){     
        return gamers[player];    
    }  
    
}

Логика простая у нас есть мапппинг, который будет сохранять информацию об игроках, которые вошли в игру.
Чтоб войти в игру joinGame, нужно заплатить больше 2000 wei.
leaveGame отвечает за выход из игры.
И getEntry отвечает за вывод информации, о там в игре вы или нет.

В целом простой контракт без особенностей.

Теперь мы можем импортировать этот контракт и его исходный код в другой контракт и использовать его методы.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Gamer.sol";

contract myGame{

    Gamer game; 
       
    constructor(address _game){
        
        game = Gamer(_game);  
             
    }
}

Gamer game - это грубо говоря наш адрес первого смарт контракта. Gamer должен совпадать с название контракта, который вы импортировали. Это замена адреса. Позже увидим как это работает.
Строчка в конструкторе: game = Gamer(_game) это такая конструкция, которая делает нашу переменную game первым контрактом со всеми его методами и их можно вызывать.

Обращаясь к game.joinGame(), мы можем вызвать эту функцию и не переопределять ее как в наследовании. Довольно удобно.

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

Напишем свой интерфейс.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IGamer{      
      function joinGame(address player, uint amount)payable external;
      
      function leaveGame(address player) external;
       
      function getEntry(address player) external view returns(uint); 
}

Все названия интерфейсов принято писать через I(название).
Как видно мы тут просто определяем каждую функцию из первого контракта и говорим что она в себя принимает и что возвращает.

Важно! Все функции не могут быть public или еще какие-то, только external

Теперь осталось импортировать его в наш первый контракт, чтоб было понятно какая функция за что отвечает.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IGamer.sol";

contract Gamer{ 
  
    mapping (address => uint) gamers;
    
    function joinGame(address player, uint amount) public {   
        require(amount > 2000, "Not anougth to join game");        
        gamers[player] = 1;    
    }
    
    function leaveGame(address player) public {    
        require(gamers[player] == 1, "you are not in game");        
        gamers[player] = 0;    
    }
    
    function getEntry(address player) public view returns(uint){     
        return gamers[player];    
    }  
    
}

Вот и все теперь у нас есть интерфейс нашего контракта.

Напишем смарт-контракт, которые будет вызывать эти методы у себя через интерфейс!

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IGamer.sol";

contract myGame{

    IGamer game;   
        
    constructor(address _game){      
    game = IGamer(_game);    
    }     
       
    function joinGame()payable public {   
    
        game.joinGame(msg.sender, msg.value);  
          
    }      
    function leave() public {
        
        game.leaveGame(msg.sender); 
           
    }        
    function get() public view returns(uint){ 
       
        return game.getEntry(msg.sender);   
     }
}

Вот мы импортируем интерфейс уже в наш новый смарт контракт myGame.

 IGamer game;   
        
    constructor(address _game){      
    game = IGamer(_game);    
    }   

Тут мы подключаемся к интерфейсу также, как я показывал в начале c контрактом.

И дальше идут функции у нашего второго контракта, где мы вызываем через интерфейс функции первого контракта и соответственно с аргументами, которые они должны принимать. Ух надеюсь понятно объяснил.

Важно! Если не передавать аргументы в первый контракт, то работать не будет
То есть обязательно в первом контракте функция joinGame должна принимать адрес и количество денег на вход, она не должна быть payable в следствии этого, а если написать msg.sender и msg.value в первом контракте, то работать не будет. Только через аргументы!

Давайте посмотрим как это работает.

Открываем первый смарт контракт, компилируем его и нажимаем deploy.

Дальше под цифрой 1 мы копируем адрес контракта который мы только что за деплоили

Под цифрой 2 мы вставляем его и нажимаем deploy.

Должно быть 2 смарт-контракта. И теперь открываем второй контракт myGame и тестим функции.

Передаем больше чем 2000 wei и вызываем joinGame. Баланс смарт-контракта должен быть больше нуля.

Вызовем get и нам вернет 1 так как мы вошли в игру.

Если вызвать функцию leave и потом снова get, то она вернет 0, так как мы вышли из игры.

Все работает! Мы лишь подключились к нашему интерфейсу, в котором нет реализации ни одной функции, но у нас они работают корректно, как в первом контракте.

На этом все. Рассказал для чего нужны нам интерфейсы в solidity и рассмотрели наглядный пример. Дальше больше. Как видите мы все ближе подбираемся к теме erc20 и erc 721, а там есть что рассказать. Удачи.

tg: мой телеграмчик)