Apprendre à écrire des contrats intelligents Waves sur RIDE et RIDE4DAPPS. Partie 2 (DAO - Organisme autonome décentralisé)


Bonjour à tous!


Dans la première partie, nous avons examiné en détail comment créer et travailler avec dApp (application décentralisée) dans Waves RIDE IDE .


Essayons maintenant un petit exemple .


Étape 3. Test du compte dApp



Quels problèmes sont immédiatement apparents avec le compte Alice dApp ?
Premièrement:
Boob et Cooper peuvent accidentellement envoyer des fonds à dApp en utilisant une transaction de transfert normale, et ne pourront donc pas y accéder à nouveau.

Deuxièmement:
Nous ne limitons pas Alice à retirer des fonds sans le consentement de Boob et / ou Cooper. Depuis, faites attention à vérifier, toutes les transactions d'Alice seront exécutées.

Troisièmement:
N'importe qui peut effectuer n'importe quelle opération à partir d'un compte Alice simplement en substituant sa clé publique à la transaction:
const unsignedTransferTx = transfer({ amount: 1, recipient: '3P6fVra21KmTfWHBdib45iYV6aFduh4WwC2', //senderPublicKey is required if you omit seed senderPublicKey: '6nR7CXVV7Zmt9ew11BsNzSvVmuyM5PF6VPbWHW9BHgPq' }) 

Malheureusement, les contrats intelligents Waves ne vous permettent pas encore de bloquer les transactions entrantes sur votre compte, Boob et Cooper doivent donc contrôler eux-mêmes leurs transactions sortantes.


Corrigeons le 2e et le 3e en désactivant Alice pour toutes les transactions sauf SetScriptTransaction , désactivant le reste en spécifiant sa PublicKey dans @Verifier. Autrement dit, nous n'autoriserons qu'Alice, en tant que développeur dApp, à mettre à jour / corriger un contrat intelligent pendant un certain temps.


Oui, Alice peut toujours mettre à jour le script afin d'obtenir plus de droits et gérer les fonds des «utilisateurs», mais elle seule peut le faire et tous les utilisateurs verront le moment des modifications non autorisées du contrat et pourront prendre des mesures. Mais tant que les transactions autres que invokeScript ne sont pas bloquées, les clients doivent être approuvés par Alice.

Déployez le script corrigé:


 @Verifier(tx) func verify() = { match tx { case d: SetScriptTransaction => sigVerify(tx.bodyBytes, tx.proofs[0], base58'x51ySWMyhE8G2AqJqmDpe3qoQM2aBwmieiJzZLK33JW') case _ => true } 

Nous essayons de retirer des pièces avec dApp Alice et sa signature. Nous obtenons l'erreur:



Nous essayons de nous retirer en retirant:


 broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"withdraw",args:[{type:"integer", value: 1000000}]}, payment: []})) 

Le script fonctionne et avec le 2ème point, nous avons compris!


Étape 4. Nous créons DAO avec vote


Malheureusement, le langage RIDE n'a pas encore prévu la possibilité de travailler avec des collections (dictionnaires, dictionnaires, itérateurs, réducteurs, etc.). Cependant, pour toutes les opérations avec des collections de valeurs-clés plates, nous pouvons concevoir un système pour travailler avec des chaînes, respectivement, avec des clés et leur déchiffrement.
Les chaînes sont très faciles à concaténer; les chaînes peuvent être divisées par index.


Nous avons tout ce dont vous avez besoin pour écrire une logique DAO dApp complexe!


Transactions de données

Transactions de données:
«La taille maximale d'une clé est de 100 caractères et une clé peut contenir des points de code Unicode arbitraires, y compris des espaces et d'autres symboles non imprimables. Les valeurs de chaîne ont une limite de 32 768 octets et le nombre maximal d'entrées possibles dans la transaction de données est de 100. Globalement, la taille maximale d'une transaction de données est d'environ 140 Ko - pour référence, presque exactement la longueur de la pièce de Shakespeare «Roméo et Juliette». "


Créez un DAO avec les conditions suivantes:
Pour qu'une startup obtienne un financement, appeler getFunds () nécessite le soutien d'au moins 2 participants - les investisseurs DAO. Il sera possible de retirer exactement autant que le montant total indiqué par les propriétaires du DAO.


Créons 3 types de clés et ajoutons une logique pour travailler avec les soldes dans 2 nouvelles fonctions vote et getFunds:
xx ... xx _ia = investisseurs, solde disponible (vote, dépôt, retrait)
xx ... xx _sv = startups, nombre de votes (vote, getFunds)
xx ... xx _sf = startups, nombre de votes (vote, getFunds)
xx ... xx = adresse publique (35 caractères)

Remarque dans Vote, nous devions mettre à jour plusieurs champs à la fois:


 WriteSet([DataEntry(key1, value1), DataEntry(key2, value2)]), 

WriteSet nous permet de faire plusieurs enregistrements à la fois dans une transaction invokeScript .


Voici à quoi cela ressemble dans le stockage de valeurs-clés DAO dApp, après que Bob et Cooper ont réapprovisionné les dépôts ia :



La fonction de dépôt a légèrement changé:



Vient maintenant le moment le plus important dans les activités de DAO - voter pour des projets de financement.


Bob vote pour le projet de 500 000 ondelettes de Neli:


 broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"vote",args:[{type:"integer", value: 500000}, {type:"string", value: "3MrXEKJr9nDLNyVZ1d12Mq4jjeUYwxNjMsH"}]}, payment: []})) 

Code de fonction de vote:


 @Callable(i) func vote(amount: Int, address: String) = { let currentKey = toBase58String(i.caller.bytes) let xxxInvestorBalance = currentKey + "_" + "ib" let xxxStartupFund = address + "_" + "sf" let xxxStartupVotes = address + "_" + "sv" let flagKey = address + "_" + currentKey let flag = match getInteger(this, flagKey) { case a:Int => a case _ => 0 } let currentAmount = match getInteger(this, xxxInvestorBalance) { case a:Int => a case _ => 0 } let currentVotes = match getInteger(this, xxxStartupVotes) { case a:Int => a case _ => 0 } let currentFund = match getInteger(this, xxxStartupFund) { case a:Int => a case _ => 0 } if (amount <= 0) then throw("Can't withdraw negative amount") else if (amount > currentAmount) then throw("Not enough balance!") else if (flag > 0) then throw("Only one vote per project is possible!") else WriteSet([ DataEntry(xxxInvestorBalance, currentAmount - amount), DataEntry(xxxStartupVotes, currentVotes + 1), DataEntry(flagKey, 1), DataEntry(xxxStartupFund, currentFund + amount) ]) } 

Dans l'entrepôt de données, nous voyons toutes les entrées nécessaires pour l'adresse Neli:



Cooper a également voté pour le projet Neli.



Jetons un coup d'œil au code de la fonction getFunds . Neli doit collecter un minimum de 2 votes afin de pouvoir retirer des fonds du DAO.



Neli va retirer la moitié du montant qui lui a été confié:


 broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"getFunds",args:[{type:"integer", value: 500000}]}, payment: []})) 


Elle réussit, c'est-à-dire que le DAO fonctionne!


Nous avons examiné le processus de création d'un DAO dans le langage de RIDE4DAPPS .
Dans les parties suivantes, nous traiterons plus en détail de la refactorisation du code et des tests de cas.


Version complète du code dans Waves RIDE IDE:


 # In this example multiple accounts can deposit their funds to DAO and safely take them back, no one can interfere with this. # DAO participants can also vote for particular addresses and let them withdraw invested funds then quorum has reached. # An inner state is maintained as mapping `address=>waves`. # https://medium.com/waves-lab/waves-announces-funding-for-ride-for-dapps-developers-f724095fdbe1 # You can try this contract by following commands in the IDE (ide.wavesplatform.com) # Run commands as listed below # From account #0: # deploy() # From account #1: deposit funds # broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"deposit",args:[]}, payment: [{amount: 100000000, asset:null }]})) # From account #2: deposit funds # broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"deposit",args:[]}, payment: [{amount: 100000000, asset:null }]})) # From account #1: vote for startup # broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"vote",args:[{type:"integer", value: 500000}, {type:"string", value: "3MrXEKJr9nDLNyVZ1d12Mq4jjeUYwxNjMsH"}]}, payment: []})) # From account #2: vote for startup # broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"vote",args:[{type:"integer", value: 500000}, {type:"string", value: "3MrXEKJr9nDLNyVZ1d12Mq4jjeUYwxNjMsH"}]}, payment: []})) # From account #3: get invested funds # broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"getFunds",args:[{type:"integer", value: 500000}]}, payment: []})) {-# STDLIB_VERSION 3 #-} {-# CONTENT_TYPE DAPP #-} {-# SCRIPT_TYPE ACCOUNT #-} @Callable(i) func deposit() = { let pmt = extract(i.payment) if (isDefined(pmt.assetId)) then throw("can hodl waves only at the moment") else { let currentKey = toBase58String(i.caller.bytes) let xxxInvestorBalance = currentKey + "_" + "ib" let currentAmount = match getInteger(this, xxxInvestorBalance) { case a:Int => a case _ => 0 } let newAmount = currentAmount + pmt.amount WriteSet([DataEntry(xxxInvestorBalance, newAmount)]) } } @Callable(i) func withdraw(amount: Int) = { let currentKey = toBase58String(i.caller.bytes) let xxxInvestorBalance = currentKey + "_" + "ib" let currentAmount = match getInteger(this, xxxInvestorBalance) { case a:Int => a case _ => 0 } let newAmount = currentAmount - amount if (amount < 0) then throw("Can't withdraw negative amount") else if (newAmount < 0) then throw("Not enough balance") else ScriptResult( WriteSet([DataEntry(xxxInvestorBalance, newAmount)]), TransferSet([ScriptTransfer(i.caller, amount, unit)]) ) } @Callable(i) func getFunds(amount: Int) = { let quorum = 2 let currentKey = toBase58String(i.caller.bytes) let xxxStartupFund = currentKey + "_" + "sf" let xxxStartupVotes = currentKey + "_" + "sv" let currentAmount = match getInteger(this, xxxStartupFund) { case a:Int => a case _ => 0 } let totalVotes = match getInteger(this, xxxStartupVotes) { case a:Int => a case _ => 0 } let newAmount = currentAmount - amount if (amount < 0) then throw("Can't withdraw negative amount") else if (newAmount < 0) then throw("Not enough balance") else if (totalVotes < quorum) then throw("Not enough votes. At least 2 votes required!") else ScriptResult( WriteSet([ DataEntry(xxxStartupFund, newAmount) ]), TransferSet([ScriptTransfer(i.caller, amount, unit)]) ) } @Callable(i) func vote(amount: Int, address: String) = { let currentKey = toBase58String(i.caller.bytes) let xxxInvestorBalance = currentKey + "_" + "ib" let xxxStartupFund = address + "_" + "sf" let xxxStartupVotes = address + "_" + "sv" let flagKey = address + "_" + currentKey let flag = match getInteger(this, flagKey) { case a:Int => a case _ => 0 } let currentAmount = match getInteger(this, xxxInvestorBalance) { case a:Int => a case _ => 0 } let currentVotes = match getInteger(this, xxxStartupVotes) { case a:Int => a case _ => 0 } let currentFund = match getInteger(this, xxxStartupFund) { case a:Int => a case _ => 0 } if (amount <= 0) then throw("Can't withdraw negative amount") else if (amount > currentAmount) then throw("Not enough balance!") else if (flag > 0) then throw("Only one vote per project is possible!") else WriteSet([ DataEntry(xxxInvestorBalance, currentAmount - amount), DataEntry(xxxStartupVotes, currentVotes + 1), DataEntry(flagKey, 1), DataEntry(xxxStartupFund, currentFund + amount) ]) } @Verifier(tx) func verify() = { match tx { case d: SetScriptTransaction => sigVerify(tx.bodyBytes, tx.proofs[0], base58'x51ySWMyhE8G2AqJqmDpe3qoQM2aBwmieiJzZLK33JW') case _ => false } } 



Première partie
Code Github
Waves RIDE IDE
Annonce du programme de subventions

Source: https://habr.com/ru/post/fr447808/


All Articles