首页 >科技周边 >IT业界 >建造以太坊Dapps:用定制令牌进行投票

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

Christopher Nolan
Christopher Nolan原创
2025-02-16 10:35:10194浏览

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