Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust State Machine: translation section 1 #128

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions Rust_State_Machine/pt-BR/Section_1/Lesson_1_Balances_Pallet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
Já vi muitos tutoriais complicados, então vamos tentar manter este simples e organizado. 😃 Espero que você goste!

# O Pallet de Saldos

[Youtube](https://youtu.be/49NApUfg-w8?si=L1n02_Fg-NT45cVn)

Nesta seção, vamos construir a primeira lógica para nossa máquina de estados: um Pallet de Saldos.

Este Pallet gerenciará os saldos dos usuários e permitirá que eles transfiram tokens entre si.

Ao longo do caminho, você aprenderá sobre matemática segura, opções, tratamento de erros e muito mais.

Ao final desta seção, você terá projetado a lógica de uma criptomoeda simples.

# Criando um Pallet de Saldos

Como mencionado anteriormente, no coração de uma blockchain está uma máquina de estados.

Podemos criar uma máquina de estados muito ingênua usando abstrações simples de Rust, e através disso aprender sobre Rust no contexto de blockchains.

Queremos manter nosso código organizado, então não vamos realmente começar a construir no arquivo `main.rs`, mas sim em módulos Rust separados. Podemos pensar no arquivo `main.rs` como a cola que une tudo, e veremos isso ao longo deste projeto.

"Pallet" é um termo específico do Polkadot SDK, que se refere a módulos Rust que contêm lógica específica para o runtime da sua blockchain. Vamos começar a usar esse termo aqui porque o que construímos aqui se assemelhará muito ao que você verá com o Polkadot SDK.

## Saldos

Praticamente toda blockchain tem lógica que lida com os saldos dos usuários nessa blockchain.

Este Pallet dirá: quanto saldo cada usuário tem, fornecerá funções que permitem aos usuários transferir esses saldos e até mesmo algumas funções de baixo nível para permitir que seu sistema de blockchain manipule esses saldos, se necessário. Pense, por exemplo, se você quiser cunhar novos tokens que não existem ainda.

Este é um ótimo ponto de partida, e o primeiro Pallet que vamos construir.

## Criando uma Struct

1. Crie um novo arquivo na pasta `src` chamado `balances.rs`

```bash
touch src/balances.rs
```

2. Neste arquivo, crie uma `struct`, que atuará como estado e ponto de entrada para este módulo:

```rust
pub struct Pallet {}
```

3. Agora volte para `src/main.rs` e importe este novo módulo, que incluirá toda a lógica dentro dele:

```rust
mod balances;
```

4. Se executarmos seu programa agora, você verá que ele ainda é compilado e executado, mas poderá mostrar alguns avisos como:

```rust
warning: struct `Pallet` is never constructed
--> src/balances.rs:1:12
|
1 | pub struct Pallet { }
| ^^^^^^
|
= note: `#[warn(dead_code)]` on by default

warning: `pr` (bin "pr") generated 1 warning
```

Tudo bem! Ainda não começamos a usar nosso Pallet, mas você pode ver que o compilador Rust está detectando nosso novo código e trazendo essa lógica para nosso programa principal. Este é o início da construção do nosso primeiro módulo de máquina de estados.

## Exercícios:

Em `balances.rs`:

```rust
/* TODO: crie uma nova struct pública chamada `Pallet`. */
```

Em `main.rs`:

```rust
/* TODO: use seu novo módulo `balances` */

fn main() {
println!("Hello, world!");
}
```

Se você tiver uma pergunta sobre a qual está curioso, sinta-se à vontade para perguntar em [🆘・seção-1](https://discord.com/channels/898706705779687435/980904325763186788) no Discord.

Normalmente, começo a entender realmente as coisas quando começo a programar. Então, vamos começar a construir algum código. :)
79 changes: 79 additions & 0 deletions Rust_State_Machine/pt-BR/Section_1/Lesson_2_Add_State.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Adicionando Estado ao Nosso Pallet

[Youtube](https://youtu.be/CCxkdf2VX8w?si=nY_7Mze68Wkw12QR)

Então, vamos adicionar algum estado simples ao nosso módulo `balances.rs`.

Podemos fazer isso adicionando campos na nossa struct `Pallet`.

Para um sistema de saldo, realmente só precisamos acompanhar uma coisa: **quanto saldo cada usuário tem em nosso sistema**.

Para isso, usaremos um `BTreeMap`, que podemos importar da biblioteca `std` do Rust.

`Maps` são objetos simples de `chave -> valor`, permitindo-nos definir um armazenamento de tamanho arbitrário onde podemos mapear algum identificador de usuário (`chave`) para o saldo da conta deles (`valor`).

1. Importe o objeto `BTreeMap`.

```rust
use std::collections::BTreeMap;
```

2. Crie um campo `balances` em `Pallet` usando o `BTreeMap`.
Para a `chave`, usaremos uma string estática simples por enquanto. Dessa forma, podemos acessar usuários como `"alice"`, `"bob"`, etc. Isso será alterado no futuro.

Para o `valor`, usaremos um `u128`, que é o maior tipo suportado nativamente no Rust. Isso permitirá que nossos usuários tenham saldos muito grandes se quisermos.

No final, isso ficará assim:

```rust
pub struct Pallet {
balances: BTreeMap<String, u128>,
}
```

3. Finalmente, precisamos de uma maneira de inicializar este objeto e seu estado. Para isso, vamos implementar uma função na `Pallet` chamada `fn new()`:

```rust
impl Pallet {
pub fn new() -> Self {
Self {
balances: BTreeMap::new(),
}
}
}
```

## Exercício:

No `balances.rs`:

```rust
use std::collections::BTreeMap;

pub struct Pallet {
// Um armazenamento simples mapeando contas (`String`) para seus saldos (`u128`).
/* TODO: Adicionar um campo `balances` que é um `BTreeMap` de `String` para `u128`. */
}

impl Pallet {
/// Cria uma nova instância do módulo de saldos.
pub fn new() -> Self {
/* TODO: Retornar uma nova instância da struct `Pallet`. */
unimplemented!() // Remova esta linha após implementar a função
}
}
```

Você pode confirmar neste ponto que tudo ainda deve estar compilando, e que você não cometeu nenhum erro pequeno.

Em seguida, vamos realmente começar a **usar** este módulo.

## Notas

É importante notar que isso **NÃO** é como o armazenamento de Pallet funciona com o Polkadot SDK, mas apenas uma emulação simples dos comportamentos.

No Polkadot SDK, existe uma camada de armazenamento separada que gerencia um banco de dados de chave-valor adequado que contém todas as informações (passadas e presentes) do nosso sistema de blockchain. Existem abstrações que se parecem e se comportam como um `BTreeMap` no Polkadot SDK, mas a lógica subjacente que mantém esses dados é muito mais complexa.

Usar campos simples em uma struct mantém este projeto simples e ilustra que cada Pallet realmente deve gerenciar seu próprio armazenamento. No entanto, essa simplificação também leva a problemas se você projetar sistemas mais complexos onde múltiplos pallets interagem entre si.

Não teremos nenhuma interação entre pallets neste projeto, no entanto, isso é definitivamente viável com o Polkadot SDK e um banco de dados adequado.
135 changes: 135 additions & 0 deletions Rust_State_Machine/pt-BR/Section_1/Lesson_3_Store_And_Read.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
Você pode encontrar a [solução para a etapa anterior aqui](https://gist.github.com/nomadbitcoin/d116a728d944026dd9fd4f3f689b75cf).

# Adicionando Interação com Saldos

[Youtube](https://youtu.be/coBI_avKIMw?si=qb-E3eBsOI_aFiOa)

Agora que estabelecemos os conceitos básicos do nosso módulo de saldos, vamos adicionar maneiras de interagir com ele.

Para fazer isso, continuaremos a criar mais funções implementadas em `Pallet` que concedem acesso para ler, escrever e atualizar o `balances: BTreeMap` que criamos.

Finalmente, veremos como é realmente começar a interagir com nosso pallet de saldos a partir do arquivo `main.rs`.

## Conhecimento Pré-requisito de Rust

Antes de continuarmos, vamos dedicar um momento para revisar alguns conceitos de Rust que usaremos nesta próxima seção.

### Option e Tratamento de Option

Um dos principais princípios de Rust é remover comportamento indefinido do seu código.

Uma maneira de ocorrer comportamento indefinido é permitindo que estados como `null` existam. Rust previne isso fazendo com que o usuário trate explicitamente todos os casos, e é aqui que entra a criação do tipo `Option`. Dedique um momento para revisar [a seção sobre `Option`](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html?highlight=option#the-option-enum-and-its-advantages-over-null-values) do livro de Rust, se necessário.

A API do `BTreeMap` usa um `Option` ao ler valores do mapa, já que pode ser que você peça para ler o valor de alguma chave que você não definiu. Por exemplo:

```rust
use std::collections::BTreeMap;

let mut map = BTreeMap::new();
map.insert("alice", 100);
assert_eq!(map.get(&"alice"), Some(&100));
assert_eq!(map.get(&"bob"), None);
```

Uma vez que temos um tipo `Option`, existem muitas maneiras diferentes de interagir com ele usando Rust.

A maneira mais verbosa é usando uma declaração de `match`:

```rust
let maybe_value = map.get(&"alice");
match maybe_value {
Some(value) => {
// fazer algo com o `value`
},
None => {
// talvez retornar um erro já que não havia valor lá
}
}
```

> 🚨 **Alerta:** O que você **NÃO DEVE** fazer é usar `unwrap()` cegamente em opções. Isso resultará em um `panic` no seu código, o que é exatamente o tipo de coisa que Rust foi projetado para prevenir! Em vez disso, você deve sempre tratar explicitamente todos os seus diferentes casos lógicos, e se deixar que Rust faça seu trabalho, seu código será super seguro.

No contexto do que estamos projetando para o módulo de saldos, temos um mapa que possui um número arbitrário de chaves de usuário e seus valores de saldo.

O que devemos fazer quando lemos o saldo de um usuário que não existe no nosso mapa?

Bem, o truque aqui é que no contexto das blockchains, um usuário ter `None` saldo e um usuário ter `0` saldo é a mesma coisa. Claro, há alguns detalhes mais finos a serem expressos entre um usuário que existe em nosso estado com valor 0 e um usuário que não existe de todo, mas para os propósitos de nossas APIs, podemos tratá-los da mesma forma.

Como isso se parece?

Bem, podemos usar `unwrap_or(...)` para tratar essa condição com segurança e tornar nossas futuras APIs mais ergonômicas de usar. Por exemplo:

```rust
use std::collections::BTreeMap;

let mut map = BTreeMap::new();
map.insert("alice", 100);
assert_eq!(*map.get(&"alice").unwrap_or(&0), 100);
assert_eq!(*map.get(&"bob").unwrap_or(&0), 0);
```

Como você pode ver, ao usar `unwrap_or(&0)` após ler do nosso mapa, somos capazes de transformar nosso `Option` em um inteiro básico, onde usuários com algum valor têm seu valor exposto e usuários com `None` são transformados em `0`.

Vamos ver como isso pode ser usado a seguir.

## Definindo e Lendo Saldos de Usuários

Como você pode ver, nossa máquina de estados inicial começa com todos sem saldo.

Para tornar nosso módulo útil, precisamos ter pelo menos algumas funções que nos permitam criar novos saldos para usuários e ler esses saldos.

1. Crie uma nova função dentro de `impl Pallet` chamada `fn set_balance`:

```rust
impl Pallet {
pub fn set_balance(&mut self, who: &String, amount: u128) {
self.balances.insert(who.clone(), amount);
}

// -- snip --
}
```

Como você pode ver, esta função simplesmente recebe informações sobre qual usuário queremos definir o saldo e qual saldo queremos definir. Isso então empurra essa informação para nosso `BTreeMap`, e isso é tudo.

2. Crie uma nova função dentro de `impl Pallet` chamada `fn balance`:

```rust
impl Pallet {
pub fn balance(&self, who: &String) -> u128 {
*self.balances.get(&who).unwrap_or(&0)
}
}
```

Como você pode ver, esta função nos permite ler o saldo dos usuários em nosso mapa. A função permite que você insira algum usuário e nós retornaremos o saldo dele.

> 🚨 **Alerta:** Note que fazemos nosso pequeno truque aqui! Em vez de expor uma API que força o usuário a lidar com um `Option`, somos capazes de fazer nossa API sempre retornar um `u128` convertendo qualquer usuário com valor `None` em `0`.

## Exercício:

No `balances.rs`:

```rust
impl Pallet {
/// Cria uma nova instância do módulo de saldos.
pub fn new() -> Self {
Self { balances: BTreeMap::new() }
}

/// Define o saldo de uma conta `who` para algum `amount`.
pub fn set_balance(&mut self, who: &String, amount: u128) {
/* Insira `amount` no BTreeMap sob `who`. */
unimplemented!()
}

/// Obtém o saldo de uma conta `who`.
/// Se a conta não tiver saldo armazenado, retornamos zero.
pub fn balance(&self, who: &String) -> u128 {
/* Retorna o saldo de `who`, retornando zero se `None`. */
unimplemented!()
}
}
```

A seguir, escreveremos nosso primeiro teste e realmente interagiremos com nosso módulo de saldos. Animado para a próxima etapa? Nós estamos!
76 changes: 76 additions & 0 deletions Rust_State_Machine/pt-BR/Section_1/Lesson_4_Basic_Tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
Você pode encontrar a [solução para a etapa anterior aqui](https://gist.github.com/nomadbitcoin/9171430e3a44c42513b5c1104ef972e2).

# Teste Básico de Saldo

[Youtube](https://youtu.be/Y_G6xJomcJU?si=1fzfU5gG5QYaI27A)

Agora que temos os conceitos básicos do nosso `Pallet` configurados, vamos realmente interagir com ele.

Para isso, voltaremos ao arquivo `main.rs` e criaremos nosso primeiro `#[test]` que utilizará o código que escrevemos até agora.

1. No seu arquivo `src/balances.rs`, adicione um novo `#[test]` chamado `fn init_balances()`:

```rust
#[test]
fn init_balances() { }
```

2. Para começar nosso teste, precisamos inicializar uma nova instância do nosso `Pallet`:

```rust
#[test]
fn init_balances() {
let mut balances = balances::Pallet::new();
}
```

Observe que tornamos essa variável `mut` já que planejamos modificar nosso estado usando nossa API recém-criada.

3. Finalmente, vamos verificar se nossas APIs de leitura e escrita estão funcionando como esperado:

```rust
#[test]
fn init_balances() {
let mut balances = balances::Pallet::new();

assert_eq!(balances.balance("alice".to_string()), 0);
balances.set_balance("alice".to_string(), 100);
assert_eq!(balances.balance("alice".to_string()), 100);
assert_eq!(balances.balance("bob".to_string()), 0);
}
```

4. Podemos executar nossos testes usando `cargo test`, onde esperamos que você veja que ele passa.

Espero que neste ponto você possa começar a ver os primeiros passos da sua simples máquina de estados blockchain.

# Exercício:

No `balances.rs`:

```rust
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn init_balances() {
/* TODO: Crie uma variável mutável `balances`, que é uma nova instância de `Pallet`. */
let mut balances = Pallet::new();

/* TODO: Verifique se o saldo de `alice` começa em zero. */
assert_eq!(balances.balance(&"alice".to_string()), 0);

/* TODO: Defina o saldo de `alice` para 100. */
balances.set_balance(&"alice".to_string(), 100);

/* TODO: Verifique se o saldo de `alice` agora é 100. */
assert_eq!(balances.balance(&"alice".to_string()), 100);

/* TODO: Verifique se o saldo de `bob` não mudou e é 0. */
assert_eq!(balances.balance(&"bob".to_string()), 0);
}
}
```

Compartilhe seu [#progress](https://discord.com/channels/898706705779687435/980906289968345128) no Discord e faça o dia do Yan melhor!
Loading
Loading