Solidity 官网

github

Solidity是一种静态类型的花括号(curly-braces)编程语言,旨在开发运行在以太坊(Ethereum)上的智能合约。

Solidity正在迅速发展

作为一门相对年轻的语言,Solidity正在迅速发展。我们的目标是每月定期发布(非突破性)版本,每年大约发布一个突破性版本。您可以在Solidity Github项目中跟踪新功能的实现状态。通过从默认分支(develop)切换到breaking branch,您可以看到下一个破坏性版本即将发生的更改。您可以通过提供输入并参与语言设计来积极地塑造Solidity。

一、入门 (v0.8.17

Solidity是一种面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态下账户行为的程序。

Solidity是一种以以太坊虚拟机(Ethereum Virtual Machine, EVM)为目标的大括号语言(curly-bracket language )。它受到c++, Python和JavaScript的影响。您可以在语言影响部分中找到有关Solidity受到哪些语言启发的更多细节。

Solidity是静态类型的,支持继承、库和复杂的用户定义类型。

使用Solidity,您可以创建用于投票、众筹、盲拍和多签名钱包等用途的合同。

在部署合约时,您应该使用最新发布的Solidity版本。除特殊情况外,只有最新版本才会收到安全补丁。此外,还会定期引入突破性的变化和新特性。我们目前使用的是0.y.z版本号表示这种快速的更改速度。

1.1 了解智能合约基础知识

如果你对智能合约的概念不熟悉,我们建议你从“智能合约介绍”部分开始,其中包括:

  • 一个用Solidity写的智能合约的简单例子。
  • 区块链基础知识。
  • 以太坊虚拟机。

1.2 了解Solidity

一旦你熟悉了基础,我们建议你阅读Solidity by Example和“语言描述”部分来理解语言的核心概念。

1.3 安装Solidity编译器

安装Solidity编译器有多种方法,只需选择您喜欢的选项,并按照安装页面上列出的步骤进行安装。

您可以使用Remix IDE直接在浏览器中尝试代码示例。Remix是一个基于web浏览器的IDE,允许您编写、部署和管理Solidity智能合约,而不需要在本地安装Solidity。

Warning

当人们编写软件时,它可能会有bug。在编写智能合约时,您应该遵循已建立的软件开发最佳实践。这包括代码审查、测试、审核和正确性证明。智能合约用户有时比他们的作者对代码更有信心,区块链和智能合约有自己独特的问题要注意,所以在使用生产代码之前,一定要阅读安全注意事项部分。

1.4 Learn More

如果你想了解更多关于在以太坊上构建去中心化应用程序的知识,以太坊开发者资源可以帮助你进一步了解以太坊的一般文档,以及广泛的教程、工具和开发框架。

如果您有任何问题,您可以尝试在以太坊StackExchange或我们的Gitter频道上搜索答案或询问。

Keyword Index

二、智能合约简介

2.1 一个简单的智能合约

让我们从一个基本示例开始,该示例设置一个变量的值,并将其公开给其他合约访问。如果您现在还不明白也没关系,我们稍后再详细说明。

2.1.1 Storage Example

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;contract SimpleStorage {uint storedData;function set(uint x) public {storedData = x;}function get() public view returns (uint) {return storedData;}
}

第一行告诉您源代码是在GPL 3.0版本下授权的。在默认发布源代码的设置中,机器可读许可说明符(license specifiers)非常重要。

下一行指定源代码是为Solidity版本0.4.16编写的,或该语言的更新版本,但不包括版本0.9.0。这是为了确保合约不能用新的(破坏的)编译器版本编译,在那里它可能会有不同的行为。pragma是编译器关于如何处理源代码的常用指令(例如pragma once)。

从Solidity 的意义上讲,合约是位于以太坊区块链上特定地址的代码(其functions)和数据(其state)的集合。行uint storedData;声明一个名为storedData的状态变量,类型为uint(256位无符号整数)。您可以把它看作数据库中的一个插槽(slot ),您可以通过调用管理数据库的函数来查询和修改它。在本例中,合约定义了可用于修改或检索变量值的函数setget

要访问当前合约的成员(如状态(state )变量),通常不需要添加this.前缀,你可以通过它的名字直接访问它。与其他一些语言不同的是,省略它不仅仅是一个风格问题,它导致了一种完全不同的访问成员的方式,但这将在后面详细介绍。

这个合约除了(由于以太坊构建的基础设施)允许任何人存储一个世界上任何人都可以访问的单个数字之外,没有(可行的)方法来阻止你发布这个数字。任何人都可以用不同的值再次调用set并覆盖您的号码,但号码仍然存储在区块链的历史记录中。稍后,您将看到如何施加访问限制,以便只有您可以更改数字。

警告

使用Unicode文本时要小心,因为外观相似(甚至相同)的字符可能有不同的代码点,因此会被编码为不同的字节数组。

所有标识符(合约名、函数名和变量名)都限制在ASCII字符集内。可以将UTF-8编码的数据存储在字符串变量中。

2.1.2 子货币(Subcurrency)例子

下面的合约实现了最简单的加密货币形式。该合约只允许其创造者创造新的货币(可能有不同的发行方案)。任何人都可以互相发送硬币,而不需要注册用户名和密码,你所需要的只是一个以太坊对。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;contract Coin {// The keyword "public" makes variables// accessible from other contractsaddress public minter;mapping (address => uint) public balances;// Events allow clients to react to specific// contract changes you declareevent Sent(address from, address to, uint amount);// Constructor code is only run when the contract// is createdconstructor() {minter = msg.sender;}// Sends an amount of newly created coins to an address// Can only be called by the contract creatorfunction mint(address receiver, uint amount) public {require(msg.sender == minter);balances[receiver] += amount;}// Errors allow you to provide information about// why an operation failed. They are returned// to the caller of the function.error InsufficientBalance(uint requested, uint available);// Sends an amount of existing coins// from any caller to an addressfunction send(address receiver, uint amount) public {if (amount > balances[msg.sender])revert InsufficientBalance({requested: amount,available: balances[msg.sender]});balances[msg.sender] -= amount;balances[receiver] += amount;emit Sent(msg.sender, receiver, amount);}
}

这份合同引入了一些新概念,让我们一个一个地看一看。

address public minter;声明一个address类型的状态变量。address类型是一个160位的值,不允许任何算术操作。它适用于存储合约的地址,或属于外部帐户的keypair 的公共一半的散列。

关键字public自动生成一个函数,允许您从合约外部访问状态变量的当前值。如果没有这个关键字,其他合约就无法访问这个变量。编译器生成的函数代码相当于以下内容(暂时忽略externalview):

function minter() external view returns (address) { return minter; }

你可以自己添加一个像上面那样的函数,但是你会有一个同名的函数和状态变量。你不需要这样做,编译器会为你计算出来。

下一行mapping (address => uint) public balances;也创建一个公共状态变量,但它是一个更复杂的数据类型。映射 mapping 类型将地址映射为无符号整数(unsigned integers)。

映射可以看作是虚拟初始化的哈希表( hash tables),这样每个可能的键从一开始就存在,并映射到一个字节表示为全零的值。但是,既不可能获得映射的所有键的列表,也不可能获得所有值的列表。记录您添加到映射中的内容,或者在不需要的上下文中使用它。或者更好的是,保留一个列表,或者使用更合适的数据类型。

在映射的情况下,由public关键字创建的getter函数更加复杂。如下图所示:

function balances(address account) external view returns (uint) {return balances[account];
}

支持查询单个账户的余额情况。
这行event Sent(address from, address to, uint amount);声明一个“事件”,该事件在函数send的最后一行中发出。以太坊客户端(如web应用程序)可以在没有太多成本的情况下侦听区块链上发出的这些事件。一旦它被触发,侦听器就会接收fromtoamount参数,这使得跟踪事务成为可能。

为了监听这个事件,你可以使用下面的JavaScript代码,它使用web3.js来创建Coin合约对象,并且任何用户界面都从上面调用自动生成的balances 函数:

Coin.Sent().watch({}, '', function(error, result) {if (!error) {console.log("Coin transfer: " + result.args.amount +" coins were sent from " + result.args.from +" to " + result.args.to + ".");console.log("Balances now:\n" +"Sender: " + Coin.balances.call(result.args.from) +"Receiver: " + Coin.balances.call(result.args.to));}
})

构造函数(constructor)是一个特殊的函数,在创建合约期间执行,之后不能调用。在这种情况下,它永久存储了创建合同的人的地址。msg变量(包括txblock)是一个特殊的全局变量,它包含允许访问区块链的属性。msg.sender始终是当前(外部)函数调用来自于哪的地址。

组成合约的函数以及用户和合约可以调用的函数是mintsend

mint函数将一定数量新创建的硬币发送到另一个地址。require函数调用定义条件,如果不满足,则恢复所有更改。在本例中,require(msg.sender == minter);确保只有契约的创建者可以调用mint。一般来说,创建者可以随心所欲地铸造任意多的代币,但在某种程度上,这将导致一种称为“溢出”的现象。注意,由于默认的Checked arithmetic,如果表达式alances[receiver] += amount;溢出,事务将回滚,即当任意精度算术中的balances[receiver] + amount大于 uint 最大值(2**256 - 1)。对于在函数send中语句balances[receiver] += amount;也是如此。

错误允许您向调用者提供关于条件或操作失败原因的更多信息。错误与 revert statement一起使用。revert语句无条件地中止并恢复与require函数类似的所有更改,但它还允许您提供错误名称和其他数据,这些数据将提供给调用者(最终提供给前端应用程序或块资源管理器),以便更容易地调试或对故障作出反应。

send 函数可以被任何人(已经拥有一些这些币的人)用来向其他人发送币。如果发送者没有足够的币来发送,if 条件的计算结果为true。结果,revert 操作将导致操作失败,同时使用InsufficientBalance错误向发送方提供错误详细信息。

注意:

如果您使用此合约将coins 发送到某个地址,当您在区块链资源管理器上查看该地址时,您将看不到任何东西,因为您发送硬币的记录和更改的余额仅存储在此特定coins 合约的数据存储中。通过使用事件,您可以创建一个“区块链资源管理器”来跟踪新硬币的交易和余额,但您必须检查coins 合同地址,而不是coins 所有者的地址。

2.2 区块链基础知识

区块链作为一个概念对于程序员来说并不难理解。原因是大多数复杂技术(挖掘、哈希、椭圆曲线加密、点对点网络等)只是为了为平台提供一组特定的功能和承诺。一旦你接受了这些给定的特性,你就不必担心底层技术——或者你必须知道亚马逊的AWS在内部是如何工作的才能使用它吗?

2.2.1 交易(Transactions

区块链是一个全球共享的交易数据库。这意味着每个人都可以通过参与网络来读取数据库中的条目。如果您想要更改数据库中的某些内容,则必须创建一个所谓的交易(transaction ),该交易必须被所有其他人接受。交易这个词意味着您想要进行的更改(假设您想同时更改两个值)要么根本没有完成,要么完全应用了。此外,当您的交易被应用到数据库时,没有其他交易可以更改它。

例如,假设有一个表列出了电子货币中所有账户的余额。如果请求从一个帐户转到另一个帐户,数据库的交易性质确保如果从一个帐户减去金额,它总是添加到另一个帐户。如果由于某种原因,无法将金额添加到目标帐户,源帐户也不会被修改。

此外,交易总是由发送方(创建者)加密签署的。这使得保护对数据库特定修改的访问变得简单。在电子货币的例子中,一个简单的检查可以确保只有持有账户钥匙的人才能从中转账。

2.2.2 Blocks

需要克服的一个主要障碍是(用比特币术语来说)所谓的“双花攻击”:如果网络中存在两个都想清空一个账户的交易,会发生什么?只有一个交易是有效的,通常是第一个被接受的交易。问题是“第一”在点对点网络中并不是一个客观的术语。

对此的抽象回答是,你不必在意。将为您选择一个全局接受的交易顺序,从而解决冲突。交易将被捆绑到所谓的“块”中,然后它们将被执行并分布到所有参与节点中。如果两笔交易相互矛盾,最后成为第二笔的交易将被拒绝,不会成为区块的一部分。

这些块在时间上形成线性序列,这就是单词“区块链”的来源。区块以相当规律的间隔被添加到链中——对于以太坊来说,这大约是每17秒一次。

作为“顺序选择机制”(称为“mining”)的一部分,可能会发生区块不时被还原的情况,但只是在链的“尖端”。在特定块上添加的块越多,该块恢复的可能性就越小。因此,您的交易可能会被恢复,甚至从区块链中删除,但等待的时间越长,这种情况发生的可能性就越小。

注意

交易不能保证包含在下一个区块或任何特定的未来区块中,因为它不是由交易的提交者决定的,而是由矿工决定哪个区块包含交易。

如果你想安排未来合约的调用,你可以使用智能合约自动化工具或oracle服务。

2.3 以太坊虚拟机(Ethereum Virtual Machine , EVM )

2.3.1 概述

以太坊虚拟机(EVM)是以太坊智能合约的运行时环境。它不仅是沙盒的,而且实际上是完全隔离的,这意味着在EVM中运行的代码不能访问网络、文件系统或其他进程智能合约甚至可以有限地访问其他智能合约

2.3.2 Accounts

以太坊中有两种帐户共享相同的地址空间:由公私钥对(即人)控制的外部帐户(External accounts)和由与帐户存储在一起的代码控制的合约帐户(contract accounts)。

外部帐户的地址是由公钥确定的,而合同的地址是在合同创建时确定的(它来源于创建者地址和从该地址发送的交易数量,即所谓的“nonce”)。

不管帐户是否存储代码,EVM都平等地对待这两种类型。

每个帐户都有一个持久的键值存储,将256位字映射到256位字,称为存储(storage)。

此外,每个账户都有一个以太币(Ether )余额(balance )(Wei更精确,1 ether10**18 wei),可以通过发送包含以太币的交易来修改。

2.3.3 Transactions

交易是从一个帐户发送到另一个帐户的消息(该帐户可能相同或为空,请参见下文)。它可以包括二进制数据(称为“有效负载(payload)”)和Ether

如果目标帐户包含代码,则执行该代码,并将有效负载作为输入数据提供。

如果没有设置目标帐户(事务没有收件人或收件人被设置为空),则交易将创建一个新合约。如前所述,该合约的地址不是零地址,而是由发送方及其发送的交易数量(“nonce”)派生的地址。这种合约创建交易的有效负载被视为EVM字节码并执行。这个执行的输出数据被永久地存储为合约的代码。这意味着为了创建合约,您不发送合约的实际代码,而是在执行时返回该代码的代码。

在创建合约时,其代码仍然为空。因此,在构造函数完成执行之前,不应该回调正在构造的合约。

2.3.4 Gas

在创建时,每笔交易都被收取一定数量的gas,这些gas必须由交易的发起者(tx.origin)支付。当EVM执行交易时,gas会根据特定的规则逐渐耗尽。如果gas在任何时候被用完(即它将是负的),则触发out-of-gas异常,该异常结束执行并恢复当前调用帧中对状态所做的所有修改。

这种机制激励了EVM执行时间的经济使用,也补偿了EVM执行者(即矿工/利益相关者)的工作。由于每个块都有最大的gas量,因此也限制了验证块所需的工作量。

gas price 是由交易的发起者设定的值,他必须预先向EVM执行者支付gas_price * gas。如果执行后剩余一些气体,则将其退还给交易发起者。在恢复更改的例外情况下,已经用完的gas不退还。

由于EVM执行者可以选择包含或不包含交易,因此交易发送者不能通过设置低gas价格来滥用系统。

2.3.5 Storage, Memory and the Stack

以太坊虚拟机有三个区域可以存储数据:存储、内存和堆栈。

每个帐户都有一个称为存储(storage)的数据区域,该数据区域在函数调用和交易之间是持久的。存储是一个将256位字映射到256位字的键值存储。从合约中枚举存储是不可能的,读取存储的成本相对较高,初始化和修改存储的成本更高。由于这个成本,您应该将存储在持久存储中的内容最小化,以满足契约运行的需要。在合约之外存储派生计算、缓存和聚合等数据。合约既不能读也不能写除它自己以外的任何存储。

第二个数据区域称为内存(memory),其中合约为每个消息调用获取一个新清除的实例。内存是线性的,可以在字节级寻址,但读取的宽度限制为256位,而写入的宽度可以是8位或256位。当访问(读或写)一个以前未触及的内存字(即字内的任何偏移量)时,内存以一个字(256位)扩展。在扩张的时候,必须支付天然气的费用。内存增长得越大,成本就越高(它的比例是2倍的)。

EVM不是寄存器机,而是堆栈机,因此所有计算都在称为堆栈(stack)的数据区域上执行。它的最大大小为1024个元素,包含256 bit 的字。对堆栈的访问以以下方式限制在顶部:可以将最上面的16个元素中的一个复制到堆栈的顶部,或者将最上面的元素与它下面的16个元素中的一个交换。所有其他操作都从堆栈中取出最上面的两个(或一个或多个,取决于操作)元素,并将结果推入堆栈。当然,为了更深入地访问堆栈,可以将堆栈元素移动到存储或内存中,但不可能在不首先删除堆栈顶部的情况下,只访问堆栈中更深层的任意元素。

2.3.6 指令集

EVM的指令集保持最小,以避免可能导致共识问题的不正确或不一致的实现。所有指令都在基本数据类型、256位字或内存片(或其他字节数组)上操作。通常的算术、位、逻辑和比较操作都有。条件跳转和无条件跳转是可能的。此外,合约可以访问当前块的相关属性,如其编号和时间戳。

有关完整列表,请参阅作为内联程序集文档一部分的操作码列表。

2.3.7 Message Calls

合约可以调用其他合约,也可以通过消息调用的方式将Ether发送到非合约账户。消息调用类似于交易,因为它们有一个源、一个目标、数据有效负载、Ether、gas和返回数据。事实上,每个交易都由一个顶级消息调用组成,而这个顶级消息调用又可以创建更多的消息调用

合约可以决定剩余gas 的多少应该随内部消息调用发送,以及它想保留多少。如果在内部调用中发生了out-of-gas异常(或任何其他异常),将通过堆栈上的错误值发出信号。在这种情况下,只有与调用一起发送的气体被用完。在Solidity中,在这种情况下,调用合约默认会导致一个手动异常,因此异常会“冒泡”调用堆栈。

如前所述,被调用的合约(可以与调用方相同)将接收一个新清除的内存实例,并有权访问调用有效负载——调用有效负载将在称为calldata的单独区域中提供。在它完成执行后,它可以返回数据,这些数据将存储在调用方预先分配的调用方内存中的某个位置。所有这些调用都是完全同步的。

调用的深度限制为1024,这意味着对于更复杂的操作,应该优先使用循环而不是递归调用。此外,只有63/64的gas可以在消息调用中转发,这导致在实践中深度限制略小于1000。

2.3.8 Delegatecall and Libraries

存在一种特殊的消息调用变体,名为delegatecall,除了目标地址的代码在调用合约的上下文中执行,和msg.sendermsg.value不改变其值之外,它与消息调用是相同的。

这意味着合约可以在运行时从不同的地址动态加载代码。存储、当前地址和余额仍然引用调用合约,只是代码取自被调用地址。

这使得在Solidity中实现“库(library)”功能成为可能:可重用的库代码,可以应用到合约的存储中,例如,为了实现复杂的数据结构。

2.3.9 Logs

可以将数据存储在一个特殊的索引数据结构中,该结构一直映射到块级别。这个被称为logs 的特性被Solidity用来实现事件。在创建日志数据后,合约不能访问日志数据,但是可以从区块链外部有效地访问它们。由于部分日志数据存储在bloom过滤器中,因此可以以一种高效且加密安全的方式搜索这些数据,因此没有下载整个区块链的网络对等端(所谓的“轻客户端”)仍然可以找到这些日志。

2.3.10 Create

合约甚至可以使用特殊的操作码创建其他合约(例如,它们不像交易那样简单地调用零地址)。这些创建调用(create calls )和普通消息调用之间的唯一区别是执行有效负载数据,结果存储为代码,调用者/创建者接收堆栈上新合约的地址。

2.3.11 Deactivate and Self-destruct

从区块链中删除代码的唯一方法是当该地址的合约执行selfdestruct 操作时。存储在该地址的剩余Ether被发送到指定的目标,然后从状态中删除存储和代码。从理论上讲,删除合约听起来是个好主意,但它有潜在的危险,就像如果有人发送以太币到删除的合约,以太币就永远丢失了。

即使一个合约被selfdestruct删除,它仍然是区块链历史的一部分,可能被大多数以太坊节点保留。所以使用自毁和从硬盘中删除数据是不一样的。

即使合约的代码不包含调用selfdestruct,它仍然可以使用delegatecallcallcode执行该操作。

如果你想停用你的合约,你应该通过改变一些内部状态来禁用它们,这会导致所有函数恢复。这使得它不可能使用合约,因为它立即返回以太币。

2.2.12 预编译的合约

有一小部分合约地址是特殊的:1到(包括)8之间的地址范围包含“预编译的合同”,可以被称为任何其他合约,但它们的行为(以及它们的gas 消耗)不是由存储在该地址的EVM代码定义的(它们不包含代码),而是在EVM执行环境中实现的。

不同的 EVM兼容链可能使用不同的预编译合约集。未来也有可能将新的预编译合约添加到以太坊主链中,但你可以合理地期望它们总是在10xffff(包括)之间的范围内。

三、安装Solidity编译器

3.1 Versioning

坚固版本遵循Semantic Versioning。此外,带有主要发行版0(如0.x.y)的补丁级别发行版将不包含破坏性更改。这意味着使用版本0.x.y 编译的代码。可以使用0.x.z (其中z > y)编译。

除了发布版本,我们还提供 nightly development builds,目的是让开发人员更容易尝试即将到来的功能并提供早期反馈。然而,请注意,虽然夜间构建通常非常稳定,但它们包含来自开发分支的前沿代码,并且不能保证总是工作。尽管我们尽了最大的努力,但它们可能包含未记录的和/或不完整的更改,这些更改不会成为实际发布的一部分。它们不是用于生产的

在部署合约时,您应该使用最新发布的Solidity版本。这是因为突破性的变化,以及新功能和错误修复是定期引入的。我们现在用的是0.x 版本号来表示这种快速的更改速度。

3.2 Remix

对于小合约和快速学习Solidity,我们推荐Remix。
Access Remix online,如果您想在不连接互联网的情况下使用它,请访问https://github.com/ethereum/remix-live/tree/gh-pages并下载页面上解释的.zip文件。Remix也是测试夜间构建的一个方便选择,无需安装多个Solidity版本。

本页上的其他选项详细说明了在计算机上安装命令行Solidity编译器软件。如果您正在处理一个较大的合约,或者需要更多的编译选项,请选择命令行编译器。

四、Solidity 合约示例

4.1 投票合约

下面的合约相当复杂,但展示了很多Solidity的特性。它实现了一个投票合约。当然,电子投票的主要问题是如何将投票权分配给正确的人,如何防止操纵。我们不会在这里解决所有问题,但至少我们将展示如何进行委托投票,以便同时实现自动计票和完全透明。

这个想法是为每张选票创建一个合同,为每个选项提供一个简短的名称。然后,作为主席的合同创建者将赋予每个地址单独的投票权。

这些地址背后的人可以选择自己投票或将选票委托给他们信任的人。

在投票时间结束时,winningProposal()将返回票数最多的提案。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/// @title Voting with delegation.
contract Ballot {// This declares a new complex type which will// be used for variables later.// It will represent a single voter.struct Voter {uint weight; // weight is accumulated by delegationbool voted;  // if true, that person already votedaddress delegate; // person delegated touint vote;   // index of the voted proposal}// This is a type for a single proposal.struct Proposal {bytes32 name;   // short name (up to 32 bytes)uint voteCount; // number of accumulated votes}address public chairperson;// This declares a state variable that// stores a `Voter` struct for each possible address.mapping(address => Voter) public voters;// A dynamically-sized array of `Proposal` structs.Proposal[] public proposals;/// Create a new ballot to choose one of `proposalNames`.constructor(bytes32[] memory proposalNames) {chairperson = msg.sender;voters[chairperson].weight = 1;// For each of the provided proposal names,// create a new proposal object and add it// to the end of the array.for (uint i = 0; i < proposalNames.length; i++) {// `Proposal({...})` creates a temporary// Proposal object and `proposals.push(...)`// appends it to the end of `proposals`.proposals.push(Proposal({name: proposalNames[i],voteCount: 0}));}}// Give `voter` the right to vote on this ballot.// May only be called by `chairperson`.function giveRightToVote(address voter) external {// If the first argument of `require` evaluates// to `false`, execution terminates and all// changes to the state and to Ether balances// are reverted.// This used to consume all gas in old EVM versions, but// not anymore.// It is often a good idea to use `require` to check if// functions are called correctly.// As a second argument, you can also provide an// explanation about what went wrong.require(msg.sender == chairperson,"Only chairperson can give right to vote.");require(!voters[voter].voted,"The voter already voted.");require(voters[voter].weight == 0);voters[voter].weight = 1;}/// Delegate your vote to the voter `to`.function delegate(address to) external {// assigns referenceVoter storage sender = voters[msg.sender];require(sender.weight != 0, "You have no right to vote");require(!sender.voted, "You already voted.");require(to != msg.sender, "Self-delegation is disallowed.");// Forward the delegation as long as// `to` also delegated.// In general, such loops are very dangerous,// because if they run too long, they might// need more gas than is available in a block.// In this case, the delegation will not be executed,// but in other situations, such loops might// cause a contract to get "stuck" completely.while (voters[to].delegate != address(0)) {to = voters[to].delegate;// We found a loop in the delegation, not allowed.require(to != msg.sender, "Found loop in delegation.");}Voter storage delegate_ = voters[to];// Voters cannot delegate to accounts that cannot vote.require(delegate_.weight >= 1);// Since `sender` is a reference, this// modifies `voters[msg.sender]`.sender.voted = true;sender.delegate = to;if (delegate_.voted) {// If the delegate already voted,// directly add to the number of votesproposals[delegate_.vote].voteCount += sender.weight;} else {// If the delegate did not vote yet,// add to her weight.delegate_.weight += sender.weight;}}/// Give your vote (including votes delegated to you)/// to proposal `proposals[proposal].name`.function vote(uint proposal) external {Voter storage sender = voters[msg.sender];require(sender.weight != 0, "Has no right to vote");require(!sender.voted, "Already voted.");sender.voted = true;sender.vote = proposal;// If `proposal` is out of the range of the array,// this will throw automatically and revert all// changes.proposals[proposal].voteCount += sender.weight;}/// @dev Computes the winning proposal taking all/// previous votes into account.function winningProposal() public viewreturns (uint winningProposal_){uint winningVoteCount = 0;for (uint p = 0; p < proposals.length; p++) {if (proposals[p].voteCount > winningVoteCount) {winningVoteCount = proposals[p].voteCount;winningProposal_ = p;}}}// Calls winningProposal() function to get the index// of the winner contained in the proposals array and then// returns the name of the winnerfunction winnerName() external viewreturns (bytes32 winnerName_){winnerName_ = proposals[winningProposal()].name;}
}

可能的改进

目前,需要许多交易将投票权分配给所有参与者。此外,如果两个或两个以上的提案拥有相同的票数,winningProposal()将不能登记为平局。你能想到解决这些问题的方法吗?

Solidity 基础(一)相关推荐

  1. Solidity 基础知识

    Solidity 基础知识 官网:Solidity 版本设置 // SPDX-License-Identifier:MIT 表示身份协议 // ^0.8.7 表示当前代码支持 0.8.7 及以后 so ...

  2. Solidity基础教程:合约的继承与方法的重写

    Solidity基础教程:合约的继承与方法的重写 合约继承 合约继承使用is关键字 contract ERC721 is Context, ERC165, IERC721, IERC721Metada ...

  3. Solidity基础教程2——Safemath

    本教程使用的开发环境是一款在线编译器--ChainIDE,具体的使用方法在之前的文章当中已经有讲解过,有需要的同学可以自行查看. 网址:https://eth.chainide.com/ 序言 变量值 ...

  4. web3 solidity 基础 ERC20 大白话搞懂

    一.什么是标准什么是ERC20标准 ERC20 是 eth 的一个标准,怎么理解标准一词呢? 标准是大家遵循的一个协议,根据这个协议大家都知道该怎么去做,例如去吃饭的时候人多,你就需要排队,然后去窗口 ...

  5. Solidity基础入门知识(十)函数的访问权限和可见性

    链客,专为开发者而生,有问必答! 此文章来自链客区块链技术问答社区,未经允许拒绝转载. 下面来继续介绍作为一个分布式网络语言所特有的internal和external这两种不同的函数调用方式,以及So ...

  6. [区块链笔记1] 入坑区块链 智能合约solidity基础

    以太坊是一个分布式计算网络,提供了运行智能合约的分布式平台. 智能合约是在以太坊虚拟机上运行的应用程序. 以太坊拥有多种高级语言可以用来编写智能合约,最流行的是solidity,基于javascrip ...

  7. Solidity基础入门讲解

    Solidity的语言类型: 静态类型的语言:编译前变量类型需要先确定 变量可以分为: 值类型:赋值或者传参时总是进行值拷贝 引用类型:传递的是地址 这里先给出一个总览图: 整型: 和其他语言类型,可 ...

  8. solidity基础学习

    0 参考来源 文档主要参考了https://cryptozombies.io的游戏dapp开发网站,一步步通过游戏来讲解solidity. 同时一些细节参考了网上的博客: https://www.co ...

  9. solidity 基础讲解

    pragma solidity >=0.4.0 <0.6.0; //源代码的版本在0.4.0-0.6.0之间 // pragma solidity ^0.4.0 表示源代码版本在0.4.0 ...

最新文章

  1. android 之 Intent、broadcast
  2. 【Android】Android 设置Activity窗体 不显示标题和全屏显示
  3. 监听端口的非阻塞性不具有继承性
  4. ABAP中的Table Control编程
  5. 【机器学习】线性回归之概率解释及局部加权线性回归
  6. [链表] --- 反转链表(leetcode 206)
  7. 神奇的编码C语言,神奇的fans (C语言代码)
  8. 1-添加自己的Lua执行函数(ESP8266-SDK开发(lua版本))
  9. [转载] vscode python导入模块
  10. CTU Open Contest 2019 J Beer Vison
  11. 把苦难装在心里--《赢在中国》(2008-05-27)
  12. Autodesk Inventor参数化建模简明教程
  13. 计算机无法传输文件,手机连接电脑无法互传文件怎么办
  14. 关于统信UOS操作系统版本介绍
  15. 纸壳CMS升级.Net5免费下载
  16. 数学--数论--直角三角形--勾股数---奇偶数列法则 a^2+b^2=c^2
  17. 计算机一级更换桌面,2018自动替换壁纸工具
  18. 计算机考研英语听力,英语听力_2017考研常识:计算机考研必知_沪江英语
  19. 基于树莓派的人脸认证门禁
  20. Bootstrap与AdminLTE

热门文章

  1. 修改360浏览器主页
  2. 隐藏的区别:display:none 和visibility:hidden
  3. PYTHON爬虫书籍
  4. “由于文件许可权错误,word无法完成保存操作”的解决办法
  5. 用python绘制熊猫图案_在python中绘制熊猫系列的CDF
  6. 微会动创始人袁帅:什么是二维码会议服务?
  7. Android 读取Txt文件内容
  8. 三菱je-a系列伺服支持modbusrtu 协议吗_你了解吗?全新正品英飞凌模块全新现货...
  9. [4G5G专题-129]:RF-架构演进的驱动力与RF常见术语
  10. 长沙理工大学计算机电路b试题,长沙理工大学考试试卷(计算机网络)