WEB3DEV

Cover image for Como construir Pallets personalizados com Substrate
Adriano P. Araujo
Adriano P. Araujo

Posted on

Como construir Pallets personalizados com Substrate

Como construir Pallets personalizados com Substrate

Aprenda a criar um pallet personalizado usando o framework de desenvolvimento Substrate

Introdução

Neste tutorial, construiremos um pallet personalizado usando o  framework de desenvolvimento Substrate e o FRAME v1. Começaremos com uma visão geral para entender os pallets e a framework FRAME. Em seguida, veremos o modelo de pallet Substrate, usando-o para criar nosso pallet personalizado, gravar casos de teste, publicar usando git e finalmente adicionar o pallet publicado ao nosso runtime.

Pré-requisitos

Este tutorial pressupõe que o leitor esteja familiarizado com a linguagem de programação Rust e tenha um entendimento básico do framework do Substrate.

Pallets

Os pallets são módulos de tempo de execução específicos do domínio, que nos permitem ter um design modular. Podemos adicionar vários pallets ao nosso tempo de execução.

Para referência, alguns dos pallets pré-construídos mais populares podem ser encontrados no site para desenvolvedores do Substrate.

FRAME v1

FRAME é o framework usado para desenvolver pallets, ele vem com um conjunto de bibliotecas e módulos iniciais. Abaixo, o esqueleto de um pallet baseado no FRAME:

1.Importações e dependências

O pallet suporta o uso de qualquer biblioteca Rust que compile com a flag  no_std.


use support::{decl_module, decl_event, decl_storage, ...}

Enter fullscreen mode Exit fullscreen mode

2.Runtime Config Trait

Todos os tipos e constantes de tempo de execução entrarão aqui. Se o pallet depender de outros pallets, suas características de configuração deverão ser adicionadas à lista de características herdadas.


pub trait Config: frame_system::Config { ... }

Enter fullscreen mode Exit fullscreen mode

3.Eventos de tempo de execução

Os eventos são um meio simples de relatar eventos específicos que ocorreram quando usuários, dApps e / ou exploradores de blocos considerarem interessantes e, que de outra forma, seriam difíceis de detectar.

Leia mais sobre Eventos de tempo de execução


decl_event! { ... }

Enter fullscreen mode Exit fullscreen mode

4.Armazenamento em tempo de execução

Isso permite o uso seguro do banco de dados de armazenamento Substrate, para que você possa manter as coisas entre os blocos.

Leia mais sobre Armazenamento em tempo de execução


decl_storage! { ... } 

Enter fullscreen mode Exit fullscreen mode

5.Erros de tempo de execução

Esse é um enum que nos permite definir tipos de erros personalizados que podem ser chamados no módulo de tempo de execução.


decl_error! { ... }

Enter fullscreen mode Exit fullscreen mode

6.Módulo de tempo de execução

Isso define o Module estrutura que é finalmente exportada deste pallet. Ele define as funções exigíveis que este pallet expõe e orquestra as ações que esse pallet realiza durante a execução do bloco.


decl_module!{...}

Enter fullscreen mode Exit fullscreen mode

Vamos agora dar uma olhada em como construir nosso pallet dentro dessa estrutura.

Usando o Modelo Pallet

Podemos começar usando o modelo de pallet do Substrate, que fornece o código inicial usando o Frame  v1

Confira o Modelo de pallet Substrate

Agora você pode clonar o repositório gerado na sua máquina local executando o seguinte comando no seu terminal:


git clone [https://github.com/](https://github.com/)<YOUR_GITHUB_USERNAME>/pallet-identity.git

Enter fullscreen mode Exit fullscreen mode

Compreendendo o código do modelo

Primeiro, estamos importando as macros do rust da biblioteca frame_support, necessárias para criar nosso módulo de tempo de execução:

 decl_module, armazenamento: decl_storage, eventos: decl_event, erros: decl_error, dispatch para DispatchResult e o Get para ‘pegar’ as característica usadas pelo armazenamento e  converte-las em tipos. Também devemos importar assegure_signed do frame_system para verificar se as transações são assinadas.

Também importamos ensure_signed de frame_system para verificar se as transações são assinadas.


#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{decl_module, decl_storage, decl_event, decl_error, dispatch, traits::Get};

use frame_system::ensure_signed;

#[cfg(test)]

mod mock;

#[cfg(test)]

mod tests;

Enter fullscreen mode Exit fullscreen mode

Configuração


pub trait Config: frame_system::Config {

///  Como esse _pallet_ emite eventos, depende da definição de ///um evento no tempo de execução.

type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;

Enter fullscreen mode Exit fullscreen mode

Declarando armazenamento

Aqui o nome de armazenamento do pallet  TemplateModule deve ser diferente do nome de armazenamento de outros pallets.

Something é um item de armazenamento, que pode armazenar um número inteiro opcional de 32 bits não assinado. Este item de armazenamento pode ser acessado no módulo de tempo de execução usando  a função  something() . Podemos definir mais itens de armazenamento, se quisermos.

Leia mais sobre declarar Itens de armazenamento


decl_storage! {

trait Store for Module<T: Config> as TemplateModule {

Something get(fn something): Option<u32>;

}

Enter fullscreen mode Exit fullscreen mode

O tempo de execução suporta a emissão de eventos especificados em chamadas de método. Podemos passar argumentos com cada evento. Aqui SomethingStored emite um número inteiro de 32 bits não assinado e um AccountId

A declaração dos eventos seria feita aqui:


decl_event!(

pub enum Event<T> where AccountId = <T as frame_system::Config>::AccountId {

SomethingStored(u32, AccountId),

}

Enter fullscreen mode Exit fullscreen mode

Podemos declarar eventos com o enum Error, que pode ser chamado nas chamadas do módulo. Isso informa ao usuário que algo deu errado.


decl_error! {

pub enum Error for Module<T: Config> {

NoneValue,

StorageOverflow,

}

Enter fullscreen mode Exit fullscreen mode

Módulo de tempo de execução

É aqui que declaramos chamadas de método ( transações ) que podem ser usadas para modificar / consultar o estado da cadeia, elas também podem emitir eventos e erros. Para cada função despachável, o Peso deve ser mencionado e deve retornar um DispatchResult


decl_module! {

pub struct Module<T: Config> for enum Call where origin: T::Origin {

// Os erros devem ser inicializados se eles forem 

          // utilizados pelo _pallet_.

type Error = Error<T>;



// Eventos devem ser inicializados se eles forem //utilizados pelo _pallet_.

fn deposit_event() = default;



/// Um exemplo despachável que recebe um valor único como /// parâmetro, registra o valor no armazenamento e emite um evento. /// Esta função deve ser despachada por um extrínseco assinado.



#[weight = 10_000 + T::DbWeight::get().writes(1)]

pub fn do_something(origin, something: u32) -> dispatch::DispatchResult {

// Verifica que o extrínseco foi assinado e obtém o signatário.

// Esta função retornará um erro se o extrínseco não estiver assinado.

// https://substrate.dev/docs/en/knowledgebase/runtime/origin

let who = ensure_signed(origin)?;



//Atualiza o armazenamento.

Something::put(something);



// Emite um evento.

Self::deposit_event(RawEvent::SomethingStored(something, who));

// Retorna o sucesso de DispatchResult

Ok(())

}



/// Um exemplo despachável que pode gerar um erro personalizado.



#[weight = 10_000 + T::DbWeight::get().reads_writes(1,1)]

pub fn cause_error(origin) -> dispatch::DispatchResult {

let _who = ensure_signed(origin)?;



// Leia um valor do armazenamento.

match Something::get() {

// Retorna um erro se o valor não tiver sido definido.

None => Err(Error::<T>::NoneValue)?,

Some(old) => {

//Incrementa o valor lido do armazenamento; retornará erro em caso //de sobrecarga 

let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;

// Atualiza o valor no armazenamento com o resultado incrementado 

Something::put(new);

Ok(())

},

}

}

}

}



Enter fullscreen mode Exit fullscreen mode

Projetando um pallet personalizado

Em nosso exemplo, estamos projetando um pallet de identidade, onde:

  • Os usuários podem gerar uma nova identidade

  • Adicionar / remover atributos a essa identidade

  • Excluir a identidade

Itens de armazenamento

  • Mapa de identidade: Identity = > AccountId

  • Mapa do atributo: ( Identity, Atribute,_Key ) = > Attribute_Value

Eventos

  • IdentityCreated ( Identity, AccountId )

  • AttributeAdded ( Identity, Atribute_Key, Attribute_Value )

  • AttributeRemoved ( Identity, Atribute_Key ),

Erros

  • IdentityAlreadyClaimed

  • IdentityNotFound

  • NotAuthorized

  • AttributeNotFound

Chamadas de função

  • create_identity ( Identity )

  • add_attribute ( Identity, Atribute_Key, Attribute_Value )

  • remove_attribute ( Identity, Atribute_Key )

Implementando o pallet

Dê uma olhada nesse código de exemplo

Armazenamento

Para armazenamento, criamos um mapeamento de  Identity para AccountId. Identity  aqui seria uma sequência transformada em um vetor de tamanho u8.

Outro item de armazenamento seria o Attribute  que é um mapeamento de um tuplo de identity vector e attribute key vector para attribute value vector.


decl_storage! {

trait Store for Module<T: Config> as IdentityModule {

pub Identity get(fn get_identity): map hasher(blake2_128_concat) Vec<u8> => Option<T::AccountId>;



// ( identity, attribute_key ) => attribute_value

pub Attribute get(fn get_attribute): map hasher(blake2_128_concat) (Vec<u8>, Vec<u8>) => Vec<u8>;

}

}

Enter fullscreen mode Exit fullscreen mode

Eventos


decl_event!(

pub enum Event<T> where AccountId = <T as frame_system::Config>::AccountId {

// Identity, AccountId

IdentityCreated(Vec<u8>, AccountId),



// Identity, Attribute Key, Attribute Value

AttributeAdded(Vec<u8>, Vec<u8>, Vec<u8>),



// Identity, Attribute Key

AttributeRemoved(Vec<u8>, Vec<u8>),

})

Enter fullscreen mode Exit fullscreen mode

Erros


// Erros informando aos usuários que algo deu errado.

decl_error! {

pub enum Error for Module<T: Config> {

IdentityAlreadyClaimed,

IdentityNotFound,

NotAuthorized,

AttributeNotFound,

}

}

Enter fullscreen mode Exit fullscreen mode

Chamadas de função

1.Criação de identidade

Permite ao usuário escolher uma identidade única. Deve gerar erro quando Identity estiver com already reivindicado.




#[weight = 10_000 + T::DbWeight::get().reads_writes(1, 1)]

pub fn create_identity(

origin, 

identity: Vec<u8>

) -> dispatch::DispatchResult {



let who = ensure_signed(origin)?;



match <Identity<T>>::get(&identity) {

// Retorna um erro se o signatário não for o proprietário da identidade

None => {

// Update storage.

<Identity<T>>::insert(&identity, &who);

// Emit an event.

Self::deposit_event(RawEvent::IdentityCreated(identity, who));

// Retorna o sucesso  da DispatchResult

Ok(())

},

Some(_) => Err(Error::<T>::IdentityAlreadyClaimed)?

}

}

Enter fullscreen mode Exit fullscreen mode

2.Adicionar atributo

Permite que o proprietário da identidade adicione valores-chave de atributo à sua própria identidade. Isso deve gerar um erro ao tentar adicionar atributos à identidade de outra pessoa ou se a identidade não for encontrada.


// Permite que os proprietários de identidade adicionem atributos à si //(key, value)

#[weight = 10_000 + T::DbWeight::get().reads_writes(1,1)]

pub fn add_attribute(

origin,

identity: Vec<u8>,

attribute_key: Vec<u8>,

attribute_value: Vec<u8>

) -> dispatch::DispatchResult {

let who = ensure_signed(origin)?;



// Lê o valor do armazenamento

match <Identity<T>>::get(&identity) {

// Retorna um erro se o signatário não for o proprietário da identidade



None => Err(Error::<T>::IdentityNotFound)?,

Some(address) => {

if address != who {

return Err(Error::<T>::NotAuthorized)?

} else{

Attribute::insert((&identity, &attribute_key), &attribute_value);

Self::deposit_event(RawEvent::AttributeAdded(identity, attribute_key, attribute_value));

Ok(())

}

},

}

}

Enter fullscreen mode Exit fullscreen mode

3.Remoção de atributo




// Permite que os proprietários de identidade removam a identidade

#[weight = 10_000 + T::DbWeight::get().reads_writes(1,1)]

pub fn remove_attribute(

origin,

identity: Vec<u8>,

attribute_key: Vec<u8>,

) -> dispatch::DispatchResult {

let who = ensure_signed(origin)?;



//Lê o valor do armazenamento.

match <Identity<T>>::get(&identity) {

//Retorna um erro se o signatário não for o proprietário da identidade



None => Err(Error::<T>::IdentityNotFound)?,

Some(address) => {

if address != who {

return Err(Error::<T>::NotAuthorized)?

} else{

Attribute::remove((&identity, &attribute_key));

Self::deposit_event(RawEvent::AttributeRemoved(identity, attribute_key));

Ok(())

}

},

}

}



Enter fullscreen mode Exit fullscreen mode

Escrevendo casos de teste

Confira o exemplo de testes.

Implementamos dois casos de teste:

  • Criação de identidade,

  • Adição de atributos e

  • Remoção atributo

# test

fn should_not_throw_errors() {

new_test_ext().execute_with(|| {

// Despacha um extrínseco assinado

//cria uma identidade "prasad" para accountId 1

let identity = "prasad".as_bytes().to_vec();

assert_ok!(IdentityModule::create_identity(Origin::signed(1), "prasad".as_bytes().to_vec() ));

// Lê o armazenamento do _pallet_ confirma um resultado esperado.

assert_eq!(IdentityModule::get_identity(&identity), Some(1));



let attribute_key = "name".as_bytes().to_vec();

let attribute_value = "prasad kumkar".as_bytes().to_vec();



// adiciona um nome ao atributo => prasad kumkar

assert_ok!(IdentityModule::add_attribute(Origin::signed(1), "prasad".as_bytes().to_vec(), "name".as_bytes().to_vec(), "prasad kumkar".as_bytes().to_vec()));

// checa o valor do atributo

assert_eq!(IdentityModule::get_attribute((&identity, &attribute_key)), attribute_value);



// Remove o atributo

assert_ok!(IdentityModule::remove_attribute(Origin::signed(1), "prasad".as_bytes().to_vec(), "name".as_bytes().to_vec()));

//Depois da remoção, o atributo deve estar vazio

assert_eq!(IdentityModule::get_attribute((identity, attribute_key)), "".as_bytes().to_vec());



});

}

Enter fullscreen mode Exit fullscreen mode
  • Account cria identidade 'prasad',

  • Chama IdentityAlreadyClaimed quando Account 2 cria a mesma identidade

  • Chama Not Authorized Error erro ao adicionar atributos a Account 1 'identidade da Account 2

# test 
fn check_for_errors() {
new_test_ext().execute_with(|| {
// Despacha um extrínseco assinado.
let identity = "prasad".as_bytes().to_vec();
assert_ok!(IdentityModule::create_identity(Origin::signed(1), "prasad".as_bytes().to_vec() ));
// Lê o armazenamento de _pallet_ e confirma o resultado esperado
assert_eq!(IdentityModule::get_identity(&identity), Some(1));

// Deve retornar um erro de que a identidade "prasad" já foi //reivindicada
let identity = "prasad".as_bytes().to_vec();
assert_noop!(
IdentityModule::create_identity(
Origin::signed(2), 
"prasad".as_bytes().to_vec()),
Error::<Test>::IdentityAlreadyClaimed);

// add_attribute é assinado por uma identidade diferente (2)
// deve retornar o erro NotAuthorized 
assert_noop!(
IdentityModule::add_attribute(
Origin::signed(2), 
"prasad".as_bytes().to_vec(), 
"name".as_bytes().to_vec(), 
"prasad kumkar".as_bytes().to_vec()
), 
Error::<Test>::NotAuthorized);
// O valor do atributo deve estar em branco
assert_eq!(IdentityModule::get_attribute((identity, "name".as_bytes().to_vec())), "".as_bytes().to_vec());
});
}

Enter fullscreen mode Exit fullscreen mode

Construindo e Testando

Para construir o pallet completo com cargo, rode cargo build --release.

Para executar os testes em relação ao código compilado, execute cargo test.

Publicação do pallet

Antes da publicação do nosso pallet, precisamos atualizar os detalhes do pallet. Aqui você precisaria atualizar o nome do pallet para pallet-identity e alterar o link do repositório.

[package]

authors = ['Prasad-Kumkar <https://github.com/prasad-kumkar>']

description = 'Basic FRAME pallet for managing identities.'

edition = '2018'

homepage = 'https://substrate.dev'

license = 'Unlicense'

name = 'pallet-identity'

repository = 'https://github.com/prasad-kumkar/pallet-identity'

version = '3.0.0'
Enter fullscreen mode Exit fullscreen mode

Depois disso, você pode publicar o código usando git:


git add .

git commit -am "commit message"

git push origin master



Enter fullscreen mode Exit fullscreen mode

Adicionando ao tempo de execução

Para adicionar este pallet ao seu tempo de execução, basta incluir a seguinte seção no arquivo de  tempo de execução Cargo.toml ( lembre-se de atualizar o link git ):


[dependencies.pallet-identity]

 default_features = false git = '[https://github.com/prasad-kumkar/pallet-identity.git](https://github.com/prasad-kumkar/pallet-identity.git)'

Enter fullscreen mode Exit fullscreen mode

Atualize também o recurso std do seu tempo de execução para incluir este pallet:


std = [

    # --snip--

    'pallet-identity/std',

]

Enter fullscreen mode Exit fullscreen mode

Tempo de execução

Você deve implementar sua característica da seguinte maneira:


/// Utilizado pelo test_module

impl pallet_identity::Config for Runtime {

type Event = Event;

}

Enter fullscreen mode Exit fullscreen mode

Inclua-o também no seu macro do construct_runtime!:

IdentityPallet: pallet_identity::{Module, Call, Storage, Event<T>}

Conclusão

Para recapitular o conhecimento adquirido durante o tutorial, nós aprendemos sobre  FRAME revisando o modelo de pallet  substrate . Em seguida, construímos nosso pallet personalizado ( pallet de identidade ) e vimos como projetar, implementar, testar esse pallet. Em seguida, analisamos a publicação do pallet e a implementação em nosso tempo de execução do substrate. Espero que isso seja útil para a construção de qualquer pallet personalizado.

Autor

Prasad Kumkar é um engenheiro de blockchain com mais de 2 anos de experiência, co-fundador da Chainvote, uma solução de governança corporativa baseada em blockchain. Seu trabalho foi realizado em várias arquiteturas de blockchain, como Substrate, Hyperledger Fabric, cadeias baseadas em EVM, Solana e Near Protocol. Sua equipe foi vencedora no WyoHackathon 2020 e ETH Denver 2021.

Contato - [email protected]

Referências

Substrate KnowledgeBase

Pallet DID

Template Pallet

Substrate Rust Docs


Este artigo foi escrito por Prasad Kumkar  e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.**

Latest comments (0)