Skip to main content
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
PDA (program-derived addresses) и CPI (cross-program invocation) — два примитива, на которых построен Raydium. PDA позволяет программе владеть детерминированными адресами без приватных ключей — так работают органы управления пулами и хранилища токенов. CPI позволяет одной программе вызвать другую — так Raydium обменивает токены через программу SPL Token и так интеграторы используют Raydium в своих потоках. Оба механизма важно понимать перед тем, как читать исходный код Raydium.

PDA: адреса без ключей

Program-Derived Address — это публичный ключ, который:
  • не лежит на кривой ed25519 (у него нет приватного ключа).
  • детерминировано вычисляется из ID программы и набора seeds.
  • может быть подписан только той программой, которая его вывела, через invoke_signed.
Каждый орган управления пулом Raydium, каждое состояние пула, каждое хранилище, каждое состояние фермы — всё это PDA.

Вычисление

PDA вычисляется путём хеширования ID программы с seeds и последующего поиска byte-значения “bump”, которое выводит результат за пределы кривой. Первый bump (обычно начиная с 255 и убывая), который даёт off-curve адрес, становится каноническим bump.
Seeds могут быть чем угодно — строки, другие публичные ключи, u64 значения в виде little-endian байтов. Соглашение Raydium — человеко-читаемый префикс, за которым следуют уникальные идентификаторы.

Паттерны PDA в Raydium

Часто используемые PDA в программах Raydium: Пользователи и интеграторы могут вычислить эти адреса без каких-либо запросов — при наличии общественных входных данных (ID пула, ID фермы, ключ пользователя) PDA детерминирован.

Канонический bump

Хотя в принципе может быть несколько bumps, дающих off-curve адреса, программы Raydium всегда используют канонический bump (найденный декрементированием от 255). Он сохраняется в данных учётной записи PDA, чтобы в последующих транзакциях его можно было передать и избежать дорогостоящего цикла вычисления:
В последующих транзакциях bump читается из состояния пула, а не вычисляется заново.

CPI: вызов других программ

Cross-Program Invocation позволяет программе вызвать инструкцию другой программы встроенно в одной транзакции. Raydium интенсивно использует CPI:
  • Инструкции swap вызывают программу SPL Token для переноса токенов.
  • CLMM вызывает Metaplex для минтинга NFT позиции.
  • Создание пула вызывает System Program для выделения учётных записей.
  • Farm v6 вызывает SPL Token для перевода награды.
Интеграторы также используют CPI для вызова в Raydium — так работают стратегии хранилищ, протоколы с плечом для LP и автоматические компаундеры. Смотрите integration-guides/cpi-integration.

invoke vs invoke_signed

Среда выполнения Solana предоставляет два примитива CPI:
  • invoke: вызов другой программы; вызванная программа наследует подписантов из внешней транзакции.
  • invoke_signed: вызов другой программы от имени PDA; среда выполнения проверяет seeds PDA и авторизует подпись.
invoke_signed — это волшебство, которое позволяет программам владеть учётными записями без управления приватными ключами.

Пример: Raydium переводит токены из хранилища пула

Хранилище пула — это Token Account, органом управления которого является PDA программы пула. Чтобы перевести токены во время swap, программа пула должна подписать как этот PDA:
Среда выполнения видит, что invoke_signed вызывается программой CPMM, проверяет, что vault_and_lp_mint_auth_seed + bump при хешировании с ID программы CPMM производит адрес pool_authority, и разрешает подпись органа управления на передачу токена. Никаких приватных ключей не требуется.

Пример: интегратор вызывает Raydium CPMM

Программа интегратора (например, escrow) может вызвать swap_base_input Raydium через CPI:
Это канонический паттерн интеграции — смотрите integration-guides/cpi-integration для полного примера escrow.

Ограничение глубины CPI

Solana ограничивает глубину CPI 4 уровнями. Инструкция верхнего уровня транзакции считается глубиной 0; каждый вызов CPI увеличивает глубину на 1. Практическое следствие: собственный swap Raydium уже использует 1-2 уровня CPI (Raydium → SPL Token). Интегратор, вызывающий Raydium, использует 2. Если этого интегратора вызывает другой интегратор, это 3. 4-й уровень — предел. Большинство композиций легко остаются под этим ограничением, но глубокое вложение (aggregator → router → Raydium → hook) может его превысить. Проектируйте плоские структуры, а не глубокие.

Оставшиеся учётные записи

Когда инструкция Raydium нуждается в переменном числе учётных записей (например, CLMM swap пересекает неизвестное число tick arrays), дополнительные учётные записи передаются как remaining accounts — добавляются к списку фиксированных учётных записей, интерпретируются по позиции. SwapV2 CPMM использует remaining accounts для дополнительных требуемых учётных записей программ transfer-hook. Клиенты получают нужные учётные записи и добавляют их:
На уровне CPI интеграторы должны пробросить remaining accounts через свою инструкцию:

Подводные камни PDA

Неправильные seeds → неправильный адрес

Ошибка, когда seeds находятся в неправильном порядке, неправильной кодировке, или включают/исключают дополнительный байт, молча производит другой PDA. Транзакция завершается неоднозначно (программа пытается прочитать учётную запись, которая не существует). Всегда unit-тестируйте вычисление seeds против известных эталонных значений.

Несохранённый bump

Если вы пересчитываете bump на каждой транзакции, вы платите за цикл вычисления. Сохраняйте канонический bump в данных PDA и читайте его оттуда.

Путаница между каноническим и неканоническим bump

Неканонические bumps (если кто-то найдёт один, дающий off-curve) разрешены invoke_signed, но отклоняются программами Raydium через assert_eq!(bump, canonical_bump). Если кто-то попытается заявить PDA с неканоническим bump, транзакция завершится с ошибкой.

Передача PDA как подписантов, когда вы не владеющая программа

Только программа, чей ID находится в вычислении PDA, может вызвать invoke_signed с его seeds. Если вы попытаетесь, среда выполнения отклонит.

Подводные камни CPI

Забывчивость пробросить remaining_accounts

Если ваша внешняя инструкция передаёт transfer-hook учётные записи в remaining_accounts, но CPI в Raydium их не пробрасывает, Raydium завершается с ошибкой, потому что не может найти hook учётные записи. Всегда включайте with_remaining_accounts в CPI, которые их требуют.

Несоответствие флагов writable

Учётная запись, которую внешняя инструкция помечает как writable, также должна быть writable в вызове CPI, если вызванная программа намерена её писать. Несоответствие → отклонение средой выполнения.

Не учитывание rent

CPI в программу, которая создаёт учётную запись (например, создание ATA), требует, чтобы плательщик имел достаточно SOL для rent. Неудачные проверки rent выглядят как неясные ошибки.

Рабочий пример: вычисление PDA Raydium CPMM

Это в точности то, что Raydium SDK делает под капотом, когда вы вызываете getPoolInfoFromRpc({ poolId }) — вычисляет ассоциированные PDA без туда-сюда запросов.

Ссылки

Источники: