Смарт-контракты
January 4, 2024

Смарт-контракт «Калькулятор»

1. Создадим контракт под названием Calculator
2. Создадим переменную result для хранения исходного значения калькулятора.
3. Создадим функции сложения, вычитания, умножения, деления, возведение в степень, извлечения квадратного корня и округление чисел.
4. Создадим функцию получить результат вычислений.

Для создания логики контракта мы будем использовать Solidity - Операторы.

Что такое Solidity - операторы?

Возьмем простое выражение 3 + 5 равно 8. Здесь 3 и 5 называются операндами а '+' называется оператором. Solidity поддерживает следующие типы операторов:

Арифметические операторы

Арифметические операторы — это своего рода математические помощники в программировании, которые позволяют выполнять такие операции, как сложение, вычитание, умножение, деление и т.д.

Предположим, что переменная A содержит 10, а переменная B содержит 20, тогда -
Solidity - журнал изменений (Link)

В следующем коде показано, как использовать арифметические операторы:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

contract Addition {
   constructor() public{
   }
   function getResult() public view returns(uint){
      uint a = 1; 
      uint b = 2;
      uint result = a + b; //арифметическая операция addition
      return result; 
   }
}
Вывод: 3

Ключевые моменты:

  • Целочисленное деление: Деление Solidity возвращает только частное, отбрасывая остаток. Поддержка дробных чисел или чисел с точкой в Solidity все еще плохо реализована. Для деления с плавающей запятой нужно будет использовать внешние библиотеки или дополнительные приемы.
  • Переполнение/недополнение. Необходимо помнить о возможных Overflow/Underflow при работе с большими числами или вычислениями. Подробней об этой уязвимости в смарт-контракте и как ее обойти здесь.
  • При преобразовании целочисленного типа большего размера в меньший - биты справа сохраняются, а биты слева теряются.
  • При преобразовании типов байтов происходит обратное. Когда больший тип байта преобразуется в меньший тип, первые байты сохраняются, а последние теряются. При преобразовании меньшего байта в больший справа добавляются нулевые байты.
  • Приоритет: Лучше использовать порядок операций PEMDAS чем BODMAS. (основное различие между ними заключается в приоритете (умножение или деление) и приоритете (сложение или вычитание)

PEMDAS - аббревиатура, используемые для запоминания порядка математических операций.

PEMDAS расшифровывается как:

- P: Parentheses (скобки)

- E: Exponents (степени)

- M: Multiplication (умножение)

- D: Division (деление)

- A: Addition (сложение)

- S: Subtraction (вычитание)

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

Порядок старшинства операторов (Link)

Операторы сравнения

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

Предположим, что переменная A содержит 10, а переменная B содержит 20, тогда -

В приведенном ниже контракте RelationalOperator демонстрируется вышеупомянутые различные типы реляционных операторов (сравнения)

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

contract RelationalOperator {

    // Объявление переменных
    uint16 public a = 20;
    uint16 public b = 10;
    
    // Инициализация переменной 
    // с логическим значением равенства a и b 
    bool public eq = a == b;
    
    // Инициализация переменной
    // с логическим значением неравенства a и b 
    bool public noteq = a != b;
    
    // Инициализация переменной 
    // с логическим значением a больше b
    bool public gtr = a > b;
    
    // Инициализация переменной
    // с логическим значением a меньше b
    bool public les = a < b;
    
    // Инициализация переменной
    // с логическим значением a больше или равно b
    bool public gtreq = a >= b;
    
    // Инициализация переменной
    // с логическим значением a меньше или равно b
    bool public leseq = a <= b;
    
}
Значения типов операторов сравнения

Ключевые моменты:

  • Совместимость типов данных: Сравниваемые значения относятся к совместимым типам данных. Сравнение несовместимых типов может привести к неожиданным результатам.
  • Строгое равенство: Solidity использует строгие проверки равенства значений. Например, 0 не считается равным false.
  • Сравнение адресов: Используйте == оператор для прямого сравнения адресов.

Логические операторы

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

Предположим, что переменная A содержит 10, а переменная B содержит 20, тогда -

В приведенном ниже примере контракт logicalOperator демонстрирует вышеупомянутые различные типы логических операторов.

  // SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;
  
  contract logicalOperator{
  
  // Определение функции для демонстрации 
  // Логический оператор
  function Logic(
  bool a, bool b) public view returns(
  bool, bool, bool){
    
  // Логический оператор И
  bool and = a&&b;
    
  // Логический оператор ИЛИ
  bool or = a||b;
    
  // Логический оператор НЕ
  bool not = !a;
  return (and, or, not);
  }
}
Случай 1: Когда a - истинно, b - истинно
Случай 2: Когда a - ложно, b - истинно
Случай 3: Когда a - нулевое (ложь), b - ненулевое (истина)
Случай 4: Когда a - ложно, b - ложно

Ключевые моменты:

  • Типы операндов: Логические операторы в основном работают с логическими значениями (true илиfalse). Однако Solidity может неявно преобразовывать ненулевые числа в true и 0 в false в определенных контекстах.
  • Упрощенная оценка: Solidity использует short-circuit для операторов И и ИЛИ. Это означает, что он оценивает второй операнд только в случае необходимости для определения окончательного результата.
  • Приоритет: Логические операторы имеют более низкий приоритет, чем операторы сравнения. Используйте круглые скобки, чтобы уточнить порядок вычислений при объединении разных операторов.

Побитовые операторы

Побитовые операторы в Solidity предлагают детальный способ управления отдельными битами двоичных данных, позволяя выполнять точные операции и оптимизацию в смарт-контрактах.

Предположим, что переменная A содержит 2, а переменная B содержит 3, тогда -

В приведенном ниже примере контракт Bitwise демонстрирует вышеупомянутые различные типы побитовых операторов.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

contract Bitwise {

  // Объявление переменных
  uint16 public a = 20;
  uint16 public b = 10;
  
  // Объявление переменных
  // to '&' value
  uint16 public and = a & b;
  
  // Объявление переменных 
  // to '|' value
  uint16 public or = a | b;
  
  // Объявление переменных
  // to '^' value
  uint16 public xor = a ^ b;
  
  // Объявление переменных
  // to '<<' value
  uint16 public leftshift = a << b;
  
  // Объявление переменных 
  // to '>>' value
  uint16 public rightshift = a >> b;
  
  // Объявление переменных
  // to '~' value
  uint16 public not = ~a;
  
}
Типы побитовых операторов

Операторы присваивания

Эти операторы предназначены для присвоения значения переменной. Операнд слева является переменной, а операнд справа — значением.

Поддерживает следующие операторы присваивания -
Примечание: Та же логика применяется к побитовым операторам, поэтому они будут выглядеть как << =, >> =, >> =, & =, | = , ^ =.

В приведенном ниже контракте AssignmentOperator демонстрируется вышеупомянутые различные типы операторов присваивания.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;
contract AssignmentOperator {    
    
    // Объявление переменных
    uint16 public assignment = 20;
    uint public assignment_add = 50;
    uint public assign_sub = 50;
    uint public assign_mul = 10;
    uint public assign_div = 50;
    uint public assign_mod = 32;
  
    // Определение функции для
    // демонстраии операторов присвоения
    function getResult() public{
        assignment_add += 10;
        assign_sub -= 20;
        assign_mul *= 10;
        assign_div /= 10;
        assign_mod %= 20;
        return;
    }
}
Значения до вызова функции
Значения после вызова функиции

Ключевые моменты:

  • Порядок оценки: Сначала вычисляется выражение справа, затем результат присваивается переменной слева.
  • Возвращаемое значение: Операторы присваивания не возвращают значение, поэтому их нельзя использовать непосредственно в выражениях.
  • Совместимость типов данных: Убедитесь, что назначаемое значение совместимо с типом данных переменной, чтобы избежать ошибок.

Условные (или тернарные) операторы

Тернарный оператор — это оператор, который принимает три операнда. Тернарные операторы пригодятся, если нужно написать простой оператор if-else в одну строку.

*Конструкция if-else способна проверить одно или же несколько условий и в случае если условие не будет верным, то выполнить другой код или проверить другое условие.

В приведенном ниже примере контракт Conditionalдемонстрирует условный оператор.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;
 
// Creating a contract
contract Conditional{
 
     // Определение функции для демонстрации
     // условного оператора
     function subtract(
       uint a, uint b) public view returns(
       uint){
      uint result = (a > b? a-b : b-a);
      return result;
 }
}
Разность чисел a и b
Функция subtract принимает два аргумента типа uint и возвращает их разность. Она использует условный оператор для определения модуля разности чисел a и b. Если a больше b, то возвращается разность a - b, иначе возвращается разность b - a.

Пример: тройная цепочка операторов

(условие 1) ? оператор 1: ((условие 2) ? оператор 2: оператор 3);
  // SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

// Контракт на демонстрацию оператора Solidity
// На данный момент этот контракт просто показывает, как
// реализовать тройную цепочку операторов
contract TripleChain {
  constructor() {}
  
  function getResult() public pure returns (string memory) {
    
    // Этот код возвращает строку
    // если a > b, мы возвращаем «a больше»
    // иначе, если a == b, мы возвращаем «a и b равны»
    // иначе b > a мы возвращаем «b больше»
    uint256 a = 200;
    uint256 b = 400;
  
    return (a > b ? "a is bigger" : ((a==b) ? 
        "a and b are equal" : "b is bigger" ));
  }
}
Случай когда: b больше

условие 1 — a>b, условие 2 — a==b.

утверждение 1 — «а больше», утверждение 2 — «а и b равны»,

утверждение 3 — «b больше».

После того когда ознакомились со всеми ключевыми операторами, приступим к смарт-контракту Calculator

Калькулятор

В приведенном ниже примере контракт Calculator демонстрирует:

Сложение, вычитание, умножение, деление, возведение в степень, извлечения квадратного корня, округления числа до десятков, сотен, тысяч и т.д.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

contract Calculator {
// Исходное значение калькулятора равно нулю.
    uint256 result = 0; 
// Функция add позволяет прибавить к результату указанное число.
    function add(uint256 num) public { 
        result += num; 
    }
// Функция subtract позволяет вычесть из результата указанное число.
     function subtract(uint256 num) public {
        result -= num; 
    }
// Функция multiply позволяет умножить текущий 
// результат на указанное число.
     function multiply(uint256 num) public {
        result *= num; 
    }
// На выходе контракта возвращается значение 
// текущего результата или значение вычислений.
    function getResult() public view returns (uint256) {
        return result;
    }
// Функция divide позволяет разделить текущий результат
// на указанное число, с проверкой на ноль.
    function divide(uint256 num) public {
    require(num != 0, "Cannot divide by zero");
    result /= num; 
    }
// Функция exponential позволяет возвести число 
// в указанную степень.
    function exponential(uint256 base, uint256 exponent) public {
    result = uint256(1);
        for(uint256 i = 0; i < exponent; i++){
        result *= base;
        }
    }
// Функция squareRoot позволяет получить квадратный корень из числа,
// возвращая только частное, отбрасывая остаток.
    function squareRoot(uint256 num) public view returns (uint256) {
    uint256 x = num;
    uint256 y = (x + 1) / 2;
    while (y < x) {
        x = y;
        y = (x + num / x) / 2;
    }
    return x;
    }
// Функция round позволяет округлить число 
// до десятков, сотен, тысяч и т.д.
    function round(uint256 num, uint256 decimalPlaces) public pure returns (uint256) {
        uint256 precision = 10**decimalPlaces;
        return (num + precision / 2) / precision * precision;
    }
}

Функция сложения "add" принимает один параметр типа uint256 под названием num. Имеет модификатор public, что означает, что она доступна для вызова извне контракта.

В теле функции используется оператор +=, который является сокращенной формой записи для операции сложения с присваиванием. В данном случае, result += num означает, что значение переменной result увеличивается на значение, переданное в аргументах функции (num). Это эквивалентно записи result = result + num;

Таким образом, функция add позволяет увеличить текущее значение result на значение, переданное в качестве аргумента.

Логика функций вычитания и умножения аналогична функции сложения. (Меняются только операторы присваивания)

Функция деления "divide" начинается с проверки с использованием функции require, чтобы убедиться, что аргумент num не равен нулю, иначе функция divideпрервет свое выполнение, выдав ошибку "Cannot divide by zero".

Далее в теле функции используется оператор /=, который является сокращенной формой записи для операции деления с присваиванием. result /= num означает, что значение переменной result делится на значение, переданное в аргументах функции (num) и результат присваивается обратно переменной result. Это эквивалентно записи result = result / num.

Таким образом, функция "divide" позволяет разделить текущее значение result на значение, переданное в качестве аргумента, при условии, что значение аргумента не равно нулю.

Функция возведения в степень принимает два аргумента: base (основание) и exponent (показатель степени) и используется для вычисления степени числа. Функция выполняет умножение base на само себя exponent раз и сохраняет результат в переменную result. Таким образом, функция возводит число base в степень exponent.

Функция квадратного корня выполняет вычисление из числа num с использованием метода Ньютона:

Переменная x инициализируется значением числаnum. Затем переменная y вычисляется как(x+1) / 2.

Затем в цикле while проверяется условие y < x. Внутри цикла x присваивается значение y, а затем y вычисляется как среднее арифметическое между x и num / x. Это повторяется до тех пор, пока значение y не станет больше или равно x.

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

Функция округления принимает два параметра:numчисло, которое нужно округлить) и decimalPlaces(число = количество нулей, на сколько нужно округлить, округление до десятков, до сотен, до тысяч и т.д.)

Внутри функции создается переменная precision, которая равна 10 в степени decimalPlaces. Это используется для определения точности округления.

Затем функция возвращает результат округления числа num до заданной цифры разряда. Для этого к числу num добавляется precision / 2, затем результат делится на precision и умножается на precision, чтобы получить округленное число. Это позволяет округлить число до нужной точности.

Исходное значение калькулятора равно 0
Использование функции сложения (add 10)
Использование функции деления на 5
Возведение 3 в 4 степень. getResult: 81
Функция умножения на 10
Вычитание: 810-2=808
Округление до сотен
Округление до тысяч
Вычисление квадратного корня из числа 1089

Вывод:

Рассмотрели операторы Solidity. Они превращают простые данные в разумные решения и в логическое управление, где каждый тип оператора необходим под свои задачи.

Используются для изменения состояния контракта (например, изменения значения переменной result), а также для возвращения значений, выполнение проверок (например, проверка деления на ноль в функции divide) и выполнения вычислений в циклах (как в функции exponential).

Этот пример показывает, что операторы Solidity играют центральную роль в разработке функциональных и безопасных смарт-контрактов.