Smart Contract Solana!
bitnomad 16 de agosto de 2024

Seu Primeiro Smart Contract Solana do Zero

Em 2024, a Solana ganhou destaque, tornando-se a quarta blockchain pública em valor total bloqueado (TVL), capturando o interesse de investidores e desenvolvedores.

No primeiro artigo desta série, exploramos os conceitos-chave da rede Solana, incluindo seu mecanismo de operação, modelo de conta e transações, estabelecendo uma base sólida para escrever contratos Solana corretos e de alto desempenho.

Neste artigo, vou te guiar na escrita de um programa Solana (ou seja, um Smart Contract Solana) para postar e exibir artigos. Isso ajudará a consolidar os conceitos do primeiro artigo e introduzir algumas funcionalidades da Solana que ainda não discutimos.

O programa e o código de teste foram publicados no GitHub.

Configurando o Ambiente

Nota: Os seguintes comandos cobrem apenas o OS Ubuntu. Alguns comandos podem não funcionar nos sistemas Windows e macOS.

👉 Você pode usar comandos alternativos ou consultar a configuração de desenvolvimento local e instalar o CLI da Solana para resolver isso.

Vamos compilar e implantar programas Solana no ambiente local. Antes de mergulhar no desenvolvimento de programas Solana, precisamos instalar alguns comandos e dependências.

Rust
Os programas Solana são predominantemente escritos na linguagem de programação Rust, então precisamos executar o seguinte comando para instalar o Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

node.js & TypeScript
O script de teste é escrito em TypeScript, então precisamos instalar o Node.js e a ferramenta TypeScript:

curl -fsSL https://deb.nodesource.com/setup_15.x | sudo -E bash -
sudo apt-get install -y nodejs npm
npm install -g typescript
npm install -g ts-node

Solana CLI
Em seguida, precisamos instalar a ferramenta CLI da Solana, que fornece comandos para tarefas como criação de carteiras e implantação de contratos:

sh -c "$(curl -sSfL https://release.solana.com/stable/install)"

Execute o seguinte comando para aplicar as alterações ao terminal atual:

source ~/.profile

Você pode executar solana --version para verificar se o CLI da Solana foi instalado com sucesso:

solana --version
solana-cli 1.17.25 (src; feat:3580551090, client)

Carteira Local
Em seguida, execute o seguinte comando para gerar uma carteira no sistema de arquivos local:

solana-keygen new

Por padrão, este comando cria um arquivo de chave privada em ~/.config/solana/id.json, que usaremos no script de teste posteriormente. Você pode usar o comando solana address para ver o endereço gerado por ele:

solana address
9FcGDHvzs8Czo4B1pQq8GPdXfdGHqNqayvh6bu4hVGfy

Devnet
O programa será implantado no devnet, então precisamos executar o seguinte comando para alternar o CLI da Solana para o devnet:

solana config set --url https://api.devnet.solana.com

Tokens SOL
Como a implantação de contratos e o envio de transações de teste não são gratuitos, precisamos solicitar alguns tokens SOL. Você pode executar solana airdrop 2 ou solicitar tokens diretamente do faucet público.

Escrevendo o Programa

O programa que vamos introduzir permite que os usuários publiquem artigos e listem todos os artigos atualmente postados. Ele lida com três tipos de instruções:

  • instrução init: Inicializa o programa criando uma conta de dados para armazenar o índice máximo atual dos artigos.
  • instrução post: Armazena um artigo postado em uma nova conta de dados.
  • instrução list: Imprime todos os artigos postados no log.

Abaixo está a estrutura do programa:

tree program/
program/
├── Cargo.lock
├── Cargo.toml
└── src
├── instructions
│ ├── init.rs
│ ├── list.rs
│ ├── mod.rs
│ └── post.rs
├── lib.rs
└── processor.rs

O arquivo Cargo.toml especifica as bibliotecas externas para o projeto:

[dependencies]
borsh = "0.10.0"
borsh-derive = "0.10.0"
solana-program = "=1.17.25"

Nota: A versão do solana-program deve corresponder à versão do CLI da Solana instalada (você pode verificar isso usando solana --version). Caso contrário, você pode encontrar erros durante a compilação.

A função de entrada do contrato é definida no processor.rs. O arquivo começa importando as dependências necessárias (vamos pular partes semelhantes em outros arquivos Rust):

Solana smart contract

Em seguida, segue a definição da função de entrada do contrato:

Solana smart contract

Na linha 10 do programa, a macro entrypoint! da biblioteca Solana especifica que a função de entrada é process_instruction. Esta função recebe três parâmetros:

program_id: O endereço implantado do programa atual.

accounts: Todas as contas envolvidas na instrução.

instruction_data: O array de bytes usado para processar instruções.

process_instruction extrai o primeiro byte de instruction_data para identificar o tipo de instrução e chama a função correspondente para lidar com ela. A seguir, veremos como essas três funções são implementadas.

init

Cada uma das três funções é definida em um arquivo com o mesmo nome no diretório program/src/instructions. Vamos começar com init.rs.

Solana smart contract

Como a instrução init não requer um array de bytes adicional, ela aceita apenas dois parâmetros: program_id e accounts.

Entre as linhas 12 e 15, o programa extrai as informações necessárias da conta sequencialmente e, em seguida, chama a função find_program_address para calcular o endereço da conta de dados usada para armazenar o índice máximo atual dos artigos, index_pda_key. Em seguida, o programa afirma que index_pda_key é igual ao endereço de index_pda.

Aqui, o endereço de index_pda é um endereço especial chamado PDAs (Program Derived Addresses – Endereços Derivados de Programas). Diferente dos endereços de contas “wallet” gerados a partir de chaves públicas, os PDAs são derivados de um array de bytes opcional, de um byte chamado “bump”, e do endereço de um programa. O array de bytes e o endereço do programa são explicitamente fornecidos pelo chamador, enquanto o “bump” é gerado automaticamente e retornado pela função find_program_address. O “bump” garante que os endereços criados dessa forma não possam colidir com endereços gerados por chave pública.

Solana Smart contract

Após confirmar que o endereço de index_pda é o esperado, usamos a instrução create_account fornecida pelo SystemProgram para criar index_pda. Na linha 22, o programa cria uma variável do tipo IndexAccountData, que registra o índice máximo atual dos artigos (inicializado em 0). Como mostrado na figura abaixo, esse tipo implementa os traits BorshSerialize e BorshDeserialize, permitindo que ele seja serializado e desserializado no formato Borsh:

Solana smart contract

Linhas 23 a 24 calculam o espaço necessário para armazenar os dados da conta e o aluguel mínimo para criar a conta.

Linhas 27 a 37 criam uma instrução create_account do SystemProgram e a invocam usando o método invoke_signed. A ação de invocar uma instrução para outro programa dentro de um programa é referida como CPI (Cross-Program Invocation – Invocação Cruzada de Programas). Na Solana, você pode usar duas funções para realizar CPI: invoke e invoke_signed.

A função invoke processa diretamente a instrução dada, e sua assinatura de função é a seguinte:

pub fn invoke(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>]
) -> Result<(), ProgramError>

A única diferença entre invoke_signed e invoke é que invoke_signed fornece uma maneira de assinar contas PDA. As contas PDA não possuem chaves pública-privada e, portanto, não podem fornecer assinaturas diretamente. invoke_signed resolve esse problema. Esta função aceita um parâmetro adicional chamado signers_seeds. Sua assinatura de função é a seguinte:

pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>],
signers_seeds: &[&[&[u8]]]
) -> Result<(), ProgramError>

De forma semelhante a como a função find_program_address funciona, invoke_signed calcula um conjunto de endereços PDA com base nos signers_seeds fornecidos e no program_id do chamador. Se account_infos contiver esses endereços PDA, eles serão marcados como signatários, como se tivessem fornecido assinaturas.

Aqui, como a instrução create_account exige que a conta sendo criada seja uma signatária, precisamos usar invoke_signed para fornecer uma assinatura para index_pda.

Solana Smart Contract

post_article

Solana Smart Contract

Em seguida, vamos analisar a implementação da instrução post. Diferente da instrução init, a instrução post aceita um artigo postado, portanto, ela possui um parâmetro adicional chamado instruction_data_inner, que armazena os dados serializados do artigo.

Como de costume, o programa começa extraindo as informações da conta necessárias para a instrução. Como geramos uma conta PDA separada para cada artigo, o programa aceita uma conta adicional, new_article_pda, na linha 23.

Semelhante à instrução init, as linhas 27 a 30 usam find_program_address para verificar se o endereço do index_pda passado está correto. Em seguida, desserializamos os dados no campo de dados para ler o índice máximo atual alocado.

As linhas 33 a 40 também verificam o endereço de new_article_pda. O programa usa a string “ARTICLE_PDA” e o índice atual para gerar um novo endereço. O índice é incrementado a cada upload de artigo, o que garante que o endereço gerado seja único.

Solana Smart Contract

Em seguida, o programa desserializa instruction_data_inner em dados de artigo, que são do tipo PostArticleData:

Solana Smart Contract

Essa estrutura possui apenas dois campos: title e content. Nas linhas 45 a 49, o programa realiza uma verificação para garantir que o artigo não seja grande demais para ser contido dentro de uma única conta.

Solana Smart Contract

Os passos seguintes também são semelhantes à instrução init: o programa primeiro calcula o espaço necessário e o aluguel para a conta, depois invoca a instrução create_account do SystemProgram para criar a conta PDA para armazenar o artigo. Finalmente, o artigo postado é serializado no campo de dados de new_article_pda, e o índice máximo atual é incrementado em um e serializado no campo de dados de index_pda.

list_articles

Solana Smart Contract

Finalmente, vamos ver a implementação da instrução list. Como essa instrução precisa listar todos os artigos, um vetor é usado na linha 15 para representar todas as contas PDA que armazenam os artigos, e o endereço da conta index_pda também é validado.

Solana Smart Contract

Depois disso, o programa verifica se o tamanho do vetor é o mesmo que o índice máximo atual e verifica a correção do endereço de cada conta.

Solana Smart Contract

Teste de Transações

Para testar nosso contrato, utilizamos um script em TypeScript localizado em client/main.ts no repositório.

Solana Smart Contract

No topo do script, importamos todas as bibliotecas necessárias e definimos três constantes globais. KEYPAIR_PATH indica o caminho para o arquivo de chave privada gerado usando solana-keygen new, PROGRAM_ID é o endereço do programa implantado que escrevemos na seção anterior, e POST_ARTICLE_SCHEMA é o objeto usado para a serialização dos artigos.

Solana Smart Contract
Solana Smart Contract

No corpo principal do script, ele primeiro chama a função loadKeyFromFile para analisar o arquivo de chave privada e obter um objeto Keypair como pagador das taxas de transação. Em seguida, usa o método findProgramAddressSync fornecido por @solana/web3.js para calcular o endereço de indexPda. Esse método utiliza o mesmo algoritmo do contrato em Rust, garantindo que ele compute o mesmo endereço com os mesmos parâmetros. O objeto de conexão especifica que usaremos o devnet para testes.

Solana Smart Contract

Em seguida, enviamos a primeira transação de inicialização. Criamos uma transação que contém uma única instrução. O campo de dados dessa instrução contém apenas um byte “0”, indicando que esta é uma invocação da instrução init. A transação é então enviada e confirmada usando sendAndConfirmTransaction.

Solana Smart Contract

A segunda transação invoca a instrução post para publicar alguns artigos. Das linhas 56 a 63, o script serializa dois artigos e calcula os endereços PDA correspondentes. Em seguida, duas instruções post são adicionadas a uma única transação para criar esses artigos.

Contrato Inteligente Solana

A transação final invoca a instrução list, e então os logs são impressos após a confirmação da transação.

Para testar o programa com o script, substitua o KEYPAIR_PATH pelo caminho para sua chave privada (caso não seja o caminho padrão). Em seguida, compile e implante o smart contract em Rust executando os seguintes comandos:

cd program
cargo build-bpf
solana program deploy ./target/deploy/hello_solana.so

Depois, copie o endereço implantado e cole-o no campo PROGRAM_ID. No diretório raiz do projeto, execute npm install para instalar as dependências e, em seguida, execute npm start para executar o script.

Depois disso, podemos usar o Solscan para visualizar os detalhes da execução das transações de teste.

Contrato Inteligente Solana

Vamos olhar diretamente para a segunda transação de publicação de artigos. Ela contém duas instruções, cada uma com o primeiro byte do Instruction Data Raw sendo 0x01. Além disso, cada instrução inclui uma instrução interna, que é uma invocação da instrução CreateAccount do System Program.

Contrato Inteligente Solana

De forma semelhante, podemos inspecionar a terceira transação para listar todos os artigos. Na seção de Program Logs, podemos verificar que os índices, títulos e conteúdos dos dois artigos são impressos.

Neste artigo, você aprendeu como configurar o ambiente de programação e execução do Solana localmente. Em seguida, detalhamos a lógica de implementação de um contrato Solana.
Finalmente, testamos as funcionalidades do contrato usando um script em TypeScript.
Com este tutorial passo a passo, acredito que você aprendeu como escrever um smart contract simples na Solana 🥳.

Recentemente a BlockSec criou a série “Solana Simplified”, que inclui artigos sobre os conceitos básicos da Solana, tutoriais sobre como escrever smart contracts e guias para analisar transações.
O objetivo é ajudar os leitores a entender o ecossistema Solana e dominar as habilidades essenciais para desenvolver projetos e realizar transações na Solana.

O próximo artigo, vai ser um guia detalhado sobre como visualizar e analisar transações da Solana usando o Phalcon Explorer (o Phalcon Explorer agora suporta Solana).

Ficou com alguma dúvida?
Participe do nosso grupo no WhatsApp e tire suas dúvidas diretamente conosco! Clique aqui para entrar.

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *