0. 리뷰
1. 개요
이전 블로그에서 프로그램을 배포하고 상호작용하는 방법을 소개했습니다. 클라이언트 측에서 프로그램의 명령을 호출하는 것 외에도, Solana는 크로스 프로그램 호출(cross-program invocation)이라는 메커니즘을 통해 프로그램들이 서로를 호출할 수 있도록 허용합니다. 이번 포스트에서는 크로스 프로그램 호출이 어떻게 사용되는지 설명합니다. 테스트 코드는 여기에 있습니다.
2. 크로스 프로그램 호출
크로스 프로그램 호출은 일반적으로 invoke 함수를 사용하여 구현됩니다. 이 섹션에서는 프로그램 내에서 lamport를 전송하는 예제를 통해 invoke의 사용법을 설명합니다.
Solana에는 새 계정을 생성하고 lamport(SOL)를 전송하는 역할을 하는 System Program이라는 네이티브 프로그램이 있습니다.
이 경우, 프로그램(예: 프로그램 A) 내에서 lamport를 전송하려면, 프로그램 A가 System Program의 transfer() 함수를 호출합니다. 아래의 구체적인 예제를 살펴보겠습니다.
2.1 코드 리뷰

3번째 줄부터 10번째 줄까지는 필요한 라이브러리를 가져옵니다. invoke() 함수는 solana_program::program 라이브러리에 있다는 점에 주목하세요.

process_instruction 함수에서, 22번째 줄부터 30번째 줄까지는 클라이언트가 전달한 세 개의 계정을 추출합니다. lamport는 from_account에서 to_account로 전송됩니다. 33번째 줄부터 44번째 줄까지, invoke() 함수가 호출됩니다. 이 함수는 두 개의 인자를 받습니다. 첫 번째는 호출할 대상 명령이고, 두 번째는 계정들의 집합입니다.
이 예제에서 대상 명령은 lamport 전송에 사용되는 transfer()입니다. 계정에는 호출되는 명령에 필요한 모든 계정이 포함됩니다. 이 경우에는 from_account와 to_account입니다.
배포된 프로그램은 다음 링크에서 확인할 수 있습니다.
https://explorer.solana.com/address/EPaLuYQ4c11BJAe9ucLbta3xGFb17Zy3cZh3UDPbXRG9?cluster=devnet
2.2 Lamport 전송

앞서 언급했듯이, 배포된 프로그램에 트랜잭션을 전송합니다. 배포된 프로그램은 System Program을 추가로 호출하여 대상 주소로 lamport를 전송합니다.
위의 코드는 클라이언트가 트랜잭션을 구성하는 방법을 보여줍니다. 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에서 프로그램은 런타임에 프로그램 파생 주소(Program Derived Addresses, PDA)라는 계정을 생성할 수 있습니다. 프로그램이 호출하는 대상 명령에 PDA로 서명된 계정이 포함된 경우, invoke() 대신 invoke_signed()를 사용해야 합니다. invoke_signed()의 사용법을 보여주기 위해 다른 예제를 사용하겠습니다. 먼저 컨트랙트 코드를 살펴보겠습니다.
3.1 코드 리뷰
이 예제에서는 프로그램(즉, 프로그램 B) 내부에서 런타임에 PDA를 생성합니다. PDA를 생성하려면 프로그램 B에서 System Program을 호출해야 합니다.

3번째 줄부터 10번째 줄까지는 필요한 라이브러리를 가져옵니다. 이번에는 invoke_signed()가 가져와진다는 점에 주목하세요(6번째 줄).

섹션 2의 예제와 유사하게, System Program과 PDA인 필요한 계정들을 추출합니다(22번째 줄 - 27번째 줄). 그런 다음 find_program_address() 함수를 사용하여 PDA의 주소와 이 PDA를 ed25519 곡선에서 벗어나게 하는 데 사용된 시드를 생성합니다(29번째 줄 - 30번째 줄). 이는 주소에 연결된 개인 키가 없음을 보장하기 위한 것입니다. 이 예제에서 클라이언트와 프로그램은 동일한 시드(즉, 'You pass butter')를 사용하여 PDA를 생성합니다. 따라서 공개 키는 동일해야 하며, 이는 31번째 줄부터 34번째 줄까지 확인됩니다. 그 후, 37번째 줄부터 47번째 줄까지 invoke_signer를 호출하여 allocate() 명령을 실행합니다. invoke() 함수와 달리, PDA를 생성하는 데 사용된 시드와 find_program_address() 함수에서 사용된 bump 시드를 인자로 추가로 받습니다.
계정을 생성/할당하려면 계정 자체가 서명을 제공해야 하며, 이 예제에서는 PDA가 서명자입니다. PDA에 서명하기 위해 invoke_signed가 사용됩니다. 구체적으로, Solana는 시드와 호출자(즉, 프로그램 B)의 programId를 사용하여 PDA를 재생성하고, 이를 주어진 계정(두 번째 인자)과 대조합니다. 일치하면 PDA에 서명됩니다.
배포된 프로그램은 아래에서 확인할 수 있습니다.
https://explorer.solana.com/address/4h3RXGsouTRvUWNG1Dqq2tuuASTXFebHC3wFzv3tSFCK?cluster=devnet
3.2 PDA 생성
두 번째 예제의 클라이언트 스크립트를 살펴보겠습니다.

104번째 줄에서, findProgramAddress() 함수를 통해 PDA와 bump 시드를 추출합니다.
112번째 줄부터 120번째 줄까지, 트랜잭션이 생성되고 전송됩니다. PDA의 속성(isSigner)이 'false'(113번째 줄)인 점에 주목하세요. 클라이언트가 서명할 수 없기 때문입니다. 런타임에 프로그램 B가 PDA에 서명하고 System Program 프로그램의 allocate 명령을 호출합니다.
다음 트랜잭션에서 할당된 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()의 차이점을 설명했습니다. 계속 지켜봐 주세요. 이 시리즈의 더 많은 글이 게시될 예정입니다.



