Перейти к основному содержанию
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →

Когда CPI — правильный инструмент

Пользовательская программа имеет смысл, когда обмен должен произойти атомарно вместе с другими изменениями состояния на цепи, которые может выполнить только ваша программа. Типичные случаи:
  • Программы эскроу / лимитных ордеров — пользователь депонирует монету в ваше эскроу, ваша программа следит за условием цены, и при срабатывании программа атомарно обменивает через Raydium и зачисляет средства на счёт пользователя.
  • Прокси-агрегаторы — единая инструкция, которая маршрутизирует обмен через Raydium плюс один или несколько других DEX с единой проверкой проскальзывания, контролируемой вашей программой.
  • Автокомпаундинг-хранилища — депонируйте LP или долю в фарме в ваше хранилище, хранилище собирает награды по расписанию, пополняет ликвидность, выпускает токены долей.
  • Стратегические хранилища — позиции LP с плечом, которые перебалансируются путём обмена через CLMM; ликвидаторы, которые закрывают позиции и обменивают залог в одной транзакции.
  • Платформы запуска токенов с пользовательским вестингом — ваша программа держит токены вестинга и выпускает их в пул Raydium по расписанию.
Если вы просто хотите отправить обмен из внешнего кода, CPI — это излишняя сложность — используйте SDK. CPI оправдывает свою сложность только тогда, когда требуется атомарность с вашим собственным состоянием.

Паттерны компоновки

Паттерн 1: тонкий прокси

Ваша программа предоставляет единую инструкцию, которая проверяет некоторую политику (например, монеты в белом списке, скидка комиссии для проверенных пользователей) и затем пересылает в Raydium.
Состояние находится в ATA пользователя. Ваша программа не владеет токенами. Минимальный след доверия.

Паттерн 2: эскроу

Ваша программа владеет PDA, который держит входную монету пользователя. При срабатывании PDA подписывает CPI в Raydium для обмена собственного баланса.
Критический момент: PDA подписывает через CpiContext::new_with_signer. См. Семена подписантов PDA.

Паттерн 3: составной многоэтапный обмен

Ваша программа выполняет несколько CPI в одной инструкции, обеспечивая единую границу проскальзывания для всех них. Инструкции обмена Raydium имеют свои собственные minimum_amount_out, но вы устанавливаете их равными 0 (или очень свободному полу) и принудительно проверяете строгий финальный минимум сами после последнего этапа.
Это даёт вам единую точку отката для всего маршрута. Используйте этот паттерн только тогда, когда вы доверяете каждому этапу быть безопасным для проскальзывания; в противном случае позвольте каждому этапу обеспечить собственный минимум.

Паттерн 4: хранилище / стратегия

Ваша программа держит LP-токены или долю в фарме в PDA. Keeper (или пользователь) вызывает compound(), что:
  1. Собирает награды из фарма.
  2. Обменивает награды на токены пула (CPI в CPMM или CLMM).
  3. Пополняет LP (ещё один CPI).
  4. Ставит новый LP (ещё один CPI).
Всё в одной транзакции, так что NAV хранилища движется атомарно. Бюджет вычислений обычно 600k–1M CU; таблицы поиска адресов обязательны.

Построение списка учётных записей

Структура Accounts вызывающей программы отражает порядок учётных записей программы Raydium, но большинство учётных записей на стороне Raydium — это UncheckedAccount, потому что Raydium валидирует их сама. Вы добавляете ограничения только на учётные записи, которыми владеете вы:
Асимметрия — строгая валидация ваших учётных записей, UncheckedAccount на стороне Raydium — это не лень. Получатель валидирует свои; двойная валидация на стороне вызывающего просто сжигает CU и рискует выйти из синхронизации, когда Raydium доставляет новое поле макета struct.

Сам CPI вызов

Семена подписантов PDA

CPI успешен только если PDA, переданный как authority, совпадает с выводом, который заявляет вызывающий. Оба должны договориться о:
  1. Последовательности семян (здесь [b"escrow", user.key().as_ref()]).
  2. Bump.
  3. ID программы вызывающего (ваша программа, не Raydium).
Raydium не волнует, кто является authority — ему важно только, что подпись authority покрывает транзакцию и что входная ATA принадлежит этому authority. Валидация происходит в anchor_spl::token::transfer: поле authority ATA должно равняться подписанту. Типичная ошибка: передача user как authority (и передача из escrow_input_ata, который принадлежит PDA эскроу). Программа SPL Token отклоняет с owner mismatch. Всегда делайте поле authority совпадающим с владельцем ATA.

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

Несколько инструкций Raydium берут список переменной длины учётных записей, добавленных после фиксированных — оставшиеся учётные записи.
  • CLMM SwapV2: 1–8 учётных записей TickArrayState для массивов ticks, которые может пересечь обмен, в направлении обмена.
  • Farm v6 Deposit / Harvest / Withdraw: пары (reward_vault, user_reward_ata), одна пара на активный слот награды.
  • Монеты Token-2022 с transfer-hook: программа transfer-hook плюс все учётные записи, которые нужны хуку.
Помощники Anchor CPI не проверяют типы оставшихся учётных записей. Передайте их:
Порядок имеет значение. Для CLMM:
Для сбора наград farm v6:
Ваша вызывающая программа должна передать оставшиеся учётные записи, которые она получает от клиента, без изменений. Не пытайтесь фильтровать или переупорядочивать их.

Бюджет вычислений для составных вызовов

CPI стоит ~1,500 CU для самого фрейма вызова; использование CU самого вызываемого складывается сверху. Примерный бюджет на CPI Raydium: Добавьте ~1,500 на каждый фрейм CPI и ~20,000 на накладные расходы вашей программы. Автокомпаундер, выполняющий harvest → swap A → swap B → deposit LP → stake LP, легко достигает 700k CU. Всегда устанавливайте явный ComputeBudgetProgram::set_compute_unit_limit:
Потолок по умолчанию 200k CU молча исчерпается задолго до завершения составного вызова.

Распространение ошибок

Программы Raydium возвращают ошибки Anchor со стабильными кодами ошибок. Ваша вызывающая программа видит их как Err(ProgramError::Custom(code)). По умолчанию пробросьте:
Или перехватите для конкретных кодов:
Соответствие код-ошибки-к-смыслу стабильно согласно политике IDL (sdk-api/anchor-idl); новые коды добавляются в конец, существующие коды никогда не меняют смысл.

Полный рабочий пример: эскроу лимитного ордера

Поток:
  1. open_order — пользователь депонирует amount_in входной input_mint в PDA эскроу; записать целевой min_amount_out и срок действия.
  2. execute_order — любой (keeper) вызывает с текущими учётными записями пула. Программа проверяет текущую цену ≥ min_amount_out, затем выполняет CPI обмена Raydium и хранит вывод в эскроу.
  3. claim — пользователь снимает выходную монету из эскроу.
Keeper платит за транзакцию (они получают fee-у keeper где-то ещё — не показано). PDA эскроу подписывает CPI. И проверка проскальзывания на стороне Raydium, и собственная проверка дельты эскроу обеспечивают полу — двойная безопасность.

Тестирование

Вытягивание программ Raydium в локальный валидатор для интеграционных тестов (из Anchor.toml):
Также клонируйте учётные записи состояния пула, чтобы ваши тесты могли фактически выполнять обмены; anchor test извлекает их из mainnet при запуске. См. sdk-api/rust-cpi.

Подводные камни, специфичные для компоновки

Reentrancy

Solana не имеет истинной reentrancy — CPI не может вызвать обратно в исходную программу в той же инвокации. Но вы всё ещё можете создать себе логическую reentrancy: CPI, который читает ваше состояние, затем ваш код читает его снова, предполагая, что CPI его не изменил. Для Raydium CPI не трогают ваше состояние, поэтому это менее проблемно, чем, например, flash-loan контексты. Но если вы комбинируете Raydium с протоколом кредитования, будьте осторожны.

Дрейф mutability учётных записей

Если ваша программа передаёт учётную запись как mut, но Raydium ожидает её read-only (или наоборот), среда выполнения отклоняет инвокацию с InvalidAccountData. Всегда проверяйте ожидаемую mutability инструкции Raydium в IDL; anchor_cp_swap::cpi::accounts::Swap обеспечивает её через типы полей.

Поле программы Token-2022

Входная и выходная монеты могут быть под разными программами токенов — одна SPL Token, одна Token-2022. CPI имеет отдельные поля input_token_program и output_token_program по этой причине. Всегда проверяйте поле owner каждой монеты и маршрутизируйте правильную программу в каждый слот.

Версионированные транзакции

Составная tx, выполняющая 2+ CPI Raydium плюс создание ATA, редко умещается в устаревшей (v0-без-LUT) транзакции. Используйте V0 с таблицами поиска адресов; извлеките публичные LUT Raydium через raydium.getRaydiumLutAddresses().

Указатели

Источники: