みなさんこんにちは!
最初の部分では、 Waves RIDE IDEでdApp(分散アプリケーション)を作成および操作する方法を詳細に検討しました。
少し例を見てみましょう。
ステージ3. dAppアカウントのテスト
Alice dAppアカウントですぐに明らかになる問題は何ですか?
まず:
BoobとCooperは、通常の送金トランザクションを使用して誤ってdAppに資金を送ってしまう可能性があるため、それらに戻ってアクセスすることはできません。
第二に:
ブリスやクーパーの同意なしに資金を引き出すことをアリスに制限しません。 確認に注意を払うため、Aliceからのすべてのトランザクションが実行されます。
第三に:
publicKeyをトランザクションに代入するだけで、誰でもAliceアカウントから任意の操作を実行できます。
const unsignedTransferTx = transfer({ amount: 1, recipient: '3P6fVra21KmTfWHBdib45iYV6aFduh4WwC2', //senderPublicKey is required if you omit seed senderPublicKey: '6nR7CXVV7Zmt9ew11BsNzSvVmuyM5PF6VPbWHW9BHgPq' })
残念ながら、Wavesスマートコントラクトではまだアカウントの着信トランザクションをブロックすることは許可されていないため、BoobとCooperは発信トランザクション自体を制御する必要があります。
SetScriptTransactionを除くすべてのトランザクションでAliceを無効にし 、@ VerifierでPublicKeyを指定して他を無効にして 、2番目と3番目を修正しましょう。 つまり、dApp開発者としてのアリスは、しばらくの間スマートコントラクトを更新/修正することのみを許可します。
はい、アリスはいつでもスクリプトを更新して、より多くの権利を取得し、「ユーザー」の手段を管理できますが、これを行うことができるのは彼女だけです。 ただし、invokeScript以外のトランザクションがブロックされない限り、クライアントはAliceによって信頼される必要があります。
修正されたスクリプトをデプロイします。
@Verifier(tx) func verify() = { match tx { case d: SetScriptTransaction => sigVerify(tx.bodyBytes, tx.proofs[0], base58'x51ySWMyhE8G2AqJqmDpe3qoQM2aBwmieiJzZLK33JW') case _ => true }
dApp Aliceとその署名を使用してコインを引き出そうとしています。 エラーが発生します:
withdrawを使用して引き出しを試みます。
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"withdraw",args:[{type:"integer", value: 1000000}]}, payment: []}))
スクリプトは機能し、2番目のポイントで理解しました!
ステージ4.投票でDAOを作成します
残念ながら、RIDE言語はまだコレクション(辞書、辞書、イテレーター、レデューサーなど)の操作を提供していません。 ただし、フラットなキーと値のコレクションを使用する操作の場合、キーとその復号化を使用して、それぞれ文字列を操作するシステムを設計できます。
文字列の連結は非常に簡単で、文字列はインデックスで分割できます。
複雑なDAO dAppロジックを記述するために必要なものはすべて揃っています!
データトランザクション:
「キーの最大サイズは100文字で、キーにはスペースやその他の印刷できない記号を含む任意のUnicodeコードポイントを含めることができます。 文字列値には32,768バイトの制限があり、データトランザクションで使用可能なエントリの最大数は100です。全体として、データトランザクションの最大サイズは約140kbです。 」
次の条件でDAOを作成します。
スタートアップが資金を得るために、 getFunds()を呼び出すには、少なくとも2人の参加者(DAO投資家)のサポートが必要です。 DAOの所有者によって示された合計金額と同額を引き出すことができます。
3種類のキーを作成し、2つの新しい関数voteとgetFundsで天びんを操作するためのロジックを追加しましょう。
xx ... xx _ia =投資家、利用可能残高(投票、入金、引き出し)
xx ... xx _sv =スタートアップ、 投票数(vote、getFunds)
xx ... xx _sf =スタートアップ、 投票数(vote、getFunds)
xx ... xx =パブリックアドレス(35文字)
投票では、複数のフィールドを一度に更新する必要がありました。
WriteSet([DataEntry(key1, value1), DataEntry(key2, value2)]),
WriteSetを使用すると、1つのinvokeScriptトランザクション内で複数のレコードを一度に作成できます。
これは、BobとCooperがia-預金を補充した後、DAO dAppキーバリューストレージでどのように見えるかです。
デポジット機能はわずかに変更されました:
今、DAOの活動で最も重要な瞬間、つまり資金調達プロジェクトへの投票があります。
ボブは、ネリの500,000ウェーブレットプロジェクトに投票します。
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"vote",args:[{type:"integer", value: 500000}, {type:"string", value: "3MrXEKJr9nDLNyVZ1d12Mq4jjeUYwxNjMsH"}]}, payment: []}))
投票機能コード:
@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) ]) }
データウェアハウスでは、Neliアドレスに必要なすべてのエントリが表示されます。
クーパーはNeliプロジェクトにも投票しました。
getFunds関数コードを見てみましょう。 Neliは、DAOから資金を引き出すために最低2票を集めなければなりません。
ネリは、彼女に預けた金額の半分を撤回します。
broadcast(invokeScript({dappAddress: address(env.accounts[1]), call:{function:"getFunds",args:[{type:"integer", value: 500000}]}, payment: []}))
彼女は成功しました、つまり、DAOは機能します!
RIDE4DAPPSの言語でDAOを作成するプロセスを確認しました。
次のパートでは、コードのリファクタリングとケーステストについて詳しく説明します。
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 } }
前編
Githubコード
Waves RIDE IDE
助成プログラムのお知らせ