WEB3DEV

Cover image for Criando um Token ERC20 Encapsulado na Avalanche com o Vyper! 🐍
Paulo Gio
Paulo Gio

Posted on

Criando um Token ERC20 Encapsulado na Avalanche com o Vyper! 🐍

Introdução

Neste artigo, vamos descrever os casos de uso de um token ERC20 e explicaremos como criá-lo do zero usando o Vyper, implantando na rede de teste da Avalanche, Fuji.

Pré-requisitos

  • Programação básica e experiência com console/terminal
  • Uma compreensão simples de Solidity ou Vyper

Requisitos

O que é um ERC20?

Um contrato inteligente ERC20 controla tokens fungíveis, e o próprio contrato nos permite transferir, gravar e fazer muitas outras coisas interessantes com esse token específico.

A interface do contrato é muito simples e não inclui muitas informações sobre o token em si.

#Funções
totalSupply()
balanceOf(account)
transfer(to, amount)
allowance(owner, spender)
approve(spender, amount)
transferFrom(from, to, amount)
Enter fullscreen mode Exit fullscreen mode

Essas seis funções fazem parte da função original definida na EIP20. Podemos estender muitas funcionalidades adicionando mais funções (mais recursos) ao nosso contrato inteligente.

Neste artigo vamos criar um contrato inteligente ERC20, que recebe AVAX e retorna WAVAX, nossa implementação encapsulada da criptomoeda.

Vamos permitir que os usuários criem tokens depositando AVAX, queimem tokens e recebam AVAX em troca.

Por que eles são importantes?

Os tokens são uma parte fundamental do espaço web3. Eles podem ser usados de várias maneiras criativas e exclusivas, como compartilhamentos em uma comunidade, valor transacional, moeda em um protocolo e muito mais. Neste tutorial, vamos criar um token que encapsula uma criptomoeda (AVAX neste caso) e esse token pode ser usado em protocolos defi, protocolos web3 e muito mais.

Configuração

Vamos usar o Brownie neste tutorial. Após instalar o Brownie, crie uma nova pasta chamada avalanche-swap e dentro dela execute o seguinte comando:

$ brownie init
Enter fullscreen mode Exit fullscreen mode

Além disso, usaremos o nó Hardhat para testar nossos contratos inteligentes, portanto, dentro da pasta do projeto, execute o seguinte comando:

$ npm install --save-dev hardhat
Enter fullscreen mode Exit fullscreen mode

Contrato Inteligente

Vamos começar com o código-modelo. Criamos um arquivo chamado contracts/WAVAX.vy, definimos a versão do Vyper como >= 0.3.7 e importamos as interfaces ERC20 e ERC20Detailed do Vyper.

# @version >=0.3.7

from vyper.interfaces import ERC20
from vyper.interfaces import ERC20Detailed

implements: ERC20
implements: ERC20Detailed
Enter fullscreen mode Exit fullscreen mode

Eventos

Precisamos definir nossos eventos (events), que são as mensagens que o contrato inteligente emite quando chamado, e as definimos conforme proposto pela EIP20.

event Transfer:
    sender: indexed(address)
    receiver: indexed(address)
    value: uint256

event Approval:
    owner: indexed(address)
    spender: indexed(address)
    value: uint256
Enter fullscreen mode Exit fullscreen mode

Estado

Nosso contrato inteligente precisa de estado, ou de variáveis que persistirão durante toda a vida do contrato inteligente. Essas variáveis contêm informações que serão úteis para nossos usuários e para os métodos que definiremos a seguir, como balanceOf, totalSupply e allowance

# @dev nome do token ("Wrapped AVAX")
name: public(String[32])

# @dev símbolo ou ticker do token ("WAVAX")
symbol: public(String[32])

# @dev a quantidade de decimais que o token contém
decimals: public(uint8)

# @dev o saldo de um determinado endereço
balanceOf: public(HashMap[address, uint256])

# @dev endereços podem permitir que outros endereços gastem seus tokens
allowance: public(HashMap[address, HashMap[address, uint256]])

# @dev o número de tokens em circulação
totalSupply: public(uint256)
Enter fullscreen mode Exit fullscreen mode

Como você pode ver, definimos uma variável para armazenar quantos decimais nosso token vai ter. Isso porque o Solidity, principal linguagem de programação utilizada nas blockchains EVM, não tem suporte para uso de decimais, então definimos os decimais como inteiros. Por exemplo, nós queremos usar 2 casas decimais. Para expressar o número 100,99, colocaríamos assim, 10099, sendo os últimos dois dígitos a parte decimal real.

Construtor

Após concluir o processo anterior, definimos o construtor. Aqui especificamos o nome do token, o símbolo e o número de casas decimais que esse token terá.

@external
def __init__():
    self.name = "Wrapped AVAX"
    self.symbol = "WAVAX"
    self.decimals = 18
Enter fullscreen mode Exit fullscreen mode

Métodos internos

Os métodos internos são bem simples. Precisamos de um método para cunhar nossos tokens AVAX encapsulados quando o usuário enviar a quantia que deseja, e outro para queimá-los quando quiser recuperar seus tokens.

@internal
def _mint(_to: address, _value: uint256):
    assert _to != empty(address)
    self.totalSupply += _value
    self.balanceOf[_to] += _value
    log Transfer(empty(address), _to, _value)
Enter fullscreen mode Exit fullscreen mode
@internal
def _burn(_to: address, _value: uint256):
    assert _to != empty(address)
    self.totalSupply -= _value
    self.balanceOf[_to] -= _value
    send(_to, _value)
    log Transfer(_to, empty(address), _value)
Enter fullscreen mode Exit fullscreen mode

Esses dois são métodos-padrão nas implementações do ERC20, pois precisamos de uma maneira de cunhar os tokens e (se assim o desejarmos) queimá-los também.

Métodos externos

Agora precisamos definir os principais métodos de um token ERC20: transfer, transferFrom e approve. Esses três métodos permitem que os detentores dos tokens os utilizem como desejarem.

O método transfer é autoexplicativo e permite que o detentor do token transfira tokens para outro usuário ou um contrato inteligente.

@external
def transfer(_to : address, _value : uint256) -> bool:
    self.balanceOf[msg.sender] -= _value
    self.balanceOf[_to] += _value
    log Transfer(msg.sender, _to, _value)
    return True
Enter fullscreen mode Exit fullscreen mode

O padrão do token precisa de um método approve para habilitar o método transferFrom que definimos a seguir. O método approve fornece ao detentor do token uma maneira de permitir que outra conta gerencie uma quantidade definida de tokens para eles.

@external
def approve(_spender : address, _value : uint256) -> bool:
    self.allowance[msg.sender][_spender] = _value
    log Approval(msg.sender, _spender, _value)
    return True
Enter fullscreen mode Exit fullscreen mode

E o método transferFrom dá ao usuário a capacidade de transferir tokens para outra conta em nome do titular do token ou gerenciar uma quantidade definida de tokens para eles.

@external
def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
    self.balanceOf[_from] -= _value
    self.balanceOf[_to] += _value

    self.allowance[_from][msg.sender] -= _value
    log Transfer(_from, _to, _value)
    return True
Enter fullscreen mode Exit fullscreen mode

Como este é um token encapsulado, precisamos de uma maneira de receber a criptomoeda que estamos tentando encapsular e cunhar uma quantidade equivalente de tokens, e também uma maneira de fazer o contrário, queimar vários tokens e receber a criptomoeda equivalente de volta.

Para o método mint (cunhagem), o usuário envia criptomoedas com a chamada do método e recebe o mesmo valor de volta em tokens.

@external
@payable
def mint():
    self._mint(msg.sender, msg.value)
Enter fullscreen mode Exit fullscreen mode

E para o método burn (queima), o usuário passa a quantidade de tokens que deseja queimar, para receber o equivalente em criptomoeda.

@external
def burn(_value: uint256):
    self._burn(msg.sender, _value)
Enter fullscreen mode Exit fullscreen mode

Também definiremos um método padrão. Este método será acionado se um usuário enviar AVAX diretamente para o contrato, sem chamar a função mint. E, por sua vez, o contrato cunhará para esse usuário o equivalente em tokens.

@external
@payable
def __default__():
    self._mint(msg.sender, msg.value)
Enter fullscreen mode Exit fullscreen mode

E é isso. Agora temos um contrato inteligente de token, que cunha tokens encapsulados. O seu contrato deve ficar mais ou menos assim:

# @versão >=0.3.7

from vyper.interfaces import ERC20
from vyper.interfaces import ERC20Detailed

implements: ERC20
implements: ERC20Detailed

event Transfer:
    sender: indexed(address)
    receiver: indexed(address)
    value: uint256

event Approval:
    owner: indexed(address)
    spender: indexed(address)
    value: uint256

# @dev nome do token ("Wrapped AVAX")
name: public(String[32])

# @dev símbolo ou ticker do token ("WAVAX")
symbol: public(String[32])

# @dev a quantidade de decimais que o token contém
decimals: public(uint8)

# @dev o saldo de um determinado endereço
balanceOf: public(HashMap[address, uint256])

# @dev endereços podem permitir que outros endereços gastem seus tokens
allowance: public(HashMap[address, HashMap[address, uint256]])

# @dev o número de tokens em circulação
totalSupply: public(uint256)


@external
def __init__():
    self.name = "Wrapped AVAX"
    self.symbol = "WAVAX"
    self.decimals = 18


@internal
def _mint(_to: address, _value: uint256):
    assert _to != empty(address)
    self.totalSupply += _value
    self.balanceOf[_to] += _value
    log Transfer(empty(address), _to, _value)


@internal
def _burn(_to: address, _value: uint256):
    assert _to != empty(address)
    self.totalSupply -= _value
    self.balanceOf[_to] -= _value
    send(_to, _value)
    log Transfer(_to, empty(address), _value)


@external
def transfer(_to : address, _value : uint256) -> bool:
    self.balanceOf[msg.sender] -= _value
    self.balanceOf[_to] += _value
    log Transfer(msg.sender, _to, _value)
    return True


@external
def approve(_spender : address, _value : uint256) -> bool:
    self.allowance[msg.sender][_spender] = _value
    log Approval(msg.sender, _spender, _value)
    return True


@external
def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
    self.balanceOf[_from] -= _value
    self.balanceOf[_to] += _value

    self.allowance[_from][msg.sender] -= _value
    log Transfer(_from, _to, _value)
    return True


@external
@payable
def mint():
    self._mint(msg.sender, msg.value)


@external
def burn(_value: uint256):
    self._burn(msg.sender, _value)


@external
@payable
def __default__():
    self._mint(msg.sender, msg.value)
Enter fullscreen mode Exit fullscreen mode

Implantar usando o Brownie

Neste tutorial, vamos usar o Brownie para implantar nossos contratos inteligentes, você pode usar qualquer outro framework de contratos inteligentes se estiver familiarizado com ele. O Hardhat ou o ApeWorx têm suporte para desenvolver e implantar contratos inteligentes do Vyper.

Como estamos implantando na Avalanche, precisamos adicionar a rede principal (mainnet) da Avalanche e a rede de teste (testnet) da Avalanche, Fuji, às nossas redes suportadas. No terminal, digite o seguinte:

$ brownie networks add Avalanche avax-testnet host=https://api.avax-test.network/ext/bc/C/rpc chainid=43113 explorer=https://testnet.snowtrace.io/ name=Fuji
Enter fullscreen mode Exit fullscreen mode
$ brownie networks add Avalanche avax-mainnet host=https://api.avax.network/ext/bc/C/rpc chainid=43114 explorer=https://snowtrace.io/ name=Mainnet
Enter fullscreen mode Exit fullscreen mode

Depois de executarmos esses dois comandos, serão adicionadas as redes Avalanche ao Brownie.

Avalanche
  ├─Mainnet: avax-mainnet
  └─Fuji: avax-testnet
Enter fullscreen mode Exit fullscreen mode

Após a conclusão, podemos escrever o script de implantação.

Nosso script é muito simples, pois só vamos pegar nosso contrato inteligente e implantá-lo.

Nosso script verificará primeiro se estamos em alguma das redes Avalanche e, se estivermos, implantará os contratos inteligentes usando uma carteira real (com algum AVAX nela), caso contrário, assumiremos que estamos trabalhando em nossa rede local e os implantaremos lá.

Para implantar em uma blockchain ativa (Fuji ou rede principal), precisamos ter uma carteira com algum AVAX nela. Você pode obter alguns tokens AVAX de teste nesta torneira (faucet). Além disso, precisamos definir nosso arquivo brownie-config.yaml para que o Brownie saiba onde encontrar nossa chave privada e também algumas configurações importantes.

Crie um arquivo brownie-config.yaml na raiz do seu projeto. Ele deve se parecer com isso.

dotenv: .env

networks:
  default: hardhat

wallets:
  from_key: ${PRIVATE_KEY}

compiler:
  vyper:
    version: "0.3.7"
Enter fullscreen mode Exit fullscreen mode

O arquivo de configuração espera que um arquivo .env funcione corretamente. Crie um na raiz do seu projeto e preencha-o com isso.

PRIVATE_KEY="SUA_CHAVE_PRIVADA_AQUI"
Enter fullscreen mode Exit fullscreen mode

Depois de definirmos nossa configuração, precisamos escrever nosso script de implantação. Na pasta scripts crie um novo arquivo chamado deploy.py. Este programa em python será responsável por implantar nossos contratos inteligentes.

O arquivo deploy.py deve se parecer com isto:

from brownie import accounts, network, config, WAVAX


def main():
    supported_networks = ["avax-mainnet", "avax-testnet"]
    active_network = network.show_active()

    deployer = accounts[0]
    if active_network in supported_networks:
        deployer = accounts.add(config["wallets"]["from_key"])

    WAVAX.deploy({"from": deployer})
Enter fullscreen mode Exit fullscreen mode

Para executar este script, escreva o seguinte comando em seu terminal. Isso implantará o contrato no seu nó local do Hardhat.

$ brownie run deploy
Enter fullscreen mode Exit fullscreen mode

Para implantá-lo em uma rede de testes, escreva o seguinte.

$ brownie run deploy --network avax-testnet
Enter fullscreen mode Exit fullscreen mode

E para implantá-lo na rede principal da Avalanche, execute o seguinte comando:

$ brownie run deploy --network avax-mainnet
Enter fullscreen mode Exit fullscreen mode

E seria isso sobre o contrato inteligente. Criamos, testamos e implantamos com sucesso nosso contrato inteligente de token encapsulado.

Contrato inteligente na rede de testes (Fuji)

WAVAX

Artigo original publicado por Rafo AV. Traduzido por Paulinho Giovannini.

Latest comments (0)