Dev/Blockchain

[Solana - Native vs Anchor] #3. Account 생성하기

TaeGit 2022. 10. 21. 22:23

목차

  • Account 생성하기
  • 프로그램 구조
  • Native 프로그래밍
  • Anchor 프로그래밍

 

Account 생성하기

이전 포스팅인 [Solana - Native vs Anchor] Account 생성하기에서 Account 하나를 생성하기 위해 Client에서 System Program으로 곧바로 생성을 요청했습니다. 이번 글 "Account 생성하기"에서는 ClientServer Program으로 Account 생성을 요청하고, Server Program에서 System Programcreate_account Instruction을 호출해 Account를 생성하는 과정을 살펴보겠습니다.

 

이런 방법은 Solana에서 CPI(Cross-Program Invocation)라고 불립니다. CPI는 하나의 Program이 다른 ProgramInstruction을 호출하는 것을 말합니다. 이러면 Client 입장에서 하나의 Instruction으로 여러 작업을 처리를 할 수 있게 됩니다.

 

Account 생성하기는 우리 Server ProgramSystem Program을 호출하는 CPI의 한 예를 설명합니다.

 

 

프로그램 구조

프로그램 구조

Client Program은 하나의 Instruction을 갖는 Transaction을 보냅니다. 이 Instruction이 갖는 keys에는 payerSystem Program과 함께 새롭게 생성할 AccountnewKey가 포함되어 있습니다.

 

Server ProgramClient로부터 3개의 Account를 받습니다. newKey Account를 생성하기 위해 System Program을 호출할 때 이 3개의 Account 정보를 이용합니다.

 

 

Native 프로그래밍

Native 프로그래밍을 먼저 살펴봅시다.

 

Server Program

entrypoint!(process_instruction);

pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {

    let account_iter = &mut accounts.iter();
    let payer = next_account_info(account_iter)?;
    let new_account = next_account_info(account_iter)?;
    let system_program = next_account_info(account_iter)?;

    msg!("Program 호출됨. System Account 만드는 중...");
    msg!("새로운 Public Key : {}", &new_account.key.to_string());

    invoke(
        &system_instruction::create_account(
            &payer.key,
            &new_account.key, 
            1 * LAMPORTS_PER_SOL,
            0, 
            &system_program::ID,
        ), 
        &[
            payer.clone(), new_account.clone(), system_program.clone()
        ],
    )?;

    msg!("Account created successfully.");
    Ok(())
}

Solana에서 Account의 생성은 System Program이 담당하고, 일반적인 Account는 모두 System Program이 소유합니다. 따라서, Server Program은 새로운 Account를 생성하기 위해 invoke 함수를 통해 System ProgramInstruction을 호출합니다.

  • invoke
    : 다른 ProgramInstruction을 호출합니다.
  • system_instruction::create_account()
    : Account를 생성하기 위해 System Program으로 보낼 Instruction을 만듭니다.
  • account_infos
    : Instruction을 받을 프로그램이 처리를 위해 필요로 하는 모든 Account들입니다.

 

 

Client Program

describe("Create a system account", async () => {
    const connection = new Connection(`http://127.0.0.1:8899`, 'confirmed');
    const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');

    const PROGRAM_ID: PublicKey = new PublicKey("6Kv8wFQtRT8XeQd4sR4jQQefwL1oJC6xE36r6U1oRJ7Z");

    it("Create the account", async () => {
        const newKeypair = Keypair.generate();

        let ix = new TransactionInstruction({
            keys: [
                {pubkey: payer.publicKey, isSigner: true, isWritable: true},
                {pubkey: newKeypair.publicKey, isSigner: true, isWritable: true},
                {pubkey: SystemProgram.programId, isSigner: false, isWritable: false}
            ],
            programId: PROGRAM_ID,
            data: Buffer.alloc(0),
        });

        await sendAndConfirmTransaction(
            connection,
            new Transaction().add(ix),
            [payer, newKeypair]
        );
    });
});

Client Program은 이전 포스팅들에서 설명했던 내용과 크게 다르지 않습니다. Server Program으로 보낼 Instruction을 생성할 때 payer, newKeypair, SystemProgram 세 개의 pubkey를 포함합니다. 이렇게 만들어진 하나의 InstructionTransaction에 담아 보냅니다.

 

 

Test

native npm test
native solana logs

테스트 결과 성공적으로 Account가 생성된 것을 볼 수 있습니다.

우리가 만든 AccountPubkeyAGxcvfNVekDSParpMZiPihjJyazs2vm8Y1ZB52gZ6RSS입니다.

 

생성된 결과를 아래와 같이 Solana Explorer를 통해서도 확인할 수 있습니다.

solana explorer 결과

 

 

Anchor 프로그래밍

다음은 Anchor 프로그래밍을 살펴봅시다.

Server Program

declare_id!("6KULEpud8tcYLZxrw2KUb1APZTeLCLdKtp2fhvVKQfqQ");

const LAMPORTS_PER_SOL: u64 = 1000000000;

#[program]
pub mod create_account {
    use super::*;

    pub fn create_system_account(ctx: Context<CreateSystemAccount>) -> Result<()> {

        msg!("Program이 호출되었습니다. System Account 생성중...");
        msg!(" 새로운 Public Key : {}", &ctx.accounts.new_account.key().to_string());

        system_program::create_account(
            CpiContext::new(
                ctx.accounts.system_program.to_account_info(),
                system_program::CreateAccount {
                    from: ctx.accounts.payer.to_account_info(),         // From Pubkey
                    to: ctx.accounts.new_account.to_account_info(),     // To Pubkey
                },
            ),
            1 * LAMPORTS_PER_SOL,   // Lamports (1 SOL)
            0,                      // Space
            &ctx.accounts.system_program.key(), // Owner
        )?;

        msg!("Account가 생성되었습니다.");
        Ok(())
    }
}

#[derive(Accounts)]
pub struct CreateSystemAccount<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(mut)]
    pub new_account: Signer<'info>,
    pub system_program: Program<'info, System>,
}
  • CreateSystemAccount
    : payer, new_account, system_program 총 3개의 Account를 갖고 있는 Struct.
  • system_program::create_account()
    : 이 함수는 Native 프로그래밍에서 사용한 것과는 다른 solana_program을 감싸고 있는 anchor_lang이 갖고 있는 함수입니다. 내부에서 Native 프로그래밍에서 사용했던 것과 같은 solana_program::system_instruction을 호출하고 있습니다.
  • CpiContext
    : 호출할 Program과 처리에 필요한 account들을 담고 있는 CpiContext 정보입니다. Anchor에서는 CPI 호출을 위해 CpiContext를 사용합니다.

 

 

Client Program

describe("create-account", () => {
  // Configure the client to use the local cluster.
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);

  const wallet = provider.wallet as anchor.Wallet;
  const program = anchor.workspace.CreateAccount as anchor.Program<CreateAccount>;

  it("Account 생성하기", async () => {

    const newKeypair = anchor.web3.Keypair.generate();

    await program.methods.createSystemAccount()
    .accounts({
      payer: wallet.publicKey,
      newAccount: newKeypair.publicKey,
      systemProgram: anchor.web3.SystemProgram.programId
    })
    .signers([wallet.payer, newKeypair])
    .rpc();

  });
});

Anchor 프로그램은 역시 자동으로 생성된 ts 파일을 통해 편하게 Server Program과 통신할 수 있게 도와줍니다.

  • methods.createSystemAccount()
    : Server Program에서 정의한 create_system_account() 함수를 호출합니다.
  • accounts()
    : payer와 systemProgram의 pubkey를 입력해주고, 새롭게 생성할 Account의 pubkey를 추가해줍니다.
  • signers()
    : 위 Account 목록 중 SystemProgram을 제외한 payernewAccount를 서명자로 넣어줍니다.

 

 

Test

anchor test

 

 

 

References

 

GitHub - solana-developers/program-examples: A repository of Solana program examples

A repository of Solana program examples. Contribute to solana-developers/program-examples development by creating an account on GitHub.

github.com