Разработка системы автоматической миграции с дедлайном

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы автоматической миграции с дедлайном
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка системы автоматической миграции с дедлайном

Миграция токенов нужна при обновлении токеномики, слиянии протоколов, или исправлении ошибки в исходном токен-контракте. Задача выглядит просто: держатели обменивают старый токен на новый в соотношении 1:1 (или по заданному ratio). Но без дедлайна и механизма сжигания система превращается в вечно висящее обязательство: протокол должен держать новые токены наготове бесконечно, а старые токены циркулируют параллельно с новыми, создавая confusion на рынке.

Дедлайн с автоматическим сжиганием немигрированных токенов решает обе проблемы.

Архитектура контракта миграции

Три участника системы:

OldToken — существующий ERC-20 контракт. Контракт миграции должен получить от держателей allowance или работать через transferFrom. Нельзя изменить существующий контракт — только взаимодействуем с ним.

NewToken — новый ERC-20 контракт. Должен поддерживать mint или предварительно пополнен достаточным количеством токенов для всех потенциальных мигрантов. Контракт миграции должен иметь MINTER_ROLE или холдинг новых токенов.

MigrationContract — логика обмена, управление дедлайном, механизм сжигания.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract TokenMigration is Ownable2Step, ReentrancyGuard {
    IERC20 public immutable oldToken;
    IERC20 public immutable newToken;
    
    uint256 public immutable migrationDeadline;
    uint256 public immutable migrationRatio; // новых токенов за 1 старый (18 decimals)
    
    uint256 public totalMigrated;
    bool public unmigatedBurned;
    
    event Migrated(address indexed user, uint256 oldAmount, uint256 newAmount);
    event UnmigratedBurned(uint256 amount);
    
    constructor(
        address _oldToken,
        address _newToken,
        uint256 _deadline,    // Unix timestamp
        uint256 _ratio        // 1e18 = 1:1, 2e18 = 2 новых за 1 старый
    ) Ownable2Step(msg.sender) {
        require(_deadline > block.timestamp + 30 days, "Deadline too soon");
        oldToken = IERC20(_oldToken);
        newToken = IERC20(_newToken);
        migrationDeadline = _deadline;
        migrationRatio = _ratio;
    }
    
    function migrate(uint256 amount) external nonReentrant {
        require(block.timestamp < migrationDeadline, "Migration closed");
        require(amount > 0, "Zero amount");
        
        uint256 newAmount = amount * migrationRatio / 1e18;
        require(newAmount > 0, "Below minimum");
        
        totalMigrated += amount;
        
        // Получаем старые токены от пользователя
        oldToken.transferFrom(msg.sender, address(this), amount);
        
        // Выдаём новые токены
        newToken.transfer(msg.sender, newAmount);
        
        emit Migrated(msg.sender, amount, newAmount);
    }
}

Почему Ownable2Step важен

Обычный Ownable позволяет передать ownership в один шаг: transferOwnership(newOwner). Если ошиблись в адресе — контракт потерян. Ownable2Step требует, чтобы новый owner принял права отдельной транзакцией. Для контракта, управляющего миграцией токенов с дедлайном, это критично.

Механизм сжигания после дедлайна

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

function burnUnmigrated() external onlyOwner {
    require(block.timestamp >= migrationDeadline, "Deadline not reached");
    require(!unmigatedBurned, "Already burned");
    
    unmigatedBurned = true;
    
    // Сжигаем старые токены, которые пришли через migrate()
    uint256 oldBalance = oldToken.balanceOf(address(this));
    if (oldBalance > 0) {
        IBurnable(address(oldToken)).burn(oldBalance);
        // Если старый токен не имеет burn() — отправляем на dead address
        // oldToken.transfer(address(0xdead), oldBalance);
    }
    
    // Возвращаем нераспределённые новые токены в treasury
    uint256 newBalance = newToken.balanceOf(address(this));
    if (newBalance > 0) {
        newToken.transfer(owner(), newBalance);
    }
    
    emit UnmigratedBurned(oldBalance);
}

Когда старый токен не имеет burn()

Большинство legacy токенов не имеют функции сжигания. Варианты:

  1. Отправить на 0x000...dEaD — неофициальный burn address, токены навсегда недоступны
  2. Отправить на address(0) — только если токен позволяет transfer to zero address (многие проверяют to != address(0))
  3. Собственная функция сжигания в MigrationContract через IUpgradeableToken(oldToken).burnFrom() — только если у контракта миграции есть BURNER_ROLE

Merkle Proof для snapshot-based миграции

Если миграция основана на снапшоте (балансы на конкретный блок, до деплоя нового контракта), пользователи не отдают токены — они доказывают право на получение новых через Merkle Proof:

contract SnapshotMigration is Ownable2Step {
    bytes32 public immutable merkleRoot;
    mapping(address => bool) public claimed;
    
    constructor(bytes32 _merkleRoot, uint256 _deadline) {
        merkleRoot = _merkleRoot;
        migrationDeadline = _deadline;
    }
    
    function claim(uint256 amount, bytes32[] calldata proof) external {
        require(block.timestamp < migrationDeadline, "Expired");
        require(!claimed[msg.sender], "Already claimed");
        
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
        require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
        
        claimed[msg.sender] = true;
        newToken.transfer(msg.sender, amount);
        
        emit Claimed(msg.sender, amount);
    }
}

Генерация Merkle Tree off-chain через @openzeppelin/merkle-tree или custom скрипт на основе снапшота баланса. Снапшот делается через The Graph subgraph или archival node query.

Обработка вестинга при миграции

Если старые токены находятся в vesting контрактах — их нельзя напрямую мигрировать пользователем. Нужны либо:

  1. Специальная функция для admin, которая мигрирует токены прямо из vesting контракта (требует интеграции с конкретным vesting контрактом)
  2. Автоматическая миграция через Tenderly Web3 Actions или keeper

Уведомление пользователей и мониторинг прогресса

Контракт должен выдавать события с достаточной информацией для построения дашборда:

event MigrationProgress(
    uint256 totalMigrated,
    uint256 totalOldSupply,
    uint256 deadline,
    uint256 timestamp
);

Subgraph на The Graph индексирует события и предоставляет GraphQL API для frontend: сколько процентов миграции завершено, сколько уникальных адресов мигрировало, кинетика по времени.

Важный практический момент: крупные держатели (>1% supply) нужно уведомить напрямую до запуска публичной миграции. Биржи, протоколы, фонды — у них могут быть внутренние процессы, которые требуют времени. Дедлайн должен давать минимум 90 дней даже для простых миграций.

Сроки разработки: 3-5 рабочих дней для базовой системы миграции, 7-10 дней для snapshot-based с Merkle Proof и subgraph. Стоимость рассчитывается индивидуально.