Розробка 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%. Вище — економічно дисфункціональна конструкція незалежно від технічної коректності.







