Hello!
In the first part, we covered in detail how to create and work with dApp (decentralized application) in Waves RIDE IDE .
Let us now test a little of a good example .
What problems immediately rumble with Alice dApp Account?
First of all:
Boob and Cooper may randomly send funds to the dApp address using a regular transfer transaction and thus will not be able to access them back.
Secondly:
We do not limit Alice in the withdrawal of funds without coordination with Boob or (and) Cooper. As, pay attention to verify, all transactions from Alice will be executed.
Thirdly:
Anyone can make any operations from an Alice account by simply substituting it with publicKey in a transaction:
const unsignedTransferTx = transfer({ amount: 1, recipient: '3P6fVra21KmTfWHBdib45iYV6aFduh4WwC2', //senderPublicKey is required if you omit seed senderPublicKey: '6nR7CXVV7Zmt9ew11BsNzSvVmuyM5PF6VPbWHW9BHgPq' })
Unfortunately, Waves smart contracts do not yet allow blocking incoming transactions to the account, so Boob and Cooper should control their outgoing transactions themselves.
Let's fix the 2nd and 3rd, banning Alice all transactions except SetScriptTransaction , banning others, specifying its PublicKey in @Verifier. That is, we will allow only Alice, as a dApp developer, for the time being, only to update / correct a smart contract.
Yes, Alice can always update the script to get more rights and manage the means of "users", but only she and all users will be able to see the moment of unauthorized change of the contract and be able to take action. But as long as no other transactions except invokeScript are blocked, clients need to trust Alice.
Deploy the fixed script:
@Verifier(tx) func verify() = { match tx { case d: SetScriptTransaction => sigVerify(tx.bodyBytes, tx.proofs[0], base58'x51ySWMyhE8G2AqJqmDpe3qoQM2aBwmieiJzZLK33JW') case _ => true }
We try to withdraw coins with dApp Alice and her signature. We get the error:
We try to withdraw via withdraw:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"withdraw",args:[{type:"integer", value: 1000000}]}, payment: []}))
The script works with the 2nd point, we figured it out!
Unfortunately, in the RIDE language, the possibilities of working with collections (dictionary dictionaries, iterators, reducers, etc.) are not yet provided. However, for any operations with flat key-value collections, we can design a system for working with strings, respectively, with keys and their decryption.
Strings are very simple to concatenate, strings can be divided into indices.
We have everything you need to write complex DAO dApp logic!
Data Transactions:
“It can be arbitrarily defined. There is a limit to the number of possible transactions in data transaction is 100. Overall, there is no limit to the total amount of data available. Shakespeare's play 'Romeo and Juliet'. "
Create a DAO with the following conditions:
In order for a startup to receive funding, calling getFunds () requires the support of at least 2 participants - DAO investors. It will be possible to withdraw exactly as much as the owners of the DAO indicated in the vote .
Let's make 3 types of keys and add logic for working with balances in 2 new functions vote and getFunds:
xx ... xx _ia = investors, available balance (vote, deposit, withdrawal)
xx ... xx _sv = startups number of votes (vote, getFunds)
xx ... xx _sf = startups number of votes (vote, getFunds)
xx ... xx = public address (35 characters)
Note in the Vote we needed to update several fields at once:
WriteSet([DataEntry(key1, value1), DataEntry(key2, value2)]),
WriteSet allows us to make multiple entries within a single invokeScript transaction.
This is how it looks in the key-value DAO dApp repository, after Bob and Cooper replenished ia- deposit:
Our deposit function has slightly changed:
Now comes the most important moment in the activities of DAO - voting for projects for financing.
Bob votes for the Neli project at 500,000 wavelets:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"vote",args:[{type:"integer", value: 500000}, {type:"string", value: "3MrXEKJr9nDLNyVZ1d12Mq4jjeUYwxNjMsH"}]}, payment: []}))
Vote function code:
@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) ]) }
In the data warehouse, we see all the necessary records for the Neli address:
Cooper also voted for the Neli project.
Let's take a look at the getFunds function code . Neli must collect at least 2 votes in order to be able to withdraw funds from the DAO.
Neli is going to withdraw half of the amount entrusted to her:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"getFunds",args:[{type:"integer", value: 500000}]}, payment: []}))
She succeeds, that is, DAO works!
We reviewed the process of creating a DAO in RIDE4DAPPS .
In the following sections, we will take a closer look at refactoring the code and testing the cases.
Full code in 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 } }
First part
Githaba code
Waves RIDE IDE
Grant Program Announcement
Source: https://habr.com/ru/post/447808/
All Articles