Skip to content

Governança Descentralizada: Desenvolvendo uma Aplicação DAO com Elementos da Web3 (PARTE 2)

Governança Descentralizada: Desenvolvendo uma Aplicação DAO com Elementos da Web3 (PARTE 2)

Implementando Contratos Inteligentes em Solidity para Nosso DAO com elementos da Web3

##Requisitos

###VS Code

Você pode usar qualquer editor de código, mas aqui estamos utilizando o Visual Studio Code. Se desejar baixá-lo, utilize este link https://code.visualstudio.com/

###Instalando o Brownie

Vamos usar o framework Brownie. O Brownie é um framework de desenvolvimento e teste baseado em Python para contratos inteligentes direcionados à Máquina Virtual Ethereum.

Para conhecer todos os conceitos básicos sobre o Brownie, você pode consultar a documentação oficial neste URL: https://eth-brownie.readthedocs.io/en/stable/

Para instalar, você pode usar as ferramentas pipx ou pip.

Abaixo está como deve ser instalado via pip:

pip install eth-brownie

Às vezes, a instalação falhará devido a algumas dependências. Para isso, a versão recomendada do Python é a 3.9.0 por enquanto.

###Clonar o Modelo

Clone o modelo usando o código abaixo:

git clone https://github.com/HarithDilshan/DAO---Template-by-Roxen.git

Vá para a pasta clonada e abra-a no VS Code.

cd .\DAO---Template-by-Roxen\
code .

A estrutura da pasta deve ser semelhante a esta:

Você verá um arquivo chamado ‘.env.deletethispart’. Você deve renomeá-lo para ‘.env’.

##Codificando Contratos Inteligentes

Vamos criar contratos inteligentes na pasta ‘contracts’. A estrutura final de arquivos será semelhante à imagem abaixo.

Primeiro, veja como os contratos são criados, e depois vou descrevê-los.

##Box.sol

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts/access/Ownable.sol";

contract Box is Ownable {
    uint256 private value;

    event ValueChanged(uint256 newValue);

    function store(uint256 newValue) public onlyOwner {
        value = newValue;
        emit ValueChanged(newValue);
    }

    function retrieve() public view returns (uint256) {
        return value;
    }
}

O contrato inteligente Box serve como um mecanismo simples de armazenamento com controles adicionais de propriedade. Aqui estão os detalhes:

Herança:

  • Herda de Ownable na biblioteca OpenZeppelin, garantindo que apenas o proprietário tenha autoridade para modificar o valor armazenado.

Variável de Estado:

  • uint256 private value: uma variável privada para armazenar o valor inteiro.

Eventos:

  • event ValueChanged(uint256 newValue): emitido sempre que o valor armazenado é modificado.

Funções:

  • function store(uint256 newValue) public onlyOwner: permite que o proprietário atualize o valor armazenado. Emite um evento ValueChanged após a modificação bem-sucedida.
  • function retrieve() public view returns (uint256): permite que qualquer pessoa veja o valor armazenado atual sem modificá-lo.

Este contrato é projetado para um cenário em que um valor controlado pelo proprietário precisa ser armazenado e potencialmente recuperado por outros usuários.

##GovernanceToken.sol

//SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/token/ERC20/Extensions/ERC20Votes.sol";

pragma solidity ^0.8.7;

contract GovernanceToken is ERC20Votes {
   // eventos para o token de governança
   event TokenTransfered(
       address indexed from,
       address indexed to,
       uint256 amount
   );

   // Eventos
   event TokenMinted(address indexed to, uint256 amount);
   event TokenBurned(address indexed from, uint256 amount);

   // máximo de tokens por usuário
   uint256 constant TOKENS_PER_USER = 1000;
   uint256 constant TOTAL_SUPPLY = 1000000 * 10**18;

   // Mapeamentos
   mapping(address => bool) public s_claimedTokens;

   // Número de detentores
   address[] public s_holders;

   constructor(uint256 _keepPercentage)
       ERC20("MoralisToken", "MRST")
       ERC20Permit("MoralisToken")
   {
       uint256 keepAmount = (TOTAL_SUPPLY * _keepPercentage) / 100;
       _mint(msg.sender, TOTAL_SUPPLY);
       _transfer(msg.sender, address(this), TOTAL_SUPPLY - keepAmount);
       s_holders.push(msg.sender);
   }

   // Reivindicar tokens gratuitamente
   function claimTokens() external {
       require(!s_claimedTokens[msg.sender], "Already claimed tokens");
       s_claimedTokens[msg.sender] = true;
       _transfer(address(this), msg.sender, TOKENS_PER_USER * 10**18);
       s_holders.push(msg.sender);
   }

   function getHolderLength() external view returns (uint256) {
       return s_holders.length;
   }

   // Substituições necessárias para o Solidity
   function _afterTokenTransfer(
       address from,
       address to,
       uint256 amount
   ) internal override(ERC20Votes) {
       super._afterTokenTransfer(from, to, amount);
       emit TokenTransfered(from, to, amount);
   }

   function _mint(address to, uint256 amount) internal override(ERC20Votes) {
       super._mint(to, amount);
       emit TokenMinted(to, amount);
   }

   function _burn(address account, uint256 amount)
       internal
       override(ERC20Votes)
   {
       super._burn(account, amount);
       emit TokenBurned(account, amount);
   }
}

// Proposta atraente em breve => Compre uma tonelada de tokens e se desfaça deles após o término da votação
// Precisamos evitar isso definindo um Snapshop de tokens em um determinado bloco.

O contrato inteligente GovernanceToken representa um token de governança com funcionalidades complexas. Detalhes chave incluem:

Herança:

  • Herda de ERC20Votes, uma extensão do token ERC-20 do OpenZeppelin com mecanismos de votação incorporados.

Eventos:

  • event TokenTransfered(address indexed from, address indexed to, uint256 amount): emitido ao transferir tokens.
  • event TokenMinted(address indexed to, uint256 amount): emitido quando novos tokens são cunhados.
  • event TokenBurned(address indexed from, uint256 amount): emitido quando tokens são queimados.

Constantes:

  • uint256 constante TOKENS_PER_USER: especifica o número máximo de tokens que cada usuário pode reivindicar.
  • uint256 constante TOTAL_SUPPLY: representa o fornecimento total de tokens.

Mapeamentos:

mapping(address => bool) public s_claimedTokens: mantém o controle dos usuários que reivindicaram tokens.

Construtor:

  • Cunha o fornecimento total para o implantador e transfere uma parte para o próprio contrato.

###Funções:

  • function claimTokens() external: permite que os usuários reivindiquem tokens, aplicando o limite definido por TOKENS_PER_USER.
  • function getHolderLength() external view returns (uint256): recupera o número de detentores de tokens.

Este contrato não apenas gerencia transferências de tokens, mas também incorpora recursos relacionados à governança, incentivando a participação do usuário e garantindo uma distribuição justa.

##TimeLock.sol

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

import "@openzeppelin/contracts/governance/TimelockController.sol";

contract TimeLock is TimelockController {
   // Atraso mínimo é o tempo mínimo necessário para que uma proposta possa ser executada
   // Os proponentes são os endereços que podem criar propostas
   // Os executores são os endereços que podem executar propostas
   // Admin é o endereço que pode definir o atraso
   constructor(
       uint256 minDelay,
       address[] memory proposers,
       address[] memory executors,
       address admin
   ) TimelockController(minDelay, proposers, executors, admin) {}
}

// O bloqueio de tempo será o proprietário do contrato regido
// Usamos o bloqueio de tempo porque queremos esperar até que uma nova votação seja executada
// Também queremos definir uma taxa mínima para poder votar, digamos 7 tokens.
// Isso dá tempo para sair se a proposta não for do seu agrado.

O contrato inteligente TimeLock estende o TimelockController do OpenZeppelin, adicionando controle baseado no tempo para propostas. Aqui está uma explicação:

Herança:

  • Herda do TimelockController na biblioteca OpenZeppelin.

Construtor:

  • Configura o timelock com parâmetros como minDelay, proposers, executores e admin.

Este contrato estabelece um mecanismo de timelock, garantindo um atraso entre a criação e a execução de propostas, ao mesmo tempo em que define funções para proponentes, executores e um administrador.

##MoralisGovernor.sol

// O atraso da votação é o tempo entre a criação da proposta e o início da votação
// Período de votação é o tempo entre o início e o término da votação
// O limite da proposta é a quantidade mínima de votos que uma conta deve ter para poder criar uma proposta
// Quorum é a quantidade mínima de votos que uma proposta deve ter para poder ser executada em porcentagem
// Configurações atualizáveis permitem alterar as configurações do contrato, como atraso, período, limite.
// Bravo Compatible permite usar as funções compatíveis com o Bravo, como getVotes e getReceipts

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract MoralisGovernor is
   Governor,
   GovernorSettings,
   GovernorCountingSimple,
   GovernorVotes,
   GovernorVotesQuorumFraction,
   GovernorTimelockControl
{
   // Contagens de propostas
   uint256 public s_proposalCount;

   constructor(
       IVotes _token,
       TimelockController _timelock,
       uint256 _votingDelay,
       uint256 _votingPeriod,
       uint256 _quorumPercentage
   )
       Governor("Governor")
       GovernorSettings(
           _votingDelay, /* 1 => 1 block */
           _votingPeriod, /* 300 blocks => 1 hour */
           0 /* 0 => Because we want anyone to be able to create a proposal */
       )
       GovernorVotes(_token)
       GovernorVotesQuorumFraction(_quorumPercentage) /* 4 => 4% */
       GovernorTimelockControl(_timelock)
   {
       s_proposalCount = 0;
   }

   // As funções a seguir são substituições exigidas pelo Solidity.

   function votingDelay()
       public
       view
       override(IGovernor, GovernorSettings)
       returns (uint256)
   {
       return super.votingDelay();
   }

   function votingPeriod()
       public
       view
       override(IGovernor, GovernorSettings)
       returns (uint256)
   {
       return super.votingPeriod();
   }

   function quorum(uint256 blockNumber)
       public
       view
       override(IGovernor, GovernorVotesQuorumFraction)
       returns (uint256)
   {
       return super.quorum(blockNumber);
   }

   function state(uint256 proposalId)
       public
       view
       override(Governor, GovernorTimelockControl)
       returns (ProposalState)
   {
       return super.state(proposalId);
   }

   function propose(
       address[] memory targets,
       uint256[] memory values,
       bytes[] memory calldatas,
       string memory description
   ) public override(Governor, IGovernor) returns (uint256) {
       s_proposalCount++;
       return super.propose(targets, values, calldatas, description);
   }

   function proposalThreshold()
       public
       view
       override(Governor, GovernorSettings)
       returns (uint256)
   {
       return super.proposalThreshold();
   }

   function _execute(
       uint256 proposalId,
       address[] memory targets,
       uint256[] memory values,
       bytes[] memory calldatas,
       bytes32 descriptionHash
   ) internal override(Governor, GovernorTimelockControl) {
       super._execute(proposalId, targets, values, calldatas, descriptionHash);
   }

   function _cancel(
       address[] memory targets,
       uint256[] memory values,
       bytes[] memory calldatas,
       bytes32 descriptionHash
   ) internal override(Governor, GovernorTimelockControl) returns (uint256) {
       return super._cancel(targets, values, calldatas, descriptionHash);
   }

   function _executor()
       internal
       view
       override(Governor, GovernorTimelockControl)
       returns (address)
   {
       return super._executor();
   }

   function supportsInterface(bytes4 interfaceId)
       public
       view
       override(Governor, GovernorTimelockControl)
       returns (bool)
   {
       return super.supportsInterface(interfaceId);
   }

   function getNumberOfProposals() public view returns (uint256) {
       return s_proposalCount;
   }
}

O contrato inteligente MoralisGovernor combina várias extensões relacionadas a governança do OpenZeppelin para criar um sistema de governança rico em recursos. As características detalhadas incluem:

Herança:

  • Herda de várias extensões relacionadas à governança da OpenZeppelin: Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl.

Construtor:

  • Inicializa o contrato de governança com parâmetros como votingDelay, votingPeriod e quorumPercentage.

Eventos:

  • Inclui vários eventos relacionados a transferências de tokens, cunhagem e queima de tokens.

Funções:

  • função propose(...) public override returns (uint256): permite que qualquer pessoa proponha uma ação de governança.
  • Funções internas como _execute(), _cancel(), _executor() gerenciam a execução e o cancelamento de propostas.

Configurações:

  • Parâmetros como votingDelay, votingPeriod, quorumPercentage são configuráveis através do construtor.

Funcionalidade Adicional:

  • getNumberOfProposals(): retorna o número total de propostas criadas.

Este contrato é uma solução abrangente de governança, aproveitando as extensões do OpenZeppelin para criar um framework de governança flexível e seguro para a tomada de decisões descentralizada.

Ao encerrarmos esta odisseia de codificação, estabelecemos as bases para a arquitetura blockchain de nossa DAO. Na Parte 3, mudaremos o foco para explorar a implantação de contratos inteligentes alimentados pelo Python, revelando os segredos por trás da funcionalidade dinâmica desta DAO.


Artigo escrito por Harith D. Jayawardhane. Traduzido por Marcelo Panegali.