February 17, 2022

Function keywords в Solidity

Друзья, привет!

Сегодня вновь окунемся в теорию, но в этот раз непосредственно в теорию по языку Solidity. Поговорим с Вами о модификаторах функций.

Область видимости функции (Function visibility)

contract MyContract {
    function myFunction () [visibility-here] {
        // do something
    }
}

Для определения области видимости выделяется 4 ключевых слова:

  • public
  • private
  • internal
  • external

Public

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

Private

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

Internal

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

External

Если функция определена, как external, то она является частью интерфейса контракта и может вызываться из других контрактов и транзакций. Она не может быть вызвана внутри текущего контракта. Это приводит к тому, что выполнение внешних функций становится дешевле.

Примеры для каждого модификатора

View vs Pure

View

View функция может исключительно читать данные storage, но не может их модифицировать. ( пример использования - getter).

Pure

Pure функции не могут ни читать, ни модифицировать storage в контракте. Они используются для вычислений / подсчетов. (пример использования - математические или криптографические операции)

Ранее, Pure функции обозначались как constant. На данный момент модификатор constant является deprecated.

Именованные аргументы функций

Solidity аналогично JavaScript поддерживает тот же формат именованных аргументов. { arg1: value1, arg2: value2, ... }

“Имена аргументов, подаваемых в функцию, должны совпадать с именами из её определения" (Solidity docs)
function setValidator(address _validatorAddress, bool canValidate) public 
{        
  is_validator[_validatorAddress] = canValidate;    
}
setValidator({
    canValidate: true,
    _validatorAddress: validatorsList[i]
    });

Payable модификатор

Функция, обозначенная как payable может получать ETH при вызове. Если вы попробуете отправить ETH в функцию, которая не отмечена, как payable, то транзакция завершиться неудачей.

function bid(bytes32 _blindedBid) public payable onlyBefore(biddingEnd) {
  bids[msg.sender].push(Bid({
    blindedBid: _blindedBid,
    deposit: msg.value
  }));
 }

Fallback функции

Fallback функции в Solidity выполняются, когда идентификатор функции не соответствует ни одной из доступных функций в смарт-контракте или если имя функции вообще не было предоставлено. Они анонимны (не указывается имя функции), не могут принимать аргументы, ничего не возвращают, и в смарт-контракте может быть только одна fallback функция. Они выступает в роли "предохранителя".

Fallback функции выполняются всякий раз, когда конкретный контракт получает эфир без каких-либо других данных, связанных с транзакцией. Для этого fallback функция должна включать модификатор payable:

contract ExampleContract { 
     function() payable { 
       ...
     }
}

Если нет payable fallback функции, контракт выдаст исключение и вернет эфир отправителю. Что, если контракт должен что-то делать после отправки эфира? Для fallback функций выставлен лимит в 2300 газа. Это не даёт возможности для выполнения других операций, особенно дорогостоящих, таких как запись в storage, создание контрактов, вызов внешних функций и отправка эфира. Следовательно, fallback функции должны быть простыми и недорогими.

Перегрузка функций

Перегрузка функций — это возможность,которая поддерживается в Solidity начиная с версии 0.4.21. Документация Solidity описывает это следующим образом:

Контракт может иметь несколько функций с одним и тем же именем, но с разными типами параметров.

Говорить много об этом не имеет смысла, поведение полностью аналогично всем популярным языкам.

contract OverloadingExample {
  function getSum(uint a, uint b) public pure returns(uint) {
    return a + b;   
  }   
  function getSum(uint a, uint b, uint c) public pure returns(uint) {
    return a + b + c;   
  }
}

Override и Virtual

Начиная с версии 0.6 появилась возможность указание этих модификаторов. Если Вы пишите контракт, подразумевая, что им кто-то может пользоваться, расширяя возможности своего контракта, то вам понадобиться ключевое слово virtual. Оно позволяет переопределить метод наследуемого контракта в вашем контракте. Давайте рассмотрим пример (ERC20.sol):

function transfer(address recipient, uint256 amount) public 
                            virtual override returns (bool) {      
  _transfer(_msgSender(), recipient, amount);      
  return true;
 }

Здесь, метод transfer обозначен, как virtual метод. То есть при наследовании от ERC20.sol, вы сможете написать свой метод transfer с тем же набором аргументов и возвращаемым значением. Это делается с помощью ключевого слова override.

Упрощенный пример с обоими ключевыми словами:

contract Base1 {
  function foo() virtual public {}
}
contract Inherited is Base1 {
  function foo() public override(Base1) {}
}

External (внешние)

Еще одной особенностью работы с памятью можно выделить external функции. External функции - функции, определенные вне сущности контракта, что позволяет избежать копирования аргументов из calldata памяти в memory.

Кроме того, лишь external функции позволяет использовать конструкцию try/catch для обработки ошибок в коде.

Выглядит это следующим образом:

interface DataFeed { 
  function getData(address token) external returns (uint value); 
}
contract FeedConsumer {    
  DataFeed feed;    
  uint errorCount;    
  function rate(address token) public returns (uint value, bool success) {
    // Permanently disable the mechanism if there are        
    // more than 10 errors.        
    require(errorCount < 10);        
    try feed.getData(token) returns (uint v) {
      return (v, true);        
    } catch Error(string memory /*reason*/) {
      // This is executed in case
      // revert was called inside getData            
      // and a reason string was provided.            
      errorCount++;            
      return (0, false);        
    } catch (bytes memory /*lowLevelData*/) {
      // This is executed in case revert() was used            
      // or there was a failing assertion, division            
      // by zero, etc. inside getData.            
      errorCount++;            
      return (0, false);        
    }    
  }
}

Функция объявляется через interface вне области контракта, и затем внутри блока try идет непосредственно её вызов.

На сегодня мы прощаемся с Вами!

Материалы:

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

Официальная документация Solidity