WEB3DEV

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

Posted on

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
Enter fullscreen mode Exit fullscreen mode

À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
Enter fullscreen mode Exit fullscreen mode

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

cd .\DAO---Template-by-Roxen\
code .
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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 porTOKENS_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.
Enter fullscreen mode Exit fullscreen mode

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;
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)