Solidity за 1 день: пишем первый смарт-контракт
Remix IDE, структура контракта, типы данных, ERC-20 токен, деплой в тестнет — полное руководство для начинающих.
Что такое смарт-контракты
Смарт-контракт — это программа, которая живёт на блокчейне и выполняется автоматически при определённых условиях. Никакой посредник не может её изменить, остановить или подделать.
Примеры смарт-контрактов:
- ERC-20 токен: цифровая валюта (USDT, LINK, UNI — это всё смарт-контракты)
- DEX (Uniswap): обмен токенов без посредников
- Lending (Aave): кредиты без банков
- NFT (ERC-721): уникальные цифровые объекты
Solidity — основной язык для смарт-контрактов на Ethereum, BSC, Polygon, Arbitrum, Base и других EVM-совместимых блокчейнах.
Подготовка
Что нужно
- Браузер — Chrome или Firefox
- MetaMask — расширение для работы с блокчейном (metamask.io)
- Remix IDE — онлайн-среда для Solidity (remix.ethereum.org)
- Тестовый ETH — бесплатные токены для тестовой сети
Настройка MetaMask
- Установите расширение MetaMask
- Создайте кошелёк (сохраните seed-фразу!)
- Добавьте тестовую сеть Sepolia:
- Network Name: Sepolia
- RPC URL:
https://rpc.sepolia.org - Chain ID: 11155111
- Currency: SepoliaETH
- Получите тестовый ETH: sepoliafaucet.com или faucet.sepolia.dev
Remix IDE
Откройте remix.ethereum.org — это ваша IDE для Solidity. Всё в браузере, ничего устанавливать не нужно.
Основы Solidity
Структура контракта
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract MyFirstContract {
// Состояние (хранится на блокчейне)
string public name;
uint256 public count;
address public owner;
// Конструктор (вызывается один раз при деплое)
constructor(string memory _name) {
name = _name;
owner = msg.sender; // Тот, кто задеплоил
}
// Функция (изменяет состояние — стоит газ)
function increment() public {
count += 1;
}
// View-функция (только чтение — бесплатно)
function getCount() public view returns (uint256) {
return count;
}
}
Типы данных
// Целые числа
uint256 public amount = 100; // Беззнаковое (0 и выше)
int256 public temperature = -10; // Со знаком
// Адреса
address public wallet = 0x1234...;
address payable public receiver; // Может получать ETH
// Строки и байты
string public name = "Hello";
bytes32 public hash;
// Булевы
bool public isActive = true;
// Маппинги (словари)
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;
// Массивы
uint256[] public numbers;
address[] public users;
// Структуры
struct User {
string name;
uint256 balance;
bool isActive;
}
mapping(address => User) public users;
// Перечисления
enum Status { Pending, Active, Completed, Cancelled }
Status public currentStatus;
Модификаторы доступа
contract AccessControl {
address public owner;
constructor() {
owner = msg.sender;
}
// Модификатор — проверка перед выполнением функции
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_; // Здесь выполняется тело функции
}
// Только владелец может вызвать
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
// Видимость функций:
// public — вызывает кто угодно
// external — только извне (не из контракта)
// internal — только из контракта и наследников
// private — только из этого контракта
}
События (Events)
contract EventExample {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function transfer(address to, uint256 amount) public {
// ... логика перевода ...
emit Transfer(msg.sender, to, amount); // Записываем в лог
}
}
Events записываются в лог блокчейна. Фронтенд может их слушать в реальном времени.
Пишем ERC-20 токен
ERC-20 — это стандарт токена. Все "монеты" на Ethereum (USDT, LINK, UNI) — это ERC-20. Давайте создадим свой.
Полный код ERC-20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract MyToken {
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(
string memory _name,
string memory _symbol,
uint256 _initialSupply
) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply * 10 ** decimals;
balanceOf[msg.sender] = totalSupply;
emit Transfer(address(0), msg.sender, totalSupply);
}
function transfer(address to, uint256 value) public returns (bool) {
require(to != address(0), "Transfer to zero address");
require(balanceOf[msg.sender] >= value, "Insufficient balance");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) public returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(
address from,
address to,
uint256 value
) public returns (bool) {
require(to != address(0), "Transfer to zero address");
require(balanceOf[from] >= value, "Insufficient balance");
require(allowance[from][msg.sender] >= value, "Insufficient allowance");
balanceOf[from] -= value;
balanceOf[to] += value;
allowance[from][msg.sender] -= value;
emit Transfer(from, to, value);
return true;
}
}
Разбор кода
balanceOf — сколько токенов у каждого адреса. Это маппинг: адрес → число.
allowance — разрешения. "Адрес X разрешил адресу Y тратить Z токенов от его имени". Это нужно для DEX (Uniswap) и других протоколов.
transfer — прямой перевод. Я отправляю токены тебе.
approve + transferFrom — делегированный перевод. Я разрешаю смарт-контракту Uniswap взять мои токены и обменять.
decimals = 18 — стандарт. 1 токен = 1 × 10^18 минимальных единиц (как 1 ETH = 10^18 wei).
Деплой в Remix
Шаг 1: Создание файла
- В Remix: File Explorer → New File →
MyToken.sol - Вставьте код ERC-20
Шаг 2: Компиляция
- Вкладка "Solidity Compiler" (слева)
- Compiler: 0.8.24
- Нажмите "Compile MyToken.sol"
- Зелёная галочка = успех
Шаг 3: Деплой в тестнет
- Вкладка "Deploy & Run Transactions"
- Environment: "Injected Provider - MetaMask"
- MetaMask подключится (убедитесь, что выбрана сеть Sepolia)
- Параметры конструктора:
- _name: "My Token"
- _symbol: "MTK"
- _initialSupply: 1000000
- Нажмите "Deploy"
- MetaMask попросит подтвердить транзакцию → Confirm
Через 15-30 секунд контракт задеплоен! Адрес контракта появится в Remix.
Шаг 4: Тестирование
В Remix внизу появится интерфейс контракта:
name()→ "My Token"symbol()→ "MTK"totalSupply()→ 1000000000000000000000000 (1M × 10^18)balanceOf(ваш адрес)→ вся эмиссияtransfer(другой адрес, 1000000000000000000)→ переведёт 1 токен
Безопасность
Типичные уязвимости
1. Reentrancy (повторный вход)
// ОПАСНО
function withdraw() public {
uint256 amount = balances[msg.sender];
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] = 0; // Обнуление ПОСЛЕ отправки!
}
// БЕЗОПАСНО (Checks-Effects-Interactions)
function withdraw() public {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0; // Обнуление ДО отправки
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
2. Integer overflow/underflow
В Solidity 0.8+ это решено — арифметика проверяется автоматически. Но в старых контрактах (0.7 и ниже) — нужна библиотека SafeMath.
3. Неправильный контроль доступа
// ОПАСНО — кто угодно может вызвать
function mint(uint256 amount) public {
totalSupply += amount;
balanceOf[msg.sender] += amount;
}
// БЕЗОПАСНО — только владелец
function mint(uint256 amount) public onlyOwner {
totalSupply += amount;
balanceOf[msg.sender] += amount;
}
Чек-лист безопасности
- [ ] Используете Solidity 0.8+
- [ ] Checks-Effects-Interactions паттерн
- [ ] Все функции с правильным контролем доступа
- [ ] Нет tx.origin для авторизации (только msg.sender)
- [ ] Обработка возвращаемых значений external calls
- [ ] Нет возможности self-destruct без контроля
- [ ] Тесты покрывают edge-кейсы
Использование OpenZeppelin
Не пишите всё с нуля. OpenZeppelin — проверенная библиотека контрактов:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor() ERC20("My Token", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 1000000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
10 строк вместо 80 — и безопаснее, потому что код OpenZeppelin проверен аудиторами.
Что дальше
- Hardhat — профессиональная среда разработки (тесты, деплой-скрипты, отладка)
- Foundry — ещё быстрее, тесты на Solidity
- Ethers.js / Viem — взаимодействие с контрактами из JavaScript
- The Graph — индексация данных блокчейна
- OpenZeppelin Defender — мониторинг и управление контрактами
Ресурсы
- solidity-by-example.org — примеры на каждую тему
- docs.soliditylang.org — официальная документация
- ethernaut.openzeppelin.com — CTF для изучения безопасности
- speedrunethereum.com — практические челленджи
Хочешь изучить это глубже? Смотри наш курс — за неделю ты будешь писать и деплоить смарт-контракты профессионального уровня.