以太坊構建DApps系列教程(六):使用定製代幣進行投票

在本系列關於使用以太坊構建DApps教程的第5部分中,咱們討論瞭如何爲Story添加內容,查看如何添加參與者從DAO購買代幣的功能以及在Story中添加提交內容。如今是編寫DAO最終形式的時候了:投票,黑名單,股息分配和退出。咱們將提供一些額外的輔助函數以便進行監測。php

若是你對這一切感受迷失了,那麼repo中會提供完整的源代碼。前端

投票和提案

咱們將發佈Votes並投票。這須要兩個新的結構:java

struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // ID of the proposal target. I.e. flag 1, target XXXXXX (hash) means proposal to delete submissions[hash]
    uint256 creationDate;
    uint256 deadline;
    mapping (address => bool) voters;
    Vote[] votes;
    address submitter;
}

Proposal[] public proposals;
uint256 proposalCount = 0;
event ProposalAdded(uint256 id, uint8 typeFlag, bytes32 hash, string description, address submitter);
event ProposalExecuted(uint256 id);
event Voted(address voter, bool vote, uint256 power, string justification);

struct Vote {
    bool inSupport;
    address voter;
    string justification;
    uint256 power;
}
複製代碼

提案將對選民進行映射,以防止人們對提案進行兩次投票,以及其餘一些應該不言自明的元數據。投票將是一個是或否投票,並將記住選民以及他們以某種方式投票的理由,以及投票權——他們但願投入該投票的代幣數量。咱們還添加了一系列Proposals,以便咱們能夠將它們存儲在某個地方,並提供一個計數器來計算有多少提案。node

讓咱們如今構建他們的附屬函數,從投票函數開始:python

modifier tokenHoldersOnly() {
    require(token.balanceOf(msg.sender) >= 10**token.decimals());
    _;
}

function vote(uint256 _proposalId, bool _vote, string _description, uint256 _votePower) tokenHoldersOnly public returns (int256) {

    require(_votePower > 0, "At least some power must be given to the vote.");
    require(uint256(_votePower) <= token.balanceOf(msg.sender), "Voter must have enough tokens to cover the power cost.");

    Proposal storage p = proposals[_proposalId];

    require(p.executed == false, "Proposal must not have been executed already.");
    require(p.deadline > now, "Proposal must not have expired.");
    require(p.voters[msg.sender] == false, "User must not have already voted.");

    uint256 voteid = p.votes.length++;
    Vote storage pvote = p.votes[voteid];
    pvote.inSupport = _vote;
    pvote.justification = _description;
    pvote.voter = msg.sender;
    pvote.power = _votePower;

    p.voters[msg.sender] = true;

    p.currentResult = (_vote) ? p.currentResult + int256(_votePower) : p.currentResult - int256(_votePower);
    token.increaseLockedAmount(msg.sender, _votePower);

    emit Voted(msg.sender, _vote, _votePower, _description);
    return p.currentResult;
}
複製代碼

注意函數修飾符:經過將該修飾符添加到咱們的合約中,咱們能夠將它附加到任何未來的函數,並確保只有令牌持有者才能執行該函數。這是一個可重複使用的安全檢查!android

投票功能作了一些健壯性檢查,例如投票權是積極的,選民有足夠的代幣實際投票等。而後咱們從存儲中獲取提案並確保它既沒有過時也沒有已經執行。對已經完成的提案進行投票是沒有意義的。咱們還須要確保這我的尚未投票。咱們能夠容許改變投票權,但這會讓DAO面臨一些漏洞,例如人們在最後一刻撤回投票等等。也許是將來版本的候選人?git

而後咱們在提案中註冊一個新的投票,更改當前結果以便於查找分數,最後發出Voted事件。可是什麼是token.increaseLockedAmount程序員

這一點邏輯增長了用戶的鎖定代幣數量。該功能只能由代幣合約的全部者執行(此時但願是DAO)而且將阻止用戶發送超過其賬戶註冊的鎖定金額的令牌數量。提案落實或執行後,此鎖定被解除。github

讓咱們編寫如今提議刪除條目的函數。web

投票刪除和黑名單

如本系列第1部分所述 ,咱們計劃了三個條目刪除功能:

  • 1.刪除條目:經過投票確認後,目標條目將被刪除。投票時間:48小時。
  • 2.緊急刪除條目[僅限全部者]:只能由全部者觸發。經過投票確認後,目標條目將被刪除。投票時間:24小時。
  • 3.緊急刪除圖像[僅限全部者]:僅適用於圖像條目。只能由全部者觸發。經過投票確認後,目標條目將被刪除。投票時間:4小時。

單個地址條目的五個刪除致使黑名單。

讓咱們看看咱們如今該怎麼作。首先,刪除功能:

modifier memberOnly() {
    require(whitelist[msg.sender]);
    require(!blacklist[msg.sender]);
    _;
}

function proposeDeletion(bytes32 _hash, string _description) memberOnly public {

    require(submissionExists(_hash), "Submission must exist to be deletable");

    uint256 proposalId = proposals.length++;
    Proposal storage p = proposals[proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = now;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = now + 2 days;

    emit ProposalAdded(proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}

function proposeDeletionUrgent(bytes32 _hash, string _description) onlyOwner public {

    require(submissionExists(_hash), "Submission must exist to be deletable");

    uint256 proposalId = proposals.length++;
    Proposal storage p = proposals[proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = now;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = now + 12 hours;

    emit ProposalAdded(proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}    

function proposeDeletionUrgentImage(bytes32 _hash, string _description) onlyOwner public {

    require(submissions[_hash].image == true, "Submission must be existing image");

    uint256 proposalId = proposals.length++;
    Proposal storage p = proposals[proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = now;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = now + 4 hours;

    emit ProposalAdded(proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}
複製代碼

一旦提出,建議書就會被添加到提案列表中,並記錄條目哈希所針對的條目。保存說明並添加一些默認值,並根據提案類型計算截止日期。該提案添加了事件,而且提案總數增長了。

接下來讓咱們看看如何執行提案。爲了可執行,提案必須有足夠的票數,而且必須超過其截止日期。執行功能將接受要執行的提議的ID。沒有簡單的方法可讓EVM當即執行全部待處理的提案。可能有太多人要等待執行,而且他們會對DAO中的數據進行大的更改,這可能會超過以太坊塊的氣體限制,從而致使交易失敗。構建一個能夠由具備明確規則的任何人調用的手動執行功能要容易得多,所以社區能夠關注須要執行的提議。

function executeProposal(uint256 _id) public {
    Proposal storage p = proposals[_id];
    require(now >= p.deadline && !p.executed);

    if (p.typeFlag == 1 && p.currentResult > 0) {
        assert(deleteSubmission(p.target));
    }

    uint256 len = p.votes.length;
    for (uint i = 0; i < len; i++) {
        token.decreaseLockedAmount(p.votes[i].voter, p.votes[i].power);
    }

    p.executed = true;
    emit ProposalExecuted(_id);
}
複製代碼

咱們經過其ID獲取提案,檢查它是否符合未執行的要求和截止日期過時,而後若是提案的類型是刪除提案且投票結果是確定的,咱們使用已經寫入的刪除功能,最後發出了咱們添加的新事件(將其添加到合約的頂部)。assert調用與require語句具備相同的用途:斷言一般在「斷言」結果爲真時使用。要求用於先決條件。在功能上它們是相同的,assert語句的差別在它們失敗時沒法接受消息參數。該功能經過爲該一個提案中的全部投票解鎖代幣而結束。

咱們可使用相同的方法添加其餘類型的提案,但首先,讓咱們更新deleteSubmission函數以禁止在其賬戶上有五個或更多刪除的用戶:這意味着他們一直在提交社區投票反對的內容。讓咱們更新deleteSubmission函數:

function deleteSubmission(bytes32 hash) internal returns (bool) {
    require(submissionExists(hash), "Submission must exist to be deletable.");
    Submission storage sub = submissions[hash];

    sub.exists = false;
    deletions[submissions[hash].submitter] += 1;
    if (deletions[submissions[hash].submitter] >= 5) {
        blacklistAddress(submissions[hash].submitter);
    }

    emit SubmissionDeleted(
        sub.index,
        sub.content,
        sub.image,
        sub.submitter
    );

    nonDeletedSubmissions -= 1;
    return true;
}
複製代碼

那更好。自動將五個刪除列入黑名單。可是,若是不給黑名單地址提供贖回的機會,那是不公平的。咱們還須要定義黑名單功能自己。讓咱們作這兩件事並將不合理的費用設置爲例如0.05以太。

function blacklistAddress(address _offender) internal {
    require(blacklist[_offender] == false, "Can't blacklist a blacklisted user :/");
    blacklist[_offender] == true;
    token.increaseLockedAmount(_offender, token.getUnlockedAmount(_offender));
    emit Blacklisted(_offender, true);
}

function unblacklistMe() payable public {
    unblacklistAddress(msg.sender);
}

function unblacklistAddress(address _offender) payable public {
    require(msg.value >= 0.05 ether, "Unblacklisting fee");
    require(blacklist[_offender] == true, "Can't unblacklist a non-blacklisted user :/");
    require(notVoting(_offender), "Offender must not be involved in a vote.");
    withdrawableByOwner = withdrawableByOwner.add(msg.value);
    blacklist[_offender] = false;
    token.decreaseLockedAmount(_offender, token.balanceOf(_offender));
    emit Blacklisted(_offender, false);
}

function notVoting(address _voter) internal view returns (bool) {
    for (uint256 i = 0; i < proposalCount; i++) {
        if (proposals[i].executed == false && proposals[i].voters[_voter] == true) {
            return false;
        }
    }
    return true;
}
複製代碼

請注意,列入黑名單的賬戶的令牌會被鎖定,直到他們發送不合格的費用爲止。

其餘類型的投票

使用咱們上面寫的函數的靈感,嘗試編寫其餘提議。對於劇透,請查看項目的GitHub倉庫並從那裏複製最終代碼。爲簡潔起見,讓咱們繼續討論DAO中剩下的其餘功能。

章節的結束

一旦達到故事的時間或章節限制,就應該結束故事了。任何人均可以在容許提取股息的日期以後調用結束函數。首先,咱們須要一個新的StoryDAO屬性和一個事件:

bool public active = true;
event StoryEnded(); 
複製代碼

而後,讓咱們構建函數:

function endStory() storyActive external {
    withdrawToOwner();
    active = false;
    emit StoryEnded();
}
複製代碼

簡單:它將收集的費用發送給全部者併發出事件後停用故事。但實際上,這並無真正改變整個DAO中的任何內容:其餘功能對它的結束沒有反應。那麼讓咱們構建另外一個修飾符:

modifier storyActive() {
    require(active == true);
    _;
}
複製代碼

而後,咱們將此修飾符添加到除withdrawToOwner以外的全部函數中,以下所示:

function whitelistAddress(address _add) storyActive public payable { 
複製代碼

若是DAO中遺留了任何代幣,讓咱們將它們取回並接管這些代幣的全部權,以便之後可以在另外一個故事中使用它們:

function withdrawLeftoverTokens() external onlyOwner {
    require(active == false);
    token.transfer(msg.sender, token.balanceOf(address(this)));
    token.transferOwnership(msg.sender);
}

function unlockMyTokens() external {
    require(active == false);
    require(token.getLockedAmount(msg.sender) > 0);

    token.decreaseLockedAmount(msg.sender, token.getLockedAmount(msg.sender));
}
複製代碼

unlockMyTokens函數用於解鎖全部鎖定的代幣,以防某些鎖定代幣爲特定用戶鎖定。它不該該發生,而且應該經過大量測試來移除此功能。

股息分配和提款

如今故事已經結束,收集的費用須要分配給全部代幣持有者。咱們能夠從新使用咱們的白名單來標記全部取消費用的人:

function withdrawDividend() memberOnly external {
    require(active == false);
    uint256 owed = address(this).balance.div(whitelistedNumber);
    msg.sender.transfer(owed);
    whitelist[msg.sender] = false;
    whitelistedNumber--;
}
複製代碼

若是這些股息未在必定時限內撤回,業主能夠抓住其他股息:

function withdrawEverythingPostDeadline() external onlyOwner {
    require(active == false);
    require(now > deadline + 14 days);
    owner.transfer(address(this).balance);
}
複製代碼

留個家庭做業,考慮從新使用相同部署的智能合約,清除其數據,並將代幣保留在底池中並從新啓動另外一章而無需從新部署是多麼容易或困難。嘗試本身這樣作,並密切關注回購,以便未來更新本系列教程!還要考慮額外的激勵機制:也許帳戶中的代幣數量會影響他們從故事結束中得到的紅利?你的想象力是極限!

部署問題

鑑於咱們的合約如今很是大,部署和/或測試它可能會超過以太坊區塊的gas限制。這是限制大型應用程序部署在以太坊網絡上的緣由。不管如何要部署它,在編譯期間嘗試使用代碼優化器,方法是更改truffle.js文件以包含用於優化的solc設置,以下所示:

// ...

module.exports = {
  solc: {
    optimizer: {
      enabled: true,
      runs: 200
    }
  },
  networks: {
    development: {
// ...
複製代碼

這將在代碼中運行優化器200次以查找在部署以前能夠縮小,移除或抽象的區域,這將顯着下降部署成本。

結論

這就是咱們詳盡的DAO開發——但課程尚未結束!咱們仍然須要爲這個故事構建和部署UI。幸運的是,後端徹底託管在區塊鏈上,構建前端的複雜程度要低得多。讓咱們看看這個系列的倒數第二部分。

======================================================================

分享一些以太坊、EOS、比特幣等區塊鏈相關的交互式在線編程實戰教程:

  • java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
  • python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
  • php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和交易等內容。
  • 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
  • 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
  • EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
  • java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
  • php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
  • tendermint區塊鏈開發詳解,本課程適合但願使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。

匯智網原創翻譯,轉載請標明出處。這裏是原文以太坊構建DApps系列教程(六):使用定製代幣進行投票

相關文章
相關標籤/搜索