Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

Hi all!

Π’ first part, we have examined in detail how to create and work with a dApp (decentralized application) in Waves RIDE IDE.

Let's now test the disassembled a little example.

Stage 3. Testing the dApp account

Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

What problems immediately rush to the voices with Alice dapper account?
First:
Boob and Cooper may accidentally send funds to a dApp address using a normal transfer transactions and thus will not be able to access them back.

Second:
We do not in any way restrict Alice from withdrawing funds without the consent of Boob and/or Cooper. Since, pay attention to verify, all transactions from Alice will be executed.

Let's fix 2 by banning Alice transfer transactions. Deploy the corrected script:
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)
οΏΌ
We are trying to withdraw coins with dApp Alice and her signature. We get an error:
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

Trying to withdraw via withdraw:

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

The script works and we figured out the 2nd point!

Stage 4. Create a DAO with voting

Unfortunately, the RIDE language does not yet provide the ability to work with collections (dictionaries, dictionaries, iterators, reducers, etc.). However, for any operations on flat collections key value we can design a system for working with strings, respectively with keys and their decryption.

Strings are very easy to concatenate, strings can be separated by indexes.
Let's collect and parse the string as a test case and check how this will affect the outcome of the transaction.
We settled on the fact that Alice could not sign the Transfer transaction, since this possibility was blocked in @verifier for this type of transaction.

Let's practice with strings and then resolve this.

RIDE Strings

The transaction is possible again, we know how to work with strings.
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

οΏΌ
In total, we have everything you need to write 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 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:
In order for a startup to get funding by calling getFunds() the support of at least 2 participants - DAO investors is required. Withdraw it will be possible exactly as much as the sum indicated on vote DAO owners.

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)

Notice in Vote we needed to update several fields at once:

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

WriteSet allows us to make several records at once within one invokeScript transactions.

This is how it looks like in the key-value store of the DAO dApp after Bob and Cooper replenished ia- deposits:
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

Our deposit function has changed slightly:
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

Now comes the most important moment in the activities of the DAO - vote for projects for funding.

Bob votes for the Neli project on 500000 wavelets:

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

Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

In the data store, we see all the necessary entries for the Neli address:
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)
Cooper also voted for the Neli project.
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

Let's take a look at the function code getFunds. Neli must collect at least 2 votes to be able to withdraw funds from the DAO.
Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

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: []}))

Learning how to write Waves smart contracts on RIDE and RIDE4DAPPS. Part 2 (DAO - Decentralized Autonomous Organization)

She succeeds, that is, the DAO works!

We reviewed the process of creating a DAO in the language RIDE4DAPPS.
In the following parts, we will take a closer look at code refactoring and case testing.

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 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
    }
}

The first part
Code on github
Waves RIDE IDE
Announcement of the grant program

Source: habr.com

Add a comment