首頁 >科技週邊 >IT業界 >建造以太坊Dapps:用定制令牌進行投票

建造以太坊Dapps:用定制令牌進行投票

Christopher Nolan
Christopher Nolan原創
2025-02-16 10:35:10205瀏覽

Building Ethereum DApps: Voting with Custom Tokens

核心要點

  • 利用DAO中的自定義代幣進行提案投票,包括刪除條目和將地址列入黑名單,從而增強去中心化治理和運營靈活性。
  • 實施強大的投票機制,要求驗證代幣所有權並在投票期間鎖定代幣,以防止操縱並確保承諾。
  • 設計具有結構化截止日期和執行標準的提案和投票系統,以維持DAO運營的有序性和效率。
  • 通過將提案創建限制為僅限成員,並啟用由DAO所有者控制的緊急刪除功能來解決潛在的安全問題。
  • 探討在以太坊上部署大型智能合約的技術挑戰和解決方案,強調代碼優化的重要性,以管理燃氣成本並確保成功部署。

本教程系列的第五部分介紹了使用以太坊構建DApp,我們討論了向故事中添加內容,以及如何為參與者添加從DAO購買代幣和向故事中添加提交內容的功能。現在是DAO最終形式的時候了:投票、列入/取消黑名單以及股息分配和提取。我們將增加一些額外的輔助函數。

如果您對這些內容感到困惑,完整的源代碼可在代碼庫中找到。

投票和提案

我們將使用提案和投票進行投票。我們需要兩個新的結構體:

<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除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;
}</code>

提案將包含一個選民映射,以防止人們對一個提案投票兩次,以及其他一些應該不言自明的元數據。投票將是讚成票或反對票,並將記住投票者及其投票方式的理由以及投票權——他們想要用於投票此提案的代幣數量。我們還添加了一個提案數組,以便我們可以將它們存儲在某個地方,以及一個用於計算提案數量的計數器。

現在讓我們構建它們的配套函數,從投票函數開始:

<code>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), "Vote power exceeds token balance.");
    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;
}</code>

請注意函數修飾符:通過將該修飾符添加到我們的合約中,我們可以將其附加到任何未來的函數中,並確保只有代幣持有者才能執行該函數。這是一個可重用的安全檢查!

投票函數進行一些健全性檢查,例如投票權為正,投票者擁有足夠的代幣來實際投票等。然後,我們從存儲中獲取提案,並確保它既沒有過期也沒有被執行。對已經完成的提案進行投票是沒有意義的。我們還需要確保這個人還沒有投票。我們可以允許更改投票權,但這會使DAO面臨一些漏洞,例如人們在最後一刻撤回投票等。也許是未來版本的候選者?

然後,我們將新的投票註冊到提案中,更改當前結果以便於查找分數,最後發出投票事件。但是什麼是token.increaseLockedAmount?

這段邏輯增加了用戶的鎖定代幣數量。該函數只能由代幣合約的所有者執行(此時希望是DAO),並將阻止用戶發送超過註冊到其帳戶的鎖定金額的代幣數量。此鎖定在提案失敗或執行後解除。

現在讓我們編寫用於提出刪除條目的函數。

投票刪除和列入黑名單

在本系列的第一部分中,我們計劃了三個條目刪除函數:

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

刪除單個地址的五個條目會導致列入黑名單。

讓我們看看我們現在如何做到這一點。首先,是刪除函數:

<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除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;
}</code>

提出後,提案將添加到提案列表中,並通過條目哈希記下目標條目。保存描述並添加一些默認值,並根據提案類型計算截止日期。添加提案事件被發出,提案總數增加。

接下來讓我們看看如何執行提案。要可執行,提案必須有足夠的投票,並且必須超過其截止日期。執行函數將接受要執行的提案的ID。沒有簡單的方法可以讓EVM一次執行所有未決提案。可能會有太多未決提案需要執行,並且它們可能會對DAO中的數據進行重大更改,這可能會超過以太坊區塊的燃氣限制,從而導致交易失敗。構建一個可由任何符合明確定義規則的人調用的手動執行函數要容易得多,這樣社區就可以關注需要執行的提案。

<code>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), "Vote power exceeds token balance.");
    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;
}</code>

我們通過其ID獲取提案,檢查它是否滿足未執行且截止日期已過要求,然後如果提案的類型是刪除提案並且投票結果為正,我們使用已編寫的刪除函數,最後發出我們添加的新事件(將其添加到合約頂部)。 assert調用在那裡與require語句的作用相同:assert通常用於“斷言”結果為真。 Require用於先決條件。在功能上它們是相同的,區別在於assert語句無法接受消息參數來處理它們失敗的情況。該函數通過為該提案中的所有投票解鎖代幣結束。

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

<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除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;
}</code>

這樣更好。自動列入黑名單,刪除五次。不給列入黑名單的地址贖回的機會是不公平的。我們還需要定義黑名單函數本身。讓我們同時做這兩件事,並將取消黑名單的費用設置為,例如,0.05以太幣。

<code>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), "Vote power exceeds token balance.");
    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;
}</code>

請注意,列入黑名單的帳戶的代幣將被鎖定,直到他們發送取消黑名單的費用。

其他類型的投票

根據我們上面編寫的函數的靈感,嘗試編寫其他提案。有關劇透,請查看項目的GitHub代碼庫並從那裡複製最終代碼。為簡潔起見,讓我們繼續討論我們仍在DAO中剩餘的其他函數。

章節結束

一旦達到故事的時間或章節限制,就該結束故事了。日期之後,任何人都可以調用結束函數,這將允許提取股息。首先,我們需要一個新的StoryDAO屬性和一個事件:

<code>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;
}</code>

然後,讓我們構建函數:

<code>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);
}</code>

簡單:它在將收集的費用發送給所有者並發出事件後停用故事。但在實際上,這並沒有真正改變DAO的整體情況:其他函數不會對它結束做出反應。因此,讓我們構建另一個修飾符:

<code>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;
}</code>

然後,我們將此修飾符添加到除withdrawToOwner之外的所有函數中,如下所示:

<code>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 < proposals.length; i++) {
        if (proposals[i].executed == false && proposals[i].voters[_voter] == true) {
            return false;
        }
    }
    return true;
}</code>

如果DAO中還有剩餘的代幣,讓我們收回它們並接管這些代幣的所有權,以便以後能夠將它們用於另一個故事:

<code>bool public active = true;
event StoryEnded();</code>

unlockMyTokens函數用於解鎖特定用戶可能鎖定的所有鎖定代幣。不應該發生這種情況,並且應該通過大量的測試來刪除此函數。

股息分配和提取

現在故事已經結束,需要將為提交收取的費用分配給所有代幣持有者。我們可以重新使用我們的白名單來標記所有已經提取費用的人:

<code>function endStory() storyActive external {
    withdrawToOwner();
    active = false;
    emit StoryEnded();
}</code>

如果這些股息在一定時間限制內未提取,所有者可以獲取剩餘部分:

<code>modifier storyActive() {
    require(active == true);
    _;
}</code>

作為家庭作業,考慮一下重新使用這個已部署的智能合約、清除其數據、保留池中的代幣並從這裡重新啟動另一章而不重新部署是多麼容易或困難。嘗試自己這樣做,並關注代碼庫以獲取本系列涵蓋此內容的未來更新!還要考慮額外的激勵機制:也許帳戶中的代幣數量會影響他們從故事結束獲得的股息?你的想像力是無限的!

部署問題

鑑於我們的合約現在相當大,部署和/或測試它可能會超過以太坊區塊的燃氣限制。這就是限制大型應用程序部署到以太坊網絡的原因。為了無論如何部署它,請嘗試在編譯期間使用代碼優化器,方法是更改truffle.js文件以包含優化的solc設置,如下所示:

<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除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;
}</code>

這將對代碼運行優化器200次,以查找可以在部署前進行縮小、刪除或抽象的區域,這應該會大大降低部署成本。

結論

這就結束了我們詳盡的DAO開發——但這門課程還沒有結束!我們仍然必須為這個故事構建和部署UI。幸運的是,由於後端完全託管在區塊鏈上,構建前端要簡單得多。讓我們在本系列的倒數第二部分中看看這一點。

關於使用自定義代幣構建以太坊DApp和投票的常見問題

區塊鏈投票在實踐中是如何運作的?

區塊鏈投票是一個利用區塊鏈技術的透明性和安全性的去中心化投票系統。理論上,它應該完美運行,但在實踐中,它經常遇到挑戰。投票過程涉及在以太坊區塊鏈上創建智能合約,每個投票都是可以驗證的交易。然而,選民匿名性、投票操縱以及使用區塊鏈平台的技術複雜性等問題可能會阻礙其實際實施。

什麼是DAO投票機制?

DAO(去中心化自治組織)投票機制是允許DAO中的代幣持有者根據其代幣所有權對提案進行投票的系統。最常見的機制包括簡單多數投票,如果提案獲得超過50%的投票,則該提案被接受;以及二次投票,其中對提案投多票的成本呈指數級增長。

安全代幣中的治理是如何運作的?

安全代幣中的治理通常通過投票系統來處理,在該系統中,代幣持有者可以對項目的各個方面進行投票。這可能包括關於項目開發、代幣經濟學甚至治理系統本身變化的決策。代幣持有者的投票權通常與其持有的代幣數量成正比。

如何設置DAO治理?

設置DAO治理涉及在以太坊區塊鏈上創建一個智能合約,該合約概述了組織的規則,包括投票權和提案機制。然後將此合約部署到區塊鏈上,並將代表投票權的代幣分發給成員。然後,成員可以提出並投票表決對組織的更改。

持有DAO治理代幣有哪些風險?

由於加密貨幣的波動性和圍繞DAO的法規不確定性,持有DAO治理代幣可能存在風險。例如,商品期貨交易委員會(CFTC)警告說,使用DAO代幣進行投票可能被視為一種市場操縱形式。此外,如果DAO管理不善或成為黑客攻擊的受害者,代幣持有者可能會損失投資。

如何為以太坊DApp中的投票創建自定義代幣?

為以太坊DApp中的投票創建自定義代幣涉及在以太坊區塊鏈上編寫和部署智能合約。此合約定義了代幣的屬性,例如其名稱、符號和總供應量。一旦合約部署完畢,代幣就可以分發給用戶,然後用戶可以使用它們來對DApp中的提案進行投票。

使用區塊鏈進行投票的好處是什麼?

區塊鏈投票提供了多種好處,包括透明度、安全性和不變性。投票被記錄為區塊鏈上的交易,使其透明且可驗證。區塊鏈的去中心化性質也使得任何單一方都難以操縱投票過程。

如何確保區塊鏈投票中的選民匿名性?

由於區塊鏈交易的透明性質,確保區塊鏈投票中的選民匿名性可能具有挑戰性。但是,可以使用諸如零知識證明之類的技術來驗証投票的有效性,而無需透露投票者的身份。

實施區塊鏈投票的挑戰是什麼?

由於技術複雜性、法規不確定性和潛在的安全風險,實施區塊鏈投票可能具有挑戰性。用戶需要熟悉區塊鏈技術才能參與投票過程,監管機構可能對區塊鏈投票系統的合法性和安全性表示擔憂。

如何減輕與DAO治理代幣相關的風險?

減輕與DAO治理代幣相關的風險包括仔細管理DAO、徹底的安全措施以及隨時了解監管發展。同樣重要的是要使您的投資組合多樣化,並且不要投資超過您所能承受的損失。

以上是建造以太坊Dapps:用定制令牌進行投票的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn