Maison >Périphériques technologiques >Industrie informatique >Construire Ethereum Dapps: Liste blanche et test d'une histoire Dao
Points clés
La troisième partie de cette série de didacticiels décrit la construction d'un DAPP en utilisant Ethereum, où nous construisons et déploiez le jeton sur le réseau de test Ethereum Rinkeby. Dans cette section, nous commencerons à écrire l'histoire Dao Code.
Nous nous guiderons en utilisant les critères répertoriés dans l'article d'introduction.
Présentation du contrat
Créons un nouveau contrat storydao.sol, dont le cadre est le suivant:
<code class="language-solidity">pragma solidity ^0.4.24; import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract StoryDao is Ownable { using SafeMath for uint256; mapping(address => bool) whitelist; uint256 public whitelistedNumber = 0; mapping(address => bool) blacklist; event Whitelisted(address addr, bool status); event Blacklisted(address addr, bool status); uint256 public daofee = 100; // 百分之几,即 100 为 1% uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 event SubmissionCommissionChanged(uint256 newFee); event WhitelistFeeChanged(uint256 newFee); uint256 public durationDays = 21; // 故事章节持续时间(天) uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目) function changedaofee(uint256 _fee) onlyOwner external { require(_fee <= 1000); // 限制最大费用为 10% daofee = _fee; emit SubmissionCommissionChanged(_fee); } function changewhitelistfee(uint256 _fee) onlyOwner external { require(_fee > 0); // 确保费用大于 0 whitelistfee = _fee; emit WhitelistFeeChanged(_fee); } function changeDurationDays(uint256 _days) onlyOwner external { require(_days >= 1); durationDays = _days; } function changeDurationSubmissions(uint256 _subs) onlyOwner external { require(_subs > 99); durationSubmissions = _subs; } }</code>
Nous importons Safemath pour effectuer à nouveau des calculs sûrs, mais cette fois, nous utilisons également le contrat propriétaire de Zeppelin, qui permet à quelqu'un de «posséder» l'histoire et d'exécuter certaines fonctions administratrices uniquement. Autrement dit, il suffit que notre Storydao soit propriétaire; n'hésitez pas à vérifier le contrat pour comprendre comment cela fonctionne.
Nous utilisons également le seul modificateur de propriétaire dans ce contrat. Les modificateurs de fonction sont essentiellement des extensions et des plug-ins pour les fonctions. Le seul modificateur de propriétaire ressemble à ceci:
<code class="language-solidity">modifier onlyOwner() { require(msg.sender == owner); _; }</code>
Lorsque uniquement est ajouté à la fonction, le corps de la fonction sera collé à l'endroit où se trouve la partie _; Par conséquent, en utilisant ce modificateur, la fonction vérifie automatiquement si l'expéditeur de messages est propriétaire du contrat, puis se poursuit comme d'habitude s'il est vrai. Sinon, il se bloquera.
En utilisant le seul modificateur de propriétaire uniquement sur les fonctions qui modifient les frais et autres paramètres de l'histoire DAO, nous nous assurons que seuls les administrateurs peuvent apporter ces modifications.
Testons la fonction initiale.
s'il n'existe pas, créez un test de dossier. Créez ensuite les fichiers TestStorydao.sol et TestStoryDao.js. Puisqu'il n'y a pas de méthode native dans la truffe pour tester les exceptions, les aides / windthrow.js sont également créés en utilisant les éléments suivants:
<code class="language-javascript">export default async promise => { try { await promise; } catch (error) { const invalidOpcode = error.message.search('invalid opcode') >= 0; const outOfGas = error.message.search('out of gas') >= 0; const revert = error.message.search('revert') >= 0; assert( invalidOpcode || outOfGas || revert, 'Expected throw, got \'' + error + '\' instead', ); return; } assert.fail('Expected throw not received'); };</code>
Remarque: Les tests de solidité sont généralement utilisés pour tester les fonctions basées sur les contrats de bas niveau, c'est-à-dire la structure interne des contrats intelligents. Les tests JS sont souvent utilisés pour tester si un contrat peut interagir correctement de l'extérieur, ce que feront nos utilisateurs finaux.
dans TestStoryDao.Sol, mettez le contenu suivant:
<code class="language-solidity">pragma solidity ^0.4.24; import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract StoryDao is Ownable { using SafeMath for uint256; mapping(address => bool) whitelist; uint256 public whitelistedNumber = 0; mapping(address => bool) blacklist; event Whitelisted(address addr, bool status); event Blacklisted(address addr, bool status); uint256 public daofee = 100; // 百分之几,即 100 为 1% uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 event SubmissionCommissionChanged(uint256 newFee); event WhitelistFeeChanged(uint256 newFee); uint256 public durationDays = 21; // 故事章节持续时间(天) uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目) function changedaofee(uint256 _fee) onlyOwner external { require(_fee <= 1000); // 限制最大费用为 10% daofee = _fee; emit SubmissionCommissionChanged(_fee); } function changewhitelistfee(uint256 _fee) onlyOwner external { require(_fee > 0); // 确保费用大于 0 whitelistfee = _fee; emit WhitelistFeeChanged(_fee); } function changeDurationDays(uint256 _days) onlyOwner external { require(_days >= 1); durationDays = _days; } function changeDurationSubmissions(uint256 _subs) onlyOwner external { require(_subs > 99); durationSubmissions = _subs; } }</code>
Cela vérifiera que le contrat Storydao est correctement déployé avec les chiffres de frais et de durée corrects. La première ligne garantit qu'elle est déployée en la lisant à partir de la liste d'adresses déployée, et la dernière section fait des affirmations - Vérifiez si la déclaration est vraie ou fausse. Dans notre exemple, nous comparons le nombre à la valeur initiale du contrat déployé. Chaque fois qu'il est "vrai", la section ASSERT.Equals publiera un événement qui stipule "vrai", ce que la truffe écoute pendant les tests.
Dans TestStorydao.js, mettez le contenu suivant:
<code class="language-solidity">modifier onlyOwner() { require(msg.sender == owner); _; }</code>
Pour que nos tests fonctionnent avec succès, nous devons également dire à Truffle que nous voulons déployer Storydao - car cela ne le fera pas pour nous. Créons donc 3_deploy_storydao.js dans la migration en utilisant presque le même contenu que la migration que nous avons écrite avant:
<code class="language-javascript">export default async promise => { try { await promise; } catch (error) { const invalidOpcode = error.message.search('invalid opcode') >= 0; const outOfGas = error.message.search('out of gas') >= 0; const revert = error.message.search('revert') >= 0; assert( invalidOpcode || outOfGas || revert, 'Expected throw, got \'' + error + '\' instead', ); return; } assert.fail('Expected throw not received'); };</code>
À ce stade, nous devons également mettre à jour (ou créer, sinon) le fichier package.json dans la racine du dossier du projet, contenant les dépendances dont nous avons besoin jusqu'à présent, et ce dont nous pouvons avoir besoin à l'avenir:
<code class="language-solidity">pragma solidity ^0.4.24; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../contracts/StoryDao.sol"; contract TestStoryDao { function testDeploymentIsFine() public { StoryDao sd = StoryDao(DeployedAddresses.StoryDao()); uint256 daofee = 100; // 百分之几,即 100 为 1% uint256 whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 uint256 durationDays = 21; // 故事章节持续时间(天) uint256 durationSubmissions = 1000; // 故事章节持续时间(条目) Assert.equal(sd.daofee(), daofee, "初始 DAO 费用应为 100"); Assert.equal(sd.whitelistfee(), whitelistfee, "初始白名单费用应为 0.01 以太币"); Assert.equal(sd.durationDays(), durationDays, "初始天数持续时间应设置为 3 周"); Assert.equal(sd.durationSubmissions(), durationSubmissions, "初始提交持续时间应设置为 1000 个条目"); } }</code>Les fichiers
et .babelrc contenant les éléments suivants:
<code class="language-javascript">import expectThrow from './helpers/expectThrow'; const StoryDao = artifacts.require("StoryDao"); contract('StoryDao Test', async (accounts) => { it("should make sure environment is OK by checking that the first 3 accounts have over 20 eth", async () =>{ assert.equal(web3.eth.getBalance(accounts[0]).toNumber() > 2e+19, true, "Account 0 has more than 20 eth"); assert.equal(web3.eth.getBalance(accounts[1]).toNumber() > 2e+19, true, "Account 1 has more than 20 eth"); assert.equal(web3.eth.getBalance(accounts[2]).toNumber() > 2e+19, true, "Account 2 has more than 20 eth"); }); it("should make the deployer the owner", async () => { let instance = await StoryDao.deployed(); assert.equal(await instance.owner(), accounts[0]); }); it("should let owner change fee and duration", async () => { let instance = await StoryDao.deployed(); let newDaoFee = 50; let newWhitelistFee = 1e+10; // 1 ether let newDayDuration = 42; let 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(await instance.daofee(), newDaoFee); assert.equal(await instance.whitelistfee(), newWhitelistFee); assert.equal(await instance.durationDays(), newDayDuration); assert.equal(await instance.durationSubmissions(), newSubsDuration); }); it("should forbid non-owners from changing fee and duration", async () => { let instance = await StoryDao.deployed(); let newDaoFee = 50; let newWhitelistFee = 1e+10; // 1 ether let newDayDuration = 42; let newSubsDuration = 1500; await expectThrow(instance.changedaofee(newDaoFee, {from: accounts[1]})); await expectThrow(instance.changewhitelistfee(newWhitelistFee, {from: accounts[1]})); await expectThrow(instance.changeDurationDays(newDayDuration, {from: accounts[1]})); await expectThrow(instance.changeDurationSubmissions(newSubsDuration, {from: accounts[1]})); }); it("should make sure the owner can only change fees and duration to valid values", async () =>{ let instance = await StoryDao.deployed(); let invalidDaoFee = 20000; let invalidDayDuration = 0; let invalidSubsDuration = 98; await expectThrow(instance.changedaofee(invalidDaoFee, {from: accounts[0]})); await expectThrow(instance.changeDurationDays(invalidDayDuration, {from: accounts[0]})); await expectThrow(instance.changeDurationSubmissions(invalidSubsDuration, {from: accounts[0]})); }) });</code>
Nous devons également exiger Babel dans la configuration de la truffe afin qu'il sache qu'il doit être utilisé lors de la compilation de tests.
Remarque: Babel est un module complémentaire à NodeJS qui nous permet d'utiliser le JavaScript de nouvelle génération dans la génération actuelle de NodeJS, afin que nous puissions écrire des importations et d'autres contenus. Si vous ne comprenez pas cela, ignorez-le et collez-le textuellement. Après l'installation, vous n'aurez peut-être plus jamais à traiter ce problème.
<code class="language-javascript">var Migrations = artifacts.require("./Migrations.sol"); var StoryDao = artifacts.require("./StoryDao.sol"); module.exports = function(deployer, network, accounts) { if (network == "development") { deployer.deploy(StoryDao, {from: accounts[0]}); } else { deployer.deploy(StoryDao); } };</code>
Maintenant, exécutez enfin le test de truffe. La sortie doit être similaire à ceci:
Pour plus d'informations sur les tests, consultez ce tutoriel, que nous avons préparé spécifiquement pour couvrir les tests de contrats intelligents.
Dans la section suivante de ce cours, nous sauterons les tests car les taper textuellement rendra le tutoriel trop long, mais veuillez vous référer au code source final du projet pour vérifier tous les tests. Le processus que nous venons de terminer a un environnement configuré pour les tests, afin que vous puissiez rédiger les tests sans autre configuration.
liste blanche
Créons un mécanisme de liste blanche qui permet aux utilisateurs de participer à la construction d'histoires. Ajoutez le cadre de fonction suivant à Storydao.sol:
<code class="language-json">{ "name": "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", "truffle": "^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", "truffle-assertions": "^0.3.1" } }</code>
Fonction sans nom () est appelée une fonction de secours, qui est appelée lorsque les fonds sont envoyés à ce contrat mais aucune instruction spécifique (c'est-à-dire aucune autre fonction n'est spécifiquement appelée). Cela permet aux gens de rejoindre Storydao en envoyant de l'éther à DAO uniquement et à la liste blanche ou à l'achat de jetons immédiatement en fonction de leur liste blanche ou non.
La fonction WhitelistSender est utilisée pour la liste blanche et peut être appelée directement, mais nous nous assurerons que la fonction de secours l'appelle automatiquement après avoir reçu de l'éther, à condition que l'expéditeur ne soit pas encore liste à blanc. La fonction WhitelistAddress est déclarée publique car elle doit également être appelée à partir d'autres contrats, tandis que la fonction de secours est externe car les fonds ne sont envoyés que des adresses externes à cette adresse. Le contrat qui appelle ce contrat peut facilement appeler directement les fonctions requises.Faisons d'abord la fonction de secours.
<code class="language-solidity">pragma solidity ^0.4.24; import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract StoryDao is Ownable { using SafeMath for uint256; mapping(address => bool) whitelist; uint256 public whitelistedNumber = 0; mapping(address => bool) blacklist; event Whitelisted(address addr, bool status); event Blacklisted(address addr, bool status); uint256 public daofee = 100; // 百分之几,即 100 为 1% uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 event SubmissionCommissionChanged(uint256 newFee); event WhitelistFeeChanged(uint256 newFee); uint256 public durationDays = 21; // 故事章节持续时间(天) uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目) function changedaofee(uint256 _fee) onlyOwner external { require(_fee <= 1000); // 限制最大费用为 10% daofee = _fee; emit SubmissionCommissionChanged(_fee); } function changewhitelistfee(uint256 _fee) onlyOwner external { require(_fee > 0); // 确保费用大于 0 whitelistfee = _fee; emit WhitelistFeeChanged(_fee); } function changeDurationDays(uint256 _days) onlyOwner external { require(_days >= 1); durationDays = _days; } function changeDurationSubmissions(uint256 _subs) onlyOwner external { require(_subs > 99); durationSubmissions = _subs; } }</code>Nous vérifions si l'expéditeur n'est pas encore liste à blanc et délégue l'appel à la fonction WhiteListAddress. Notez que nous avons commenté la fonction Buytokens parce que nous ne l'avons pas encore.
Ensuite, traitons avec la liste blanche.
<code class="language-solidity">modifier onlyOwner() { require(msg.sender == owner); _; }</code>Notez que cette fonction accepte une adresse comme argument, plutôt que de l'extraire d'un message (d'une transaction). Cela a l'avantage supplémentaire de pouvoir les autres si quelqu'un ne peut pas se permettre de rejoindre le DAO, par exemple.
Nous commençons par quelques chèques de santé mentale: l'expéditeur doit être liste à blanc ou sur liste noire (interdite) et doit avoir envoyé suffisamment de frais pour payer. Si ces conditions sont satisfaisantes, l'adresse est ajoutée à la liste blanche, un événement de liste blanche est émis, et enfin, si le nombre d'éthers envoyés est supérieur au nombre d'éthers requis pour payer les frais de liste blanche, le reste sera utilisé pour acheter les jetons.
Remarque: nous utilisons Sub au lieu de - Soustraire, car il s'agit de la fonction SafeMath pour les calculs sûrs.
Tant que les utilisateurs envoient 0,01 éther ou plus d'éther au contrat Storydao, ils peuvent désormais eux-mêmes ou les autres.
Conclusion
Dans ce tutoriel, nous avons construit la partie initiale de DAO, mais il y a encore beaucoup de travail à faire. Restez à l'écoute: dans la section suivante, nous traiterons de l'ajout de contenu à l'histoire!FAQ sur la construction de Dapps Ethereum et de listes blanches
Comment fonctionne le processus de liste blanche dans DAPP?
Les contrats intelligents sont des contrats auto-exécutés et leurs conditions d'accord sont directement rédigées dans le code. Ils jouent un rôle essentiel dans les DAPP car ils automatisent l'exécution de la logique métier sur la blockchain. Ils garantissent la transparence, la sécurité et l'immuabilité car une fois déployés, ils ne peuvent pas être modifiés ou falsifiés.
Les tests sont une partie cruciale du développement DAPP pour assurer sa fonctionnalité et sa sécurité. Vous pouvez utiliser des outils comme la truffe et la ganache pour tester. Truffle fournit à Ethereum un environnement de développement, un cadre de test et un pipeline d'actifs, tandis que Ganache vous permet de créer une blockchain privée Ethereum pour les tests.
DAO représente une organisation autonome décentralisée. Il s'agit d'un type d'organisation représenté par des règles codées comme des programmes informatiques transparents, contrôlés par les membres de l'organisation et non affectés par le gouvernement central. Les transactions et les règles financières de DAO sont maintenues sur la blockchain, ce qui en fait un DAPP.
Assurer que la sécurité du DAPP implique une variété de pratiques. Cela comprend la rédaction de contrats intelligents sécurisés, les tests approfondis, la réalisation d'audits de sécurité et la maintenance des logiciels et des dépendances à jour. Il est également important de suivre les meilleures pratiques pour le codage sécurisé et de se tenir au courant des dernières vulnérabilités et menaces de sécurité dans l'espace de la blockchain.
Metamask est une extension de navigateur qui vous permet d'interagir avec la blockchain Ethereum et les DAPPS directement à partir de votre navigateur. Il peut également servir de portefeuille Ethereum pour gérer vos jetons Ethereum et ERC-20. Il est important dans le développement DAPP car il fournit aux utilisateurs une interface conviviale afin que les utilisateurs puissent interagir avec votre DAPP sans exécuter un nœud Ethereum complet.
Une fois que vous avez développé et testé votre DAPP, vous pouvez le déployer dans le mainnet Ethereum ou TestNet. Cela implique de compiler vos contrats intelligents, de les déployer sur la blockchain et de connecter vos DAPP à ces contrats. Vous pouvez terminer ce processus à l'aide d'outils tels que Truffle et Infura.
DAPP Development est confronté à certains défis. Cela comprend la gestion des problèmes d'évolutivité du réseau Ethereum, d'assurer la sécurité des DAPP, de gérer les prix volatils du gaz pour les transactions et de fournir une interface conviviale. Cela nécessite également de garder un œil sur les technologies et les réglementations de la blockchain en évolution rapide.
La mise à jour des DAPP après déploiement peut être difficile car les contrats intelligents sur la blockchain sont immuables. Cependant, vous pouvez concevoir des contrats améliorables en séparant les données et la logique en différents contrats ou la mise à niveau de contrats à l'aide d'appels délégués. La planification des mises à niveau et des changements pendant la phase de conception du DAPP est très importante.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!