Loading...
Loading...

Банковский депозит | Стейкинг |
|---|---|
Банк держит ваши деньги | Смарт-контракт держит ваши токены |
Банк может обанкротиться | Код контракта открыт и проверяем |
Проценты устанавливает банк | Проценты устанавливает алгоритм |
Нужно доверять банку | Нужно доверять только коду |
Фиксированная ставка | Может быть переменная ставка |
Участник | Что получает | Что даёт |
|---|---|---|
Стейкер (ты) | Пассивный доход 💰 | Свои токены на время |
Проект | Стабильность и доверие 🛡️ | Часть прибыли |
Сеть | Безопасность и децентрализация 🔐 | Вознаграждения |
// SPDX-License-Identifier: MITpragma solidity ^0.8.19;
// Импортируем нужные библиотекиimport "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // Для работы с токенамиimport "@openzeppelin/contracts/security/ReentrancyGuard.sol"; // Защита от атак
/** * 🏦 Простой стейкинг контракт - как банковский депозит, но в блокчейне! * * Что он умеет: * 1. Принимать токены от пользователей (депозит) * 2. Блокировать их на определённое время * 3. Начислять проценты * 4. Возвращать токены + проценты */contract SimpleStaking is ReentrancyGuard {
// 📋 ОСНОВНЫЕ ПЕРЕМЕННЫЕ
IERC20 public stakingToken; // Какой токен мы принимаем для стейкинга IERC20 public rewardToken; // Каким токеном платим вознаграждение
address public owner; // Владелец контракта (администратор)
// 📊 Информация о депозите каждого пользователя struct UserDeposit { uint256 amount; // Сколько токенов заблокировано uint256 startTime; // Когда начался депозит uint256 lockDays; // На сколько дней заблокировано uint256 rewardRate; // Процентная ставка (в сотых долях процента) bool isActive; // Активен ли депозит }
// Депозиты каждого пользователя mapping(address => UserDeposit) public deposits;
// 📈 Статистика uint256 public totalStaked; // Общая сумма всех депозитов uint256 public totalRewardsPaid; // Всего выплачено вознаграждений
// 📢 События (как уведомления) event Deposited(address user, uint256 amount, uint256 days, uint256 rate); event Withdrawn(address user, uint256 amount, uint256 reward);
// 🏗️ Конструктор - запускается один раз при создании контракта constructor(address _stakingToken, address _rewardToken) { stakingToken = IERC20(_stakingToken); // Устанавливаем токен для стейкинга rewardToken = IERC20(_rewardToken); // Устанавливаем токен для вознаграждений owner = msg.sender; // Создатель контракта становится владельцем }
// 💰 ФУНКЦИЯ 1: Создать депозит (застейкать токены) function createDeposit(uint256 _amount, uint256 _days) external nonReentrant { // Проверки безопасности require(_amount > 0, "Сумма должна быть больше 0"); require(_days >= 7, "Минимальный срок - 7 дней"); require(_days <= 365, "Максимальный срок - 365 дней"); require(!deposits[msg.sender].isActive, "У вас уже есть активный депозит");
// Рассчитываем процентную ставку в зависимости от срока uint256 rewardRate = calculateRewardRate(_days);
// Переводим токены от пользователя в контракт require( stakingToken.transferFrom(msg.sender, address(this), _amount), "Ошибка перевода токенов" );
// Сохраняем информацию о депозите deposits[msg.sender] = UserDeposit({ amount: _amount, startTime: block.timestamp, // Текущее время блокчейна lockDays: _days, rewardRate: rewardRate, isActive: true });
// Обновляем общую статистику totalStaked += _amount;
// Уведомляем о создании депозита emit Deposited(msg.sender, _amount, _days, rewardRate); }
// 📊 Вспомогательная функция: рассчитать процентную ставку function calculateRewardRate(uint256 _days) public pure returns (uint256) { // Чем дольше срок, тем выше ставка if (_days >= 365) return 2000; // 20% годовых if (_days >= 180) return 1500; // 15% годовых if (_days >= 90) return 1200; // 12% годовых if (_days >= 30) return 1000; // 10% годовых return 800; // 8% годовых (минимум) }
// 💸 ФУНКЦИЯ 2: Забрать депозит (анстейкинг) function withdrawDeposit() external nonReentrant { UserDeposit storage deposit = deposits[msg.sender];
// Проверки безопасности require(deposit.isActive, "У вас нет активного депозита");
// Проверяем, прошёл ли срок блокировки uint256 lockEndTime = deposit.startTime + (deposit.lockDays * 1 days); require(block.timestamp >= lockEndTime, "Срок депозита ещё не истёк");
// Рассчитываем вознаграждение uint256 reward = calculateReward(msg.sender); uint256 totalAmount = deposit.amount + reward;
// Обнуляем депозит (безопасность: сначала изменяем состояние) deposit.isActive = false; uint256 depositAmount = deposit.amount; deposit.amount = 0;
// Обновляем статистику totalStaked -= depositAmount; totalRewardsPaid += reward;
// Переводим основную сумму require( stakingToken.transfer(msg.sender, depositAmount), "Ошибка перевода основных токенов" );
// Переводим вознаграждение (если есть) if (reward > 0) { require( rewardToken.transfer(msg.sender, reward), "Ошибка перевода вознаграждения" ); }
// Уведомляем о выводе emit Withdrawn(msg.sender, depositAmount, reward); }
// 🧮 ФУНКЦИЯ 3: Рассчитать вознаграждение function calculateReward(address _user) public view returns (uint256) { UserDeposit memory deposit = deposits[_user];
// Если депозит неактивен, вознаграждение = 0 if (!deposit.isActive) { return 0; }
// Рассчитываем количество дней с начала депозита uint256 daysPassed = (block.timestamp - deposit.startTime) / 1 days;
// Ограничиваем расчёт сроком депозита if (daysPassed > deposit.lockDays) { daysPassed = deposit.lockDays; }
// Формула: (сумма * ставка * дни) / (365 * 10000) // Ставка в базисных пунктах (1000 = 10%) uint256 reward = (deposit.amount * deposit.rewardRate * daysPassed) / (365 * 10000);
return reward; }
// 📋 ФУНКЦИЯ 4: Получить информацию о депозите function getDepositInfo(address _user) external view returns ( uint256 amount, // Сколько заблокировано uint256 startTime, // Когда начался депозит uint256 lockDays, // На сколько дней uint256 rewardRate, // Процентная ставка uint256 currentReward, // Текущее вознаграждение uint256 daysLeft, // Дней до окончания bool canWithdraw // Можно ли уже забрать ) { UserDeposit memory deposit = deposits[_user];
amount = deposit.amount; startTime = deposit.startTime; lockDays = deposit.lockDays; rewardRate = deposit.rewardRate; currentReward = calculateReward(_user);
if (deposit.isActive) { uint256 lockEndTime = deposit.startTime + (deposit.lockDays * 1 days); if (block.timestamp >= lockEndTime) { daysLeft = 0; canWithdraw = true; } else { daysLeft = (lockEndTime - block.timestamp) / 1 days; canWithdraw = false; } } else { daysLeft = 0; canWithdraw = false; } }
// 🔧 ФУНКЦИЯ 5: Экстренный вывод (без процентов) function emergencyWithdraw() external nonReentrant { UserDeposit storage deposit = deposits[msg.sender];
require(deposit.isActive, "У вас нет активного депозита");
uint256 amount = deposit.amount;
// Обнуляем депозит deposit.isActive = false; deposit.amount = 0;
// Обновляем статистику totalStaked -= amount;
// Возвращаем только основную сумму (без процентов) require( stakingToken.transfer(msg.sender, amount), "Ошибка перевода токенов" );
emit Withdrawn(msg.sender, amount, 0); }}// У Макса есть 1000 CoolCoin токенов// Он хочет застейкать их на 90 дней
// Сначала нужно дать разрешение контракту тратить токеныawait coolCoinToken.approve(stakingContract.address, 1000);// Макс создаёт депозит на 90 днейawait stakingContract.createDeposit(1000, 90);
// Контракт автоматически:// 1. Проверяет, что у Макса нет активного депозита// 2. Рассчитывает ставку: 90 дней = 12% годовых// 3. Переводит 1000 токенов от Макса в контракт// 4. Записывает информацию о депозите// Через несколько дней Макс может проверить свой доходconst info = await stakingContract.getDepositInfo(maxAddress);
console.log(`Заблокировано: ${info.amount} токенов`);console.log(`Текущее вознаграждение: ${info.currentReward} токенов`);console.log(`Дней до окончания: ${info.daysLeft}`);console.log(`Можно забрать: ${info.canWithdraw}`);// Через 90 дней Макс может забрать депозитawait stakingContract.withdrawDeposit();
// Макс получает:// - 1000 токенов (основная сумма)// - ~30 токенов вознаграждения (12% * 90/365 дней)// - Итого: ~1030 токеновДепозит: 1000 токеновСрок: 90 днейСтавка: 12% годовых (1200 в базисных пунктах)
Формула: (сумма × ставка × дни) ÷ (365 × 10000)Расчёт: (1000 × 1200 × 90) ÷ (365 × 10000) = 108,000,000 ÷ 3,650,000 = 29.6 токенов вознаграждения
Итого: 1000 + 29.6 = 1029.6 токенов
# 🚀 Продвинутые механизмы (для любознательных)
Макс был впечатлён: «Круто, Алекс! А что ещё умеют делать продвинутые стейкинг протоколы?»
— «Отличный вопрос! Наш базовый контракт — это только начало. В реальных DeFi проектах используют намного более сложные механизмы. Но не волнуйся, я объясню их простыми словами!»
## 💰 Механизм 1: Сложный процент (Compound Interest)
— «Помнишь, как в банке работает сложный процент? Если не снимать проценты, то в следующем месяце проценты начисляются уже на большую сумму!»
### 🏦 Аналогия с банком
**Обычный депозит:**- Год 1: 1000$ → +10% → 1100$ (снимаешь 100$)- Год 2: 1000$ → +10% → 1100$ (снимаешь 100$)- Год 3: 1000$ → +10% → 1100$ (снимаешь 100$)- **Итого за 3 года: 1000$ + 300$ = 1300$**
**Сложный процент:**- Год 1: 1000$ → +10% → 1100$ (не снимаешь)- Год 2: 1100$ → +10% → 1210$ (не снимаешь)- Год 3: 1210$ → +10% → 1331$ (не снимаешь)- **Итого за 3 года: 1331$ (на 31$ больше!)**
### 🔄 В стейкинге
```javascript// Обычный стейкинг// Каждый месяц забираешь вознаграждениеmonth1: stake(1000) → earn(10) → withdraw(10) → balance: 1000month2: stake(1000) → earn(10) → withdraw(10) → balance: 1000month3: stake(1000) → earn(10) → withdraw(10) → balance: 1000// Итого: 1030 токенов
// Компаундинг// Вознаграждение автоматически добавляется к основной суммеmonth1: stake(1000) → earn(10) → compound → balance: 1010month2: stake(1010) → earn(10.1) → compound → balance: 1020.1month3: stake(1020.1) → earn(10.2) → compound → balance: 1030.3// Итого: 1030.3 токенов (чуть больше!)Статус | Требования | Бонусы |
|---|---|---|
🥉 Bronze | 0+ полётов | Обычное обслуживание |
🥈 Silver | 10+ полётов | +20% миль, приоритетная регистрация |
🥇 Gold | 50+ полётов | +50% миль, бизнес-лаунж |
💎 Diamond | 100+ полётов | +100% миль, первый класс |
// Система уровней в стейкингеconst tiers = { bronze: { min: 1000, bonus: '0%', perks: 'Базовая ставка' }, silver: { min: 10000, bonus: '+5%', perks: 'Ранний доступ к новым пулам' }, gold: { min: 50000, bonus: '+15%', perks: 'Сниженные комиссии' }, diamond: { min: 200000, bonus: '+30%', perks: 'Персональный менеджер' },};
// Пример расчёта// У Макса 25,000 токенов = Gold статус// Базовое вознаграждение: 100 токенов// Бонус за Gold: +15%// Итого: 100 + 15 = 115 токенов// ❌ ПЛОХОЙ код (уязвимый)function withdraw() external { uint256 amount = balances[msg.sender]; // Читаем баланс: 1000
token.transfer(msg.sender, amount); // Отправляем 1000 токенов
balances[msg.sender] = 0; // Обнуляем баланс (слишком поздно!) // Хакер может вызвать функцию снова, пока баланс ещё не обнулён!}
// ✅ ХОРОШИЙ код (безопасный)function withdraw() external { uint256 amount = balances[msg.sender]; // Читаем баланс: 1000
balances[msg.sender] = 0; // Сначала обнуляем баланс!
token.transfer(msg.sender, amount); // Потом отправляем токены // Теперь повторный вызов не сработает, баланс уже 0}// Не полагаемся на точное время, используем блокиuint256 lockPeriodInBlocks = 30 * 24 * 60 * 4; // ~30 дней (блок каждые 15 сек)
// Или добавляем буферыrequire(block.timestamp >= depositTime + lockPeriod + 1 hours, "Ещё рано");// ❌ Опасноuint8 balance = 255; // Максимум для uint8balance = balance + 1; // Переполнение! Теперь balance = 0
// ✅ Безопасноuint256 balance = 255; // uint256 может хранить огромные числа// Или используем проверкиrequire(balance + amount >= balance, "Переполнение!");Предложение: "Снизить комиссии с 1% до 0.5%"
Голоса ЗА:- Алекс: 10,000 токенов = 10,000 голосов- Макс: 5,000 токенов = 5,000 голосов- Анна: 15,000 токенов = 15,000 голосовИтого ЗА: 30,000 голосов
Голоса ПРОТИВ:- Боб: 20,000 токенов = 20,000 голосовИтого ПРОТИВ: 20,000 голосов
Результат: 30,000 > 20,000 → Предложение принято!// У Макса есть:// - 1000 токенов в стейкинге// - Редкая NFT "Golden Dragon" (+25% бонус)
// Без NFT:базовое_вознаграждение = 100 токенов
// С NFT:бонус = 100 * 25% = 25 токеновитого = 100 + 25 = 125 токенов
// Макс получает на 25% больше просто за то, что у него есть крутая NFT!

