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

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

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

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

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

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

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

Rebase-токен — це ERC-20, де кількість токенів на балансі кожного держателя автоматично змінюється залежно від зовнішної умови. Не через transfer, не через mint/burn — баланс змінюється у всіх одночасно. Перший відомий приклад — Ampleforth (AMPL), де supply коригується щодня залежно від відхилення ціни від target ($1 для AMPL). Механіка проста в описі, нетривіальна в реалізації та з серйозними наслідками для сумісності з 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) {
    // Конвертуємо зовнішній 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 щоб наблизити market cap до target.

Supply delta calculation:

function calculateSupplyDelta(uint256 currentPrice, uint256 targetPrice)
    internal view returns (int256) {
    // Відхилення від таргету
    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), не тому що тобі мінтнули токени, а тому що змінився _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: головна точка болі

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 протоколи

Aave та Compound зберігають deposited amount. Після negative rebase collateral зменшується — це може створити неочікувану ліквідацію. Після 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 для rebase токенів ІЗ-ЗА 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"
    );

    // Максимальна зміна ±10% за один rebase
    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.