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

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

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

Rebase-токен — это ERC-20, где количество токенов на балансе каждого держателя автоматически изменяется в зависимости от внешнего условия. Не через transfer, не через mint/burn — баланс меняется у всех одновременно. Первый известный пример — Ampleforth (AMPL), где supply корректируется daily в зависимости от отклонения цены от target ($1 в случае AMPL). Механика простая в описании, нетривиальная в реализации и с серьёзными implikациями для совместимости с DeFi протоколами.

Механика rebase: как это работает

Ключевое разграничение: внешний баланс (то что видит пользователь) и внутренний баланс (то что хранит контракт).

Контракт хранит _gonBalances — фиксированные "доли" каждого держателя от общего пула. Внешний баланс вычисляется как:

externalBalance = _gonBalances[account] / _gonsPerFragment

При rebase меняется только _gonsPerFragment — и все балансы "автоматически" изменяются без итерации по держателям.

// Упрощённая реализация
uint256 private constant TOTAL_GONS = type(uint256).max / 2; // большое число
uint256 private _totalSupply;
uint256 private _gonsPerFragment;

mapping(address => uint256) private _gonBalances;

constructor(uint256 initialSupply) {
    _totalSupply = initialSupply;
    _gonsPerFragment = TOTAL_GONS / initialSupply;
    _gonBalances[msg.sender] = TOTAL_GONS;
}

function balanceOf(address account) public view returns (uint256) {
    return _gonBalances[account] / _gonsPerFragment;
}

function rebase(int256 supplyDelta) external onlyOracle returns (uint256) {
    if (supplyDelta == 0) return _totalSupply;

    if (supplyDelta < 0) {
        _totalSupply -= uint256(-supplyDelta);
    } else {
        _totalSupply += uint256(supplyDelta);
    }

    // Clamp: не даём totalSupply уйти ниже 1
    if (_totalSupply > MAX_SUPPLY) _totalSupply = MAX_SUPPLY;

    _gonsPerFragment = TOTAL_GONS / _totalSupply;

    emit LogRebase(epoch, _totalSupply);
    return _totalSupply;
}

function transfer(address to, uint256 value) public override returns (bool) {
    // Конвертируем external value в gons
    uint256 gonValue = value * _gonsPerFragment;
    _gonBalances[msg.sender] -= gonValue;
    _gonBalances[to] += gonValue;
    emit Transfer(msg.sender, to, value);
    return true;
}

Три типа rebase механизмов

Elastic supply с price target

Классический Ampleforth-style. Oracle сообщает текущую цену, контракт корректирует supply чтобы приблизить рыночную cap к target.

Supply delta calculation:

function calculateSupplyDelta(uint256 currentPrice, uint256 targetPrice)
    internal view returns (int256) {
    // Deviation от таргета
    int256 priceDeviation = int256(currentPrice) - int256(targetPrice);
    int256 deviationPercent = (priceDeviation * 1e18) / int256(targetPrice);

    // Supply увеличивается если цена выше таргета, уменьшается если ниже
    // Dampening factor чтобы избежать overshooting
    int256 supplyDelta = (int256(_totalSupply) * deviationPercent)
        / int256(REBASE_LAG * 1e18);

    return supplyDelta;
}

REBASE_LAG — количество периодов для полного достижения equilibrium. Ampleforth использовал lag=10 (10 rebase'ов чтобы "дойти" до таргета при постоянном отклонении).

Oracle для цены — не spot price, а TWAP из DEX + агрегатор (Chainlink). Spot price манипулируется flash loans.

Yield-bearing rebase (stETH-style)

Lido's stETH — ребейсящий токен, где баланс растёт пропорционально staking rewards. Если застейкал 1 ETH и получил 1 stETH, через год у тебя ~1.035 stETH (при 3.5% APR), не потому что тебе mint'нули токены, а потому что изменился _gonsPerFragment.

// stETH механика: rebase пропорционально total pooled ETH
function rebase(uint256 totalPooledEther) external onlyBeaconChainOracle {
    uint256 prevTotalShares = _totalShares;  // эквивалент наших gons
    // totalShares не меняется, но totalPooledEther растёт
    // => sharesToEth ratio растёт => все балансы растут
    emit TokenRebased(
        prevTotalShares,
        _totalShares,
        prevTotalPooledEther,
        totalPooledEther,
        sharesMintedAsFees
    );
    _totalPooledEther = totalPooledEther;
}

function getPooledEthByShares(uint256 sharesAmount) public view returns (uint256) {
    return sharesAmount * _getTotalPooledEther() / _getTotalShares();
}

Это положительный rebase — supply только растёт (или стагнирует при нулевом yield). Значительно проще в понимании пользователями, чем elastic supply.

Inflationary с treasury

Supply растёт по расписанию (например, 2% в год), новые токены идут в treasury или stakers. Это скорее периодический mint чем настоящий rebase, но реализуется похожей механикой с gons.

Проблема совместимости с DeFi: главный pain point

Rebase-токены ломают assumptions большинства DeFi протоколов. Это самая важная часть, которую нужно понять до начала разработки.

AMM (Uniswap, Curve)

Uniswap хранит reserves как абсолютные значения. После rebase реальный баланс токена в пуле изменится, но reserves в Uniswap не обновятся до следующего обмена. Это создаёт arb возможность, но также означает что LP позиции "разъезжаются" с реальным балансом.

Решение: для AMM интеграции использовать wrapped версию — wstETH вместо stETH. Wrapped версия хранит shares (gons), а не rebasing amount. Курс конвертации — отдельная функция.

// Wrapped non-rebasing версия
contract WrappedRebaseToken is ERC20 {
    IRebaseToken public immutable underlying;

    function wrap(uint256 amount) external returns (uint256 sharesAmount) {
        underlying.transferFrom(msg.sender, address(this), amount);
        sharesAmount = underlying.getSharesByPooledTokens(amount);
        _mint(msg.sender, sharesAmount);
    }

    function unwrap(uint256 sharesAmount) external returns (uint256 amount) {
        _burn(msg.sender, sharesAmount);
        amount = underlying.getPooledTokensByShares(sharesAmount);
        underlying.transfer(msg.sender, amount);
    }

    // balanceOf возвращает shares — стабильное число
}

Lido именно так решила проблему: stETH используют как rebasing, wstETH — для DeFi протоколов (Aave, Compound, Uniswap V3 liquidity).

Lending protocols

Aave и Compound хранят deposited amount. После negative rebase залог уменьшается — это может создать неожиданную ликвидацию. После positive rebase лишние токены застрянут в протоколе, доступные только тому, кто их "заберёт" arb транзакцией.

Решение: в большинстве lending протоколов используется wrapped версия или сам протокол поддерживает rebasing (Aave поддерживает через aTokens — их собственный rebasing механизм).

ERC-20 transfer assumptions

Некоторые контракты делают такое:

uint256 before = token.balanceOf(address(this));
token.transferFrom(msg.sender, address(this), amount);
uint256 received = token.balanceOf(address(this)) - before;
// received может != amount для rebasing токенов ИЗ-ЗА rebase между двумя вызовами

Это редко, но встречается. Нужно тестировать интеграции.

Oracle для rebase: надёжность критична

Если oracle для price/yield rate скомпрометирован или манипулирован — последствия катастрофические: злоумышленник может вызвать rebase до нуля или до MAX_SUPPLY.

Защиты:

  • TWAP вместо spot price (минимум 30-минутное окно для elastic supply)
  • Bounds check: максимальное изменение supply за один rebase (например, ±10%)
  • Timelock: между обновлением oracle параметров и применением — N часов
  • Multi-oracle aggregation: использовать Chainlink + собственный TWAP, расхождение > X% — rebase не происходит
function rebase() external {
    uint256 chainlinkPrice = getChainlinkPrice();
    uint256 twapPrice = getTWAPPrice();

    // Если цены расходятся более чем на 2% — пропускаем rebase
    require(
        absDiff(chainlinkPrice, twapPrice) * 100 / chainlinkPrice < 2,
        "Oracle mismatch"
    );

    // Максимальное изменение за один rebase ±10%
    int256 supplyDelta = calculateSupplyDelta(twapPrice);
    int256 maxDelta = int256(_totalSupply / 10);
    supplyDelta = clamp(supplyDelta, -maxDelta, maxDelta);

    _rebase(supplyDelta);
}

Gas и производительность

Rebase сам по себе — O(1) операция, не O(n) по держателям. Это ключевое преимущество gons-подхода. Но:

  • balanceOf на один SLOAD дороже чем у обычного ERC-20 (деление + умножение)
  • transfer аналогично — чуть дороже из-за конвертации в gons

Для high-frequency DEX операций разница заметна. Benchmark: обычный ERC-20 transfer ~51,000 gas, rebasing ERC-20 transfer ~57,000–65,000 gas (+10–25%).

Аудит и известные уязвимости

Integer precision loss — деление в gons вычислениях может создать dust accounts (балансы, которые округляются до 0 при обратной конвертации). Тестировать граничные случаи: минимальный депозит, минимальный transfer.

Front-running rebase — если rebase предсказуем (фиксированное время), арбитражёры покупают перед positive rebase, продают после. Частично решается рандомизацией времени rebase или использованием committed randomness.

Negative rebase до нуля — контракт должен иметь floor на минимальный totalSupply.

Когда rebase оправдан

Rebase имеет смысл для:

  • Yield-bearing tokens (stETH-style) — пользователю удобно видеть растущий баланс вместо exchange rate
  • Algorithmic stablecoin с elastic supply (высокий риск, сложная механика)
  • Inflationary governance token где нужно равномерное разводнение всех держателей

Rebase не нужен для: стандартных utility токенов, токенов с emission расписанием, большинства governance токенов. В этих случаях проще обычный mint/burn.