
Cześć wszystkim!
W W poprzednich częściach szczegółowo omówiliśmy, jak tworzyć i pracować z dApp (aplikacją zdecentralizowaną) w .
Teraz spróbujmy nieco przetestować to, co zostało omówione .
Krok 3. Testowanie konta dApp

Jakie problemy od razu rzucają się w oczy na głosie z Alice dApp Konto?
Po pierwsze:
Boob i Cooper mogą przez przypadek wysłać środki na adres dApp za pomocą zwykłej transfer transakcji i w ten sposób nie będą mogli uzyskać do nich dostępu z powrotem.Po drugie:
W żaden sposób nie ograniczamy Alice w wypłacie środków bez zgody Booba lub (i) Coopera. Zauważ, że weryfikacja dotyczy wszystkich transakcji od Alice.
Naprawmy punkt 2, zakazując Alice transfer transakcji. Wdrażamy poprawiony skrypt:


Próbujemy wypłacić monety z dApp Alice i jej podpisem. Otrzymujemy błąd:

Próbujemy wypłacić przez withdraw:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"withdraw",args:[{type:"integer", value: 1000000}]}, payment: []}))Skrypt działa i zrozumieliśmy punkt 2!
Krok 4. Tworzymy DAO z głosowaniem
Niestety, język RIDE obecnie nie przewiduje możliwości pracy z kolekcjami (słowniki-słowników, iteratory, reduktory itp.). Jednak w przypadku jakichkolwiek operacji z płaskimi kolekcjami klucz-wartość możemy zaprojektować system pracy z ciągami, odpowiednio z kluczami i ich odszyfrowaniem.
Ciągi można bardzo łatwo konkatować, ciągi można dzielić na indeksy.
Zróbmy prosty przykład, aby zbudować i przeanalizować ciąg i sprawdzić, jak wpłynie to na wynik transakcji.
Zatrzymaliśmy się na tym, że Alice nie mogła podpisać transakcji Transfer, ponieważ ta możliwość była zablokowana w @verifier dla tego typu transakcji.
Spróbujmy ćwiczyć z ciągami, a potem to umożliwimy.
RIDE Strings
Transakcja znów jest możliwa, potrafimy pracować z ciągami.


Podsumowując, mamy wszystko, co potrzebne do napisania skomplikowanej logiki DAO dApp.
Transakcje danych
Transakcje danych:
„Maksymalny rozmiar klucza wynosi 100 znaków, a klucz może zawierać dowolne punkty kodowe Unicode, w tym spacje i inne symbole nietypowe. Wartości ciągów mają limit 32 768 bajtów, a maksymalna liczba możliwych wpisów w transakcji danych wynosi 100. Całkowity maksymalny rozmiar transakcji danych wynosi około 140 kb — dla odniesienia, prawie dokładnie o długości sztuki Szekspira „Romeo i Julia”.
Tworzymy DAO z następującymi warunkami:
Aby startup mógł uzyskać finansowanie, wywołując getFunds() wymagana jest zgoda co najmniej 2 uczestników — inwestorów DAO. Wypłacić można będzie dokładnie tyle, ile w sumie wskazano na głosowaniu właściciele DAO.
Stwórzmy 3 typy kluczy i dodajmy logikę do pracy z bilansem w 2 nowych funkcjach vote i getFunds:
xx…xx_ia = inwestorzy, dostępny balans (vote, deposit, withdrawal)
xx…xx_sv = startupy, liczba głosów (vote, getFunds)
xx…xx_sf = startupy, liczba głosów (vote, getFunds)
xx…xx = publiczny adres (35 znaków)
Zauważ, że w funkcji Vote musieliśmy zaktualizować kilka pól:
WriteSet([DataEntry(key1, value1), DataEntry(key2, value2)]),WriteSet pozwala nam na dokonanie kilku zapisów w ramach jednej invokeScript transakcji.
Tak to wygląda w przechowalni kluczy-wartości DAO dApp, po tym jak Bob i Cooper dokonali wpłat ia-depozyty:

Funkcja depozytu została nieco zmieniona:

Teraz nadchodzi najważniejszy moment w działalności DAO — głosowanie na projekty do finansowania.
Bob głosuje na projekt Neli na 500000 waveletów:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"vote",args:[{type:"integer", value: 500000}, {type:"string", value: "3MrXEKJr9nDLNyVZ1d12Mq4jjeUYwxNjMsH"}]}, payment: []}))
W bazie danych widzimy wszystkie niezbędne zapisy dla adresu Neli:

Cooper również zagłosował za projektem Neli.

Przyjrzyjmy się kodowi funkcji getFunds. Neli musi zebrać minimum 2 głosy, aby móc wypłacić środki z DAO.

Neli planuje wypłacić połowę powierzonej jej kwoty:
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"getFunds",args:[{type:"integer", value: 500000}]}, payment: []}))
Udaje jej się, co oznacza, że DAO działa!
Omówiliśmy proces tworzenia DAO w języku RIDE4DAPPS.
W kolejnych częściach zajmiemy się bardziej szczegółowo refaktoryzowaniem kodu i testowaniem przypadków.
Pełna wersja kodu w
# 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
}
}
Źródło: habr.com
