Aprendendo a escrever contratos inteligentes do Waves no RIDE e RIDE4DAPPS. Parte 2 (DAO - Organização Autônoma Descentralizada)


Olá pessoal!


Na primeira parte, examinamos em detalhes como criar e trabalhar com o dApp (aplicativo descentralizado) no Waves RIDE IDE .


Vamos testar um pequeno exemplo agora .


Etapa 3. Testando a conta do dApp



Quais problemas são imediatamente aparentes com a conta Alice dApp ?
Em primeiro lugar:
Boob e Cooper podem enviar fundos acidentalmente para o dApp usando uma transação de transferência normal e, portanto, não poderão acessá-los novamente.

Em segundo lugar:
Não limitamos Alice a retirar fundos sem o consentimento de Boob e / ou Cooper. Como, preste atenção na verificação, todas as transações da Alice serão executadas.

Terceiro:
Qualquer pessoa pode executar qualquer operação de uma conta Alice simplesmente substituindo seu publicKey na transação:
const unsignedTransferTx = transfer({ amount: 1, recipient: '3P6fVra21KmTfWHBdib45iYV6aFduh4WwC2', //senderPublicKey is required if you omit seed senderPublicKey: '6nR7CXVV7Zmt9ew11BsNzSvVmuyM5PF6VPbWHW9BHgPq' }) 

Infelizmente, os contratos inteligentes do Waves ainda não permitem bloquear transações de entrada em sua conta, portanto, Boob e Cooper devem controlar suas próprias transações de saída.


Vamos corrigir o 2º e o 3º desativando Alice para todas as transações, exceto SetScriptTransaction , desativando o restante especificando sua PublicKey no @Verifier. Ou seja, permitiremos apenas que Alice, como desenvolvedora do dApp, atualize / corrija um contrato inteligente por um tempo.


Sim, Alice sempre pode atualizar o script para obter mais direitos e gerenciar os meios de "usuários", mas somente ela pode fazer isso e todos os usuários verão o momento de alterações não autorizadas no contrato e poderão agir. Mas, desde que outras transações que não sejam o invokeScript não sejam bloqueadas, Alice precisará confiar nos clientes.

Implemente o script corrigido:


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

Estamos tentando retirar moedas com o dApp Alice e sua assinatura. Temos o erro:



Tentamos retirar através de retirada:


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

O script funciona e com o segundo ponto que descobrimos!


Etapa 4. Criamos o DAO com votação


Infelizmente, a linguagem RIDE ainda não forneceu a possibilidade de trabalhar com coleções (dicionários, dicionários, iteradores, redutores, etc.). No entanto, para qualquer operação com coleções de valores-chave simples, podemos projetar um sistema para trabalhar com cadeias, respectivamente, com chaves e sua descriptografia.
As strings são muito fáceis de concatenar; as strings podem ser divididas por índice.


Temos tudo o que você precisa para escrever uma lógica complexa do DAO dApp !


Transações de dados

Transações de dados:
“O tamanho máximo de uma chave é de 100 caracteres e uma chave pode conter pontos de código Unicode arbitrários, incluindo espaços e outros símbolos não imprimíveis. Os valores das strings têm um limite de 32.768 bytes e o número máximo de entradas possíveis na transação de dados é 100. No geral, o tamanho máximo de uma transação de dados é de cerca de 140kb - para referência, quase exatamente o comprimento da peça de Shakespeare 'Romeu e Julieta'. "


Crie um DAO com as seguintes condições:
Para que uma startup possa obter financiamento, ligar para getFunds () requer o apoio de pelo menos 2 participantes - investidores do DAO. Será possível retirar exatamente o valor total indicado pelos proprietários do DAO.


Vamos criar 3 tipos de chaves e adicionar lógica para trabalhar com saldos em 2 novas funções vote e getFunds:
xx ... xx _ia = investidores, saldo disponível (voto, depósito, retirada)
xx ... xx _sv = startups, número de votos (voto, getFunds)
xx ... xx _sf = startups, número de votos (votação, getFunds)
xx ... xx = endereço público (35 caracteres)

Observe na votação que precisamos atualizar vários campos ao mesmo tempo:


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

WriteSet nos permite fazer vários registros ao mesmo tempo em uma transação invokeScript .


É assim que fica no armazenamento de valor-chave do DAO dApp, depois que Bob e Cooper reabastecem os depósitos:



A função de depósito mudou ligeiramente:



Agora chega o momento mais importante nas atividades do DAO - votação de projetos para financiamento.


Bob Votos para o projeto de 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 da função de votação:


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

No armazém de dados, vemos todas as entradas necessárias para o endereço Neli:



Cooper também votou no projeto Neli.



Vamos dar uma olhada no código da função getFunds . Neli deve coletar no mínimo 2 votos para poder retirar fundos do DAO.



Neli vai retirar metade do valor que lhe foi confiado:


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


Ela consegue, ou seja, o DAO funciona!


Analisamos o processo de criação de um DAO na linguagem do RIDE4DAPPS .
Nas partes a seguir, trataremos mais detalhadamente da refatoração de código e teste de caso.


Versão completa do código no 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 } } 



Primeira parte
Código do Github
Waves RIDE IDE
Anúncio do Programa de Subsídios

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


All Articles