Ir para o conteúdo

Adicionar um módulo personalizado

Introdução

Ao fornecer uma biblioteca abrangente de módulos pré-construídos que abordam muitos requisitos comuns, a estrutura simplifica enormemente o processo de construção de um blockchain e acelera a implantação e evolução em uma rede com tecnologia Tanssi. No entanto, abordar um caso de uso inovador geralmente exige um esforço de desenvolvimento para atender totalmente aos requisitos e, no Substrate, adicionar lógica personalizada se traduz em escrever e integrar módulos de tempo de execução.

O exemplo apresentado no artigo Modularidade mostra um módulo de loteria simples que expõe duas transações:

  • Buy tickets - esta função gerencia a entrada de um usuário na loteria. Essencialmente, ela verifica se o participante tem saldo suficiente, não está participando e cuida da transferência de fundos para registrar o usuário na loteria
  • Award prize - esta função que lida com um usuário que entra na loteria. Em alto nível, ela busca um número pseudo-aleatório para obter um vencedor e lida com a distribuição do prêmio

A implementação dessas transações também usa armazenamento, emite eventos, define erros personalizados e depende de outros módulos para lidar com a moeda (para cobrar pelos bilhetes e transferir o valor total para o vencedor) e aleatorizar a seleção do vencedor.

Neste artigo, as seguintes etapas, necessárias para construir e adicionar o módulo de exemplo ao tempo de execução, serão abordadas:

  1. Criar os arquivos do módulo de loteria (pacote).
  2. Configurar as dependências do módulo.
  3. Adicionar lógica personalizada.
  4. Configurar o tempo de execução com o novo módulo.

É importante ressaltar que nenhum dos códigos apresentados neste artigo se destina ao uso em produção.

Verificando Pré-requisitos

Para seguir as etapas deste guia, você precisará ter o seguinte:

Você pode ler mais sobre como instalar o Rust e o Cargo no artigo de pré-requisitos.

Criando os arquivos do módulo de loteria

Antes de iniciar seu processo de codificação, é essencial criar os arquivos que contêm sua lógica. Os módulos Substrate são abstratos e destinados ao reaproveitamento em diferentes tempos de execução com várias personalizações. Para conseguir isso, você usará o Cargo, o gerenciador de pacotes do Rust, para criar o módulo como um novo pacote.

Como mencionado na seção de pré-requisitos, a primeira etapa é clonar o repositório Tanssi e, na pasta raiz, navegar até pallets, onde o módulo será criado.

cd container-chains/pallets

Em seguida, crie o pacote do módulo com o Cargo:

cargo new lottery-example

Por padrão, o Cargo cria o novo pacote em uma pasta com o nome fornecido (lottery-example, neste caso), contendo um arquivo de manifesto, Cargo.toml e uma pasta src com um arquivo main.rs. Para respeitar a convenção de nomenclatura usada no Substrate, o arquivo main.rs é renomeado para lib.rs:

mv lottery-example/src/main.rs lottery-example/src/lib.rs

Depois de executar todos os comandos, o módulo é criado e está pronto para conter a lógica personalizada que você adicionará nas seções a seguir.

Configurar as dependências do módulo

Como o módulo funciona como um pacote independente, ele tem seu próprio arquivo Cargo.toml, onde você deve especificar os atributos e dependências do módulo.

Por exemplo, você pode usar atributos para especificar detalhes como o nome do módulo, versão, autores e outras informações relevantes. Por exemplo, no módulo lottery-example, o arquivo Cargo.toml pode ser configurado da seguinte forma:

#[pallet::storage]
#[pallet::getter(fn get_participants)]
pub(super) type Participants<T: Config> = StorageValue<
    _,
    BoundedVec<T::AccountId, T::MaxParticipants>,
    OptionQuery
>;

Este arquivo também define as dependências do módulo, como a funcionalidade principal que permite a integração perfeita com o tempo de execução e outros módulos, acesso ao armazenamento, emissão de eventos e muito mais.

O exemplo completo do arquivo Cargo.toml define, além dos atributos, as dependências exigidas pelo Substrate:

Ver o arquivo Cargo.toml completo
[package]
name = "module-lottery-example"
version = "4.0.0-dev"
description = "Simple module example"
authors = [""]
homepage = ""
edition = "2021"
publish = false

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [
    "derive",
] }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
frame-benchmarking = { 
    version = "4.0.0-dev", 
    default-features = false, 
    optional = true, 
    git = "https://github.com/paritytech/substrate.git", 
    branch = "polkadot-v1.0.0" 
}
frame-support = { 
    version = "4.0.0-dev", 
    default-features = false, 
    git = "https://github.com/paritytech/substrate.git", 
    branch = "polkadot-v1.0.0" 
}
frame-system = { 
    version = "4.0.0-dev", 
    default-features = false, 
    git = "https://github.com/paritytech/substrate.git", 
    branch = "polkadot-v1.0.0" 
}

[dev-dependencies]
sp-core = { version = "21.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
sp-io = { version = "23.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
sp-runtime = { version = "24.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }

[features]
default = ["std"]
std = [
    "codec/std",
    "frame-benchmarking?/std",
    "frame-support/std",
    "frame-system/std",
    "scale-info/std",
]
runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"]
try-runtime = ["frame-support/try-runtime"]

Adicionando lógica personalizada

Conforme apresentado na seção módulo personalizado do artigo sobre modularidade, a criação de um módulo envolve a implementação das seguintes macros de atributo, das quais as três primeiras são obrigatórias:

  • #[frame_support::pallet] - atributo de entrada que marca o módulo como utilizável no runtime
  • #[pallet::pallet] - aplicado a uma estrutura usada para recuperar informações do módulo com facilidade
  • #[pallet::config] - atributo obrigatório para definir a configuração dos tipos de dados do módulo
  • #[pallet::call] - macro usada para definir funções expostas como transações, permitindo que sejam despachadas para o runtime; aqui os desenvolvedores adicionam transações e lógica personalizadas
  • #[pallet::error] - como transações podem falhar (por exemplo, fundos insuficientes) e, por segurança, um módulo não deve gerar exceções, todos os possíveis erros devem ser identificados e listados em um enum para serem retornados em uma execução malsucedida
  • #[pallet::event] - eventos podem ser definidos e usados para fornecer mais informações ao usuário
  • #[pallet::storage] - macro usada para definir elementos que serão persistidos em storage; como recursos são escassos em uma blockchain, deve ser usada com parcimônia para armazenar apenas informações essenciais

Implementando a estrutura básica do módulo

As duas primeiras macros obrigatórias, #[frame_support::pallet] e #[pallet::pallet], fornecem a estrutura básica do módulo e são necessárias para habilitar o módulo a ser usado em um tempo de execução Substrate.

A seguir, é apresentada a estrutura geral de um módulo Substrate personalizado.

#[frame_support::pallet(dev_mode)]
pub mod pallet {
    ...
    #[pallet::pallet]
    pub struct Pallet<T>(_); 

       // Toda a lógica vai aqui
}

A próxima etapa seria adicionar a terceira macro obrigatória (#[pallet::config]) e toda a lógica personalizada, conforme mostrado nas seções a seguir.

Implementando a configuração do módulo

Para tornar os módulos altamente adaptáveis, sua configuração é abstrata o suficiente para permitir que sejam adaptados aos requisitos específicos do caso de uso que o tempo de execução implementa.

A implementação da macro #[pallet::config] é obrigatória e define a dependência do módulo em outros módulos e os tipos e valores especificados pelas configurações específicas do tempo de execução.

No módulo lottery-example personalizado que você está construindo, o módulo depende de outros módulos para gerenciar a moeda e a função aleatória para selecionar o vencedor. O módulo também lê e usa o preço do bilhete e o número máximo de participantes diretamente das configurações do tempo de execução. Consequentemente, a configuração precisa incluir essas dependências:

  • Events - o módulo depende da definição de um evento do tempo de execução para poder emiti-los
  • Currency - o módulo lottery-example precisa poder transferir fundos, portanto, precisa da definição do sistema monetário do tempo de execução
  • Randomness - este módulo é usado para selecionar de forma justa o vencedor do prêmio da lista de participantes. Ele gera os números aleatórios usando os hashes de bloco anteriores e o número do bloco atual como semente
  • Ticket coste - o preço a ser cobrado dos compradores que participam da loteria
  • Maximum number of participants - o limite máximo de participantes permitido em cada rodada da loteria
  • Module Id - o identificador exclusivo do módulo é necessário para acessar a conta do módulo para manter os fundos dos participantes até serem transferidos para o vencedor

A implementação da configuração descrita para este exemplo é mostrada no seguinte trecho de código:

#[pallet::config]
pub trait Config: frame_system::Config {

    // Definição do evento
    type RuntimeEvent: From<Event<Self>>
        + IsType<<Self as frame_system::Config>::RuntimeEvent>;

    // Moeda
    type Currency: Currency<Self::AccountId>;

    // Aleatoriedade
    type MyRandomness: Randomness<Self::Hash, BlockNumberFor<Self>>;

    // Custo do bilhete
    #[pallet::constant]
    type TicketCost: Get<BalanceOf<Self>>;

    // Número máximo de participantes
    #[pallet::constant]
    type MaxParticipants: Get<u32>;

    // ID do módulo
    #[pallet::constant]
    type PalletId: Get<PalletId>;
}

Esta definição abstrata de dependências é crucial para evitar o acoplamento a um caso de uso específico e para permitir que os módulos sirvam como blocos de construção básicos para as redes Substrate.

Implementando Transações

Chamadas representam o comportamento que um tempo de execução expõe na forma de transações que podem ser despachadas para processamento, expondo a lógica personalizada adicionada ao módulo.

Cada chamada está incluída na macro #[pallet::call] e apresenta os seguintes elementos:

  • Call Index - é um identificador exclusivo obrigatório para cada chamada despachável
  • Weight - é uma medida do esforço computacional que uma extrínseca leva ao ser processada. Mais sobre pesos está na documentação do Polkadot
  • Origin - identifica a conta de assinatura que está fazendo a chamada
  • Result - o valor de retorno da chamada, que pode ser um Erro se alguma coisa der errado

A seguinte trecho apresenta a estrutura geral da implementação da macro mencionada e os elementos de chamada:

#[pallet::call]
impl<T: Config> Pallet<T> {

    #[pallet::call_index(0)]
    #[pallet::weight(0)]
    pub fn one_call(origin: OriginFor<T>) -> DispatchResult { }

    #[pallet::call_index(1)]
    #[pallet::weight(0)]
    pub fn another_call(origin: OriginFor<T>) -> DispatchResult { }

    // Outras chamadas
}

Neste módulo lottery-example, definimos duas chamadas com a seguinte lógica:

#[pallet::call]
impl<T: Config> Pallet<T> {

    #[pallet::call_index(0)]    
    #[pallet::weight(0)]
    pub fn buy_ticket(origin: OriginFor<T>) -> DispatchResult {

        // 1. Valida a assinatura de origem
        // 2. Verifica se o usuário tem saldo suficiente para pagar o preço do bilhete
        // 3. Verifica se o usuário já não está participando
        // 4. Adiciona o usuário como um novo participante do prêmio
        // 5. Transfere o custo do bilhete para a conta do módulo, para ser mantido até ser transferido para o vencedor
        // 6. Notifica o evento
    }

    #[pallet::call_index(1)]
    #[pallet::weight(0)]
    pub fn award_prize(origin: OriginFor<T>) -> DispatchResult {
        // 1. Valida a assinatura de origem
        // 2. Obtém um número aleatório do módulo de aleatoriedade
        // 3. Seleciona o vencedor da lista de participantes
        // 4. Transfere o prêmio total para a conta do vencedor
        // 5. Redefine a lista de participantes e prepara-se para outra rodada da loteria
    }
}

Essas chamadas também emitem eventos para manter o usuário informado e podem retornar erros caso alguma das validações dê errado.

Aqui está a implementação completa das chamadas com a lógica da loteria personalizada:

Ver o código de chamadas completo
#[pallet::call]
impl<T: Config> Pallet<T> {

    #[pallet::call_index(0)]
    #[pallet::weight(0)]
    pub fn buy_ticket(origin: OriginFor<T>) -> DispatchResult {

        // 1. Validates the origin signature
        let buyer = ensure_signed(origin)?;

        // 2. Checks that the user has enough balance to afford the ticket price
        ensure!(
            T::Currency::free_balance(&buyer) >= T::TicketCost::get(),
            Error::<T>::NotEnoughCurrency
        );

        // 3. Checks that the user is not already participating
        if let Some(participants) = Self::get_participants() {
            ensure!(
                !participants.contains(&buyer),
                Error::<T>::AccountAlreadyParticipating
            );
        }

        // 4. Adds the user as a new participant for the prize
        match Self::get_participants() {
            Some(mut participants) => { 
                ensure!(
                    participants.try_push(buyer.clone()).is_ok(), 
                    Error::<T>::CanNotAddParticipant
                );
                Participants::<T>::set(Some(participants));
            }, 
            None => {
                let mut participants = BoundedVec::new();
                ensure!(
                    participants.try_push(buyer.clone()).is_ok(), 
                    Error::<T>::CanNotAddParticipant
                );
                Participants::<T>::set(Some(participants));
            }
        };

        // 5. Transfers the ticket cost to the module's account
        // to be hold until transferred to the winner
        T::Currency::transfer(
            &buyer, 
            &Self::get_pallet_account(), 
            T::TicketCost::get(), 
            ExistenceRequirement::KeepAlive)?;

        // 6. Notify the event
        Self::deposit_event(Event::TicketBought { who: buyer });
        Ok(())
    }

    #[pallet::call_index(1)]
    #[pallet::weight(0)]
    pub fn award_prize(origin: OriginFor<T>) -> DispatchResult {

        // 1. Validates the origin signature
        let _who = ensure_root(origin)?;

        match Self::get_participants() {
            Some(participants) => { 

                // 2. Gets a random number from the randomness module
                let nonce = Self::get_and_increment_nonce();
                let (random_seed, _) = T::MyRandomness::random(&nonce);
                let random_number = <u32>::decode(&mut random_seed.as_ref())
                    .expect("secure hashes should always be bigger than u32; qed");

                // 3. Selects the winner from the participants lit
                let winner_index = random_number as usize % participants.len();
                let winner = participants.as_slice().get(winner_index).unwrap();

                // 4. Transfers the total prize to the winner's account
                let prize = T::Currency::free_balance(&Self::get_pallet_account());
                T::Currency::transfer(
                    &Self::get_pallet_account(), 
                    &winner, 
                    prize, 
                    ExistenceRequirement::AllowDeath)?;

                // 5. Resets the participants list, and gets ready for another lottery round
                Participants::<T>::kill();

                // 6. Notify the event
                Self::deposit_event(Event::PrizeAwarded { winner: winner.clone() } );
            }, 
            None => {
                // Notify the event (No participants)
                Self::deposit_event(Event::ThereAreNoParticipants);
            }
        };

        Ok(())
    }
}

Implementando erros personalizados

A macro #[pallet::error] é usada para anotar uma enumeração de erros potenciais que poderiam ocorrer durante a execução. É crucial para a segurança garantir que todas as situações de erro sejam tratadas com elegância, sem causar a falha do tempo de execução.

O exemplo a seguir desta implementação de macro mostra os erros que podem ocorrer no módulo da loteria:

// Erros informam aos usuários que algo deu errado.
#[pallet::error]
pub enum `Error`<T> {
    NotEnoughCurrency,
    AccountAlreadyParticipating,
    CanNotAddParticipant,
}

Implementando eventos

A macro #[pallet::event] é aplicada a uma enumeração de eventos para informar o usuário sobre quaisquer alterações no estado ou ações importantes que ocorreram durante a execução no tempo de execução.

Como exemplo, para o módulo lottery-example, esta macro pode ser configurada com os seguintes eventos:

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {

    // Evento emitido quando um bilhete é comprado
    TicketBought { who: T::AccountId },
    // Evento emitido quando o prêmio é concedido
    PrizeAwarded { winner: T::AccountId },
    // Evento emitido quando não há participantes  
    ThereAreNoParticipants,
    }

Implementando o armazenamento para persistência de estado

A macro #[pallet::storage] inicializa uma estrutura de armazenamento de tempo de execução. No ambiente altamente restrito de blockchains, decidir o que armazenar e qual estrutura usar pode ser fundamental em termos de desempenho. Mais sobre esse tópico é abordado na documentação Substrate.

Neste exemplo, o módulo lottery-example precisa de uma estrutura de armazenamento de valor básica para persistir a lista de participantes em um vetor de capacidade limitada (BoundedVec). Isso pode ser inicializado da seguinte forma:

#[pallet::storage]
#[pallet::getter(fn get_participants)]
pub(super) type Participants<T: Config> = StorageValue<
    _,
    BoundedVec<T::AccountId, T::MaxParticipants>,
    OptionQuery
>;

O módulo completo

Para juntar todas as peças, após implementar todas as macros necessárias e adicionar a lógica personalizada, o módulo agora está completo e pronto para ser usado no tempo de execução.

Ver o arquivo do módulo completo
#![cfg_attr(not(feature = "std"), no_std)]

/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;

#[frame_support::pallet(dev_mode)]
pub mod pallet {

    use super::*;
    use frame_support::pallet_prelude::{*, ValueQuery, OptionQuery};
    use frame_system::pallet_prelude::*;
    use scale_info::prelude::vec::Vec;

    use frame_support::
    {
        sp_runtime::traits::AccountIdConversion,
        traits:: {
            Currency, ExistenceRequirement, Randomness
        },
        PalletId,
    };

    type BalanceOf<T> = 
        <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

    #[pallet::pallet]
    pub struct Pallet<T>(_);

    /// Configure the module by specifying the parameters and types on which it depends.
    #[pallet::config]
    pub trait Config: frame_system::Config {

        // Event definition
        type RuntimeEvent: From<Event<Self>> 
            + IsType<<Self as frame_system::Config>::RuntimeEvent>;

        // Currency 
        type Currency: Currency<Self::AccountId>;

        // Randomness
        type MyRandomness: Randomness<Self::Hash, BlockNumberFor<Self>>;

        // Ticket cost
        #[pallet::constant]
        type TicketCost: Get<BalanceOf<Self>>;

        // Maximum number of participants
        #[pallet::constant]
        type MaxParticipants: Get<u32>;

        // Module Id
        #[pallet::constant]
        type PalletId: Get<PalletId>;
    }

    // The pallet's runtime storage items.
    #[pallet::storage]
    #[pallet::getter(fn get_participants)]
    pub(super) type Participants<T: Config> = StorageValue<
        _,
        BoundedVec<T::AccountId, T::MaxParticipants>,
        OptionQuery
    >;

    #[pallet::storage]
    #[pallet::getter(fn get_nonce)]
    pub(super) type Nonce<T: Config> = StorageValue<
        _,
        u64,
        ValueQuery
    >;

    // Pallets use events to inform users when important changes are made.
    // https://docs.substrate.io/main-docs/build/events-errors/
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        /// Event emitted when a ticket is bought
        TicketBought { who: T::AccountId },
        /// Event emitted when the prize is awarded
        PrizeAwarded { winner: T::AccountId },
        /// Event emitted when the prize is to be awarded, but there are no participants
        ThereAreNoParticipants,
    }

    // Errors inform users that something went wrong
    #[pallet::error]
    pub enum Error<T> {
        NotEnoughCurrency,
        AccountAlreadyParticipating,
        CanNotAddParticipant,
    }

    #[pallet::call]
    impl<T: Config> Pallet<T> {

        #[pallet::call_index(0)]
        #[pallet::weight(0)]
        pub fn buy_ticket(origin: OriginFor<T>) -> DispatchResult {

            // 1. Validates the origin signature
            let buyer = ensure_signed(origin)?;

            // 2. Checks that the user has enough balance to afford the ticket price
            ensure!(
                T::Currency::free_balance(&buyer) >= T::TicketCost::get(),
                Error::<T>::NotEnoughCurrency
            );

            // 3. Checks that the user is not already participating
            if let Some(participants) = Self::get_participants() {
                ensure!(
                    !participants.contains(&buyer),
                    Error::<T>::AccountAlreadyParticipating
                );
            }

            // 4. Adds the user as a new participant for the prize
            match Self::get_participants() {
                Some(mut participants) => { 
                    ensure!(
                        participants.try_push(buyer.clone()).is_ok(), 
                        Error::<T>::CanNotAddParticipant
                    );
                    Participants::<T>::set(Some(participants));
                }, 
                None => {
                    let mut participants = BoundedVec::new();
                    ensure!(
                        participants.try_push(buyer.clone()).is_ok(), 
                        Error::<T>::CanNotAddParticipant
                    );
                    Participants::<T>::set(Some(participants));
                }
            };

            // 5. Transfers the ticket cost to the module's account
            // to be hold until transferred to the winner
            T::Currency::transfer(
                &buyer, 
                &Self::get_pallet_account(), 
                T::TicketCost::get(), 
                ExistenceRequirement::KeepAlive)?;

            // 6. Notify the event
            Self::deposit_event(Event::TicketBought { who: buyer });
            Ok(())
        }

        #[pallet::call_index(1)]
        #[pallet::weight(0)]
        pub fn award_prize(origin: OriginFor<T>) -> DispatchResult {

            // 1. Validates the origin signature
            let _who = ensure_root(origin)?;

            match Self::get_participants() {
                Some(participants) => { 

                    // 2. Gets a random number from the randomness module
                    let nonce = Self::get_and_increment_nonce();
                    let (random_seed, _) = T::MyRandomness::random(&nonce);
                    let random_number = <u32>::decode(&mut random_seed.as_ref())
                        .expect("secure hashes should always be bigger than u32; qed");

                    // 3. Selects the winner from the participants lit
                    let winner_index = random_number as usize % participants.len();
                    let winner = participants.as_slice().get(winner_index).unwrap();

                    // 4. Transfers the total prize to the winner's account
                    let prize = T::Currency::free_balance(&Self::get_pallet_account());
                    T::Currency::transfer(
                        &Self::get_pallet_account(), 
                        &winner, 
                        prize, 
                        ExistenceRequirement::AllowDeath)?;

                    // 5. Resets the participants list, and gets ready for another lottery round
                    Participants::<T>::kill();

                    // 6. Notify the event
                    Self::deposit_event(Event::PrizeAwarded { winner: winner.clone() } );
                }, 
                None => {
                    // Notify the event (No participants)
                    Self::deposit_event(Event::ThereAreNoParticipants);
                }
            };

            Ok(())
        }
    }

    impl<T: Config> Pallet<T> {

        fn get_pallet_account() -> T::AccountId {
            T::PalletId::get().into_account_truncating()
        }

        fn get_and_increment_nonce() -> Vec<u8> {
            let nonce = Nonce::<T>::get();
            Nonce::<T>::put(nonce.wrapping_add(1));
            nonce.encode()
        }
    }
}

Configurar o tempo de execução

Finalmente, com o módulo finalizado, ele pode ser incluído no tempo de execução. Ao fazer isso, as transações buy_tickets e award_prize serão chamáveis pelos usuários. Isso também significa que a API Polkadot.js será decorada com este módulo e todas as chamadas disponíveis que ele contém.\n\nPara configurar o tempo de execução, abra o arquivo lib.rs, que contém a definição para o tempo de execução do modelo incluído e está localizado (no caso de usar o compatível com EVM) na pasta:

*/container-chains/templates/frontier/runtime/src/

Para adicionar o módulo da loteria, configure os módulos da seguinte forma:

// Adicione a configuração para o módulo de aleatoriedade. Nenhum parâmetro necessário.
impl pallet_insecure_randomness_collective_flip::Config for Runtime {
}

// // ID de módulo personalizado
 parameter_types! {
    pub const PalletId: PalletId = PalletId(*b"loex5678");
}

// Adicione a configuração para o módulo da loteria
impl pallet_lottery_example::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type Currency = Balances;
    type TicketCost = ConstU128<1000000000000000>;
    type PalletId = PalletId;
    type MaxParticipants = ConstU32<500>;
    type MyRandomness = RandomCollectiveFlip;
    }
Com os módulos configurados, adicione a macro construct_runtime! (que define os módulos que serão incluídos ao construir o tempo de execução) e os módulos de aleatoriedade e loteria.

construct_runtime!(
    pub struct Runtime {
        ...
        // Inclua a lógica personalizada do pallet-template no tempo de execução.
        RandomCollectiveFlip: pallet_insecure_randomness_collective_flip,
        Lottery: pallet_lottery_example,
        ...
}
)

Com tudo definido, a rede agora tem suporte para uma implementação básica de uma loteria.

As informações apresentadas aqui foram fornecidas por terceiros e estão disponíveis apenas para fins informativos gerais. A Tanssi não endossa nenhum projeto listado e descrito no Site de Documentação da Tanssi (https://docs.tanssi.network/). A Tanssi Foundation não garante a precisão, integridade ou utilidade dessas informações. Qualquer confiança depositada nelas é de sua exclusiva responsabilidade. A Tanssi Foundation se exime de toda responsabilidade decorrente de qualquer confiança que você ou qualquer outra pessoa possa ter em qualquer parte deste conteúdo. Todas as declarações e/ou opiniões expressas nesses materiais são de responsabilidade exclusiva da pessoa ou entidade que as fornece e não representam necessariamente a opinião da Tanssi Foundation. As informações aqui não devem ser interpretadas como aconselhamento profissional ou financeiro de qualquer tipo. Sempre busque orientação de um profissional devidamente qualificado em relação a qualquer assunto ou circunstância em particular. As informações aqui podem conter links ou integração com outros sites operados ou conteúdo fornecido por terceiros, e tais sites podem apontar para este site. A Tanssi Foundation não tem controle sobre esses sites ou seu conteúdo e não terá responsabilidade decorrente ou relacionada a eles. A existência de qualquer link não constitui endosso desses sites, de seu conteúdo ou de seus operadores. Esses links são fornecidos apenas para sua conveniência, e você isenta e exonera a Tanssi Foundation de qualquer responsabilidade decorrente do uso dessas informações ou das informações fornecidas por qualquer site ou serviço de terceiros.
Última atualização: 9 de dezembro de 2025
| Criada: 9 de dezembro de 2025