Back to Blog

Безопасность экосистемы Solana (2) — Взаимодействие между программами

18 марта 2022 г.
5 min read

0. Обзор

1. Обзор

В предыдущем блоге мы рассказали, как развертывать программу и взаимодействовать с ней. Помимо вызова инструкций программы со стороны клиента, Solana также позволяет программам вызывать друг друга через механизм, называемый межпрограммным вызовом (cross-program invocation). В этой статье мы проиллюстрируем, как используется межпрограммный вызов. Тестовый код находится здесь.

2. Межпрограммный вызов

Межпрограммный вызов обычно реализуется с помощью функции invoke. В этом разделе мы проиллюстрируем использование invoke на примере перевода лампортов внутри программы.

В Solana существует родная программа под названием System Program, которая отвечает за создание новых аккаунтов и перевод лампортов (SOL). В данном случае, чтобы перевести лампорты внутри программы (например, Программы А), Программа А вызовет функцию transfer() в System Program. Давайте разберем конкретный пример ниже.

2.1 Обзор кода

С 3-й по 10-ю строки импортируются необходимые библиотеки. Обратите внимание, что функция invoke() находится в библиотеке solana_program::program.

В функции process_instruction строки с 22 по 30 извлекают три аккаунта, переданные клиентом. Обратите внимание, что лампорты будут переведены из from_account в to_account. Со строки 33 по 44 вызывается функция invoke(). Она принимает два аргумента. Первый — это целевая вызываемая инструкция, а второй — набор аккаунтов. В этом примере целевая инструкция — transfer(), которая используется для перевода лампортов. Аккаунты включают в себя все аккаунты, необходимые для вызываемой инструкции. В данном случае это from_account и to_account.

Развернутую программу можно найти по следующей ссылке.

https://explorer.solana.com/address/EPaLuYQ4c11BJAe9ucLbta3xGFb17Zy3cZh3UDPbXRG9?cluster=devnet

2.2 Перевод лампортов

Как уже упоминалось, мы отправляем транзакцию в развернутую программу. Развернутая программа затем вызовет System Program для перевода лампортов на целевой адрес. Приведенный выше код показывает, как клиент формирует транзакции. Строки с 93 по 101 извлекают programId развернутой программы. Строки с 103 по 109 генерируют адреса отправителя, получателя и System Program. Строки с 111 по 117 конструируют транзакцию. В строке 118 транзакция отправляется в кластер Solana. Обратите внимание, что единственный подписант в этой транзакции — отправитель, что задано в строке 112. Отправитель должен авторизовать (т.е. подписать) транзакцию, которая списывает деньги с его/ее счета, а получатель — нет (строка 113).

Транзакцию можно найти по следующей ссылке.

https://explorer.solana.com/tx/4cxqnff8SakVcE9y5phmh6utcbBUGaLDPvqMRgSy9aPGdNVH6DCsQyJXCCzRGvW5CpygUi5pqhgBQxczXnWCoqPJ?cluster=devnet

3. Invoke или Invoke_signed

В Solana программы могут генерировать аккаунты во время выполнения, которые называются производными адресами программ (PDA). Если целевая инструкция, вызываемая программой, содержит подписанные аккаунты с PDA, следует использовать invoke_signed() вместо invoke(). Мы используем другой пример, чтобы продемонстрировать использование invoke_signed(). Теперь давайте сначала разберем код контракта.

3.1 Обзор кода

В этом примере мы создаем PDA во время выполнения внутри программы (т.е. Программы B). Чтобы создать PDA, Программа B должна вызвать System Program.

Строки с 3 по 10 импортируют необходимые библиотеки. Обратите внимание, что в этот раз импортируется invoke_signed() (строка 6).

Как и в примере из раздела 2, мы извлекаем необходимые аккаунты (строки 22–27), которыми являются System Program и PDA. Затем мы используем функцию find_program_address() для генерации адреса PDA и начального числа (seed), используемого для вывода этого PDA за пределы кривой ed25519 (строки 29–30). Это гарантирует, что у адреса нет связанного закрытого ключа. В этом примере клиент и программа используют одно и то же число (т.е. 'You pass butter') для генерации PDA. Таким образом, открытые ключи должны совпадать, что проверяется в строках с 31 по 34. После этого мы вызываем invoke_signer для выполнения инструкции allocate() (строки 37–47). В отличие от функции invoke(), она принимает на один аргумент больше — это начальные числа (seeds), которые использовались для создания PDA, а также bump-seed, использованный функцией find_program_address().

Чтобы создать/выделить аккаунт, сам аккаунт должен предоставить подпись, и в этом примере PDA выступает в роли подписанта. Для подписи PDA используется invoke_signed. В частности, Solana будет использовать начальные числа и programId вызывающей стороны (т.е. Программы B), чтобы воссоздать PDA и сопоставить его с предоставленными аккаунтами (второй аргумент). Если они равны, PDA будет подписан.

Развернутую программу можно проверить ниже.

https://explorer.solana.com/address/4h3RXGsouTRvUWNG1Dqq2tuuASTXFebHC3wFzv3tSFCK?cluster=devnet

3.2 Создание PDA

Давайте разберем клиентский скрипт для второго примера.

В строке 104 мы извлекаем PDA и bump-seed с помощью функции findProgramAddress(). Со строки 112 по 120 создается и отправляется транзакция. Обратите внимание, что свойство (isSigner) у PDA здесь 'false' (строка 113), так как клиент не может его подписать. Во время выполнения Программа B подпишет PDA и вызовет инструкцию allocate в программе System Program.

Вы можете найти выделенный PDA в следующей транзакции.

https://explorer.solana.com/tx/5fzdfzbD4281pY1HJm37f1fxSEMGmWtQmbmFPzEiP9v6hWF8GRRZmfcMDvPJugTrs3npnCsaWUGvw15URyBhx3LS?cluster=devnet

3.3 Можно ли использовать Invoke()?

Чтобы показать, что invoke здесь использовать нельзя, мы используем другую программу (т.е. Программу C) для вызова инструкции allocation.

Единственное изменение, которое мы внесли, — замена функции invoke_signed() на invoke(). Мы заметили, что аккаунт создать не удалось.

Ошибка показывает, что для успешного выполнения межпрограммного вызова необходим подписант. Это означает, что требуется функция invoke_signed().

4. Заключение

В этой статье мы рассказали, как реализовать межпрограммный вызов с помощью функции invoke(). Мы также использовали различные примеры, чтобы проиллюстрировать различия между invoke() и invoke_signed(). Оставайтесь с нами, впереди еще больше статей из этой серии.

Читайте другие статьи из этой серии:

Sign up for the latest updates
Потери около 5,57 млн USD: атаки на X100 Freezer и NEM | Еженедельный обзор BlockSec
Security Insights

Потери около 5,57 млн USD: атаки на X100 Freezer и NEM | Еженедельный обзор BlockSec

Этот еженедельный отчёт охватывает два инцидента безопасности в сетях Ethereum и BNB Chain за период 19.10.2026–25.10.2026. В нём рассматриваются захват управления X100 Freezer, профинансированный с помощью флеш-кредита, и ошибка при расчёте налога, затронувшая пул ликвидности токена NEM в сети BNB Chain. Оценочные потери в рамках отчёта составляют в общей сложности около 5,57 млн USD: ~4,75 млн USD по рассматриваемой транзакции X100 и ~820 тыс. USD в результате инцидента с пулом NEM. В эту сумму не входят отдельно опубликованные данные о последующих выводах средств из X100.

Потери на сумму около 3,87 млн USD: взломы Autheo Network и UNCX | Еженедельный обзор BlockSec
Security Insights

Потери на сумму около 3,87 млн USD: взломы Autheo Network и UNCX | Еженедельный обзор BlockSec

В этом еженедельном отчёте рассматриваются 3 инцидента безопасности за период 12.10.2026–18.10.2026. Общий оценочный ущерб составил около 3,87 млн USD. Ущерб Autheo Network от компрометации закрытого ключа составил примерно 1,76 млн USD, Qubic Wallet потерял примерно 1,30 млн USD из-за ненадёжной генерации seed, а ошибка учёта в контрактах Smart Token Vesting V2 сети UNCX Network на Ethereum, BNB Chain, Base и Robinhood Chain привела к ущербу примерно в 807 тыс. USD. В подробном анализе инцидента UNCX объясняется, как некорректный расчёт совокупного количества долей смешивал транши вестинга, созданные при разных соотношениях токенов и долей, и позволял выводить из общих пулов больше положенного.

Что такое сертификация SEAL? Подтверждение операционной безопасности в криптоиндустрии
Security Insights

Что такое сертификация SEAL? Подтверждение операционной безопасности в криптоиндустрии

Сертификация SEAL обеспечивает независимое подтверждение операционной безопасности криптопроекта в согласованных пределах проверки. Узнайте о шести модулях программы, требованиях к подтверждающим материалам, пределах действия сертификации и процессе оценки, который проводит BlockSec.