Solidity 基础(一)

这篇具有很好参考价值的文章主要介绍了Solidity 基础(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 contracts
    address public minter;
    mapping (address => uint) public balances;

    // Events allow clients to react to specific
    // contract changes you declare
    event Sent(address from, address to, uint amount);

    // Constructor code is only run when the contract
    // is created
    constructor() {
        minter = msg.sender;
    }

    // Sends an amount of newly created coins to an address
    // Can only be called by the contract creator
    function 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 address
    function 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 delegation
        bool voted;  // if true, that person already voted
        address delegate; // person delegated to
        uint 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 reference
        Voter 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 votes
            proposals[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 view
            returns (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 winner
    function winnerName() external view
            returns (bytes32 winnerName_)
    {
        winnerName_ = proposals[winningProposal()].name;
    }
}

可能的改进

目前,需要许多交易将投票权分配给所有参与者。此外,如果两个或两个以上的提案拥有相同的票数,winningProposal()将不能登记为平局。你能想到解决这些问题的方法吗?文章来源地址https://www.toymoban.com/news/detail-486833.html

到了这里,关于Solidity 基础(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Solidity入门1: 3. 函数类型

    solidity官方文档里把函数归到数值类型 函数结构 看着些复杂,咱们从前往后一个一个看(方括号中的是可写可不写的): function : 声明函数时的固定用法,想写函数,就要以function开头。 function name : 函数名。 (parameter types) : 圆括号里写函数的参数,也就是

    2024年04月09日
    浏览(44)
  • 以太坊开发学习-solidity(三)函数类型

    目录 函数类型 函数类型 solidity官方文档里把函数归到数值类型 函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量, 也可以将一个函数作为参数进行传递,还能在函数调用中返回函数类型变量。 函数类型有两类:-  内部(internal)  函数和  外

    2024年04月09日
    浏览(58)
  • Github官网进不去怎么办?Github无法访问怎么办?

    打开hosts文件,可能需要管理员权限。 win10 hosts位置:C:WindowsSystem32driversetc 在末尾新建一行,添加如下内容: 访问 http://tool.chinaz.com/dns/ ,在输入框中填写 github.com,然后点击检测按钮,会列出响应ip,如图: 选最小TTL值的IP 在hosts文件结尾输入: 执行 ipconfig /flushdns 刷新

    2024年02月10日
    浏览(56)
  • 玩以太坊链上项目的必备技能(类型-引用类型-Solidity之旅三)

    在前文我们讲述了值类型,也就说再修改值类型的时候,每次都有一个独立的副本,如:string 类型的状态变量,其值是无法修改,而是拷贝出一份该状态的变量,将新值存起来。对于处理稍微复杂地值类型时,拷贝将变得愈发大了,也正是介于此,才考虑到将数据存放在 内

    2024年01月24日
    浏览(44)
  • linux下无法登陆github官网解决方案

    执行 sudo vi /etc/hosts 修改文件内容: 按下 esc键, 然后输入 :wq 会出现如下错误 E45: ‘readonly’ option is set (add ! to override) 这时先强制退出, 输入 :q! 然后输入 sudo !! 输入密码后, 就可以编辑文件了 编辑完文件后按下 esc键, 然后输入 :wq! 验证文件已被编辑: cat /etc/hosts ——————

    2024年01月25日
    浏览(46)
  • 一起学solidity写智能合约——地址类型(address)

    在区块链中说地址重不重要,其实会被笑,然后会让你去恶补一下知识点,哈哈哈哈哈哈,地址可以没有区块链,但是区块链不能没有地址,这句话不是开玩笑。 地址类型是在以太坊的诞生之后出现的一个特有类型,他是一个大小160位的二进制数字 在地址中我们可以说有很

    2023年04月16日
    浏览(39)
  • 以太坊智能合约开发:Solidity 语言中的数据类型

    本文我们介绍Solidity语言的数据类型,重点是值类型,包括布尔类型、整型、地址类型、字节类型、字符串类型和枚举类型。并且通过两个智能合约例子,用于演示这些数据类型的声明与使用方法。 访问 Github 仓库 获取更多资料。 Solidity中关于数据类型的定义如下: Solidity是

    2024年02月02日
    浏览(69)
  • 关于CentOS7虚拟机配置静态IP地址及ping不通通百度等官网

    一、IP地址的配置: 1、查看MAC地址。 点击  ---虚拟机  -----  设置  -----  网络适配器  ------  高级  -----   记住MAC地址,后面会用到!!!       2、查看子网掩码和网关IP 点击  ---编辑  ----- 虚拟网络编辑器 ------  VMnet8 NAT模式  ------  NAT设置  -----  记住子网掩码和IP网

    2024年02月09日
    浏览(55)
  • Github 打不开官网不能使用命令,现在一款简单又实用的免费 GitHub 加速神器介绍给你!

    今天给继续大家推荐一个堪称 GitHub 加速神器的开源项目。 这个开源项目就是: FastGitHub ,它主要解决 GitHub 打不开、用户头像无法加载、releases 无法上传下载、git-clone、git-pull、git-push 失败等问题。 该项目的好处就是专门针对 GitHub 访问速度慢的问题,具有合法性,可靠性,

    2024年02月03日
    浏览(60)
  • 关于CentOS7虚拟机配置静态IP地址及ping通百度等官网问题的解答

    1、查看MAC地址。 点击  ---虚拟机  -----  设置  -----  网络适配器  ------  高级  -----    记住MAC地址,后面会用到!!!  2、查看子网掩码和网关IP 点击  ---编辑  ----- 虚拟网络编辑器 ------  VMnet8 NAT模式  ------  NAT设置  -----   记住子网掩码和IP网关,后面会用到!!!

    2023年04月10日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包