以太坊构建DApps系列教程(六):使用定制代币进行投票

本文深入探讨以太坊去中心化自治组织(DAO)的投票机制,包括提案、投票流程、执行及治理功能,如分红、退出和黑名单管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在本系列关于使用以太坊构建DApps教程的第5部分中,我们讨论了如何为Story添加内容,查看如何添加参与者从DAO购买代币的功能以及在Story中添加提交内容。现在是编写DAO最终形式的时候了:投票,黑名单,股息分配和退出。我们将提供一些额外的辅助函数以便进行监测。

如果你对这一切感觉迷失了,那么repo中会提供完整的源代码。

投票和提案

我们将发布Votes并投票。这需要两个新的结构:

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,以便我们可以将它们存储在某个地方,并提供一个计数器来计算有多少提案。

让我们现在构建他们的附属函数,从投票函数开始:

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

注意函数修饰符:通过将该修饰符添加到我们的合约中,我们可以将它附加到任何将来的函数,并确保只有令牌持有者才能执行该函数。这是一个可重复使用的安全检查!

投票功能做了一些健壮性检查,例如投票权是积极的,选民有足够的代币实际投票等。然后我们从存储中获取提案并确保它既没有过期也没有已经执行。对已经完成的提案进行投票是没有意义的。我们还需要确保这个人还没有投票。我们可以允许改变投票权,但这会让DAO面临一些漏洞,例如人们在最后一刻撤回投票等等。也许是未来版本的候选人?

然后我们在提案中注册一个新的投票,更改当前结果以便于查找分数,最后发出Voted事件。但是什么是token.increaseLockedAmount

这一点逻辑增加了用户的锁定代币数量。该功能只能由代币合约的所有者执行(此时希望是DAO)并且将阻止用户发送超过其帐户注册的锁定金额的令牌数量。提案落实或执行后,此锁定被解除。

让我们编写现在提议删除条目的函数。

投票删除和黑名单

如本系列第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、比特币等区块链相关的交互式在线编程实战教程:

  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文以太坊构建DApps系列教程(六):使用定制代币进行投票

以太坊是一个平台,它上面提供各种模块让用户来搭建应用,如果将搭建应用比作造房子,那么以太坊就提供了墙面、屋顶、地板等模块,用户只需像搭积木一样把房子搭起来,因此在以太坊上建立应用的成本和速度都大大改善。具体来说,以太坊通过一套图灵完备的脚本语言(Ethereum Virtual Machinecode,简称EVM语言)来建立应用,它类似于汇编语言。我们知道,直接用汇编语言编程是非常痛苦的,但以太坊里的编程并不需要直接使用EVM语言,而是类似C语言、Python、Lisp等高级语言,再通过编译器转成EVM语言。上面所说的平台之上的应用,其实就是合约,这是以太坊的核心。合约是一个活在以太坊系统里的自动代理人,他有一个自己的以太币地址,当用户向合约的地址里发送一笔交易后,该合约就被激活,然后根据交易中的额外信息,合约会运行自身的代码,最后返回一个结果,这个结果可能是从合约的地址发出另外一笔交易。需要指出的是,以太坊中的交易,不单只是发送以太币而已,它还可以嵌入相当多的额外信息。如果一笔交易是发送给合约的,那么这些信息就非常重要,因为合约将根据这些信息来完成自身的业务逻辑。合约所能提供的业务,几乎是无穷无尽的,它的边界就是你的想象力,因为图灵完备的语言提供了完整的自由度,让用户搭建各种应用。白皮书举了几个例子,如储蓄账户、用户自定义的子货币等。 2013年年末,以太坊创始人Vitalik Buterin发布了以太坊初版白皮书,启动了项目。2014年7月24日起,以太坊进行了为期42天的以太币预售。2016年初,以太坊的技术得到市场认可,价格开始暴涨,吸引了大量开发者以外的人进入以太坊的世界。中国三大比特币交易所之二的火币网及OKCoin币行都于2017年5月31日正式上线以太坊。 [1] 自从进入2016年以来,那些密切关注数字货币产业的人都急切地观察着第二代加密货币平台以太坊的发展动向。作为一种比较新的利用比特币技术的开发项目,以太坊致力于实施全球去中心化且无所有权的的数字技术计算机来执行点对点合约。简单来说就是,以太坊是一个你无法关闭的世界计算机。加密架构与图灵完整性的创新型结合可以促进大量的新产业的出现。反过来,传统行业的创新压力越来越大,甚至面临淘汰的风险。比特币网络事实上是一套分布式的数据库,而以太坊则更进一步,她可以看作是一台分布式的计算机:区块链是计算机的ROM,合约是程序,而以太坊的矿工们则负责计算,担任CPU的角色。这台计算机不是、也不可能是免费使用的,不然任何人都可以往里面存储各种垃圾信息和执行各种鸡毛蒜皮的计算,使用它至少需要支付计算费和存储费,当然还有其它一些费用。最为知名的是2017年初以摩根大通、芝加哥交易所集团、纽约梅隆银行、汤森路透、微软、英特尔、埃森哲等20多家全球top金融机构和科技公司成立的企业以太坊联盟。而以太坊催生的加密货币以太币近期又成了继比特币之后受追捧的资产。  智能合约的潜在应用很多。彭博社商业周刊称它是“所有人共享但无法篡改的软件”。更高级的软件有可能用以太坊创建网络商店。区块链程序以太坊可以用来创建去中心化的程序、自治组织和智能合约,据纽约时报的报导,在2016年5月已经有数十个可用的程序。预期的应用目标涵盖金融、物联网、农田到餐桌(farm-to-table)、智能电网、体育,菠菜等。去中心化自治组织有潜力让许多原本无法运行或成本过高的营运模型成为可能。较知名的应用有:去中心化创业投资:The DAO用以太币资金创立,目标是为商企业和非营利机构创建新的去中心化营业模式、The Rudimental让独立艺术家在区块链上进行群众募资。社会经济平台:Backfeed。去中心化预测市场:Augur。物联网:Ethcore(一间以太坊公司)研发的客户端、Chronicled(一间区块链公司)发表了以太坊区块链的实物资产验证平台;芯片公司、物理IP创建者和生产者可以用植入的蓝牙或近场通信进行验证。Slock.It开发的智能锁可以在付费后自动打开,让用户在付费后可以帮电动车充电、或是打开租屋的房门。虚拟宝物交易平台:FreeMyVunk。版权授权:Ujo Music平台让创作人用智能合约发布音乐,消费者可以直接付费给创作人。伊莫珍·希普用此平台发布了一首单曲。智能电网:TransActive Grid让用户可以和邻居买卖能源。去中心化期权市场:Etheropt。钉住汇率的代币:DigixDAO提供与黄金挂钩的代币,在2016年四月正式营运。Decentralized Capital提供和各种货币挂钩的代币。移动支付:Everex让外劳汇款回家乡。客户端软件以太坊的两个主要的客户端软件是Geth和Parity。企业软件企业软件公司也正测试用以太坊作为各种用途。已知有兴趣的公司包括微软、IBM、摩根大通。德勤和ConsenSys在2016年
标题基于SpringBoot+Vue的社区便民服务平台研究AI更换标题第1章引言介绍社区便民服务平台的研究背景、意义,以及基于SpringBoot+Vue技术的研究现状和创新点。1.1研究背景与意义分析社区便民服务的重要性,以及SpringBoot+Vue技术在平台建设中的优势。1.2国内外研究现状概述国内外在社区便民服务平台方面的发展现状。1.3研究方法与创新点阐述本文采用的研究方法和在SpringBoot+Vue技术应用上的创新之处。第2章相关理论介绍SpringBoot和Vue的相关理论基础,以及它们在社区便民服务平台中的应用。2.1SpringBoot技术概述解释SpringBoot的基本概念、特点及其在便民服务平台中的应用价值。2.2Vue技术概述阐述Vue的核心思想、技术特性及其在前端界面开发中的优势。2.3SpringBoot与Vue的整合应用探讨SpringBoot与Vue如何有效整合,以提升社区便民服务平台的性能。第3章平台需求分析与设计分析社区便民服务平台的需求,并基于SpringBoot+Vue技术进行平台设计。3.1需求分析明确平台需满足的功能需求和性能需求。3.2架构设计设计平台的整体架构,包括前后端分离、模块化设计等思想。3.3数据库设计根据平台需求设计合理的数据库结构,包括数据表、字段等。第4章平台实现与关键技术详细阐述基于SpringBoot+Vue的社区便民服务平台的实现过程及关键技术。4.1后端服务实现使用SpringBoot实现后端服务,包括用户管理、服务管理等核心功能。4.2前端界面实现采用Vue技术实现前端界面,提供友好的用户交互体验。4.3前后端交互技术探讨前后端数据交互的方式,如RESTful API、WebSocket等。第5章平台测试与优化对实现的社区便民服务平台进行全面测试,并针对问题进行优化。5.1测试环境与工具介绍测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值