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 eventoValueChanged
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 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.
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
eadmin
.
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
equorumPercentage
.
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
comovotingDelay
,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)