Fermer

juillet 24, 2018

Voter avec des jetons personnalisés –


Dans partie 5 de cette série de didacticiels sur la construction de DApps avec Ethereum, nous avons traité de l'ajout de contenu à l'histoire, comment ajouter la possibilité pour les participants d'acheter des jetons du DAO et d'ajouter des soumissions dans l'histoire. Il est maintenant temps pour la forme finale de DAO: vote, blacklisting / unblacklisting, distribution de dividendes et retrait. Nous allons ajouter quelques fonctions d'aide supplémentaires pour faire bonne mesure

Si vous vous perdez dans tout cela, le code source complet est disponible dans le repo .

Votes and Proposals

Nous allons émettre des propositions et voter avec des votes. Nous avons besoin de deux nouvelles structures:

 struct Proposal {
    description de la chaîne;
    bool exécuté;
    int256 currentResult;
    uint8 typeFlag; // 1 = supprimer
    bytes32 cible; // ID de la cible de la proposition. C'est à dire. drapeau 1, cible XXXXXX (hash) signifie proposition de suppression des soumissions [hash]
    uint256 creationDate;
    date limite uint256;
    la cartographie (adresse => bool) des électeurs;
    Votez [] votes;
    adresse émetteur;
}

Proposition [] propositions publiques;
uint256 proposalCount = 0;
l'événement ProposalAdded (uint256 id, uint8 typeFlag, octet32 hash, description de la chaîne, adresse de l'émetteur);
événement ProposalExecuted (uint256 id);
événement voté (adresse électeur, vote booléen, puissance uint256, justification de chaîne);

struct Vote {
    bool inSupport;
    adresse électeur;
    justification de la chaîne;
    puissance uint256;
}

Une Proposition aura une cartographie des électeurs pour empêcher les gens de voter sur une proposition deux fois, et d'autres métadonnées qui devraient être explicites. Le vote sera soit un vote par oui ou par non, et se souviendra de l'électeur avec leur justification pour voter d'une certaine manière, et le pouvoir de vote – le nombre de jetons qu'ils veulent consacrer au vote pour cette proposition. Nous avons également ajouté un tableau de propositions afin que nous puissions les stocker quelque part, et un compteur pour compter le nombre de propositions.

Construisons maintenant leurs fonctions d'accompagnement, en commençant par la fonction de vote:

 modificateur tokenHoldersOnly () {
    require (token.balanceOf (msg.sender)> = 10 ** token.decimals ());
    _;
}

fonction vote (uint256 _proposalId, bool_vote, chaîne _description, uint256 _votePower) tokenHoldersOnly public returns (int256) {

    require (_votePower> 0, "Au moins un pouvoir doit être donné au vote.");
    require (uint256 (_votePower) <= token.balanceOf(msg.sender), "Voter must have enough tokens to cover the power cost.");

    Proposal storage p = proposals[_proposalId];

    require(p.executed == false, "Proposal must not have been executed already.");
    require(p.deadline > maintenant, "La proposition ne doit pas avoir expiré.");
    require (p.voters [msg.sender] == false, "L'utilisateur ne doit pas déjà avoir voté.");

    uint256 voteid = p.votes.length ++;
    Vote stockage pvote = p.votes [voteid];
    pvote.inSupport = _vote;
    pvote.justification = _description;
    pvote.voter = msg.sender;
    pvote.power = _votePower;

    p.voters [msg.sender] = true;

    p.currentResult = (_vote)? p.currentResult + int256 (_votePower): p.currentResult - int256 (_votePower);
    token.increaseLockedAmount (msg.sender, _votePower);

    Emit voté (msg.sender, _vote, _votePower, _description);
    return p.currentResult;
}

Notez le modificateur de fonction: en ajoutant ce modificateur dans notre contrat, nous pouvons l'attacher à n'importe quelle fonction future et nous assurer que seuls les détenteurs de jetons peuvent exécuter cette fonction. C'est une vérification de sécurité réutilisable!

La ​​fonction de vote vérifie que le vote est positif, que l'électeur a assez de jetons pour voter, etc. Nous récupérons ensuite la proposition de stockage et vérifions qu'elle n'est pas expirée ni déjà exécutée. Il ne serait pas logique de voter sur une proposition déjà faite. Nous devons également nous assurer que cette personne n'a pas encore voté. Nous pourrions permettre de changer le pouvoir de vote, mais cela ouvre la DAO à certaines vulnérabilités comme des gens qui retirent leurs votes à la dernière minute, etc … Peut-être un candidat pour une future version?

Ensuite, nous enregistrons un nouveau vote dans la proposition. résultat actuel pour faciliter la recherche des scores, et enfin émettre l'événement voté. Mais qu'est-ce que token.increaseLockedAmount ?

Ce peu de logique augmente le nombre de jetons verrouillés pour un utilisateur. La fonction est uniquement exécutable par le propriétaire du contrat de jeton (à ce stade, espérons-le DAO) et empêchera l'utilisateur d'envoyer un nombre de jetons supérieur au montant verrouillé enregistré sur son compte. Ce verrou est levé après l'exécution ou l'exécution de la proposition

Écrivons les fonctions pour proposer la suppression d'une entrée maintenant.

Vote pour supprimer et liste noire

Comme établi dans partie 1 dans cette série, nous avons prévu trois fonctions de suppression d'entrée prévues:

  1. Supprimer l'entrée : lorsqu'elle est confirmée par un vote, l'entrée cible est supprimée. Temps de suffrage: 48 heures .
  2. Suppression d'urgence entrée [Only Owner]: peut seulement être déclenché par le propriétaire. Lorsqu'elle est confirmée par un vote, l'entrée cible est supprimée. Temps de réponse: 24 heures .
  3. Urgence remove image [Only Owner]: s'applique uniquement aux entrées d'image. Peut seulement être déclenché par le propriétaire. Lorsqu'elle est confirmée par un vote, l'entrée cible est supprimée. Temps de vote: 4 heures .

Cinq suppressions d'une seule adresse 'entrées mènent à une liste noire.

Voyons comment nous pouvons le faire maintenant. Tout d'abord, les fonctions de suppression:

 modificateur memberOnly () {
    require (liste blanche [msg.sender]);
    require (! blacklist [msg.sender]);
    _;
}

function proposDeletion (bytes32 _hash, chaîne _description) memberOnly public {

    require (submissionExists (_hash), "La soumission doit exister pour pouvoir être supprimée");

    uint256 proposalId = propositions.length ++;
    Stockage de proposition p = propositions [proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = maintenant;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = maintenant + 2 jours;

    émettent ProposalAdded (proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}

function proposDeletionUrgent (bytes32 _hash, chaîne _description) onlyOwner public {

    require (submissionExists (_hash), "La soumission doit exister pour pouvoir être supprimée");

    uint256 proposalId = propositions.length ++;
    Stockage de proposition p = propositions [proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = maintenant;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = maintenant + 12 heures;

    émettent ProposalAdded (proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}

function proposerDeletionUrgentImage (bytes32 _hash, chaîne _description) onlyOwner public {

    require (soumissions [_hash] .image == true, "La soumission doit être une image existante");

    uint256 proposalId = propositions.length ++;
    Stockage de proposition p = propositions [proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = maintenant;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = maintenant + 4 heures;

    émettent ProposalAdded (proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}

Une fois proposée, une proposition est ajoutée à la liste des propositions et note quelle entrée est ciblée par le hachage d'entrée. La description est enregistrée et certaines valeurs par défaut sont ajoutées, et un délai est calculé en fonction du type de proposition. L'événement ajouté à la proposition est émis et le nombre total de propositions est augmenté.

Voyons maintenant comment exécuter une proposition. Pour être exécutable, une proposition doit avoir suffisamment de votes, et doit avoir dépassé sa date limite. La fonction d'exécution acceptera l'ID de la proposition à exécuter. Il n'y a pas de moyen facile de faire en sorte que l'EVM exécute toutes les propositions en attente à la fois. Il est possible qu'un trop grand nombre soit en attente d'exécution et qu'ils apportent de gros changements aux données dans le DAO, ce qui pourrait dépasser la limite de gaz des blocs Ethereum, échouant ainsi la transaction. Il est beaucoup plus facile de construire une fonction d'exécution manuelle pouvant être appelée par n'importe qui avec des règles bien définies, ainsi la communauté peut garder un œil sur les propositions qui doivent être exécutées.

 function executeProposal (uint256 _id) public {
    Stockage de proposition p = propositions [_id];
    require (maintenant> = p.deadline &&! p.executed);

    if (p.typeFlag == 1 && p.currentResult> 0) {
        assert (deleteSubmission (p.target));
    }

    uint256 len = p.votes.length;
    pour (uint i = 0; i <len; i ++) {
        token.decreaseLockedAmount (p.votes [i] .voter, p.votes [i] .power);
    }

    p.executed = true;
    émettre ProposalExecuted (_id);
}

Nous saisissons la proposition par son ID, vérifions qu'elle répond aux exigences de ne pas avoir été exécutées et que le délai est expiré, puis si le type de proposition est une proposition de suppression et que le résultat du vote est positif, nous utilisons le déjà écrit fonction de suppression, pour finalement émettre un nouvel événement que nous avons ajouté (ajoutez-le au début du contrat). L'appel assert sert ici le même but que l'instruction require : assert est généralement utilisé lorsque vous "affirmez" qu'un résultat est vrai. Require est utilisé pour les prérequis. Fonctionnellement, ils sont identiques, avec la différence de affirmer les déclarations ne sont pas en mesure d'accepter les paramètres de message pour les cas où ils échouent. La fonction se termine par débloquer les jetons pour tous les votes dans cette proposition.

Nous pouvons utiliser cette même approche pour ajouter d'autres types de propositions, mais d'abord, mettons à jour la fonction deleteSubmission pour interdire les utilisateurs qui ont cinq suppressions ou plus sur leur compte: cela signifie qu'ils ont régulièrement soumis du contenu contre lequel la communauté a voté. Mettons à jour la fonction deleteSubmission :

 function deleteSubmission (hastes32 hash) internal returns (bool) {
    require (submissionExists (hash), "Submission doit exister pour être effaçable.");
    Submission storage sub = soumissions [hash];

    sub.exists = false;
    suppressions [soumissions[hash] .submitter] + = 1;
    if (suppressions [soumissions[hash] .submitter]> = 5) {
        blacklistAddress (soumissions [hash] .submitter);
    }

    Emission SubmissionDeleted (
        sub.index,
        sous-contenu,
        sous.image,
        sub.smitter
    )

    nonDeletedSubmissions - = 1;
    retourner vrai;
}

C'est mieux. Auto-blacklisting sur cinq suppressions. Toutefois, il ne serait pas juste de ne pas donner aux adresses figurant sur la liste noire une chance de se racheter. Nous devons également définir la fonction de liste noire elle-même. Faisons ces deux choses et définissons la taxe de liste noire à, par exemple, 0.05 ether.

 function blacklistAddress (adresse _offender) internal {
    require (liste noire [_offender] == false, "Impossible de mettre en liste noire un utilisateur sur liste noire: /");
    liste noire [_offender] == true;
    token.increaseLockedAmount (_offender, token.getUnlockedAmount (_offender));
    Emit Blacklisted (_offender, true);
}

function unblacklistMe () payable public {
    unblacklistAddress (msg.sender);
}

function unblacklistAddress (adresse _offender) payable public {
    require (msg.value> = 0.05 ether, "Unblacklisting fee");
    require (blacklist [_offender] == true, "Impossible de débloquer une liste d'utilisateurs non-blacklistés: /");
    require (notVoting (_offender), "le délinquant ne doit pas être impliqué dans un vote.");
    withdrawableByOwner = withdrawableByOwner.add (msg.value);
    liste noire [_offender] = false;
    token.decreaseLockedAmount (_offender, token.balanceOf (_offender));
    Emit Blacklisted (_offender, false);
}

function notVoting (adresse _voter) affichage interne returns (bool) {
    pour (uint256 i = 0; i <proposalCount; i ++) {
        if (propositions [i] .executed == false && propositions [i] .voters [_voter] == true) {
            return false;
        }
    }
    retourner vrai;
}

Notez que les jetons d'un compte sur une liste noire sont verrouillés jusqu'à ce qu'ils envoient les frais de déblocage

Autres types de votes

En s'inspirant des fonctions décrites ci-dessus, essayez d'écrire les autres propositions. Pour les spoilers, consultez le rapport GitHub du projet et copiez le code final à partir de là. Par souci de brièveté, passons aux autres fonctions qui nous restent dans le DAO

Chapter End

Une fois la limite de temps ou de chapitre de l'histoire atteinte, il est temps de mettre fin à l'histoire. N'importe qui peut appeler la fonction de fin après la date qui permettra des retraits de dividendes. Premièrement, nous avons besoin d'un nouvel attribut StoryDAO et d'un événement:

 bool public active = true;
événement StoryEnded ();

Ensuite, construisons la fonction:

 function endStory () storyActive external {
    retireToOwner ();
    actif = faux;
    émettre StoryEnded ();
}

Simple: il désactive l'histoire après avoir envoyé les frais collectés au propriétaire et émet l'événement. Mais en réalité, cela ne change vraiment rien à l'ensemble du DAO: les autres fonctions ne réagissent pas à la fin. Alors construisons un autre modificateur:

 modificateur storyActive () {
    require (actif == vrai);
    _;
}

Ensuite, nous ajoutons ce modificateur à toutes les fonctions sauf withdrawToOwner comme ceci:

 function whitelistAddress (address _add) storyActive public payable {

Dans le cas où des jetons sont restés dans le DAO, récupérons-les et prenons possession de ces jetons afin de pouvoir les utiliser plus tard sur une autre histoire:

 function retireLeftoverTokens () external onlyOwner {
    require (actif == faux);
    token.transfer (msg.sender, token.balanceOf (adresse (this)));
    token.transferOwnership (msg.sender);
}

function unlockMyTokens () external {
    require (actif == faux);
    require (token.getLockedAmount (msg.sender)> 0);

    token.decreaseLockedAmount (msg.sender, token.getLockedAmount (msg.sender));
}

La fonction unlockMyTokens est là pour déverrouiller tous les jetons verrouillés au cas où certains restent verrouillés pour un utilisateur spécifique. Cela ne devrait pas arriver, et cette fonction devrait être supprimée par une bonne quantité de tests

Répartition des dividendes et retraits

Maintenant que l'histoire est terminée, les frais perçus pour les soumissions doivent être distribués à tous les détenteurs de jetons. Nous pouvons réutiliser notre liste blanche pour marquer tous ceux qui ont fait le retrait des frais:

 function withdrawDividend () memberOnly external {
    require (actif == faux);
    uint256 Doit = ​​adresse (this) .balance.div (whitelistedNumber);
    msg.sender.transfer (dû);
    liste blanche [msg.sender] = false;
    whitelistedNumber--;
}

Si ces dividendes ne sont pas retirés dans un certain délai, le propriétaire peut récupérer le reste:

 function withdrawEverythingPostDeadline () external onlyOwner {
    require (actif == faux);
    exiger (maintenant> délai + 14 jours);
    owner.transfer (adresse (this) .balance);
}

Pour les devoirs, considérez comme il est facile ou difficile de réutiliser ce même contrat intelligent déployé, d'effacer ses données et de conserver les jetons dans le pot et de recommencer un autre chapitre sans redéploiement. Essayez de le faire vous-même et gardez un œil sur le rapport pour les futures mises à jour de la série de tutoriels couvrant cela! Pensez aussi à d'autres mécanismes d'incitation: peut-être que le nombre de jetons dans un compte influence le dividende qu'ils tirent de la fin de l'histoire? Votre imagination est la limite!

Problèmes de déploiement

Étant donné que notre contrat est assez grand maintenant, le déploiement et / ou le test pourrait dépasser la limite de gaz des blocs Ethereum. C'est ce qui limite le déploiement de grandes applications sur le réseau Ethereum. Pour le déployer de toute façon, essayez d'utiliser l'optimiseur de code lors de la compilation en modifiant le fichier truffle.js pour inclure les paramètres solc pour l'optimisation, comme suit:

 // ...

module.exports = {
  solc: {
    optimiseur: {
      enabled: true,
      fonctionne: 200
    }
  },
  réseaux: {
    développement: {
// ...

Ceci exécutera 200 fois l'optimiseur sur le code pour trouver les zones qui peuvent être minifiées, supprimées ou abstraites avant le déploiement, ce qui devrait réduire considérablement les coûts de déploiement.

Conclusion

Ceci conclut notre développement DAO exhaustif – mais le cours n'est pas encore fini! Nous devons encore construire et déployer l'interface utilisateur pour cette histoire. Heureusement, avec l'arrière complètement hébergé sur la blockchain, la construction de l'avant est beaucoup moins compliquée. Regardons cela dans notre pénultième partie de cette série.






Source link