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

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

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

Производство

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

Паттерны PDA в Raydium

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

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

Хотя в принципе может быть несколько bumps, производящих off-curve адреса, программы Raydium всегда используют канонический bump (найденный путём уменьшения от 255). Он хранится в данных аккаунта PDA, чтобы последующие транзакции могли передать его и пропустить (дорогостоящий) цикл производства:
(CLMM’s PoolState вместо этого хранит bump: [u8; 1], поэтому проверьте структуру конкретной программы, а не предполагайте одну форму.) В последующих транзакциях bump читается из состояния пула, а не пересчитывается.

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

Cross-Program Invocation позволяет программе вызывать инструкции другой программы встроенно в рамках одной транзакции. Raydium использует CPI широко:
  • Инструкции swap вызывают программу SPL Token для перемещения токенов.
  • CLMM вызывает Metaplex для минтинга позиционного NFT.
  • Создание пула вызывает System Program для выделения аккаунтов.
  • Farm v6 вызывает SPL Token для передачи вознаграждений.
Интеграторы также используют CPI для вызова в Raydium — так работают стратегии хранилищ, протоколы с кредитным плечом и auto-compounders. См. 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 производит адрес pool_authority при хешировании с ID программы CPMM, и разрешает подпись полномочий на передачу токена. Никаких приватных ключей.

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

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

Лимит глубины CPI

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

Оставшиеся аккаунты

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

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

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

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

Не сохранение bump

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

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

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

Передача 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), требует, чтобы payer имел достаточно SOL для rent. Неудачные проверки rent появляются как неясные ошибки.

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

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

Указатели

Источники: