Разработка reflection-токена

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка reflection-токена
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • 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

Разработка reflection-токена

Reflection-токены — один из самых технически проблемных классов ERC-20. Идея проста: часть каждой транзакции автоматически распределяется между всеми держателями пропорционально их балансу. На практике наивная реализация требует итерации по всем холдерам при каждом transfer, что означает газ, пропорциональный числу держателей — при 10 000 холдеров транзакция станет настолько дорогой, что контракт перестанет работать. Умная реализация через rFactor (reflection factor) решает это за O(1).

Механизм reflection: математика

Стандартный подход — два вида балансов: rBalance (reflection balance) и tBalance (token balance). Держатели хранят rBalance, который автоматически растёт при каждой транзакции.

Ключевая идея: вместо перераспределения токенов между всеми держателями, меняется курс конвертации rBalance → tBalance. При transfer часть rAmount исключается из rTotal (сжигается в reflection пространстве), что увеличивает rate = rTotal / tTotal для всех остальных.

contract ReflectionToken is IERC20, Ownable {
    uint256 private constant MAX = ~uint256(0);
    
    uint256 private _tTotal;          // общее supply в token-space
    uint256 private _rTotal;          // общее supply в reflection-space
    
    uint256 private _tFeeTotal;       // накопленные комиссии
    
    uint256 public taxFee = 5;        // 5% — распределяется держателям
    uint256 public liquidityFee = 3;  // 3% — в пул ликвидности
    uint256 public burnFee = 2;       // 2% — сжигается
    
    mapping(address => uint256) private _rOwned;  // reflection balance
    mapping(address => uint256) private _tOwned;  // только для excluded адресов
    mapping(address => bool) private _isExcluded; // excluded от reflection
    
    constructor(uint256 totalSupply) {
        _tTotal = totalSupply * 10**18;
        _rTotal = (MAX - (MAX % _tTotal));  // максимально возможное rTotal кратное tTotal
        _rOwned[msg.sender] = _rTotal;
    }
    
    function _getRate() private view returns (uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply / tSupply;
    }
    
    function _getCurrentSupply() private view returns (uint256, uint256) {
        uint256 rSupply = _rTotal;
        uint256 tSupply = _tTotal;
        
        // excluded аккаунты (пулы ликвидности, контракты) не участвуют в reflection
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply)
                return (_rTotal, _tTotal);
            rSupply -= _rOwned[_excluded[i]];
            tSupply -= _tOwned[_excluded[i]];
        }
        
        if (rSupply < _rTotal / _tTotal) return (_rTotal, _tTotal);
        return (rSupply, tSupply);
    }
    
    function balanceOf(address account) public view returns (uint256) {
        if (_isExcluded[account]) return _tOwned[account];
        return tokenFromReflection(_rOwned[account]);
    }
    
    function tokenFromReflection(uint256 rAmount) public view returns (uint256) {
        require(rAmount <= _rTotal, "Amount too large");
        return rAmount / _getRate();
    }
    
    function _transferStandard(address sender, address recipient, uint256 tAmount) private {
        // вычисляем все значения
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee,
         uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint256 tBurn) 
            = _getValues(tAmount);
        
        // обновляем reflection балансы
        _rOwned[sender] -= rAmount;
        _rOwned[recipient] += rTransferAmount;
        
        // reflection fee: уменьшаем rTotal — это автоматически увеличивает
        // балансы всех держателей без явного перебора
        _reflectFee(rFee, tFee);
        
        // обработка liquidity и burn
        _takeLiquidity(tLiquidity);
        _burn(sender, tBurn);
        
        emit Transfer(sender, recipient, tTransferAmount);
    }
    
    function _reflectFee(uint256 rFee, uint256 tFee) private {
        _rTotal -= rFee;     // ключевая операция: уменьшение rTotal
        _tFeeTotal += tFee;  // статистика
    }
}

Проблема excluded addresses

Адреса пулов ликвидности (Uniswap pair, PancakeSwap pair) должны быть excluded от reflection. Если пул участвует в reflection, его баланс токена будет постоянно расти, нарушая соотношение token/ETH в пуле и создавая арбитражные возможности. Это классическая ошибка в ранних reflection-токенах.

function excludeFromReward(address account) public onlyOwner {
    require(!_isExcluded[account], "Already excluded");
    if (_rOwned[account] > 0) {
        _tOwned[account] = tokenFromReflection(_rOwned[account]);
    }
    _isExcluded[account] = true;
    _excluded.push(account);
}

При добавлении в excluded сохраняется текущий tBalance (сконвертированный из rBalance) — иначе аккаунт потеряет средства.

Уязвимости и риски

Итерация по excluded: функция _getCurrentSupply() итерируется по массиву excluded адресов. Если этот массив большой (атакующий мог добавить много адресов через какую-то функцию), транзакции начнут выходить из gas limit. _excluded.length должен быть строго ограничен, а функция excludeFromReward — только для owner.

Precision loss при высоком числе транзакций: _rTotal уменьшается с каждой транзакцией. Теоретически после огромного числа транзакций _rTotal может стать настолько мало, что _getRate() вернёт 0 и контракт сломается. На практике при разумном supply и комиссиях это происходит через тысячи лет, но инвариант стоит проверять в тестах.

Anti-whale меры: без дополнительных ограничений крупные держатели могут манипулировать reflection. Стандартное решение — maxTransactionAmount и maxWalletSize:

uint256 public maxTxAmount = _tTotal / 100;      // 1% от supply
uint256 public maxWalletToken = _tTotal / 50;    // 2% от supply

function _transfer(address from, address to, uint256 amount) internal {
    require(amount <= maxTxAmount, "Exceeds max tx");
    if (!_isExcluded[to]) {
        require(balanceOf(to) + amount <= maxWalletToken, "Exceeds max wallet");
    }
    // ...
}

Auto-liquidity механизм

Многие reflection-токены включают auto-liquidity: накопленная liquidityFee периодически конвертируется в LP-токены через Uniswap/PancakeSwap. Это поддерживает ликвидность без участия команды.

bool inSwapAndLiquify = false;
uint256 private numTokensSellToAddToLiquidity = _tTotal / 1000; // 0.1%

function _transfer(...) {
    uint256 contractBalance = balanceOf(address(this));
    bool overMinTokenBalance = contractBalance >= numTokensSellToAddToLiquidity;
    
    if (overMinTokenBalance && !inSwapAndLiquify && from != uniswapV2Pair) {
        inSwapAndLiquify = true;
        swapAndLiquify(numTokensSellToAddToLiquidity);
        inSwapAndLiquify = false;
    }
    // ...
}

Флаг inSwapAndLiquify предотвращает рекурсивный вызов при swap токенов обратно через Uniswap.

Экономическая целесообразность

Высокие transfer-комиссии (5–10% reflection + 3–5% liquidity + burn) создают buy/sell spread, который делает токен малопригодным для реального использования как платёжное средство. Reflection-токены работают как механизм вознаграждения долгосрочных держателей и инструмент борьбы с high-frequency trading, но при чрезмерных комиссиях отпугивают ликвидных трейдеров, что создаёт poor price discovery.

Рекомендуемый диапазон суммарной комиссии: 5–8%. Выше — экономически дисфункциональная конструкция независимо от технической корректности реализации.