Версії для прикладів у «Типові помилки 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 тестування дозволяє запустити програму на локальному валідаторі з повним контролем над акаунтами.
Алгоритм:
- Напишіть тест, який повторює точну послідовність дій, що призвела до помилки.
- Додайте
.logs()до результату виклику транзакції — це виведе всі логи, включно з CPI-ланцюгом. - Спростіть тест до мінімального прикладу, який ще відтворює помилку.
- Змініть один параметр за раз і спостерігайте, коли помилка зникає.
Очікуваний результат: ви отримуєте мінімальний тест, який чітко показує, яка саме умова викликає помилку.
Профілактика
Юніт-тести для 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 — об'єднає всі набуті знання в робочий проєкт.