Версії для прикладів у «Типові помилки PDA та CPI у Solana» перевірено 2 серпня 2026 року. Стабільна гілка Anchor v1 має релізи 1.0.x і орієнтується на Solana 3.x; Anchor v2 у документації позначений як alpha. Приклади для Anchor 0.29–0.32 залишаються лише відтворюваними прикладами для зафіксованого legacy-середовища: їх не слід переносити в новий проєкт без міграції залежностей і повторного тестування.

Робота з PDA (Program Derived Address) та CPI (Cross-Program Invocation) — це момент, де більшість нових розробників у Solana стикаються з першими неочевидними помилками. Транзакція падає, лог містить незрозумілий код, а клієнт не отримує зрозумілого повідомлення. Цей матеріал систематизує типові помилки, пояснює їхні причини та дає конкретні кроки для діагностики на Devnet.

Середовище: Anchor 0.30.x, Solana CLI 1.18.x, кластер Devnet.
Передумови: базове розуміння PDA та CPI з попередніх матеріалів розділу.
Обмеження: приклади наведені для навчального середовища Devnet і не призначені для production без додаткових перевірок.

Помилки PDA

Seeds не збігаються (SeedsMustMatch)

Ця помилка виникає, коли програма намагається знайти PDA за одними seeds, а клієнт передає акаунт, згенерований за іншими. Anchor перевіряє, що переданий акаунт збігається з PDA, обчисленим у програмі.

Причина: клієнт і програма використовують різний порядок seeds, різні роздільники або різні значення.

Концептуальний приклад:

Клієнт генерує PDA з seeds [b"vault", user.key().as_ref()], а програма очікує [user.key().as_ref(), b"vault"]. Порядок має значення — результат хешування буде різним.

Як перевірити на Devnet: виведіть seeds, які використовує клієнт, і порівняйте з тими, що вказані в макросі #[account(seeds = [...])]. Якщо використовуєте solana-keygen grind для пошуку PDA, переконайтеся, що seeds у команді повністю збігаються з програмними.

Невірний bump (InvalidBump)

Bump — це додатковий байт, який гарантує, що згенерована адреса не має відповідного приватного ключа. Anchor зазвичай знаходить правильний bump автоматично, але помилки виникають, коли bump передається явно.

Причина: збережений bump не відповідає канонічному bump для цих seeds. Це трапляється, коли bump був збережений у попередній версії програми з іншими seeds, або коли був переданий випадковий bump.

Безпечний крок: якщо не маєте чіткої причини зберігати bump явно, дозвольте Anchor обчислювати його автоматично через seeds::program. Якщо bump зберігається в акаунті — перевірте, що при ініціалізації записується саме канонічний bump, який повернув Pubkey::find_program_address.

PDA не належить програмі (OwnerMismatch)

Кожен акаунт у Solana має поле owner. Для PDA owner — це програма, яка його створила. Якщо передати акаунт з іншим owner, Anchor відхилить транзакцію.

Причина: акаунт був створений іншою програмою, або взагалі не ініціалізований (тоді owner — System Program). Також трапляється, коли після оновлення програми старі PDA мають owner у вигляді старого program_id.

Як перевірити: виконайте solana account <адреса_PDA> на Devnet і подивіться на поле Owner. Воно має збігатися з program_id вашої програми.

Помилки CPI

Невірний порядок акаунтів у invoke

При виклику invoke або invoke_signed акаунти передаються як масив. Цільова програма очікує їх у суворому порядку, який визначений у її process_instruction. Будь-яке зміщення — це передача неправильного акаунта на неправильну позицію.

Причина: розробник додає або переставляє акаунти в масиві, не перевіряючи, як цільова програма їх інтерпретує.

Концептуальний приклад:

System Program очікує: [from, to]. Ви передаєте: [to, from]. Результат — помилка, бо System Program спробує списати кошти з акаунта to, який може не бути signer.

Правило: завжди дивіться в документацію або вихідний код цільової програми і дотримуйтеся точного порядку акаунтів. У Anchor порядок акаунтів у структурах Accounts визначає порядок у CPI.

Missing signer для CPI

Коли програма викликає іншу програму від імені PDA, необхідно передати правильні підписи через invoke_signed. Якщо signer не вказано або передано неправильний bump — CPI завершується помилкою.

Причина: використано invoke замість invoke_signed для операції, що вимагає підпису PDA. Або масив signer_seeds порожній чи містить неправильні seeds.

Очікуваний результат правильного виклику: цільова програма отримує підпис і успішно виконує операцію. Якщо підпис відсутній — цільова програма поверне помилку ще до виконання логіки.

Program not found (невірний program_id)

При виклику CPI вказується program_id цільової програми. Якщо цей ідентифікатор не відповідає жодній розгорнутій програмі — транзакція падає.

Причина: скопійовано program_id з Mainnet, а працюєте на Devnet. Або program_id містить друкарську помилку. Або програма ще не розгорнута.

Як перевірити: виконайте solana program show <program_id>. Якщо програма не знайдена — перевірте правильність ідентифікатора та кластер, на якому працюєте.

Помилки Token CPI

InsufficientFunds для токен-операції

SPL Token Program повертає цю помилку, коли на акаунті недостатньо токенів для операції (переказ, burn, mint з обмеженням).

Причина: фактичний баланс менший за суму в транзакції. У контексті CPI це часто означає, що розробник неправильно обчислив суму або передав не той токен-акаунт.

Безпечний крок діагностики: перед викликом token CPI прочитайте баланс акаунта через token::accessor::amount і порівняйте з сумою операції. Це можна зробити в межах тієї ж транзакції.

MintMismatch — невірний mint account

Ця помилка виникає, коли операція з токеном очікує один mint, а отримує інший. Наприклад, ви намагаєтеся переказати USDC, але передали токен-акаунт з mint USDT.

Причина: у клієнті або в програмі передано токен-акаунт, чий mint не збігається з очікуваним. У Anchor це часто ловиться обмеженням mint::mint або token::token з вказаним mint.

Як перевірити на Devnet: виконайте spl-token account <токен_акаунт> і подивіться на поле Mint. Воно має збігатися з очікуваним mint address.

Як діагностувати

solana logs — читання ланцюга CPI

Коли транзакція падає, перше джерело інформації — логи. На Devnet можна отримати їх командою:

solana logs <transaction_signature>

У логах кожен CPI-виклик відображається окремо. Ви побачите послідовність: ваша програма викликає іншу, та викликає третю і так далі. Помилка містить program_id програми, де вона сталася, і код помилки.

Що шукати:

  • Рядок Program <program_id> invoke [1] — це вхід у CPI. Число в дужках показує глибину вкладеності.
  • Рядок Program <program_id> failed — програма, де сталася помилка.
  • Код помилки після failed: — для Anchor-програм це числовий код, який можна декодувати через anchor idl parse або переглянути в errors enum.

Обмеження: логи Devnet зберігаються обмежений час. Якщо транзакція старша — доведеться відтворити помилку заново.

Тестові сценарії для відтворення помилки

Найнадійніший спосіб діагностики — відтворити помилку в локальному тесті. Anchor тестування дозволяє запустити програму на локальному валідаторі з повним контролем над акаунтами.

Алгоритм:

  1. Напишіть тест, який повторює точну послідовність дій, що призвела до помилки.
  2. Додайте .logs() до результату виклику транзакції — це виведе всі логи, включно з CPI-ланцюгом.
  3. Спростіть тест до мінімального прикладу, який ще відтворює помилку.
  4. Змініть один параметр за раз і спостерігайте, коли помилка зникає.

Очікуваний результат: ви отримуєте мінімальний тест, який чітко показує, яка саме умова викликає помилку.

Профілактика

Юніт-тести для PDA-генерації

Багато помилок PDA можна виявити ще до розгортання. Напишіть юніт-тест (не інтеграційний, а саме юніт-тест), який перевіряє, що seeds у клієнті та в програмі генерують однаковий PDA.

Концептуальний підхід:

  • Визначте seeds як константу або функцію, яку використовує і клієнт, і програма.
  • Напишіть тест, який викликає Pubkey::find_program_address з цими seeds і program_id.
  • Перевірте, що результат збігається з PDA, який використовується в макросі #[account(seeds = [...])].

Обмеження: це не замінює інтеграційні тести, але виловляє більшість помилок seeds і bump на етапі компіляції або запуску тестів.

Інтеграційні тести для CPI-викликів

Кожен CPI-виклик у вашій програмі повинен мати відповідний інтеграційний тест. Це не просто перевірка «працює чи ні» — це перевірка коректності передачі акаунтів, підписів та обробки помилок цільової програми.

Що покривати тестами:

  • Успішний CPI-виклик з правильними акаунтами.
  • CPI з неправильним порядком акаунтів — очікувана помилка.
  • CPI без необхідного signer — очікувана помилка.
  • CPI з PDA-signer з правильним і неправильним bump.

Відокремлення від production: у навчальних проєктах на Devnet достатньо базових тестів з assert!. Для production потрібні більш детальні перевірки, включно з перевіркою станів акаунтів після виклику, але це виходить за межі поточного матеріалу.

Наступний логічний крок: після того, як ви впізнаєте та вмієте діагностувати типові помилки PDA та CPI, ви готові до створення повноцінного застосунку. Наступний матеріал — Перший full-stack застосунок Solana — об'єднає всі набуті знання в робочий проєкт.

Джерела