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







