Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Здравейте на всички!

В първата В частите подробно разгледахме как да създадем и работим с dApp (децентрализирано приложение) в Waves RIDE IDE.

Нека сега да тестваме разгледаното пример.

Етап 3. Тестване на dApp акаунта

Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Какви проблеми веднага се набиват на очи с Alice dApp Акаунт?
Първо:
Boob и Cooper могат случайно да изпратят средства на адреса на dApp чрез обикновена прехвърляне транзакция и така не могат да получат достъп обратно до тях.

На второ място:
Ние никак не ограничаваме Alice в извеждането на средства без съгласие с Boob или(и) Cooper. Както виждате на verify, всички транзакции от Alice ще се изпълняват.

Нека поправим второто, като забраним на Alice прехвърляне транзакции. Деплойваме поправения скрипт:
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Опитваме се да изтеглим монети от dApp на Alice с нейния подпис. Получаваме грешка:
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Опитваме да изтеглим чрез withdraw:

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

Скриптът работи и с втория пункт сме разбрали!

Етап 4. Създаваме DAO с гласуване

За съжаление, в езика RIDE не са предвидени възможности за работа с колекции (речници-словарници, итератори, редюсери и прочее). Обаче, за всякакви операции с плоски колекции ключ-стойност можем да проектираме система за работа с низове, съответно с ключовете и тяхната декодировка.

Низовете са много лесни за конкатенация, низовете могат да се разделят по индекси.
Нека в качеството на тестов пример да съберем и разгледаме низ и да проверим как това влияе на резултата от транзакцията.
Спираме се на това, че Alice не е могла да подпише транзакция за Transfer, тъй като тази възможност е била блокирана в @verifier за такъв тип транзакции.

Нека да се упражняваме с низовете и след това да разрешим това.

RIDE Низове

Транзакцията отново е възможна, можем да работим с низове.
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)


В крайна сметка имаме всичко необходимо за написването на сложна логика DAO dApp.

Данни за транзакции

Данни за транзакции:
„Максималният размер за ключ е 100 символа, а ключът може да съдържа произволни Unicode кодови точки, включително интервали и други непечатаеми символи. Стойностите на низовете имат ограничение от 32 768 байта, а максималният брой възможни записи в данъчната транзакция е 100. Общият максимален размер на данъчната транзакция е около 140kb — за справка, почти точно дължината на пиесата на Шекспир 'Ромео и Жулиета'.”

Създаваме DAO с следните условия:
За да получи финансиране, стартапът трябва да предизвика getFunds() необходима е подкрепата на минимум 2 участници — инвеститори в DAO. Изход може да бъде точно толкова, колкото е общата сума, посочена на гласуването на притежателите на DAO.

Нека създадем 3 типа ключове и добавим логика за работа с балансите в две нови функции vote и getFunds:
xx…xx_ia = инвеститори, наличен баланс (vote, deposit, withdrawal)
xx…xx_sv = стар startups, брой гласове (vote, getFunds)
xx…xx_sf = стар startups, брой гласове (vote, getFunds)
xx…xx = публичен адрес (35 символа)

Обърнете внимание, че в Vote ни беше необходимо да обновим няколко полета:

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

WriteSet ни позволява да направим няколко записа в рамките на една invokeScript транзакция.

Ето как изглежда в key-value хранилището на DAO dApp, след като Боб и Купър попълниха ia-депозити:
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Функцията за депозит малко се промени:
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Сега настъпва най-важният момент в дейността на DAO — гласуването за проекти за финансиране.

Боб гласува за проекта Нели с 500000 wavelets:

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

Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

В хранилището на данни виждаме всички необходими записи за адреса на Нели:
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)
Купър също гласува за проекта Нели.
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Нека разгледаме кода на функцията getFunds. Нели трябва да събере поне 2 гласа, за да може да изтегли средства от DAO.
Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Нели планира да изтегли половината от поверената и сума:

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

Учим се да пишем смарт договори Waves на RIDE и RIDE4DAPPS. Част 2 (DAO — децентрализирана автономна организация)

Тя успява, т.е. DAO работи!

Разгледахме процеса на създаване на DAO на езика RIDE4DAPPS.
В следващите части ще се задълбочим в рефакторинга на кода и тестването на случаите.

Цялата версия на кода в 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
    }
}

Първа част
Кодът в GitHub
Waves RIDE IDE
Анонс на програмата за грантове

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster