Aprendiendo a escribir contratos inteligentes de Waves en RIDE y RIDE4DAPPS. Parte 2 (DAO - Organización autónoma descentralizada)


Hola a todos!


En la primera parte, examinamos en detalle cómo crear y trabajar con dApp (aplicación descentralizada) en Waves RIDE IDE .


Probemos un pequeño ejemplo ahora .


Etapa 3. Prueba de cuenta dApp



¿Qué problemas son aparentes inmediatamente con la cuenta Alice dApp ?
En primer lugar:
Boob y Cooper pueden enviar fondos accidentalmente a dApp mediante una transacción de transferencia normal y, por lo tanto, no podrán volver a acceder a ellos.

En segundo lugar:
No limitamos a Alice a retirar fondos sin el consentimiento de Boob y / o Cooper. Dado que, preste atención para verificar, todas las transacciones de Alice se ejecutarán.

En tercer lugar:
Cualquiera puede realizar cualquier operación desde una cuenta de Alice simplemente sustituyendo su PublicKey en la transacción:
const unsignedTransferTx = transfer({ amount: 1, recipient: '3P6fVra21KmTfWHBdib45iYV6aFduh4WwC2', //senderPublicKey is required if you omit seed senderPublicKey: '6nR7CXVV7Zmt9ew11BsNzSvVmuyM5PF6VPbWHW9BHgPq' }) 

Desafortunadamente, los contratos inteligentes de Waves aún no le permiten bloquear las transacciones entrantes en su cuenta, por lo que Boob y Cooper deben controlar sus transacciones salientes.


Arreglemos el 2do y 3er deshabilitando Alice para todas las transacciones excepto SetScriptTransaction , deshabilitando el resto especificando su PublicKey en @Verifier. Es decir, solo permitiremos que Alice, como desarrollador de dApp, actualice / corrija un contrato inteligente por un tiempo.


Sí, Alice siempre puede actualizar el script para obtener más derechos y administrar los medios de "usuarios", pero solo ella puede hacerlo y todos los usuarios verán el momento de los cambios no autorizados en el contrato y podrán tomar medidas. Pero mientras las transacciones que no sean invokeScript no estén bloqueadas, Alice debe confiar en los clientes.

Implemente el script corregido:


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

Estamos tratando de retirar monedas con dApp Alice y su firma. Obtenemos el error:



Intentamos retirar a través de retirar:


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

¡El guión funciona y con el segundo punto lo descubrimos!


Etapa 4. Creamos DAO con votación


Desafortunadamente, el lenguaje RIDE aún no permite trabajar con colecciones (diccionarios, diccionarios, iteradores, reductores, etc.). Sin embargo, para cualquier operación con colecciones planas de valores clave , podemos diseñar un sistema para trabajar con cadenas, respectivamente, con claves y su descifrado.
Las cadenas son muy fáciles de concatenar; las cadenas se pueden dividir por índice.


¡Tenemos todo lo que necesita para escribir una lógica compleja de DAO dApp !


Transacciones de datos

Transacciones de datos:
“El tamaño máximo de una clave es de 100 caracteres, y una clave puede contener puntos de código Unicode arbitrarios, incluidos espacios y otros símbolos no imprimibles. Los valores de cadena tienen un límite de 32,768 bytes y el número máximo de entradas posibles en la transacción de datos es 100. En general, el tamaño máximo de una transacción de datos es de alrededor de 140 kb, como referencia, casi exactamente la duración de la obra de Shakespeare 'Romeo y Julieta'. "


Cree un DAO con las siguientes condiciones:
Para que una startup pueda obtener financiamiento, llamar a getFunds () requiere el apoyo de al menos 2 participantes: inversores de DAO. Será posible retirar exactamente la cantidad total indicada por los propietarios del DAO.


Hagamos 3 tipos de teclas y agreguemos lógica para trabajar con saldos en 2 nuevas funciones vote y getFunds:
xx ... xx _ia = inversores, saldo disponible (voto, depósito, retiro)
xx ... xx _sv = startups, número de votos (vote, getFunds)
xx ... xx _sf = startups, número de votos (vote, getFunds)
xx ... xx = dirección pública (35 caracteres)

Tenga en cuenta que en Vote necesitamos actualizar varios campos a la vez:


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

WriteSet nos permite hacer varios registros a la vez dentro de una transacción invokeScript .


Así es como se ve en el almacenamiento de valor-clave DAO dApp, después de que Bob y Cooper repongan los depósitos ia :



La función de depósito ha cambiado ligeramente:



Ahora llega el momento más importante en las actividades de la DAO: votar por proyectos para financiamiento.


Bob vota por el proyecto 500,000 Wavelets de Neli:


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

Código de función de voto:


 @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) ]) } 

En el almacén de datos, vemos todas las entradas necesarias para la dirección Neli:



Cooper también votó por el proyecto Neli.



Echemos un vistazo al código de función getFunds . Neli debe recolectar un mínimo de 2 votos para poder retirar fondos del DAO.



Neli va a retirar la mitad de la cantidad que se le ha confiado:


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


Ella tiene éxito, es decir, el DAO funciona!


Analizamos el proceso de creación de un DAO en el lenguaje de RIDE4DAPPS .
En las siguientes partes, trataremos con más detalle la refactorización de código y las pruebas de casos.


Versión completa del código en 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 } } 



Primera parte
Código de Github
Olas RIDE IDE
Anuncio del programa de subvenciones

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


All Articles