Fermer

juillet 16, 2018

Mise en liste blanche et test d'une histoire DAO –


Dans partie 3 de cette série de tutoriels sur la construction de DApps avec Ethereum, nous avons construit et déployé notre jeton sur l'Ethereum testnet Rinkeby. Dans cette partie, nous commencerons à écrire le code Story DAO.

Nous utiliserons les conditions énoncées dans le post d'introduction pour nous guider.

Contour du contrat

Créons un nouveau contrat, StoryDao.sol avec ce squelette:

 pragma solidity ^ 0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
importer "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";

contrat StoryDao est propriétaire
    en utilisant SafeMath pour uint256;

    liste blanche (address => bool) whitelist;
    uint256 whitelistedNumber public = 0;
    mappage (adresse => booléen) liste noire;
    event Whitelisted (adresse addr, statut booléen);
    event Blacklisté (adresse addr, statut booléen);

    uint256 public daofee = 100; // centièmes de pour cent, c'est-à-dire 100 est 1%
    uint256 whitelistfee public = 10000000000000000; // dans Wei, c'est 0.01 ether

    event SubmissionCommissionChanged (uint256 newFee);
    event WhitelistFeeChanged (uint256 newFee);

    uint256 durée publique Jours = 21; // durée du chapitre de l'histoire en jours
    uint256 public durationSubmissions = 1000; // durée du chapitre de l'histoire dans les entrées

    function changedaofee (uint256 _fee) onlyOwner externe {
        require (_fee <daofee, "Les nouveaux frais doivent être inférieurs aux anciens frais.");
        daofee = _fee;
        Emission SubmissionCommissionChanged (_fee);
    }

    function changewhitelistfee (uint256 _fee) onlyOwner externe {
        require (_fee <whitelistfee, "Les nouveaux frais doivent être inférieurs aux anciens frais.");
        whitelistfee = _fee;
        Emettre WhitelistFeeChanged (_fee);
    }

    function lowerSubmissionFee (uint256 _fee) onlyOwner externe {
        require (_fee < submissionZeroFee, "New fee must be lower than old fee.");
        submissionZeroFee = _fee;
        emit SubmissionFeeChanged(_fee);
    }

    function changeDurationDays(uint256 _days) onlyOwner external {
        require(_days > = 1);
        durationDays = _days;
    }

    function changeDurationSubmissions (uint256 _subs) onlyOwner externe {
        require (_subs> 99);
        durationSubmissions = _subs;
    }
}

Nous importons SafeMath pour avoir des calculs sécurisés, mais cette fois nous utilisons également le contrat Ownable de Zeppelin, qui permet à quelqu'un de "posséder" l'histoire et d'exécuter certaines fonctions d'administration seulement. Dire simplement que notre StoryDao est Ownable est suffisant; N'hésitez pas à consulter le contrat pour voir comment cela fonctionne.

Nous utilisons également le modificateur onlyOwner de ce contrat. Les modificateurs de fonction sont essentiellement des extensions, des plugins pour les fonctions. Le modificateur onlyOwner ressemble à ceci:

 modificateur onlyOwner () {
  require (msg.sender == propriétaire);
  _;
}

Lorsque onlyOwner est ajouté à une fonction, le corps de cette fonction est collé dans la partie où se trouve la partie _ et tout ce qui précède son exécution premier. Donc, en utilisant ce modificateur, la fonction vérifie automatiquement si l'expéditeur du message est également le propriétaire du contrat et continue comme d'habitude si c'est le cas. Sinon, il se bloque.

En utilisant le modificateur onlyOwner sur les fonctions qui modifient les frais et autres paramètres de notre histoire DAO, nous nous assurons que seul l'administrateur peut faire ces changements.

Test

Testons les fonctions initiales

Créez le dossier test s'il n'existe pas. Puis à l'intérieur, créez les fichiers TestStoryDao.sol et TestStoryDao.js . Parce qu'il n'y a aucune façon native de tester les exceptions dans Truffle, créez également helpers / expectThrow.js avec le contenu:

 export default async promise => {
    essayez {
      attendre la promesse;
    } catch (erreur) {
      const invalidOpcode = error.message.search ('opcode invalide')> = 0;
      const outOfGas = erreur.message.search ('out of gas')> = 0;
      const revert = error.message.search ('revert')> = 0;
      affirmer(
        invalidOpcode || outOfGas || revenir,
        'Jet attendu, got ' '+ erreur +'  'à la place',
      )
      revenir;
    }
    assert.fail ('Jet attendu non reçu');
  }

Note: Les tests de solidité sont généralement utilisés pour tester des fonctions de bas niveau, basées sur des contrats, les internes d'un contrat intelligent. Les tests JS sont généralement utilisés pour tester si le contrat peut être correctement interagi de l'extérieur, ce que feront nos utilisateurs finaux

Dans TestStoryDao.sol mettez le contenu suivant :

 solidité pragma ^ 0,4,24;

importer "truffle / Assert.sol";
import "truffle / DeployedAddresses.sol";
importer "../contracts/StoryDao.sol";

contrat TestStoryDao {

    function testDeploymentIsFine () public {
        StoryDao sd = StoryDao (DeployedAddresses.StoryDao ());

        uint256 daofee = 100; // centièmes de pour cent, c'est-à-dire 100 est 1%
        uint256 whitelistfee = 10000000000000000; // dans Wei, c'est 0.01 ether

        uint256 durationDays = 21; // durée du chapitre de l'histoire en jours
        uint256 durationSubmissions = 1000; // durée du chapitre de l'histoire dans les entrées

        Assert.equal (sd.daofee (), daofee, "La taxe DAO initiale doit être de 100");
        Assert.equal (sd.whitelistfee (), whitelistfee, "Les frais initiaux de mise en liste blanche doivent être 0,01 ether");
        Assert.equal (sd.durationDays (), durationDays, "La durée initiale du jour doit être fixée à 3 semaines");
        Assert.equal (sd.durationSubmissions (), durationSubmissions, "La durée de soumission initiale doit être fixée à 1000 entrées");
    }
}

Ceci vérifie que le contrat StoryDao est correctement déployé avec les bons numéros pour les frais et la durée. La première ligne s'assure qu'il est déployé en le lisant depuis la liste des adresses déployées, et la dernière section fait quelques assertions – vérifiant qu'une revendication est vraie ou fausse. Dans notre cas, nous comparons les nombres aux valeurs initiales du contrat déployé. Chaque fois que c'est "vrai", la partie Assert.equals émettra un événement qui dit "True", ce que Truffle est en train d'écouter quand il est en test.

In TestStoryDao.js , mettez le contenu suivant:

 import expectThrow from './helpers/expectThrow';

const StoryDao = artefacts.require ("StoryDao");

contrat ('StoryDao Test', async (comptes) => {

    il ("devrait s'assurer que l'environnement est OK en vérifiant que les 3 premiers comptes ont plus de 20 eth", async () => {
        assert.equal (web3.eth.getBalance (accounts [0]). toNumber ()> 2e + 19, true, "Le compte 0 a plus de 20 eth");
        assert.equal (web3.eth.getBalance (accounts [1]). toNumber ()> 2e + 19, true, "Le compte 1 a plus de 20 eth");
        assert.equal (web3.eth.getBalance (accounts [2]). toNumber ()> 2e + 19, true, "Le compte 2 a plus de 20 eth");
    });

    il ("devrait faire du déployeur le propriétaire", async () => {
        Laisser instance = attendre StoryDao.deployed ();
        assert.equal (await instance.owner (), accounts [0]);
    });

    il ("devrait laisser le propriétaire changer les frais et la durée", async () => {
        Laisser instance = attendre StoryDao.deployed ();

        laissez newDaoFee = 50;
        laissez newWhitelistFee = 1e + 10; // 1 éther
        laissez newDayDuration = 42;
        laissez newSubsDuration = 1500;

        instance.changedaofee (newDaoFee, {from: accounts [0]});
        instance.changewhitelistfee (newWhitelistFee, {from: accounts [0]});
        instance.changedurationdays (newDayDuration, {from: accounts [0]});
        instance.changedurationsubmissions (newSubsDuration, {from: accounts [0]});

        assert.equal (attend instance.daofee (), newDaoFee);
        assert.equal (pending instance.whitelistfee (), newWhitelistFee);
        assert.equal (attend instance.durationDays (), newDayDuration);
        assert.equal (pending instance.durationSubmissions (), newSubsDuration);
    });

    il ("devrait interdire aux non-propriétaires de modifier les frais et la durée", async () => {
        Laisser instance = attendre StoryDao.deployed ();

        laissez newDaoFee = 50;
        laissez newWhitelistFee = 1e + 10; // 1 éther
        laissez newDayDuration = 42;
        laissez newSubsDuration = 1500;

        wait expectThrow (instance.changedaofee (newDaoFee, {from: accounts [1]}));
        wait expectWow (instance.changewhitelistfee (newWhitelistFee, {from: accounts [1]}));
        wait expectThrow (instance.changedurationdays (newDayDuration, {from: accounts [1]}));
        wait expectThrow (instance.changedurationsubmissions (newSubsDuration, {from: accounts [1]}));
    });

    il ("devrait s'assurer que le propriétaire ne peut changer les frais et la durée que pour des valeurs valides", async () => {
        Laisser instance = attendre StoryDao.deployed ();

        Soit invalidDaoFee = 20000;
        laissez invalidDayDuration = 0;
        laissez invalidSubsDuration = 98;

        wait expectThrow (instance.changedaofee (invalidDaoFee, {from: accounts [0]}));
        wait expectThrow (instance.changedurationdays (invalidDayDuration, {from: accounts [0]}));
        wait expectThrow (instance.changedurationsubmissions (invalidSubsDuration, {from: accounts [0]}));
    })
});

Pour que nos tests soient couronnés de succès, nous devons aussi dire à Truffle que nous voulons que le StoryDao soit déployé – parce qu'il ne le fera pas pour nous. Alors créons 3_deploy_storydao.js dans migrations avec un contenu presque identique à la migration précédente que nous avons écrit:

 var Migrations = artefacts.require ("./ Migrations.sol");
var StoryDao = artefacts.require ("./ StoryDao.sol");

module.exports = fonction (déployeur, réseau, comptes) {
  if (network == "développement") {
    deployer.deploy (StoryDao, {from: accounts [0]});
  } autre {
    deployer.deploy (StoryDao);
  }
}

À ce stade, nous devrions également mettre à jour (ou créer, si ce n'est pas le cas) un fichier package.json à la racine de notre dossier de projet avec les dépendances dont nous avions besoin jusqu'à présent et dont nous aurions peut-être besoin. avenir proche:

 {
  "nom": "storydao",
  "devDependencies": {
    "babel-preset-es2015": "^ 6.18.0",
    "babel-preset-stage-2": "^ 6.24.1",
    "babel-preset-stage-3": "^ 6.17.0",
    "babel-polyfill": "^ 6.26.0",
    "babel-register": "^ 6.23.0",
    "dotenv": "^ 6.0.0",
    "truffe": "^ 4.1.12",
    "openzeppelin-solidity": "^ 1.10.0",
    "openzeppelin-solidity-metadata": "^ 1.2.0",
    "openzeppelin-zos": "",
    "truffle-wallet-provider": "^ 0.0.5",
    "ethereumjs-wallet": "^ 0.6.0",
    "web3": "^ 1.0.0-beta.34",
    "affirmations de truffes": "^ 0.3.1"
  }
}

Et un fichier .babelrc avec le contenu:

 {
  "préréglages": ["es2015", "stage-2", "stage-3"]
}

Et nous avons aussi besoin de Babel dans notre configuration Truffle pour qu'il sache qu'il devrait l'utiliser lors de la compilation des tests.

Note: Babel est un add-on pour NodeJS qui nous permet d'utiliser le JavaScript de prochaine génération. NodeJS de génération actuelle, donc nous pouvons écrire des choses comme importer etc. Si cela dépasse votre compréhension, il suffit de l'ignorer et de coller ce mot à mot. Vous n'aurez probablement plus jamais à le faire après l'avoir installé de cette façon.

 require ('dotenv'). Config ();

================== AJOUTER CES DEUX LIGNES ===============
require ('registre-babel');
require ('babel-polyfill');
============================================= =====

const WalletProvider = require ("truffle-wallet-provider");
const Wallet = require ('ethereumjs-wallet');

// ...

Maintenant, enfin lance test de la truffe . La sortie devrait être similaire à celle-ci:

 Un test réussi

Pour plus d'informations sur les tests, voir ce tutoriel que nous avons préparé spécifiquement pour tester les contrats intelligents

Dans les parties suivantes de ce cours, nous allons passer les tests, car les taper rendrait les tutoriels trop longs, mais s'il vous plaît se référer au code source final du projet pour les inspecter tous. Le processus que nous venons de passer a mis en place l'environnement de test, donc vous pouvez écrire les tests sans aucune configuration supplémentaire.

Whitelist

Construisons maintenant le mécanisme de liste blanche, qui permet aux utilisateurs de construire l'histoire. Ajoutez les squelettes de fonction suivants à StoryDao.sol :

 function whitelistAddress (adresse _add) public payable {
    // expéditeur de liste blanche si suffisamment d'argent a été envoyé
}

function () externe payable {
    // si elle n'est pas en liste blanche, la liste blanche si elle est suffisamment payée
    // si en liste blanche, mais X jetons au prix X pour le montant
}

La fonction sans nom function () est appelée une fonction de repli et c'est la fonction qui est appelée quand de l'argent est envoyé à ce contrat sans une instruction spécifique fonction spécifiquement). Cela permet aux utilisateurs de rejoindre le StoryDao en envoyant simplement Ether au DAO et en recevant instantanément la liste blanche, ou en achetant des jetons, selon qu'ils sont ou non déjà inclus dans la liste blanche.

La ​​fonction whitelistSender est disponible pour peut être appelée directement, mais nous ferons en sorte que la fonction de repli l'appelle automatiquement lorsqu'elle reçoit de l'éther si l'expéditeur n'a pas encore été ajouté à la liste blanche. La fonction whitelistAddress est déclarée publique car elle devrait être appelable à partir d'autres contrats, et la fonction de repli est externe parce que l'argent ira à cette adresse seulement à partir d'adresses externes. Les contrats appelant ce contrat peuvent facilement appeler les fonctions requises directement.

Commençons d'abord par la fonction de repli.

 function () external payable {

    if (! whitelist [msg.sender]) {
        liste blanche (msg.sender);
    } autre {
        // buyTokens (msg.sender, msg.value);
    }
}

Nous vérifions si l'expéditeur n'est pas déjà sur la liste blanche, et déléguons l'appel à la fonction whitelistAddress . Notez que nous avons mis en commentaire notre fonction buyTokens parce que nous ne l'avons pas encore.

Ensuite, traitons la whitelisting.

 function whitelistAddress (adresse _add) public payable {
    require (! whitelist [_add]"Le candidat ne doit pas figurer sur la liste blanche.");
    require (! blacklist [_add]"Le candidat ne doit pas être sur une liste noire");
    require (msg.value> = whitelistfee, "L'expéditeur doit envoyer suffisamment d'éther pour couvrir les frais de mise en liste blanche.");

    liste blanche [_add] = true;
    liste blanche numéro ++;
    Emit Whitelisted (_add, true);

    if (msg.value> whitelistfee) {
        // buyTokens (_add, msg.value.sub (whitelistfee));
    }
}

Notez que cette fonction accepte l'adresse en tant que paramètre et ne l'extrait pas du message (de la transaction). Cela a l'avantage supplémentaire de pouvoir ajouter d'autres personnes sur la liste blanche, par exemple si quelqu'un n'a pas les moyens de rejoindre le DAO.

Nous commençons la fonction avec quelques vérifications: l'expéditeur ne doit pas être déjà en liste blanche ) et doit avoir envoyé assez pour couvrir les frais. Si ces conditions sont satisfaisantes, l'adresse est ajoutée à la liste blanche, l'événement Whitelisted est émis et, enfin, si la quantité d'éther envoyée est supérieure à la quantité d'éther nécessaire pour couvrir la taxe d'inscription, le reste est utilisé pour acheter les jetons.

Note: nous utilisons sous au lieu de - pour soustraire, parce que c'est une fonction SafeMath pour des calculs sécurisés.

Les utilisateurs peuvent maintenant obtenir eux-mêmes ou d'autres whitelisted tant qu'ils envoient 0,01 éther ou plus au contrat StoryDao.

Conclusion

Nous avons construit la partie initiale de notre DAO dans ce tutoriel, mais beaucoup plus de travail reste. Restez à l'écoute: dans la partie suivante, nous aborderons l'ajout de contenu à l'histoire!






Source link