
Hello everyone!
In In this section, we will thoroughly examine how to create and work with a dApp (decentralized application) in .
Now let's test the disassembled .
Stage 3. Testing dApp Account

What problems immediately stand out with Alice dApp Account?
Firstly:
Boob and Cooper might accidentally send funds to the dApp address via a regular transfer transaction and thus, would be unable to access it back.Secondly:
We do not restrict Alice from withdrawing funds without the agreement of Boob or Cooper. Note that, as observed in verify, all transactions from Alice will be executed.
Let's fix the second issue by prohibiting Alice transfer transactions. We will deploy the modified script:


We attempt to withdraw coins from dApp Alice with her signature. We receive an error:

We try to withdraw through withdraw:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"withdraw",args:[{type:"integer", value: 1000000}]}, payment: []}))The script works, and we have figured out the second point!
Stage 4. Creating a DAO with Voting
Unfortunately, the RIDE language currently does not support working with collections (dictionaries of dictionaries, iterators, reducers, etc.). However, for any operations with flat collections key-value we can design a system to work with strings, accordingly with keys and their deciphering.
Strings are very easy to concatenate, and they can be split by indices.
Let's collect and dissect a string as a test example and check how this will affect the transaction outcome.
We stopped at the fact that Alice could not sign the Transfer transaction since that capability was blocked in @verifier for that type of transaction.
Let's practice with strings and then we'll allow it.
RIDE Strings
The transaction is possible again; we know how to work with strings.


In summary, we have everything necessary for writing complex logic DAO dApp.
Data Transactions
Data Transactions:
“The maximum size for a key is 100 characters, and a key can contain arbitrary Unicode code points including spaces and other non-printable symbols. String values have a limit of 32,768 bytes and the maximum number of possible entries in a data transaction is 100. Overall, the maximum size of a data transaction is around 140kb — for reference, almost exactly the length of Shakespeare’s play ‘Romeo and Juliet’.”
We create a DAO with the following conditions:
For a startup to obtain funding by invoking getFunds() at least 2 participants — DAO investors must support it. Output it will be exactly as much as indicated in the sum on the voting DAO owners.
Let's create 3 types of keys and add logic to work 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 that in Vote we needed to update several fields at once:
WriteSet([DataEntry(key1, value1), DataEntry(key2, value2)]),WriteSet allows us to make several entries within one invokeScript transaction.
This is how it looks in the key-value store of the DAO dApp after Bob and Cooper topped up ia-deposits:

The deposit function has slightly changed:

Now comes the most important moment in the DAO's activities — voting for projects to be funded.
Bob votes for the project Neli with 500000 wavelets:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"vote",args:[{type:"integer", value: 500000}, {type:"string", value: "3MrXEKJr9nDLNyVZ1d12Mq4jjeUYwxNjMsH"}]}, payment: []}))
In the data store, we see all the necessary entries for the address Neli:

Cooper also voted for the project Neli.

Let's take a look at the function code getFunds. Neli needs to gather at least 2 votes to be able to withdraw funds from the DAO.

Neli plans 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, meaning the DAO works!
We have reviewed the process of creating a DAO using the RIDE4DAPPS.
In the next parts, we will take a closer look at code refactoring and case testing.
The complete code version in
# 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 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 ScriptResult(
WriteSet([
DataEntry(xxxInvestorBalance, currentAmount - amount),
DataEntry(xxxStartupVotes, currentVotes + 1),
DataEntry(xxxStartupFund, currentFund + amount)
]),
TransferSet([ScriptTransfer(i.caller, amount, unit)])
)
}
@Verifier(tx)
func verify() = {
match tx {
case t: TransferTransaction =>false
case _ => true
}
}
Source: habr.com
