Смарт-контракт «Калькулятор»
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;
}
}- Целочисленное деление: Деление Solidity возвращает только частное, отбрасывая остаток. Поддержка дробных чисел или чисел с точкой в Solidity все еще плохо реализована. Для деления с плавающей запятой нужно будет использовать внешние библиотеки или дополнительные приемы.
- Переполнение/недополнение. Необходимо помнить о возможных Overflow/Underflow при работе с большими числами или вычислениями. Подробней об этой уязвимости в смарт-контракте и как ее обойти здесь.
- При преобразовании целочисленного типа большего размера в меньший - биты справа сохраняются, а биты слева теряются.
- При преобразовании типов байтов происходит обратное. Когда больший тип байта преобразуется в меньший тип, первые байты сохраняются, а последние теряются. При преобразовании меньшего байта в больший справа добавляются нулевые байты.
PEMDAS - аббревиатура, используемые для запоминания порядка математических операций.
- M: Multiplication (умножение)
Это специальный порядок операций, который используется при выполнении математических выражений, чтобы определить, какие операции выполнять первыми.
Порядок старшинства операторов (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);
}
}
- Типы операндов: Логические операторы в основном работают с логическими значениями (
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;
}
}Функция 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" ));
}
}условие 1 — a>b, условие 2 — a==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, чтобы получить округленное число. Это позволяет округлить число до нужной точности.
Вывод:
Рассмотрели операторы Solidity. Они превращают простые данные в разумные решения и в логическое управление, где каждый тип оператора необходим под свои задачи.
Используются для изменения состояния контракта (например, изменения значения переменной result), а также для возвращения значений, выполнение проверок (например, проверка деления на ноль в функции divide) и выполнения вычислений в циклах (как в функции exponential).
Этот пример показывает, что операторы Solidity играют центральную роль в разработке функциональных и безопасных смарт-контрактов.