Solidity
Solidity — це мова програмування для створення смарт-контрактів, які виконуються у Ethereum Virtual Machine або EVM. Вона найчастіше використовується в Ethereum та інших EVM-сумісних блокчейнах для створення токенів, DeFi-протоколів, NFT, DAO, on-chain логіки, dApp і Web3-застосунків.
Solidity має синтаксис, схожий на C-подібні мови, але її модель виконання суттєво відрізняється від звичайної backend-розробки: код виконується в блокчейні, операції коштують gas, дані можуть бути публічними, а помилки в контракті можуть мати фінансові наслідки.
Основна ідея: Solidity дозволяє описувати правила, які виконуються в блокчейні автоматично, прозоро й без централізованого сервера.
Загальний опис
Solidity використовується для програмування смарт-контрактів — програм, які зберігаються в блокчейні й виконуються EVM. Контракти можуть зберігати стан, приймати транзакції, викликати інші контракти, випускати токени, вести облік балансів, керувати правами доступу й автоматизувати частину бізнес-логіки.
Solidity застосовується для:
- ERC-20 токенів;
- NFT;
- DeFi-протоколів;
- DAO;
- governance contracts;
- staking;
- vesting;
- escrow;
- marketplaces;
- on-chain games;
- identity і access control;
- bridges;
- oracles integration;
- dApps;
- smart contract wallets;
- tokenized assets.
Перевага: Solidity дає можливість створювати програмовані правила для цифрових активів і взаємодій, які виконуються в блокчейні.
Смарт-контракт
Смарт-контракт — це програма, яка розгортається в блокчейні й виконується за правилами мережі. Користувачі взаємодіють із контрактом через транзакції або read-only виклики.
Смарт-контракт може:
- зберігати дані;
- приймати й відправляти активи;
- перевіряти умови;
- виконувати розрахунки;
- викликати інші контракти;
- створювати події;
- керувати ролями;
- реалізовувати токени;
- автоматизувати частину угод.
Важливо: смарт-контракт після розгортання може бути складно або неможливо змінити. Тому помилки в логіці потрібно знаходити до deployment.
Ethereum і EVM
Ethereum — одна з головних платформ для смарт-контрактів.
EVM або Ethereum Virtual Machine — середовище виконання, у якому працює байткод смарт-контрактів.
Solidity-код компілюється в EVM bytecode, який потім розгортається в блокчейні.
Схематично:
Solidity code
→ compiler
→ EVM bytecode
→ deployment transaction
→ smart contract on blockchain
Практична роль: Solidity — це високорівнева мова, а EVM — середовище, яке фактично виконує скомпільований контракт.
EVM-сумісні мережі
Solidity використовується не лише в Ethereum, а й у багатьох EVM-сумісних мережах.
До таких мереж можуть належати:
- Ethereum;
- Polygon;
- BNB Smart Chain;
- Avalanche C-Chain;
- Arbitrum;
- Optimism;
- Base;
- Fantom;
- Gnosis Chain;
- інші EVM-сумісні L1 і L2 мережі.
Практична роль: один і той самий Solidity-код часто можна адаптувати для різних EVM-сумісних блокчейнів, але умови gas, інфраструктура й ризики можуть відрізнятися.
Перша програма на Solidity
Простий приклад контракту:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HelloWorld {
string public message = "Hello, world!";
}
У цьому прикладі:
- `SPDX-License-Identifier` описує ліцензію;
- `pragma solidity ^0.8.0;` задає сумісну версію компілятора;
- `contract HelloWorld` створює контракт;
- `string public message` зберігає текст у стані контракту;
- `public` автоматично створює getter.
Суть прикладу: Solidity-контракт схожий на клас, але після deployment він стає on-chain програмою з власною адресою.
Pragma
`pragma` задає версію компілятора Solidity.
Приклад:
pragma solidity ^0.8.0;
Це означає, що контракт призначений для компілятора версії 0.8.x, сумісної із зазначеним діапазоном.
Важливо: версію Solidity потрібно фіксувати уважно, бо різні версії компілятора можуть мати різну поведінку, оптимізації й правила безпеки.
SPDX License Identifier
У Solidity-файлах часто додають SPDX-ідентифікатор ліцензії.
Приклад:
// SPDX-License-Identifier: MIT
Це допомагає:
- явно вказати ліцензію;
- спростити аудит;
- полегшити публікацію коду;
- зробити контракт зрозумілішим для екосистеми.
Практична роль: SPDX-коментар — невелика, але корисна частина стандартного Solidity-файлу.
Контракти
Contract у Solidity — основна одиниця коду.
Приклад:
contract Counter {
uint256 public value;
function increment() public {
value += 1;
}
}
Контракт може містити:
- state variables;
- functions;
- events;
- modifiers;
- structs;
- enums;
- mappings;
- constructor;
- errors;
- inheritance;
- interfaces;
- libraries.
Практична роль: контракт у Solidity описує on-chain стан і правила взаємодії з цим станом.
State variables
State variables — це змінні, які зберігаються в storage контракту.
Приклад:
uint256 public totalSupply;
address public owner;
bool public paused;
State variables зберігаються в блокчейні, тому їх зміна коштує gas.
Увага: запис у storage є однією з дорожчих операцій у Solidity, тому структуру стану потрібно проектувати уважно.
Типи даних
Основні типи Solidity:
- `bool`;
- `uint`;
- `uint256`;
- `int`;
- `address`;
- `string`;
- `bytes`;
- `bytes32`;
- `enum`;
- `struct`;
- arrays;
- mappings.
Приклад:
bool public active = true;
uint256 public count = 0;
address public owner;
string public name = "Token";
bytes32 public dataHash;
Практична роль: правильний вибір типів впливає на безпеку, gas-витрати й зрозумілість контракту.
uint і int
`uint` — беззнакове ціле число.
`int` — знакове ціле число.
Найчастіше використовують `uint256`.
Приклад:
uint256 public balance;
int256 public change;
У сучасних версіях Solidity переповнення й недоповнення цілих чисел перевіряються автоматично, якщо не використано `unchecked`.
Важливо: навіть із автоматичними перевірками арифметику в фінансових контрактах потрібно тестувати дуже уважно.
Address
`address` — тип для адрес Ethereum/EVM.
Приклад:
address public owner;
constructor() {
owner = msg.sender;
}
`address payable` може отримувати native token через transfer/call.
Приклад:
address payable public treasury;
Практична роль: address використовується для користувачів, контрактів, власників, отримувачів платежів і перевірки доступу.
msg.sender
`msg.sender` — адреса, яка викликала поточну функцію.
Приклад:
function setOwner(address newOwner) public {
require(msg.sender == owner, "Only owner");
owner = newOwner;
}
Увага: `msg.sender` може бути не кінцевим користувачем, а іншим контрактом. Це важливо для meta-transactions, proxies і contract calls.
msg.value
`msg.value` — кількість native token, надіслана разом із викликом функції.
Приклад:
function deposit() public payable {
require(msg.value > 0, "No value sent");
}
Функція має бути `payable`, щоб приймати native token.
Практична роль: `msg.value` використовується для платежів, deposits, minting за оплату й escrow-логіки.
Functions
Функції описують дії, які можна виконувати з контрактом.
Приклад:
function increment() public {
value += 1;
}
function getValue() public view returns (uint256) {
return value;
}
Функції можуть бути:
- `public`;
- `external`;
- `internal`;
- `private`;
- `view`;
- `pure`;
- `payable`.
Практична роль: функції є публічним або внутрішнім API смарт-контракту.
Visibility
Visibility визначає, звідки можна викликати функцію або змінну.
| Visibility | Значення |
|---|---|
| public | Можна викликати ззовні й усередині контракту |
| external | Зазвичай викликається ззовні контракту |
| internal | Доступно в цьому контракті й контрактах-нащадках |
| private | Доступно лише в цьому контракті |
Важливо: visibility не приховує дані з блокчейну. Навіть `private` змінні можуть бути прочитані з raw storage.
View і pure
`view` означає, що функція читає стан, але не змінює його.
`pure` означає, що функція не читає й не змінює стан контракту.
Приклад:
function getValue() public view returns (uint256) {
return value;
}
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
Практична роль: `view` і `pure` допомагають відрізняти read-only логіку від транзакцій, які змінюють стан.
Constructor
Constructor виконується один раз під час deployment контракту.
Приклад:
contract Owned {
address public owner;
constructor() {
owner = msg.sender;
}
}
Constructor часто використовується для:
- встановлення owner;
- початкової конфігурації;
- задання адрес залежностей;
- встановлення параметрів токена;
- ініціалізації стану.
Практична роль: constructor задає початковий стан контракту перед його використанням.
Require, revert і assert
Solidity має кілька способів зупинити виконання.
`require` використовується для перевірки умов:
require(amount > 0, "Amount must be positive");
`revert` явно скасовує виконання:
revert("Operation failed");
`assert` зазвичай використовується для внутрішніх інваріантів:
assert(total >= balance);
Важливо: перевірки в Solidity — це частина безпеки контракту. Не можна покладатися лише на frontend.
Custom errors
Custom errors дозволяють економніше описувати помилки.
Приклад:
error NotOwner();
function onlyOwnerAction() public {
if (msg.sender != owner) {
revert NotOwner();
}
}
Custom errors часто дешевші за довгі рядкові повідомлення.
Практична роль: custom errors допомагають зробити помилки структурованішими й економнішими за gas.
Events
Events дозволяють контракту записувати інформацію в logs блокчейну.
Приклад:
event Transfer(address indexed from, address indexed to, uint256 amount);
function transfer(address to, uint256 amount) public {
emit Transfer(msg.sender, to, amount);
}
Events використовуються для:
- frontend updates;
- indexing;
- analytics;
- audit trail;
- token transfers;
- marketplace activity;
- protocol monitoring.
Практична роль: events — це основний спосіб повідомляти зовнішнім системам про on-chain дії контракту.
Modifiers
Modifier дозволяє додати перевірку або спільну логіку до функцій.
Приклад:
modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}
function pause() public onlyOwner {
paused = true;
}
`_;` означає місце, де виконується тіло функції.
Практична роль: modifiers часто використовують для access control, pause checks і повторюваних перевірок.
Mapping
Mapping — key-value структура в Solidity.
Приклад:
mapping(address => uint256) public balances;
Використання:
balances[msg.sender] += amount;
Mapping часто використовується для:
- балансів;
- allowlists;
- approvals;
- ролей;
- ownership;
- станів користувачів;
- параметрів за адресою.
Увага: mapping не можна напряму перебрати як масив. Якщо потрібна ітерація, треба окремо зберігати список ключів.
Struct
Struct дозволяє створювати власні структури даних.
Приклад:
struct User {
string name;
uint256 balance;
bool active;
}
mapping(address => User) public users;
Struct використовується для:
- користувацьких профілів;
- позицій у DeFi;
- заявок;
- orders;
- proposals;
- metadata;
- налаштувань.
Практична роль: struct допомагає групувати пов’язані поля в одну логічну структуру.
Enum
Enum описує набір фіксованих значень.
Приклад:
enum Status {
Pending,
Active,
Closed
}
Status public status;
Enums використовуються для:
- станів;
- етапів процесу;
- типів заявок;
- статусів голосування;
- lifecycle контракту;
- order states.
Практична роль: enum робить стани контракту зрозумілішими, ніж набір чисел або рядків.
Arrays
Solidity підтримує масиви.
Приклад dynamic array:
uint256[] public numbers;
function addNumber(uint256 value) public {
numbers.push(value);
}
Fixed-size array:
uint256[3] public fixedNumbers;
Важливо: великі масиви в storage можуть бути дорогими для gas, особливо якщо функції проходять по всіх елементах.
Storage, memory і calldata
Solidity має різні області зберігання даних.
| Область | Значення |
|---|---|
| storage | Постійне on-chain сховище контракту |
| memory | Тимчасові дані під час виконання функції |
| calldata | Read-only дані зовнішнього виклику |
Приклад:
function setName(string calldata newName) external {
name = newName;
}
Практична роль: правильне використання storage, memory і calldata впливає на gas і коректність роботи контракту.
Inheritance
Solidity підтримує inheritance.
Приклад:
contract Ownable {
address public owner;
constructor() {
owner = msg.sender;
}
}
contract MyContract is Ownable {
function onlyOwnerData() public view returns (address) {
return owner;
}
}
Inheritance використовується для:
- повторного використання логіки;
- access control;
- standard contracts;
- token contracts;
- abstract base contracts;
- extension patterns.
Увага: inheritance у смарт-контрактах потрібно використовувати обережно, бо складна ієрархія може ускладнити аудит.
Interfaces
Interface описує зовнішній API контракту без реалізації.
Приклад:
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
Interfaces використовуються для:
- виклику інших контрактів;
- стандартів;
- інтеграцій;
- ABI-сумісності;
- dependency boundaries;
- тестування.
Практична роль: interface дозволяє контракту взаємодіяти з іншим контрактом через відомий набір функцій.
Libraries
Library — повторно використовуваний код без власного звичайного стану контракту.
Приклад:
library MathUtils {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
}
Libraries використовуються для:
- математичних операцій;
- helper-функцій;
- роботи зі структурами;
- повторного використання без inheritance;
- gas optimization у деяких сценаріях.
Практична роль: libraries допомагають винести спільну логіку й не дублювати код між контрактами.
ABI
ABI або Application Binary Interface описує, як зовнішні системи взаємодіють із контрактом.
ABI містить інформацію про:
- функції;
- аргументи;
- типи;
- return values;
- events;
- errors.
Frontend, scripts і wallets використовують ABI, щоб викликати контракт.
Практична роль: ABI є мостом між смарт-контрактом і зовнішніми застосунками.
Gas
Gas — одиниця вартості обчислень в EVM.
Gas витрачається на:
- deployment контракту;
- запис у storage;
- виклики функцій;
- цикли;
- створення контрактів;
- зовнішні виклики;
- логування events;
- обчислення.
Важливо: Solidity-розробник має думати не лише про правильність коду, а й про вартість його виконання.
Transactions і calls
Взаємодія з контрактом може бути двох основних типів.
Transaction змінює стан і потребує gas.
Call читає стан і не змінює блокчейн.
Приклад read-only функції:
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
Приклад state-changing функції:
function deposit() public payable {
balances[msg.sender] += msg.value;
}
Практична роль: frontend має розрізняти читання даних і транзакції, які змінюють стан і потребують підтвердження користувача.
Receive і fallback
`receive` викликається, коли контракт отримує native token без calldata.
Приклад:
receive() external payable {
}
`fallback` викликається, коли функція не знайдена або calldata не відповідає ABI.
Приклад:
fallback() external payable {
}
Увага: receive і fallback потрібно проектувати обережно, бо вони можуть несподівано приймати платежі або виклики.
ERC-20
ERC-20 — стандарт fungible token у Ethereum/EVM-екосистемі.
ERC-20 токени використовуються для:
- utility tokens;
- governance tokens;
- stablecoins;
- DeFi;
- rewards;
- payments;
- staking;
- liquidity pools.
Типові функції ERC-20:
- `totalSupply`;
- `balanceOf`;
- `transfer`;
- `approve`;
- `allowance`;
- `transferFrom`.
Практична роль: ERC-20 зробив токени сумісними з wallets, exchanges, DeFi-протоколами й аналітичними інструментами.
ERC-721
ERC-721 — стандарт NFT, тобто non-fungible token.
ERC-721 використовується для:
- цифрового мистецтва;
- collectibles;
- game assets;
- memberships;
- certificates;
- identity tokens;
- tokenized rights;
- унікальних активів.
Практична роль: ERC-721 дозволяє створювати унікальні токени, де кожен tokenId представляє окремий актив.
ERC-1155
ERC-1155 — multi-token standard, який може підтримувати як fungible, так і non-fungible токени в одному контракті.
ERC-1155 використовується для:
- game items;
- collections;
- semi-fungible tokens;
- batch transfers;
- NFT-платформ;
- asset bundles.
Практична роль: ERC-1155 зручний, коли в одному проєкті потрібно керувати багатьма типами токенів.
OpenZeppelin
OpenZeppelin — популярна бібліотека перевірених смарт-контрактів і інструментів.
OpenZeppelin часто використовують для:
- ERC-20;
- ERC-721;
- ERC-1155;
- Ownable;
- AccessControl;
- Pausable;
- ReentrancyGuard;
- upgradeable contracts;
- governance;
- security helpers.
Практична порада: для стандартних токенів і access control краще використовувати перевірені бібліотеки, ніж писати все з нуля.
Access control
Access control визначає, хто може виконувати певні дії.
Простий приклад owner-перевірки:
modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}
Складніші контракти можуть використовувати ролі:
- admin;
- minter;
- burner;
- pauser;
- operator;
- upgrader;
- guardian.
Критично: помилка в access control може дозволити стороннім адресам керувати коштами, mint, upgrade або змінювати критичні параметри контракту.
Ownable
Ownable — поширений патерн, де контракт має власника.
Власник може мати право:
- змінювати параметри;
- pause/unpause;
- встановлювати адреси;
- керувати ролями;
- запускати адміністративні дії.
Важливо: owner-адреса є критичною точкою довіри. Для важливих контрактів часто використовують multisig або governance замість одного приватного ключа.
Pausable
Pausable — патерн, який дозволяє тимчасово зупинити частину функцій контракту.
Використовується для:
- emergency stop;
- реагування на інциденти;
- тимчасового блокування ризикових дій;
- контрольованого запуску;
- upgrade або migration windows.
Увага: pause-механізм корисний для безпеки, але він також додає централізований контроль, який потрібно пояснювати користувачам.
Reentrancy
Reentrancy — один із найвідоміших класів вразливостей у смарт-контрактах. Він виникає, коли зовнішній контракт може повторно увійти у функцію до завершення попереднього виконання.
Захисні підходи:
- checks-effects-interactions;
- ReentrancyGuard;
- обережність із зовнішніми викликами;
- оновлення стану до переказу коштів;
- ретельне тестування.
Критично: зовнішні виклики в Solidity завжди потрібно розглядати як потенційно небезпечні. Спочатку перевірки й оновлення стану, потім взаємодія з іншими контрактами.
Checks-effects-interactions
Checks-effects-interactions — патерн безпечного порядку дій.
Загальна логіка:
1. Checks: перевірити умови
2. Effects: оновити стан контракту
3. Interactions: викликати зовнішні контракти або відправити кошти
Практична роль: цей патерн зменшує ризик reentrancy і робить порядок виконання зрозумілішим для аудиту.
Front-running і MEV
У публічних блокчейнах транзакції можуть бути видимі до включення в блок. Це створює ризики front-running, sandwich attacks і MEV.
Ризикові сценарії:
- DEX trades;
- auctions;
- liquidations;
- NFT minting;
- oracle updates;
- reward claims;
- order matching.
Важливо: Solidity-контракт не існує в ізоляції. Потрібно враховувати mempool, порядок транзакцій і економічні стимули учасників мережі.
Oracles
Oracle передає зовнішні дані в блокчейн.
Oracles використовуються для:
- цін активів;
- randomness;
- погоди;
- спортивних результатів;
- cross-chain data;
- proof of reserves;
- off-chain events.
Увага: oracle є критичною залежністю. Якщо oracle дає неправильні дані, контракт може виконати неправильну логіку.
Randomness
Генерація випадковості в блокчейні складна, бо on-chain дані часто передбачувані або можуть бути впливовими для валідаторів.
Не варто використовувати прості джерела як єдину основу випадковості для цінних призів.
Критично: випадковість для лотерей, NFT minting або ігор із цінністю має використовувати спеціальні перевірені механізми, а не прості on-chain значення.
Upgradeable contracts
Смарт-контракти зазвичай immutable, але існують upgradeable patterns через proxy.
Proxy-підхід дозволяє:
- зберегти адресу;
- оновити implementation;
- виправити помилки;
- додати функції;
- розділити storage і logic.
Але він додає складність:
- storage layout;
- admin access;
- upgrade risks;
- initializer замість constructor;
- audit complexity;
- довіру до upgrader.
Важливо: upgradeability дає гнучкість, але зменшує простоту й може створити додаткові ризики централізованого контролю.
Proxy
Proxy — контракт, який приймає виклики й делегує їх implementation-контракту.
Поширені підходи:
- transparent proxy;
- UUPS proxy;
- beacon proxy;
- minimal proxy clones.
Практична роль: proxy використовується, коли потрібно оновлювати логіку контракту без зміни адреси, з якою взаємодіють користувачі.
Remix IDE
Remix IDE — браузерне середовище для написання, компіляції, тестування й розгортання Solidity-контрактів.
Remix корисний для:
- навчання;
- швидких експериментів;
- прототипів;
- перевірки синтаксису;
- простого deployment;
- взаємодії з контрактами;
- debugging у навчальних сценаріях.
Практична роль: Remix — зручний стартовий інструмент для вивчення Solidity й швидкої перевірки контрактів.
Hardhat
Hardhat — популярне середовище розробки для Ethereum/Solidity.
Hardhat використовується для:
- компіляції;
- тестування;
- local blockchain;
- deployment scripts;
- debugging;
- plugins;
- TypeScript/JavaScript tooling;
- integration tests;
- mainnet forking.
Практична роль: Hardhat добре підходить для командної Solidity-розробки з тестами, deployment scripts і JavaScript/TypeScript-екосистемою.
Foundry
Foundry — швидкий toolkit для Solidity-розробки.
Foundry зазвичай включає:
- Forge для тестування;
- Cast для взаємодії з мережами;
- Anvil для локального node;
- Solidity-based tests;
- fuzz testing;
- scripting.
Практична роль: Foundry популярний серед Solidity-розробників, які хочуть писати тести й scripts безпосередньо в Solidity.
Truffle
Truffle — один із ранніх framework-ів для Ethereum-розробки.
Він використовувався для:
- компіляції;
- migrations;
- deployment;
- тестування;
- contract artifacts;
- JavaScript-based workflow.
Історична роль: Truffle був важливим інструментом ранньої Solidity-екосистеми, хоча в нових проєктах часто обирають Hardhat або Foundry.
Testing
Тестування Solidity-контрактів є критично важливим.
Потрібно тестувати:
- access control;
- arithmetic;
- edge cases;
- revert conditions;
- events;
- token transfers;
- upgrade behavior;
- permissions;
- paused states;
- external calls;
- oracle scenarios;
- attacks simulation;
- gas usage.
Критично: у смарт-контрактах тестування — не формальність. Помилка може призвести до втрати коштів або блокування активів.
Fuzz testing
Fuzz testing перевіряє контракт на багатьох згенерованих вхідних даних.
Fuzz testing корисний для:
- арифметики;
- invariants;
- edge cases;
- DeFi-логіки;
- access rules;
- unexpected inputs;
- state transitions.
Практична роль: fuzz testing допомагає знаходити помилки, які складно передбачити через ручні test cases.
Invariant testing
Invariant testing перевіряє властивості, які мають залишатися істинними завжди.
Приклади інваріантів:
- сума балансів не перевищує total supply;
- користувач не може зняти більше, ніж має;
- paused контракт не виконує заборонені дії;
- резерви не стають від’ємними;
- governance rules не порушуються.
Практична роль: invariant testing особливо корисний для DeFi, токенів і складних протоколів.
Static analysis
Static analysis допомагає знайти потенційні проблеми без виконання контракту.
Інструменти можуть виявляти:
- reentrancy risks;
- unchecked calls;
- access control issues;
- shadowing;
- dangerous patterns;
- unused variables;
- gas inefficiencies;
- suspicious external calls.
Важливо: static analysis корисний, але не замінює тести, review, аудит і розуміння бізнес-логіки.
Audit
Smart contract audit — це незалежна перевірка коду, архітектури, безпеки й економічної логіки контракту.
Аудит зазвичай перевіряє:
- access control;
- reentrancy;
- oracle assumptions;
- token logic;
- upgradeability;
- storage layout;
- arithmetic;
- edge cases;
- governance;
- admin powers;
- economic attacks;
- integration risks.
Критично: аудит не гарантує повної безпеки, але для важливих контрактів без аудиту запускати production-логіку з коштами небезпечно.
DeFi
DeFi або decentralized finance — одна з головних сфер використання Solidity.
Solidity використовується для:
- DEX;
- lending;
- borrowing;
- liquidity pools;
- staking;
- yield farming;
- derivatives;
- vaults;
- stablecoins;
- liquidations;
- governance tokens.
Важливо: DeFi-контракти мають не лише технічні, а й економічні ризики: liquidity, oracle manipulation, MEV, governance attacks і systemic dependencies.
NFT
NFT — non-fungible token, тобто унікальний токен.
Solidity використовується для NFT-контрактів, які можуть містити:
- ownership;
- minting;
- transfers;
- approvals;
- metadata;
- royalties;
- allowlists;
- reveal logic;
- marketplace integration.
Практична роль: Solidity дозволяє створювати NFT-колекції, marketplaces і on-chain правила володіння цифровими активами.
DAO
DAO або decentralized autonomous organization використовує смарт-контракти для governance.
DAO-контракти можуть реалізовувати:
- proposals;
- voting;
- quorum;
- timelocks;
- token-based governance;
- treasury management;
- execution rules;
- roles;
- delegates.
Практична роль: Solidity може автоматизувати правила голосування й виконання рішень у decentralized governance.
dApp
dApp — decentralized application, який зазвичай складається з:
- frontend;
- wallet connection;
- smart contracts;
- ABI;
- RPC provider;
- indexing service;
- off-chain backend у деяких випадках;
- storage для metadata;
- monitoring.
Практична роль: Solidity відповідає за on-chain логіку dApp, але повний застосунок зазвичай має ще frontend, індексацію й off-chain інфраструктуру.
Wallets
Користувачі взаємодіють із Solidity-контрактами через wallets.
Wallet може:
- підписувати транзакції;
- показувати дані контракту;
- взаємодіяти з dApp;
- керувати accounts;
- підтверджувати gas;
- підписувати повідомлення;
- підключатися до мереж.
Увага: контракт має бути безпечним навіть тоді, коли frontend або wallet показує користувачу неповну інформацію.
Indexing
Дані блокчейну часто індексують окремі сервіси.
Індексація потрібна для:
- швидкого пошуку;
- історії подій;
- dashboards;
- аналітики;
- NFT metadata;
- user portfolios;
- marketplace listings;
- protocol metrics.
Events контракту часто є основою для індексації.
Практична роль: smart contract зберігає on-chain стан, а індексатор робить ці дані зручними для frontend і аналітики.
Solidity і JavaScript
Solidity часто використовується разом із JavaScript або TypeScript.
| Критерій | Solidity | JavaScript / TypeScript |
|---|---|---|
| Основна роль | On-chain smart contracts | Frontend, scripts, tests, deployment |
| Середовище | EVM | Browser, Node.js |
| Дані | On-chain state | Off-chain UI, integration, tooling |
| Вартість виконання | Gas | Залежить від runtime |
| Типові інструменти | solc, Hardhat, Foundry | ethers.js, viem, web3.js, scripts |
Висновок: Solidity описує on-chain правила, а JavaScript/TypeScript часто керує frontend, тестами й deployment workflow.
Solidity і Vyper
Vyper — інша мова для EVM-смарт-контрактів.
| Критерій | Solidity | Vyper |
|---|---|---|
| Синтаксис | C/JavaScript-подібний | Python-подібний |
| Популярність | Найпоширеніша EVM-мова | Нішевіша |
| Можливості | Багато feature, inheritance, libraries | Більш обмежений дизайн |
| Фокус | Гнучкість і екосистема | Простота й auditability |
Висновок: Solidity має більшу екосистему, а Vyper робить ставку на простіший і більш обмежений дизайн для частини сценаріїв.
Solidity і Rust
Rust використовується в інших блокчейн-екосистемах і деяких smart contract платформах.
| Критерій | Solidity | Rust |
|---|---|---|
| Основна ніша | EVM smart contracts | Systems programming, Solana, WASM smart contracts, backend |
| Типізація | Статична | Статична з ownership model |
| Runtime | EVM | Native/WASM/інший runtime залежно від платформи |
| Екосистема | Ethereum/EVM | Ширша systems і blockchain екосистема |
| Складність | Спеціалізована для EVM | Складніша, але універсальніша |
Висновок: Solidity — головний вибір для EVM, а Rust часто використовується там, де потрібна інша blockchain runtime або системна продуктивність.
Solidity і Python
Python часто використовується для scripts, testing, analytics і blockchain automation, але не є основною мовою EVM-контрактів.
| Критерій | Solidity | Python |
|---|---|---|
| Основна роль | Смарт-контракти | Скрипти, аналітика, backend, testing tools |
| Виконання | EVM | Python runtime |
| Вартість операцій | Gas | Звичайні обчислення off-chain |
| Безпека | On-chain ризики й незворотність | Залежить від застосунку |
Висновок: Python може бути корисним навколо Web3-проєкту, але on-chain логіка EVM зазвичай пишеться на Solidity.
Переваги Solidity
Основні переваги Solidity:
- головна мова Ethereum/EVM;
- велика екосистема;
- підтримка ERC-стандартів;
- OpenZeppelin;
- сумісність із wallets і dApps;
- багато tooling;
- Hardhat;
- Foundry;
- Remix;
- широка спільнота;
- можливість створювати токени;
- DeFi і NFT ecosystem;
- ABI-сумісність;
- підтримка багатьох EVM-мереж.
Головна перевага: Solidity є стандартним інструментом для створення смарт-контрактів у найбільшій EVM-екосистемі.
Обмеження Solidity
Solidity має обмеження.
Можливі проблеми:
- високі security-ризики;
- gas-вартість;
- складність upgradeability;
- публічність даних;
- immutable deployment;
- складність тестування економічних сценаріїв;
- залежність від oracle;
- MEV і front-running;
- складність DeFi-інтеграцій;
- потреба в аудиті;
- складність роботи з великими даними;
- обмеження EVM;
- ризик втрати коштів через помилки.
Помилка: писати Solidity як звичайний backend. Смарт-контракт виконується в іншому середовищі, де помилки дорожчі, дані публічні, а зміни часто незворотні.
Коли варто використовувати Solidity
Solidity варто використовувати для:
- Ethereum smart contracts;
- EVM-сумісних мереж;
- ERC-20 токенів;
- NFT;
- DeFi-протоколів;
- DAO;
- staking;
- vesting;
- escrow;
- on-chain governance;
- tokenized assets;
- dApps;
- smart contract wallets;
- blockchain-based automation.
Практична порада: Solidity доречна тоді, коли логіка справді має виконуватися on-chain, а не просто бути частиною звичайного backend.
Коли Solidity може бути невдалим вибором
Solidity може бути не найкращим вибором для:
- звичайних web-застосунків без blockchain-потреби;
- приватних бізнес-даних;
- великих обсягів даних;
- частих обчислень із високою вартістю gas;
- задач, які краще виконувати off-chain;
- систем, де потрібна повна конфіденційність;
- проєктів без бюджету на тестування й аудит;
- логіки, яку потрібно часто змінювати.
Важливо: не всю бізнес-логіку потрібно переносити в блокчейн. On-chain має бути лише те, що справді потребує прозорості, довіри, токенізації або децентралізованого виконання.
Безпека Solidity
Безпека є центральною темою Solidity-розробки.
Потрібно контролювати:
- reentrancy;
- access control;
- integer logic;
- oracle manipulation;
- front-running;
- MEV;
- upgradeability risks;
- storage collision;
- unchecked external calls;
- signature replay;
- authorization bugs;
- emergency pause;
- governance attacks;
- dependency risks;
- economic exploits.
Критично: Solidity-контракт із коштами потрібно проектувати як фінансову систему: з threat model, тестами, аудитом, monitoring і планом реагування.
Приватність даних
Дані в публічному блокчейні зазвичай доступні для перегляду.
Навіть якщо змінна позначена як `private`, її значення може бути прочитане з storage.
Не варто зберігати on-chain:
- паролі;
- приватні ключі;
- секретні токени;
- персональні дані без потреби;
- комерційні секрети;
- незашифровану приватну інформацію;
- приховані random seed;
- конфіденційні документи.
Критично: `private` у Solidity означає обмеження доступу з інших контрактів, але не конфіденційність даних у блокчейні.
Хороші практики Solidity
Рекомендовано:
- використовувати перевірені бібліотеки;
- фіксувати версію компілятора;
- писати тести;
- використовувати fuzz і invariant testing;
- перевіряти access control;
- мінімізувати storage writes;
- уникати зайвих циклів по великих масивах;
- використовувати events;
- документувати припущення;
- перевіряти зовнішні виклики;
- не зберігати секрети on-chain;
- застосовувати checks-effects-interactions;
- використовувати ReentrancyGuard там, де потрібно;
- проводити аудит для важливих контрактів;
- робити deployment checklist.
Головне правило: хороший Solidity-код має бути простим, протестованим, аудитованим і написаним із розумінням on-chain ризиків.
Типові помилки початківців
Поширені помилки:
- думати, що `private` приховує дані;
- не тестувати edge cases;
- не перевіряти access control;
- писати власний ERC-20 з нуля без потреби;
- ігнорувати reentrancy;
- робити великі цикли по storage;
- не враховувати gas;
- покладатися лише на frontend-перевірки;
- неправильно використовувати `tx.origin`;
- не розуміти proxy storage layout;
- не перевіряти return values;
- довіряти oracle без перевірки;
- не робити аудит важливого контракту;
- зберігати secrets у контракті.
Небезпека: помилка в Solidity може бути не просто bug, а прямий фінансовий ризик для користувачів і протоколу.
Приклади задач на Solidity
Простий лічильник
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Counter {
uint256 public value;
function increment() public {
value += 1;
}
function decrement() public {
require(value > 0, "Value is zero");
value -= 1;
}
}
Контракт із власником
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract OwnableExample {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}
function changeOwner(address newOwner) public onlyOwner {
require(newOwner != address(0), "Zero address");
owner = newOwner;
}
}
Подія
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract EventExample {
event MessageChanged(address indexed user, string message);
string public message;
function setMessage(string calldata newMessage) external {
message = newMessage;
emit MessageChanged(msg.sender, newMessage);
}
}
Mapping балансів
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract BalanceBook {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function getBalance(address account) external view returns (uint256) {
return balances[account];
}
}
Struct і enum
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Orders {
enum Status {
Created,
Paid,
Cancelled
}
struct Order {
address buyer;
uint256 amount;
Status status;
}
mapping(uint256 => Order) public orders;
uint256 public nextOrderId;
function createOrder(uint256 amount) external {
orders[nextOrderId] = Order({
buyer: msg.sender,
amount: amount,
status: Status.Created
});
nextOrderId += 1;
}
}
Підказка: у Solidity-прикладах важливо дивитися не лише на синтаксис, а й на access control, storage, gas, external calls і можливі edge cases.
Джерела
- Офіційна документація Solidity.
- Ethereum Developer Documentation.
- Ethereum Yellow Paper.
- OpenZeppelin Contracts Documentation.
- Hardhat Documentation.
- Foundry Book.
- Remix IDE Documentation.
- Ethers.js Documentation.
- Viem Documentation.
- ERC Standards.
- ConsenSys Smart Contract Best Practices.
- Матеріали щодо EVM, gas, ABI, DeFi security, NFT standards, proxy patterns і smart contract audits.
Висновок
Solidity — це головна мова програмування для створення смарт-контрактів у Ethereum та EVM-сумісних мережах. Вона використовується для токенів, NFT, DeFi, DAO, staking, escrow, governance, dApps і різних on-chain механізмів.
Solidity має велику екосистему, стандарти ERC, бібліотеки OpenZeppelin, інструменти Hardhat, Foundry, Remix і широку підтримку в Web3-інфраструктурі. Водночас вона потребує високої дисципліни: помилки в контрактах можуть бути незворотними, дані зазвичай публічні, операції коштують gas, а безпека залежить від тестів, review, аудитів і правильної архітектури.
Головна думка: Solidity — це мова для on-chain правил і цифрових активів. Її сила в програмованій довірі, але відповідальність розробника значно вища, ніж у звичайній backend-розробці.
Див. також
- Програмування
- Мова програмування
- Blockchain
- Ethereum
- EVM
- Smart contract
- Web3
- DeFi
- NFT
- DAO
- ERC-20
- ERC-721
- ERC-1155
- OpenZeppelin
- Hardhat
- Foundry
- Remix IDE
- JavaScript
- TypeScript
- Rust
- Vyper
- Налагодження коду
- Логування
- Безпека застосунків
- Приватність даних