Back to Blog

Protegendo o Ecossistema Solana (2) — Chamadas Entre Programas

March 18, 2022
6 min read

0. Revisão

1. Visão Geral

No blog anterior, apresentamos como implantar e interagir com o programa. Além de invocar as instruções de um programa pelo lado do cliente, o Solana também permite que programas se chamem mutuamente por meio de um mecanismo chamado invocação entre programas. Nesta publicação, ilustramos como a invocação entre programas é utilizada. O código de teste está aqui.

2. Invocação Entre Programas

A invocação entre programas geralmente é implementada com a função invoke. Nesta seção, ilustramos o uso de invoke transferindo lamports dentro de um programa.

No Solana, existe um programa nativo chamado System Program que desempenha um papel na criação de novas contas e na transferência de lamports (SOL). Neste caso, para transferir lamports dentro de um programa (por exemplo, Programa A), o programa A invocará a função transfer() no System Program. Vamos analisar o exemplo concreto abaixo.

2.1 Revisão do Código

Da linha 3 à linha 10, as bibliotecas necessárias são importadas. Observe que a função invoke() está na biblioteca solana_program::program.

Na função process_instruction, da linha 22 à linha 30, as três contas passadas pelo cliente são extraídas. Observe que os lamports serão transferidos de from_account para to_account. Da linha 33 à linha 44, a função invoke() é invocada. Ela recebe dois argumentos. O primeiro é a instrução alvo invocada e o segundo é um conjunto de contas. Neste exemplo, a instrução alvo é transfer(), que é usada para transferir lamports. As contas incluem todas as contas exigidas pela instrução sendo invocada. Neste caso, from_account e to_account.

O programa implantado pode ser encontrado no seguinte link.

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

2.2 Transferindo os Lamports

Como mencionado, enviamos a transação para o programa implantado. O programa implantado invocará o System Program para transferir os lamports para o endereço de destino. O código acima mostra como o cliente constrói as transações. Da linha 93 à linha 101, o programId do programa implantado é extraído. Da linha 103 à linha 109, os endereços do remetente, destinatário e do System Program são gerados. Da linha 111 à linha 117, a transação é construída. Na linha 118, a transação é enviada ao cluster Solana. Observe que o único assinante nesta transação é o remetente, definido na linha 112. O remetente deve autorizar (ou seja, assinar) a transação que retira dinheiro de sua conta, e o destinatário não precisa fazê-lo (linha 113).

A transação pode ser encontrada no seguinte link.

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

3. Invoke ou Invoke_signed

No Solana, os programas podem gerar contas em tempo de execução, chamadas de Endereços Derivados de Programa (PDA). Se a instrução alvo invocada por um programa contiver contas assinadas com PDA, invoke_signed() deve ser usado em vez de invoke(). Usaremos outro exemplo para demonstrar o uso de invoke_signed(). Agora vamos primeiro analisar o código do contrato.

3.1 Revisão do Código

Neste exemplo, criamos um PDA em tempo de execução dentro de um programa (ou seja, Programa B). Para criar um PDA, o System Program deve ser invocado no programa B.

Da linha 3 à linha 10, as bibliotecas necessárias serão importadas. Observe que invoke_signed() é importado desta vez (linha 6).

Semelhante ao exemplo da Seção 2, extraímos as contas necessárias (linha 22 - linha 27), que são o System Program e o PDA. Em seguida, usamos a função find_program_address() para gerar o endereço do PDA e a semente usada para empurrar esse PDA para fora da curva ed25519 (linha 29 - linha 30). Isso garante que o endereço não tenha uma chave privada associada. Neste exemplo, o cliente e o programa usam a mesma semente (ou seja, 'You pass butter') para gerar o PDA. Portanto, a chave pública deve ser a mesma, o que é verificado da linha 31 à linha 34. Após isso, invocamos invoke_signer para emitir a instrução allocate() da linha 37 à linha 47. Diferente da função invoke(), ela recebe mais um argumento, que são as sementes usadas para criar o PDA, bem como a semente de incremento usada pela função find_program_address().

Para criar/alocar uma conta, a própria conta deve fornecer uma assinatura, e neste exemplo o PDA é o assinante. Para assinar o PDA, invoke_signed é usado. Especificamente, o Solana usará as sementes e o programId do chamador (ou seja, Programa B) para recriar o PDA e compará-lo com as contas fornecidas (o segundo argumento). Se forem iguais, o PDA será assinado.

O programa implantado pode ser verificado abaixo.

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

3.2 Criando um PDA

Vamos analisar o script do cliente para o segundo exemplo.

Na linha 104, extraímos o PDA e a semente de incremento por meio da função findProgramAddress(). Da linha 112 à linha 120, a transação será criada e enviada. Observe que a propriedade (isSigner) do PDA é 'false' (linha 113) aqui, pois não pode ser assinado pelo cliente. Em tempo de execução, o Programa B assinará o PDA e invocará a instrução allocate no programa System Program.

Você pode encontrar o PDA alocado na seguinte transação.

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

3.3 Podemos Usar Invoke()?

Para demonstrar que invoke não pode ser usado aqui, usamos outro programa (ou seja, Programa C) para invocar a instrução allocation.

A única mudança que fizemos foi substituir a função invoke_signed() pela função invoke(). Observamos que a conta não pôde ser criada.

O erro mostra que a invocação entre programas precisa de um assinante para ser executada com sucesso. Ou seja, a função invoke_signed() é necessária.

4. Conclusão

Neste artigo, apresentamos como implementar a invocação entre programas por meio da função invoke(). Também usamos diferentes exemplos para ilustrar as diferenças entre invoke() e invoke_signed(). Fique ligado, pois mais artigos desta série serão publicados.

Leia outros artigos desta série: