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

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка reflection-токена
Середній
~2-3 дні
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    919
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    609
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    884

Розробка 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 баланс
    mapping(address => uint256) private _tOwned;  // тільки для excluded адрес
    mapping(address => bool) private _isExcluded; // виключені з 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 адрес

Адреси пулів ліквідності (Uniswap pair, PancakeSwap pair) повинні бути виключені з reflection. Якщо пул бере участь у reflection, його баланс токена постійно зростає, порушуючи співвідношення token/ETH у пулі та створюючи arb можливості. Це класична помилка в ранніх 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 адрес. Якщо цей масив великий (атакуючий міг додати багато адрес через якусь функцію), транзакції почнуть виходити за газовий ліміт. _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%. Вище — економічно дисфункціональна конструкція незалежно від технічної коректності.