Оракули — це точка входу зовнішнього стану в вашу програму. Будь-яка помилка в їхній інтеграції означає, що атакувальник може підсунути маніпульовану ціну, застаріле значення або взагалі порожні дані. Нижче — інженерний розбір того, як перевіряти джерело, виявляти застарілі дані, будувати fallback і уникати типових помилок із Switchboard та Pyth.
Перевірка джерела даних oracle
Перша лінія захисту — переконатися, що ви читаєте дані саме з того акаунта oracle, який очікуєте, і що цей акаунт належить правильній програмі.
Верифікація програми-власника
Кожен акаунт oracle в Solana належить конкретній on-chain програмі. Перед десеріалізацією обов'язково перевіряйте поле owner акаунта. Для Pyth це програма з відомим публічним ключем, для Switchboard — відповідна програма агрегатора. Якщо ви приймаєте адреси oracle-акаунтів як параметри від користувача без перевірки власника, атакувальник може створити фейковий акаунт із довільними даними та передати його адресу у вашу програму.
Верифікація структури акаунта
Обидва oracle-провайдери використовують фіксовані структури даних. Pyth-акаунти містять специфічний дискримінатор (перші 8 байтів), який відрізняє типи акаунтів (price feed, mapping, publisher). Switchboard використовує власний формат агрегатора з визначеними полями. Десеріалізація без перевірки дискримінатора або розміру даних може призвести до читання сміттєвих значень як коректних цін.
Перевірка конкретного фіду
Не покладайтеся на те, що користувач передасть правильний акаунт фіду. У production-програмах адреси oracle-акаунтів мають бути або захардкоджені як константи, або зчитуватися з конфігураційного PDA, який контролює адміністрація програми. Якщо адреса фіду параметризується, вона має проходити перевірку проти білого списку, що зберігається в програмі.
Що саме перевіряти перед використанням даних
- Owner акаунта збігається з офіційною програмою oracle (перевірте актуальний публічний ключ у документації провайдера).
- Розмір даних акаунта відповідає очікуваній структурі.
- Дискримінатор (перші 8 байт) збігається з очікуваним типом акаунта.
- Адреса фіду присутня в білому списку або захардкоджена.
Обробка застарілих даних oracle
Ціна, яка була правильною хвилину тому, може бути катастрофічною зараз. Oracle-дані мають час життя, і ваша програма зобов'язана його перевіряти.
Часова свіжість
Pyth зберігає publish_time — Unix-таймстамп останнього оновлення агрегованої ціни. Switchboard зберігає last_updated_timestamp у структурі агрегатора. Ваша програма має порівнювати це значення з поточним часом (через Clock sysvar) і відхиляти транзакцію, якщо різниця перевищує допустимий поріг.
Поріг визначається бізнес-логікою: для high-frequency операцій це може бути кілька секунд, для довгострокових контрактів — хвилини. Ключове правило — поріг має бути явно заданий як константа в коді, а не обчислюватися динамічно з неперевірених даних.
Slot-свіжість
Окрім wall-clock часу, варто враховувати slot, у якому було оновлено фід. Якщо фід оновлювався багато слотів тому, це може свідчити про те, що publishers перестали подавати дані, навіть якщо wall-clock час ще не перевищив поріг. Це особливо актуально під час сетапів тестових мереж або періодів високої навантаженості.
Кількість валідних publishers
Pyth надає num_valid_slots та кількість publishers, що внесли вклад у агреговану ціну. Якщо ціна сформована з одного publisher замість десятків — це сигнал підвищеного ризику. Встановіть мінімальну кількість валідних publishers як умову продовження виконання.
Що саме перевіряти
publish_time(Pyth) абоlast_updated_timestamp(Switchboard) проти Clock sysvar.- Різниця не перевищує визначений константою поріг.
- Кількість publishers, що внесли вклад, не нижче мінімального порогу.
- Стан раунду агрегатора (для Switchboard) — раунд має бути закритий, а не відкритий.
Моделі fallback при відмові oracle
Oracle — це зовнішня залежність, яка може перестати оновлюватися в будь-який момент. Програма має визначити, що робити в такій ситуації, до того як це станеться.
Circuit breaker
Найпростіша і найбезпечніша модель: якщо дані oracle застарілі, недоступні або мають недостатню кількість publishers — транзакція просто відхиляється з конкретним кодом помилки. Це підходить для протоколів, де краще зупинити операції, ніж працювати з ненадійними даними (наприклад, ліквідаційні механізми кредитних протоколів).
Адміністративне перевизначення
Програма зберігає PDA-акаунт конфігурації, в якому авторизований адміністратор може встановити ціну вручну. Якщо oracle-дані недостовірні, програма використовує адміністративну ціну. Ця модель вимагає чіткої політики: хто має право встановлювати ціну, як часто, з яким затвердженням. Без таких обмежень це створює централізовану точку довіри.
Багатооракульний консенсус
Програма читає ціну з кількох незалежних oracle-провайдерів (наприклад, Pyth і Switchboard для одного торгового пари) і застосовує правило узгодження: середнє, медіана або вимога збігу в межах відсоткового відхилення. Якщо провайдери розходяться більше ніж на визначений поріг — транзакція відхиляється. Це найскладніша модель, яка вимагає підтримки кількох інтеграцій і обережної обробки крайових випадків.
План відкату
Якщо ви впроваджуєте fallback, передбачте механізм відкату до попередньої моделі. Для circuit breaker це може бути адміністративний прапорець у конфігураційному PDA, який тимчасово знижує пороги свіжості. Для адміністративного перевизначення — можливість вимкнути його і повернутися до виключно oracle-даних. Зміна режиму має логуватися в окремому акаунті аудиту.
Типові помилки інтеграції Switchboard та Pyth
Pyth: ігнорування експоненти ціни
Pyth зберігає ціну як i64 та окремо — expo (експоненту). Реальна ціна обчислюється як price * 10^expo. Помилка — використовувати price без застосування expo або застосувати експоненту з неправильним знаком. Це призводить до того, що ціна BTC може інтерпретуватися як 30 замість 30 000 або навпаки. Перевірте: після застосування експоненти значення має відповідати очікуваному порядку величини для цього активу.
Pyth: використання ema замість поточної ціни
Pyth-акаунти містять як поточну агреговану ціну, так і експоненційне ковзне середнє (EMA). Помилка — випадково прочитати EMA-значення там, де логіка очікує поточну ціну. EMA завжди відстає від ринкової ціни, і використання її для спотових операцій створює арбітражну можливість.
Pyth: робота з негативними цінами
Тип i64 допускає негативні значення. Хоча для більшості активів це аномалія, програма має явно перевіряти, що ціна є додатною, якщо бізнес-логіка цього вимагає. Інакше негативне значення може пройти через математичні обчислення і призвести до непередбачуваних результатів (наприклад, неправильний розрахунок collateral).
Switchboard: використання даних з відкритого раунду
Switchboard-агрегатор має життєвий цикл раунду: відкритий → закритий. Під час відкритого раунду publishers ще подають дані, і агреговане значення може змінюватися. Читання ціни з відкритого раунду означає використання нестабільного проміжного значення. Програма має перевіряти, що раунд закритий, перед використанням даних.
Switchboard: ігнорування стану агрегатора
Агрегатор може бути в стані, коли немає жодного завершеного раунду (наприклад, щойно створений або після тривалої перерви в оновленнях). Спроба прочитати ціну в такому стані дасть нульове або дефолтне значення, яке не має нічого спільного з реальною ринковою ціною. Перевіряйте, що агрегатор має принаймні один завершений раунд із валідними даними.
Спільна помилка: відсутність unit-тестів із фіксованими даними
Інтеграція з oracle часто тестується лише проти devnet, де фіди оновлюються нерегулярно. У результаті тести то проходять, то ні, залежно від стану devnet-оракулів. Production-якісне тестування вимагає створення фіксованих тестових акаунтів oracle із відомими даними (конкретна ціна, конкретний таймстамп, конкретна кількість publishers) і перевірки всіх гілок логіки: свіжі дані, застарілі дані, недостатня кількість publishers, відкритий раунд, нульова ціна.
Спільна помилка: довіра до клієнтської бібліотеки без перевірки
Офіційні клієнтські бібліотеки Pyth і Switchboard інкапсульовують десеріалізацію та деякі перевірки. Проте вони розраховані на загальний випадок використання. Ваша програма може мати специфічні вимоги (жорсткіший поріг свіжості, мінімальна кількість publishers, додаткові перевірки ціни), які бібліотека не виконує. Не припускайте, що виклик функції бібліотеки замінює власні перевірки — бібліотека дає дані, ваша програма вирішує, чи відповідають вони вашим критеріям безпеки.
Усі наведені патерни перевірки мають бути реалізовані безпосередньо в on-chain логіці програми. Off-chain перевірки (у CLI, у скриптах, у frontend) не замінюють on-chain валідацію, оскільки транзакцію можна скласти й підписати без участі off-chain шару.