SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
21.11.2024

Lição 093: Padrões de Atualização de Contratos Inteligentes

No mundo das aplicações descentralizadas (dApps) e do desenvolvimento em blockchain, a capacidade de atualizar contratos inteligentes é crucial para melhorar funcionalidades, corrigir bugs e se adaptar a novas exigências. Nesta lição, exploraremos padrões comuns de atualização para contratos inteligentes, especificamente no contexto da blockchain Solana utilizando Rust.

Por Que a Atualização é Importante

Os contratos inteligentes são imutáveis uma vez implementados. Embora isso ofereça segurança, representa desafios quando é necessário fazer alterações. A atualizabilidade permite que os desenvolvedores:

  • Corrijam bugs e vulnerabilidades de segurança.
  • Adicionem novas funcionalidades e melhorias.
  • Se adaptem a requisitos de negócios em mudança.

Padrões Comuns de Atualização

  1. Padrão Proxy

    O Padrão Proxy envolve a implementação de um contrato proxy que encaminha chamadas para um contrato de implementação. Dessa forma, você pode atualizar o contrato de implementação sem alterar o endereço do proxy.

    Exemplo de Padrão Proxy na Solana

    Aqui está uma implementação simplificada do Padrão Proxy utilizando Solana e Rust.

    use anchor_lang::prelude::*;
    
    declare_id!("SeuIDdeProgramaAqui");
    
    #[program]
    pub mod meu_contrato {
       use super::*;
    
       pub fn inicializar(ctx: Context<Inicializar>, implementacao: Pubkey) -> ProgramResult {
           let proxy = &mut ctx.accounts.proxy;
           proxy.implementacao = implementacao;
           Ok(())
       }
    
       pub fn atualizar(ctx: Context<Atualizar>, nova_implementacao: Pubkey) -> ProgramResult {
           let proxy = &mut ctx.accounts.proxy;
           proxy.implementacao = nova_implementacao;
           Ok(())
       }
    
       pub fn encaminhar(ctx: Context<Encaminhar>, instrucao: Vec<u8>) -> ProgramResult {
           let proxy = &ctx.accounts.proxy;
           let program_id = proxy.implementacao;
           let contas = &ctx.remaining_accounts;
    
           // Encaminhar a chamada para o contrato de implementação
           let instrucao = Instruction {
               program_id,
               accounts: contas.to_vec(),
               data: instrucao,
           };
    
           let resultado = invoke(&instrucao, ctx.accounts.remetente.to_account_info().key)?;
           Ok(resultado)
       }
    }
    
    #[account]
    pub struct Proxy {
       pub implementacao: Pubkey,
    }
    
    #[derive(Accounts)]
    pub struct Inicializar<'info> {
       #[account(init)]
       pub proxy: Account<'info, Proxy>,
       pub pagador: Signer<'info>,
       pub sistema_programa: Program<'info, System>,
    }
    
    #[derive(Accounts)]
    pub struct Atualizar<'info> {
       #[account(mut)]
       pub proxy: Account<'info, Proxy>,
    }
    
    #[derive(Accounts)]
    pub struct Encaminhar<'info> {
       #[account(mut)]
       pub proxy: Account<'info, Proxy>,
       pub remetente: Signer<'info>,
       pub contas_remanescentes: Vec<AccountInfo<'info>>,
    }

    Neste exemplo, definimos uma conta Proxy que contém o endereço do contrato de implementação. A função inicializar configura o proxy, enquanto a função atualizar permite a alteração do endereço da implementação.

  2. Padrão de Armazenamento Eterno

    Outro padrão é o Padrão de Armazenamento Eterno, onde todos os dados de estado são armazenados em um contrato de armazenamento separado. O contrato principal pode ser atualizado, mas o armazenamento permanece inalterado.

    Exemplo de Padrão de Armazenamento Eterno

    #[program]
    pub mod meu_contrato {
       use super::*;
    
       pub fn definir_valor(ctx: Context<DefinirValor>, valor: u64) -> ProgramResult {
           let armazenamento = &mut ctx.accounts.armazenamento;
           armazenamento.valor = valor;
           Ok(())
       }
    
       pub fn obter_valor(ctx: Context<ObterValor>) -> ProgramResult {
           let armazenamento = &ctx.accounts.armazenamento;
           msg!("Valor armazenado: {}", armazenamento.valor);
           Ok(())
       }
    }
    
    #[account]
    pub struct Armazenamento {
       pub valor: u64,
    }
    
    #[derive(Accounts)]
    pub struct DefinirValor<'info> {
       #[account(mut)]
       pub armazenamento: Account<'info, Armazenamento>,
    }
    
    #[derive(Accounts)]
    pub struct ObterValor<'info> {
       pub armazenamento: Account<'info, Armazenamento>,
    }

    Neste exemplo, usamos uma conta Armazenamento separada para manter o estado. O contrato principal pode ser atualizado enquanto o Armazenamento permanece intacto, preservando o estado durante as atualizações.

  3. Padrão Diamond

    O Padrão Diamond permite que vários contratos sejam agregados sob um único endereço, possibilitando extensibilidade e modularidade. Cada função pode ser implementada em contratos separados, facilitando as atualizações.

    Exemplo de Padrão Diamond

    use std::collections::HashMap;
    
    #[program]
    pub mod meu_diamante {
       use super::*;
    
       pub fn invocar_funcao(ctx: Context<InvocarFuncao>, seletor: String, args: Vec<u8>) -> ProgramResult {
           let diamante = &ctx.accounts.diamante;
           if let Some(contra) = diamante.funcoes.get(&seletor) {
               // Lógica de invocação real aqui
               msg!("Invocando {} no contrato {:?}", seletor, contra);
           }
           Ok(())
       }
    }
    
    #[account]
    pub struct Diamante {
       pub funcoes: HashMap<String, Pubkey>,
    }
    
    #[derive(Accounts)]
    pub struct InvocarFuncao<'info> {
       #[account(mut)]
       pub diamante: Account<'info, Diamante>,
    }

    Neste exemplo, a conta Diamante mantém um mapeamento de seletores de função para endereços de contratos. Isso permite que os desenvolvedores gerenciem e atualizem facilmente as funções individuais representadas por seus respectivos contratos.

Conclusão

A atualizabilidade de contratos inteligentes é um aspecto crítico do desenvolvimento de aplicações descentralizadas. Cada padrão—Proxy, Armazenamento Eterno e Diamond—oferece benefícios únicos dependendo das necessidades do seu projeto. Compreender esses padrões permite que os desenvolvedores garantam que seus contratos inteligentes permaneçam adaptáveis e manuteníveis no cenário em rápida evolução da blockchain.

À medida que você explora esses padrões mais a fundo, considere as compensações em segurança, complexidade e governança envolvidas na escolha da melhor abordagem para sua aplicação específica. Boa codificação!

Did you like this article? Rate it from 1 to 5:

Thank you for voting!