WEB3DEV

Cover image for Criação de uma Organização Autônoma Descentralizada (DAO) do zero
Dimitris Carvalho Calixto
Dimitris Carvalho Calixto

Posted on

Criação de uma Organização Autônoma Descentralizada (DAO) do zero

As DAOs (Decentralized Autonomous Organizations, Organizações Autônomas Descentralizadas) são a base do ecossistema descentralizado. As DAOs são organizações governadas por contratos inteligentes e cujos processos de tomada de decisão são executados de forma descentralizada. Neste artigo, vamos nos aprofundar nos conceitos avançados do Solidity para criar uma DAO do zero._

Image

Visão geral da estrutura de uma DAO

Nossa DAO consistirá nos seguintes componentes

  1. Um contrato inteligente que representa a própria DAO.
  2. Um token que representa o poder de voto e a propriedade dentro da DAO.
  3. Um mecanismo de proposta para enviar e votar em propostas.
  4. Uma tesouraria para gerenciar fundos e executar propostas que tenham sido aprovadas.

Pré-requisitos

Para seguir este tutorial, você deve ter um conhecimento básico de Solidity, Ethereum e do ambiente de desenvolvimento Truffle.

Etapa 1: Criação do token da DAO

Primeiro, vamos criar um novo token ERC20 para a nossa DAO. Esse token será usado para representar o poder de voto dentro da organização.


// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

contract DAOToken is ERC20 {

    constructor(uint256 initialSupply) ERC20("DAO Token", "DAO") {

        _mint(msg.sender, initialSupply);

    }

}

Enter fullscreen mode Exit fullscreen mode

Neste contrato, estamos usando a biblioteca OpenZeppelin para criar um novo token ERC20. O construtor recebe o suprimento inicial como parâmetro e cria os tokens para o implantador.

Etapa 2: Criação do contrato da DAO

Em seguida, vamos criar o contrato principal da DAO. Crie um novo arquivo chamado DAO.sol


// SPDX-License-Identifier: MIT

prasgma solidity ^0.8.0;

import "./DAOToken.sol";

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

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

contract DAO is Ownable {

    using EnumerableSet for EnumerableSet.AddressSet;

    // O contrato do token da DAO

    DAOToken public daoToken;

    // A quantidade mínima de tokens necessária para criar uma proposta

    uint256 public constant MIN_PROPOSAL_THRESHOLD = 1000 * 10**18;

    // A quantidade mínima de tokens necessária para votar em uma proposta

    uint256 public constant MIN_VOTING_THRESHOLD = 100 * 10**18;

    // Estrutura da proposta

    struct Proposal {

        uint256 id;

        address proposer;

        string description;

        uint256 amount;

        address payable recipient;

        uint256 startTime;

        uint256 endTime;

        uint256 yesVotes;

        uint256 noVotes;

        EnumerableSet.AddressSet voters;

        bool executed;

    }

    // Lista de todas as propostas

    Proposal[] public proposals;

    // Mapping para verificar se um endereço tem uma proposta ativa

    mapping(address => bool) public activeProposals;

    // Evento para uma nova proposta

    event NewProposal(uint256 indexed proposalId, address indexed proposer, string description);

    // Evento para a execução de uma proposta

    event ProposalExecuted(uint256 indexed proposalId, address indexed proposer, address indexed recipient, uint256 amount);

    constructor(DAOToken _daoToken) {

        daoToken = _daoToken;

    }

    // Função para criar uma nova proposta

    function createProposal(string memory _description, uint256 _amount, address payable _recipient) external {

        require(daoToken.balanceOf(msg.sender) >= MIN_PROPOSAL_THRESHOLD, "Insufficient tokens to create proposal");

        require(!activeProposals[msg.sender], "You already have an active proposal");



        Proposal memory newProposal = Proposal({

            id: proposals.length,

            proposer: msg.sender,

            description: _description,

            amount: _amount,

            recipient: _recipient,

            startTime: block.timestamp,

            endTime: block.timestamp + 7 days,

            yesVotes: 0,

            noVotes: 0,

            voters: new EnumerableSet.AddressSet(),

            executed: false

        });



        proposals.push(newProposal);

        activeProposals[msg.sender] = true;

        emit NewProposal(newProposal.id, msg.sender, _description);

    }



    // Função para votar em uma proposta

    function vote(uint256 _proposalId, bool _support) external {

        require(daoToken.balanceOf(msg.sender) >= MIN_VOTING_THRESHOLD, "Insufficient tokens to vote");

        Proposal storage proposal = proposals[_proposalId];

        require(block.timestamp >= proposal.startTime && block.timestamp <= proposal.endTime, "Invalid voting period");

        require(!proposal.voters.contains(msg.sender), "You have already voted on this proposal");



        uint256 voterWeight = daoToken.balanceOf(msg.sender);

        if (_support) {

            proposal.yesVotes += voterWeight;

        } else {

            proposal.noVotes += voterWeight;

        }



        proposal.voters.add(msg.sender);

    }



    // Função para executar uma proposta

    function executeProposal(uint256 _proposalId) external {

        Proposal storage proposal = proposals[_proposalId];

        require(!proposal.executed, "Proposal has already been executed");

        require(block.timestamp > proposal.endTime, "Voting period is still ongoing");

        require(proposal.yesVotes > proposal.noVotes, "Proposal has not reached majority support");



        proposal.recipient.transfer(proposal.amount);

        proposal.executed = true;

        activeProposals[proposal.proposer] = false;

        emit ProposalExecuted(_proposalId, proposal.proposer, proposal.recipient, proposal.amount);

    }



    // Função de retirada de fundos da DAO

    function withdraw(uint256 _amount) external onlyOwner {

        payable(owner()).transfer(_amount);

    }



    // Função de fallback para aceitar Ether

    receive() external payable {}

}

Enter fullscreen mode Exit fullscreen mode

Neste contrato, definimos a estrutura principal da DAO

  • O contrato de token da DAO é importado e armazenado como uma variável.
  • Uma estrutura de proposta é definida com os campos necessários, como ID da proposta, o propositor, descrição, valor, destinatário e detalhes da votação.
  • Uma lista armazena todas as propostas e um mapeamento mantém o controle das propostas ativas.
  • São criadas funções para lidar com a criação, votação e execução de propostas.

Etapa 3: implantando e testando a DAO

Agora que criamos o contrato da DAO, vamos implantá-lo e testar sua funcionalidade. Primeiro, vamos criar um arquivo de migração para nossos contratos


const DAOToken = artifacts.require("DAOToken"); const DAO = artifacts.require("DAO");

module.exports = async function (deployer) {

  // Implantar o contrato DAOToken com um fornecimento inicial de 1.000.000 tokens

  await deployer.deploy(DAOToken, "1000000" + "0".repeat(18));

  const daoTokenInstance = await DAOToken.deployed();



  // Implantar o contrato da DAO com uma referência ao contrato DAOToken

  await deployer.deploy(DAO, daoTokenInstance.address);

  const daoInstance = await DAO.deployed();

};

Enter fullscreen mode Exit fullscreen mode

Agora vamos adicionar alguns testes para garantir que nosso contrato da DAO esteja funcionando conforme o esperado.


const { assert } = require("chai");

const { expectRevert, time } = require("@openzeppelin/test-helpers");

const DAOToken = artifacts.require("DAOToken");

const DAO = artifacts.require("DAO");

contract("DAO", ([deployer, user1, user2, recipient]) => {

  beforeEach(async () => {

    this.daoToken = await DAOToken.new("1000000" + "0".repeat(18), { from: deployer });

    this.dao = await DAO.new(this.daoToken.address, { from: deployer });

  });

  it("should create a proposal and vote on it", async () => {

    // Transferência de tokens para o usuário1

    await this.daoToken.transfer(user1, "1100" + "0".repeat(18), { from: deployer });

    // Usuário1 cria uma proposta

    await this.dao.createProposal("Fund project X", "100" + "0".repeat(18), recipient, {

    from: user1,

    });

    //Verifiqua os detalhes da proposta

    const proposal = await this.dao.proposals(0);

    assert.equal(proposal.id, "0");

    assert.equal(proposal.proposer, user1);

    assert.equal(proposal.description, "Fund project X");

    assert.equal(proposal.amount, "100" + "0".repeat(18));

    assert.equal(proposal.recipient, recipient);

    // Usuário1 vota na proposta

    await this.dao.vote(0, true, { from: user1 });

    // Verifica os resultados da votação

    assert.equal((await this.dao.proposals(0)).yesVotes, "1100" + "0".repeat(18));

    assert.equal((await this.dao.proposals(0)).noVotes, "0");



    // Avanço rápido para depois do período de votação

    await time.increase(time.duration.days(8));



    // Executa a proposta

    await this.dao.executeProposal(0, { from: user1 });



    // Verifica se a proposta foi executada

    assert.equal((await this.dao.proposals(0)).executed, true);

  });

  it("should not allow a user with insufficient tokens to create a proposal", async () => {

    // Tentativa de criar uma proposta com tokens insuficientes



    await expectRevert(

    this.dao.createProposal("Fund project X", "100" + "0".repeat(18), recipient, { from: user2 }),

    "Insufficient tokens to create proposal"

    );

  });

  // Adicione mais testes conforme necessário

});

Enter fullscreen mode Exit fullscreen mode

Neste arquivo de teste, abordamos dois testes principais

  • Criar uma proposta e votar nela.
  • Garantir que um usuário com tokens insuficientes não possa criar uma proposta. Você pode executar os testes usando o seguinte comando:

Etapa 4: Interagindo com a DAO

Você pode usar uma interface da Web para interagir com a DAO implantada.

Os principais componentes da interface da Web podem incluir

  • Um painel que exibe o saldo do token da DAO e a lista de propostas.
  • Um formulário para criar uma nova proposta.
  • Uma interface de votação para votar em propostas existentes.
  • Um botão para executar propostas aprovadas.

Conclusão

O potencial da Web 3.0 e dos aplicativos descentralizados é vasto e oferece oportunidades interessantes para os desenvolvedores moldarem o futuro da Internet. Ao aproveitar o poder da tecnologia blockchain, podemos criar aplicativos mais seguros, transparentes e resilientes que capacitam os usuários e promovem a descentralização.

Como desenvolvedor, você tem a chance de contribuir para o crescimento do ecossistema descentralizado e criar soluções inovadoras para resolver problemas do mundo real. Eu o incentivo a explorar as inúmeras possibilidades que as tecnologias web3 oferecem, desde finanças descentralizadas (DeFi) e tokens não fungíveis (NFTs) até armazenamento descentralizado e gerenciamento de identidade.

Artigo escrito por Faucet Consumer. A versão original pode ser encontrada aqui. Traduzido e adaptado por Dimitris Calixto.

Top comments (0)