solidity

Solidity标志

Solidity是一种面向契约的高级语言。 其语法类似于JavaScript,专门针对Etehreum虚拟机(EVM)。

Solidity是以静态方式输入的,并且接受用户定义的继承,库和复杂类型等。

如您所见,可以创建合同投票,为众筹,盲目拍卖,多签名钱包等等。

nota

现在测试Solidity的最好方法是使用 Remix (可能需要一段时间才能加载,请耐心等待)。

可用的Solidity集成

  • Remix
    基于浏览器的集成开发环境(IDE),集成了Solidity的编译器和运行时环境,无需面向服务器的组件。
  • 以太坊工作室
    一个专门的集成开发环境(IDE),通过一个shell(shell)提供对一个完整的以太坊环境的访问。
  • IntelliJ IDEA插件
    适用于IntelliJ IDEA(以及JetBrains IDE的其余部分)的Solidity插件。
  • Visual Studio扩展
    适用于包含Solidity编译器的Microsoft Visual Studio的Solidity插件。
  • SublimeText包
    打包以突出显示SublimeText编辑器中的Solidity语法。
  • Etheratom
    Atom编辑器插件提供:突出显示语法,编译环境和运行时环境(与后台和虚拟机中的节点兼容)。
  • 原子的Linter de Solidity
    Atom编辑器的插件,为Solidity提供线迹。
  • Linter de Solium for Atom
    用于使用Solium作为基础的Atom的可配置solidity linting程序。
  • Solium
    严格遵循 Solidity Style Guide 所规定的规则的命令行界面的Solidity线程程序 .
  • Visual Studio代码的扩展
    适用于Microsoft Visual Studio的Solidity插件,包括突出显示语法和Solidity编译器。
  • Emacs Solidity
    Emacs编辑器插件,包括突出语法和报告编译错误。
  • Vim Solidity
    包含高亮语法的Vim编辑器的插件。 Vim编辑器的插件提供语法高亮显示。
  • Vim Syntastic
    包含编译验证的Vim编辑器的插件。

停产:

  • IDE混合
    基于Qt的集成开发环境(IDE),用于Solidity智能合约的设计,调试和测试。

Solidity工具

  • DAPP
    Solidity的施工工具,包装管理和部署助手。
  • SolidityREPL
    通过Solidity命令行控制台即时测试Solidity。
  • solgraph
    显示Solidity控制流程并突出显示潜在的安全漏洞。
  • evmdis
    反汇编Ethereum虚拟机(EVM),其对所述字节代码的静态分析,从而提供抽象比EVM的总操作的更高的水平。
  • Doxity
    Solidity文档生成器。

Solidity的替代语法和语法分析器

语言文档

接下来,我们先看一下 solidity 编写的 一个 简单的智能合约 ,然后介绍 区块 Ethereum虚拟机 .

在下一节中,我们将 通过几个 合同的例子来 解释 Solidity的 不同 特征 . 请记住,您始终可以 在浏览器中 尝试这些合约 !

最后一节(也是最广泛的一节)深入介绍了Solidity的各个方面。

如果您仍然有疑问或疑问,您可以在 以太坊 网站上搜索并询问 ,或者加入我们的 gitter频道 . 总是欢迎提高想法来提高Solidity或这个文件。

还有 俄文版本(русскийперевод) .

内容

关键字索引 , 搜索页面

智能合同简介

一个简单的智能合约

我们从最基本的例子开始。 如果你现在什么都不了解,什么都不会发生,我们会在稍后详细介绍。

存储

pragma solidity ^0.4.0;

contract SimpleStorage {
    uint storedData;

    function set(uint x) {
        storedData = x;
    }

    function get() constant returns (uint) {
        return storedData;
    }
}

第一行简单地说,源代码已经在Solidity的0.4.0版本或另一个完全兼容的上一级(0.5.0之前的版本)中编写过。 这是为了确保合同不会与新版本的编译器有所不同。 保留字 pragma 是这样调用的,因为一般来说,“编译指示”是指示编译器如何使用源代码操作的指令(例如: 编译指示一次 )。 .

Solidity合同是 位于以太坊区块链上特定地址 的代码(其 function )和数据(其 状态 )的集合。 该行 声明了一个名为 type (无符号的256位整数) 的状态变量 . 这可以理解为数据库中的独特部分,可以通过调用管理所述数据库的代码的函数来查阅或修改该部分。 在以太坊的情况下,这始终是业主合同。 在这种情况下,功能 可用于修改或查询变量的值。 uint storedData; storedData uint set get

要访问一个状态变量,没有必要 this. 在其他语言中像往常一样 使用前缀 .

这个合同还没有做太多(由于以太坊建立的基础设施),它只是允许任何人存储一个可用的号码,没有一个(可行)的方式来防止发布这个号码的可能性。 当然,任何人都可以 set 用不同的值再次 拨打电话 并覆盖最初的号码,但是这个号码总是会保存在区块链的历史记录中。 稍后,我们将看到如何施加访问限制,以便只有您可以更改数字。

子货币示例

以下合约将实施加密货币的最简单形式。 您可以从头开始生成硬币,但是只有创建合同的人才能这样做(实施不同的排放计划是微不足道的)。 更重要的是,任何人都可以不用注册用户名和密码就可以将金币发送给他人 - 所有你需要的是一对以太坊钥匙。

pragma solidity ^0.4.0;

contract Coin {
    // La palabra clave "public" hace que dichas variables
    // puedan ser leídas desde fuera.
    address public minter;
    mapping (address => uint) public balances;

    // Los eventos permiten a los clientes ligeros reaccionar
    // de forma eficiente a los cambios.
    event Sent(address from, address to, uint amount);

    // Este es el constructor cuyo código
    // sólo se ejecutará cuando se cree el contrato.
    function Coin() {
        minter = msg.sender;
    }

    function mint(address receiver, uint amount) {
        if (msg.sender != minter) return;
        balances[receiver] += amount;
    }

    function send(address receiver, uint amount) {
        if (balances[msg.sender] < amount) return;
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        Sent(msg.sender, receiver, amount);
    }
}

本合同引入了一些我们将逐一详细介绍的新概念。

该行 声明一个可公开访问的地址(地址)类型的状态变量。 该类型 是一个不允许算术运算的160位值。 存储属于外部人员的合同或密钥对的地址是合适的。 保留字 自动生成一个允许访问状态变量当前值的函数。 没有这个保留字,其他合约就无法访问变量。 这个功能是这样的: address public minter; address public

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

当然,添加一个像这样的函数是行不通的,因为我们应该有一个名字相同的函数和一个状态变量,但幸运的是,您已经意识到了这个想法 - 编译器会为您设想。

下一行 也创建一个公共状态变量,但它是一个更复杂的数据类型。 类型将方向映射为无符号整数。 映射可以看作 散列表 ,这些 散列表 被虚拟初始化,使得每个候选键都存在并映射到其字节表示全部为零的值。 由于不可能获得映射的所有关键字的列表,也不能获得所有值的列表,所以这种说法并没有太多进展。 这就是为什么我们必须考虑(或者更好的是保留一个列表或者使用更高级的数据类型)添加到映射中的东西,或者在没有必要的情况下使用它,就像这样。 由保留字创建的getter函数 mapping (address => uint) public balances; public 这种情况有点复杂。 大概是这样的:

function balances(address _account) returns (uint) {
    return balances[_account];
}

正如您所看到的,您可以使用此功能以简单的方式查阅单个帐户的余额。

该行 声明了在执行的最后一行触发的事件 . 用户界面(例如服务器界面,当然)可以聆听那些在区块链上触发的事件,而不需要太多的成本。 一旦它们被触发,监听器也将接收到这些参数 , 并且 这使得跟踪事务变得更容易。 为了听这个事件,你可以使用 event Sent(address from, address to, uint amount); send from to amount

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

如何 balances 从用户界面调用 自动生成的函数 很有趣的 .

特殊函数 Coin 是在创建合同期间运行的构造函数,不能在以后调用。 永久存储创建合同的人的地址:( msg tx y一起 block )是一个全局魔术变量,包含允许访问区块链的属性。 msg.sender 始终是调用当前(外部)函数的地址。

最后,功能真的是在合同中并可以由用户和合同如被称为 mint send . 如果您使用 mint 与合同创建者不同的帐户 拨打电话 ,则不会发生任何事情。 另一方面, send 每个人(已经有一些这些硬币的人)都可以使用 来发送硬币给其他人。 请记住,如果您使用此合约将硬币发送至地址,则当您通过发送硬币在区块链浏览器中搜索地址时,该合约将不会反映出来,并且该余额将仅保存在存储区中这个货币合约。 通过使用事件,创建一个监控新货币交易和余额的“区块链浏览器”相对容易。

区块链的基本原理

区块链是开发人员不太了解的概念。 原因是大部分的复杂性(挖掘, 哈希 , 椭圆曲线密码学 , P2P网络 等等)都是为了提供一系列的功能和期望。 一旦您接受这些功能,您就不必担心被浸入的技术 - 或者您是否真的需要了解Amazon AWS如何在内部使用它才能使用它?

交易

区块链是全球共享的事务数据库。 这意味着每个人只需通过参与网络就可以读取数据库中的条目。 如果您想更改数据库中的某些内容,则必须创建一个必须为其他人所接受的事务。 单词交易意味着你想要做的改变(假设你想同时改变两个值)或者完全应用,或者不会发生。 而且,当您的交易应用于数据库时,其他交易无法修改。

举个例子,设想一个表格,列出电子货币中所有账户的余额。 如果请求从一个账户转移到另一个账户,数据库的交易性质保证了从一个账户窃取的金额被添加到另一个账户。 如果由于任何原因,无法将金额添加到目标帐户,则原始帐户也不会被修改。

进一步来说,交易总是由发件人(创建者)进行密码签名。 这使得它更强大,以保证访问数据库的特定修改。 在电子货币的例子中,简单的检查确保只有拥有账户密钥的人可以从中转账。

要克服的一个主要障碍是用比特币来说,称为“双重支出”攻击:如果网络中的两个现有交易想要删除一个帐户,会发生什么?冲突?

这个抽象的答案是你没有什么可担心的。 交易顺序将由您选择,交易将在所谓的“块”中被凝集,然后在所有参与节点之间执行和分配。 如果两笔交易相互抵触,则以第二名结尾的交易将被拒绝,不会成为该笔交易的一部分。

这些块在时间上形成了字链块或区块链来的线性序列。 每隔一段时间就会把链条添加到链条中 - 对于以太坊来说,这意味着每17秒钟就会有一次。

作为“订单选择机制”(被称为“采矿”)的一部分,必须发生这样的情况:块不时地被倒转,而只是在链的末端或“尖端”。 顶部添加的块越多,可能性越小。 在这种情况下,会发生什么情况是您的交易被逆转,甚至从区块链中被删除,但您等待的时间越长,越不可能。

以太坊虚拟机

介绍

以太坊虚拟机(以英文缩写为EVM)是在以太坊执行智能合约的环境。 由于它完全隔离,因此它超越了沙箱配置,这意味着在EVM中运行的代码无法访问网络,也无法访问文件系统,也无法访问任何其他进程。 即使智能合约也限制了其他智能合约的使用。

账户

有两种类型的在Ethereum共享相同地址空间的帐户: 外部帐户 由一对公共-私有密钥的控制(对EJ:人)和 占合同 其通过所存储的代码连同帐户控制。

外部帐户的方向由公共密钥创建合同时确定,而帐户地址合同在时间限定的(它是从地址创建者和从该地址数据发送的号码导出称为“随机数”)。

无论帐户是否存储代码,这两种类型均由EVM平等对待。

每个帐户都有一个持久的键值存储器,可将256位字映射到称为 存储器的 256位字 .

在此外,每个帐户具有 balances 在醚(在“味”是精确的),其可以被修改通过 发送交易,包括以太。

交易

交易是从一个帐户发送到另一个帐户的消息(应该是相同的或特殊的零帐户,见下文)。 它可以包括二进制数据(有效载荷)和以太网。

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

如果目的地帐户是零帐户(有地址的帐户 0 ),交易会创建一个 新的合同 . 如上所述,合同的地址不是地址零,而是从发行者派生的地址和发送的交易数(“随机数”)。 创建合约的事务的二进制数据由EVM作为字节码获得并执行。 此执行的输出永久存储为合同代码。 这意味着为了创建合同,合同的当前代码不会被发送,实际上发送的代码将返回最终的代码。

一旦他们被创建,每笔交易都会收取一定数量的 天然气 ,其目的是限制执行交易所需的工作量,并为此付款。 当EVM执行交易时,天然气将按照特定的规则逐渐消耗。

气体的价格 (天然气价格)是由交易,谁必须支付的创建者设置的值 从运输法案。 如果执行后还有一些气体,你将得到报销。 gas_price * gas

如果所有的天然气都在一个点上消耗了(例如,它是负的),则会抛出一个气体异常的异常,这个异常在当前执行的情况下反转了对状态的所有修改。

存储,内存和堆栈

每个帐户都有一个称为 存储 的永久内存区域 . 存储是将256位字与256位字映射的键值存储区。 从合同中列出内部存储是不可能的,而且读取甚至更改存储的成本相对较高。 合同不能读取或写入您的存储之外的存储。

第二个内存区域被称为 内存 ,合同从中获取每个消息调用(消息调用)的敏捷明确实例。 内存是线性的,可以在字节级别进行处理,但读数限制在256位的宽度,而写入可以是8位或256位宽。 当访问(用于读取或写入)存储器字而没有先前修改(例如,任何字的偏移量)时,存储器由字(256位)扩展。 在扩张时,必须支付燃气费用。 内存越多,成长越多(按比例缩放)。

EVM不是一个记录机器,它是一个堆栈机器,所有操作都在一个称为 堆栈 的区域完成 . 它具有1024个元素的最大空间并包含256位字。 访问栈的限制如下:可以将前16个元素中的一个复制到堆栈顶部,或者在前16个元素之一之后交换顶部元素。 其他操作将堆栈中的两个最高级元素(或一个或多个,取决于操作)取出并将结果放入堆栈中。 当然,可以将堆栈的元素移动到存储器或内存中,但是不可能简单地访问堆栈中较深的任意元素,而不首先删除已经堆叠的元素。

指令集

为了避免可能导致共识问题的错误实施,EVM的指令集保持最小。 所有指令都使用基本数据类型(256位字)进行操作。 通常的算术,比特,逻辑和比较操作都存在。 有条件跳转和无条件跳转都是允许的。 更重要的是,契约可以访问当前块的相关属性,比如它们的编号和时间戳。

消息呼叫

合同可以调用其他合同,也可以使用消息调用将以太网发送给除合同外的其他帐户。 消息调用与交易类似,就是说他们有源,目的地,数据,以太网,天然气和返回数据。 实际上,每个事务包含一个高级消息调用,可以随后创建后续消息调用。

合同可以决定 剩余的 天然气 可以通过内部信息呼叫发送多少,以及要保留多少。 如果在内部呼叫(或任何其他异常)期间发生气体异常,它将显示为输入堆栈的错误值。 在这种情况下,只有随呼叫一起发送的气体才被使用。 在Solidity中,在这些情况下,进行调用的合约默认会导致手动异常,所以这些异常等同于调用堆栈。

如所提到的,被称为合同(其可以是相同的进行呼叫)将收到空的存储器的一个实例,将可使用呼叫数据-在一个单独的被提供区域 称为区域 calldata . 完成执行后,它可以返回将被存储在先前保留它的调用者的存储器中的位置的数据。

调用被 限制 在1024的深度,这意味着对于更复杂的操作,您应该优先使用递归调用的循环。

Delegatecall / Callcode和库

这里是 一个名为消息调用特殊的变种 delegatecall 这等同于与在目标地址代码在调用的上下文和执行的异常的呼叫消息 msg.sender msg.value 没有改变它们的值。

这意味着合同可以在运行时从不同地址动态加载代码。 存储,当前地址和余额仍然是指进行呼叫的合同,只有代码是从被叫地址中获取的。

这使得可以在Solidity中实现“库”功能:可重用的库代码,可以应用于合同存储,例如,以实现复杂的数据结构。

log

可以将数据存储在将整个路径映射到块级别的索引数据结构中。 这个称为 日志的 function 用于Solidity实现 event . 合约创建后无法访问日志数据,但可以从区块链外有效访问。 作为日志数据的一部分存储在 布隆过滤器 ,你可以有效的加密安全搜索到这些数据,这样谁没有下载整个blockchain(“轻客户端”)等网络成员仍然可以找他们

创建

合同甚至可以使用特殊的操作码创建其他合同(例如:他们根本不称为地址零)。 这些 创建调用 和正常消息调用 之间的唯一区别 是数据被执行,结果作为代码存储,调用者/创建者在堆栈中接收新契约的地址。

自我毁灭

从区块链中删除代码的唯一可能性是在该地址的合同执行操作时 selfdestruct . 存储在该地址中的剩余Ether被发送到指定的收件人,然后存储和状态码被删除。

警告

即使合同不包含呼叫 selfdestruct ,我仍然可以使用 delegatecall 执行该操作 callcode .

nota

以太坊客户可能会或可能不会执行旧合同。 另外,文件节点可以选择维护合约存储并无限期地编码。

nota

目前 外部帐户 不能从状态中删除。

安装Solidity

版本控制

Solidity版本遵循 语义版本 ,除了版本之外,还有 每晚的开发版本 . 夜间搭建的操作不能保证,可能包含无证的更改或故障。 我们建议使用最新版本。 以下软件包安装程序将使用最新版本。

Remix

如果您只想尝试小合同的Solidity,您可以使用 不需要安装的 Remix . 如果你想在没有互联网连接的情况下使用它,你可以访问 https://github.com/ethereum/browser-solidity/tree/gh-pages 并按照该页面的说明下载.ZIP。

npm / Node.js

这可能是本地安装Solidity最方便和便捷的方式。

通过使用Emscripten将C ++代码编译成Javascript来提供平台无关的Javascript库。 它可以直接用于项目(如Remix)。 访问 solc-js 存储库 查看说明。

它还包含一个名为 solcjs 的命令行工具 ,可以通过npm进行安装:

npm install -g solc

nota

solcjs 命令行选项 solc 不兼容 ,等待 solcjs 行为 的工具(如 geth ) 不能与 solcjs 一起 使用 .

Docker

我们为编译器提供更新的docker版本。 存储库 stable 包含已发布的版本,而 nightly 包含开发分支的可能不稳定的更改。

docker run ethereum/solc:stable solc --version

目前,docker镜像包含可执行的编译器,所以你将不得不链接代码和输出文件夹。

二进制包

Solidity二进制软件包提供 solidity / release .

我们也有Ubuntu的PPA。 最新的稳定版本。

sudo add-apt-repository ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install solc

如果你想要最新的开发版本:

sudo add-apt-repository ppa:ethereum/ethereum
sudo add-apt-repository ppa:ethereum/ethereum-dev
sudo apt-get update
sudo apt-get install solc

Arch Linux也有软件包,但限于最新的开发版本:

pacman -S solidity-git

自从Jenkins转移到TavisCI之后,Homebrew还没有预编译的软件包,但是Homebrew仍然应该从代码中构建。 不久,预制的软件包将被添加。

brew update
brew upgrade
brew tap ethereum/ethereum
brew install solidity
brew linkapps solidity

如果你需要特定版本的Solidity,你可以从Github安装一个自制软件。

请参阅 Github上的solidity.rb提交 .

按照历史链接,直到你看到一个特定的提交文件的链接 solidity.rb .

install brew :

brew unlink solidity
# Install 0.4.8
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb

Gentoo还提供了一个Solidity软件包,可以安装在 emerge :

demerge ev-lang/solidity

从代码构建

克隆存储库

要克隆源代码,请执行以下命令:

git clone --recursive https://github.com/ethereum/solidity.git
cd solidity

如果您想帮助开发Solidity,您必须分叉Solidity并且添加您的个人分叉作为辅助遥远的:

cd solidity
git remote add personal git@github.com:[username]/solidity.git

Solidity有git的子模块。 确保它们正确加载:

git submodule  update  -init  --recursive

先决条件 - macOS

对于macOS,请确保您已 install 最新版本的 Xcode . 这包含 编译铛C ++ ,打造C ++在OS X应用程序如果您安装的Xcode所需要的工具的 第一次,你就 必须要 接受使用条款,才能做的命令行构建:

sudo xcodebuild -license accept

我们的OS X构建需要安装 Homebrew 软件包管理器 来安装外部依赖项。 在这里你可以看到如何 卸载Homebrew ,如果你想重新开始。

先决条件 - Windows

您需要在Windows中为Solidity构建安装以下依赖项:

软件 笔记
Git for Windows git存储库的命令行工具。
CMake的 多平台生成器
Visual Studio 2015 C ++编译器和开发环境。

外部依赖关系

现在我们有一个易于使用的脚本,可以在macOS,Windows和各种Linux发行版上安装所有外部依赖项。 这曾经是一个多阶段的手动过程,但现在它是一个单一的行:

./scripts/install_deps.sh

或者在Windows中:

scripts\install_deps.bat

命令行构建

在Linux,macOS和其他Unix系统中,Build Solidity非常相似:

mkdir build
cd build
cmake .. && make

甚至更简单:

#nota: esto instalará los binarios de solc y soltest en usr/local/bin
./scripts/build.sh

即使对于Windows:

mkdir build
cd build
cmake -G "Visual Studio 14 2015 Win64" ..

这些最后的指令应该导致在 build目录下 创建 solidity.sln . 双击该文件应打开Visual Studio。 我们建议构建 RelWithDebugInfo config ,但它们都工作。

或者,如果没有,您可以在命令行上为Windows构建,如下所示:

cmake --build . --config RelWithDebInfo

版本链详细

Solidity版本链由4部分组成:

  • 版本号
  • 预发布标签,一般采用格式 develop.YYYY.MM.DD nightly.YYYY.MM.DD
  • 以格式提交 commit.GITHASH
  • 该平台有任意数量的项目,包含平台和编译器的细节

如果有本地修改,则提交将具有后缀 .mod .

这些部件按照Semver的要求进行组合,其中Solidity预发布标签等同于Semver预发布版,并且合并的Solidity和平台提交使Semver构建的元数据成为可能。

释放的一个例子: 0.4.8+commit.60cc1668.Emscripten.clang .

预发布示例: 0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang

有关版本的重要信息

发布后,补丁版本的级别会增加,因为我们假设只有补丁级别发生变化。 集成更改时,根据Semver版本和更改的紧迫性,版本会增加。 最后,一个版本总是与当前夜间版本的版本完成,但没有说明符 prerelease .

例如:

  1. 发行版本是0.4.0
  2. 截至目前,每晚构建版本为0.4.1
  3. 兼容的变化被引入 - 版本没有变化
  4. 引入不受支持的更改 - 版本增加到0.5.0
  5. 发布是0.5.0

这种行为与 编译指示版本 很好地结合在一起 .

通过例子的坚实性

表决

下面的合同是相当复杂的,但它显示了许多特点的Solidity。 这是一个投票合同。 电子投票的主要问题之一是将投票权分配给合适的人以避免操纵。 我们不会解决所有的问题,但至少我们会看到如何建立一个委托投票系统,以便同时 自动和完全透明 地计票 .

这个想法是为每个投票创建一个合同,并为每个选项简要命名。 作为总裁的合同的创造者将负责给每个地址一个一个的投票权。

控制这些地址的人可以投票或委托投票给信任的人。

投票期结束后,将 winningProposal() 返回最多投票的提案。

pragma solidity ^0.4.11;

/// @title Votación con voto delegado
contract Ballot {
    // Declara un nuevo tipo de dato complejo, que será
    // usado para almacenar variables.
    // Representará a un único votante.
    struct Voter {
        uint weight; // el peso del voto se acumula mediante la delegación de votos
        bool voted;  // true si esa persona ya ha votado
        address delegate; // persona a la que se delega el voto
        uint vote;   // índice de la propuesta votada
    }

    // Representa una única propuesta.
    struct Proposal {
        bytes32 name;   // nombre corto (hasta 32 bytes)
        uint voteCount; // número de votos acumulados
    }

    address public chairperson;

    // Declara una variable de estado que
    // almacena una estructura de datos `Voter` para cada posible dirección.
    mapping(address => Voter) public voters;

    // Una matriz dinámica de estructuras de datos de tipo `Proposal`.
    Proposal[] public proposals;

    // Crea una nueva votación para elegir uno de los `proposalNames`.
    function Ballot(bytes32[] proposalNames) {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        // Para cada nombre propuesto
        // crea un nuevo objeto de tipo Proposal y lo añade
        // al final del array.
        for (uint i = 0; i < proposalNames.length; i++) {
            // `Proposal({...})` crea una nuevo objeto de tipo Proposal
            // de forma temporal y se añade al final de `proposals`
            // mediante `proposals.push(...)`.
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    // Da a `voter` el derecho a votar en esta votación.
    // Sólo puede ser ejecutado por `chairperson`.
    function giveRightToVote(address voter) {
        // Si el argumento de `require` da como resultado `false`,
        // finaliza la ejecución y revierte todos los cambios
        // producidos en el estado y los balances de Ether.
        // A veces es buena idea usar esto por si las funciones
        // están siendo ejecutadas de forma incorrecta. Pero ten en cuenta
        // que de esta forma se consumirá todo el gas enviado
        // (está previsto que esto cambie en el futuro).
        require((msg.sender == chairperson) && !voters[voter].voted);
        voters[voter].weight = 1;
    }

    /// Delega tu voto a `to`.
    function delegate(address to) {
        // Asignación por referencia
        Voter sender = voters[msg.sender];
        require(!sender.voted);

        // No se permite la delegación a uno mismo.
        require(to != msg.sender);

        // Propaga la delegación en tanto que `to` también delegue.
        // Por norma general, los bucles son muy peligrosos
        // porque si tienen muchas iteracciones puede darse el caso
        // de que empleen más gas del disponible en un bloque.
        // En este caso, eso implica que la delegación no será ejecutada,
        // pero en otros casos puede suponer que un
        // contrato se quede completamente bloqueado.
        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;

            // Encontramos un bucle en la delegación. No está permitido.
            require(to != msg.sender);
        }

        // Dado que `sender` es una referencia, esto
        // modifica `voters[msg.sender].voted`
        sender.voted = true;
        sender.delegate = to;
        Voter delegate = voters[to];
        if (delegate.voted) {
            // Si la persona en la que se ha delegado el voto ya ha votado,
            // se añade directamente al número de votos.
            proposals[delegate.vote].voteCount += sender.weight;
        } else {
            // Si la persona en la que se ha delegado el voto
            // todavía no ha votado, se añade al peso de su voto.
            delegate.weight += sender.weight;
        }
    }

    /// Da tu voto (incluyendo los votos que te han delegado)
    /// a la propuesta `proposals[proposal].name`.
    function vote(uint proposal) {
        Voter sender = voters[msg.sender];
        require(!sender.voted);
        sender.voted = true;
        sender.vote = proposal;

        // Si `proposal` está fuera del rango de la matriz,
        // esto lanzará automáticamente una excepción y
        // se revocarán todos los cambios
        proposals[proposal].voteCount += sender.weight;
    }

    /// @dev Calcula la propuesta ganadora teniendo en cuenta
    // todos los votos realizados.
    function winningProposal() constant
            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;
            }
        }
    }

    // Llama a la función winningProposal() para obtener
    // el índice de la propuesta ganadora y así luego
    // devolver el nombre.
    function winnerName() constant
            returns (bytes32 winnerName)
    {
        winnerName = proposals[winningProposal()].name;
    }
}

可能的改进

目前,许多交易需要给予所有参与者投票权。 你能想出更好的办法吗?

盲目拍卖

在这一节中,我们将看到创造一个在以太坊盲目拍卖的合同是多么容易。 我们将开始拍卖,每个人都可以看到正在出价。 随后,我们将扩大这个合同,把它转换成一个盲目拍卖,在竞标期结束之前不可能看到实际的投标。

简单的公开拍卖

以下拍卖合同的总体思路是任何人都可以在投标期间提交出价。 作为投标的一部分,钱/乙醚被发送连接投标人与他们的出价。 如果超过了最高出价,先前的高出价就会让您退钱。 投标后,合同必须由受益人手工拨打才能收回 - 合同不能自行启动。

pragma solidity ^0.4.11;

    contract BlindAuction {
        struct Bid {
            bytes32 blindedBid;
            uint deposit;
        }
    
        address public beneficiary;
        uint public auctionStart;
        uint public biddingEnd;
        uint public revealEnd;
        bool public ended;
    
        mapping(address => Bid[]) public bids;
    
        address public highestBidder;
        uint public highestBid;
    
        // Retiradas permitidas de pujas previas
        mapping(address => uint) pendingReturns;
    
        event AuctionEnded(address winner, uint highestBid);
    
        /// Los modificadores son una forma cómoda de validar los
        /// inputs de las funciones. Abajo se puede ver cómo
        /// `onlyBefore` se aplica a `bid`.
        /// El nuevo cuerpo de la función pasa a ser el del modificador,
        /// sustituyendo `_` por el anterior cuerpo de la función.
        modifier onlyBefore(uint _time) { require(now < _time); _; }
        modifier onlyAfter(uint _time) { require(now > _time); _; }
    
        function BlindAuction(
            uint _biddingTime,
            uint _revealTime,
            address _beneficiary
        ) {
            beneficiary = _beneficiary;
            auctionStart = now;
            biddingEnd = now + _biddingTime;
            revealEnd = biddingEnd + _revealTime;
        }
    
        /// Efectúa la puja de manera oculta con `_blindedBid`=
        /// keccak256(value, fake, secret).
        /// El ether enviado sólo se recuperará si la puja se revela de
        /// forma correcta durante la fase de revelacin. La puja es
        /// válida si el ether junto al que se envía es al menos "value"
        /// y "fake" no es cierto. Poner "fake" como verdadero y no enviar
        /// la cantidad exacta, son formas de ocultar la verdadera puja
        /// y aún así realizar el depósito necesario. La misma dirección
        /// puede realizar múltiples pujas.
        function bid(bytes32 _blindedBid)
            payable
            onlyBefore(biddingEnd)
        {
            bids[msg.sender].push(Bid({
                blindedBid: _blindedBid,
                deposit: msg.value
            }));
        }
    
        /// Revela tus pujas ocultas. Recuperarás los fondos de todas
        /// las pujas inválidas ocultadas de forma correcta y de
        /// todas las pujas salvo en aquellos casos en que sea la más alta.
        function reveal(
            uint[] _values,
            bool[] _fake,
            bytes32[] _secret
        )
            onlyAfter(biddingEnd)
            onlyBefore(revealEnd)
        {
            uint length = bids[msg.sender].length;
            require(_values.length == length);
            require(_fake.length == length);
            require(_secret.length == length);
    
            uint refund;
            for (uint i = 0; i < length; i++) {
                var bid = bids[msg.sender][i];
                var (value, fake, secret) =
                        (_values[i], _fake[i], _secret[i]);
                if (bid.blindedBid != keccak256(value, fake, secret)) {
                    // La puja no ha sido correctamente revelada.
                    // No se recuperan los fondos depositados.
                    continue;
                }
                refund += bid.deposit;
                if (!fake && bid.deposit >= value) {
                    if (placeBid(msg.sender, value))
                        refund -= value;
                }
                // Hace que el emisor no pueda reclamar dos veces
                // el mismo depósito.
                bid.blindedBid = 0;
            }
            msg.sender.transfer(refund);
        }
    
        // Esta función es "internal", lo que significa que sólo
        // se podrá llamar desde el propio contrato (o contratos
        // que deriven de él).
        function placeBid(address bidder, uint value) internal
                returns (bool success)
        {
            if (value <= highestBid) {
                return false;
            }
            if (highestBidder != 0) {
                // Devolverle el dinero de la puja
                // al anterior pujador con la puja más alta.
                pendingReturns[highestBidder] += highestBid;
            }
            highestBid = value;
            highestBidder = bidder;
            return true;
        }
    
        /// Retira una puja que ha sido superada.
        function withdraw() returns (bool) {
            var amount = pendingReturns[msg.sender];
            if (amount > 0) {
                // Es importante poner esto a cero porque el receptor
                // puede llamar a esta función de nuevo como parte
                // de la recepción antes de que `send` devuelva su valor.
                // (ver la observacin de arriba sobre condiciones -> efectos
                // -> interacción).
                pendingReturns[msg.sender] = 0;
    
                if (!msg.sender.send(amount)){
                    // Aquí no es necesario lanzar una excepción.
                    // Basta con reiniciar la cantidad que se debe devolver.
                    pendingReturns[msg.sender] = amount;
                    return false;
                }
            }
            return true;
        }
    
        /// Finaliza la subasta y envía la puja más alta
        /// al beneficiario.
        function auctionEnd()
            onlyAfter(revealEnd)
        {
            require(!ended);
            AuctionEnded(highestBidder, highestBid);
            ended = true;
            // Enviamos todo el dinero que tenemos, porque
            // parte de las devoluciones pueden haber fallado.
            beneficiary.transfer(this.balance);
        }
    }
                                                                

盲目拍卖

接下来,您将把之前的公开拍卖扩大到盲目拍卖。 盲目拍卖的好处是,招标期结束,压力不增加。 在一个透明的计算平台上创建一个盲目的拍卖可能看起来是矛盾的,但是密码学使它成为可能。

投标期间 ,投标人不会发送他的投标,而是一个散列版本。 由于目前认为实际上不可能找到两个哈希值相等的值(足够长),所以投标人以这种方式进行投标。 投标期后,投标人必须公开竞标。 为了做到这一点,他们发送破译的价值,合同证明哈希值与投标期间提供的哈希值相对应。

另一个复杂情况是如何使拍卖同时 具有约束力和盲目性 :在拍卖结束后防止投标人发送货币的唯一方法就是随着投标一起发送。 由于价值转移不能隐藏在以太坊,任何人都可以看到金额。

以下合同通过接受任何至少与出价一样高的价值来解决这个问题。 由于这只能在披露阶段检查,所以有些出价可能是 无效的 . 这是有目的的(甚至可以防止发送出价非常高的情况下的错误)。 投标人可以通过多次高价或低价无效投标混淆竞争对手。

pragma solidity ^0.4.11;

    contract BlindAuction {
        struct Bid {
            bytes32 blindedBid;
            uint deposit;
        }
    
        address public beneficiary;
        uint public auctionStart;
        uint public biddingEnd;
        uint public revealEnd;
        bool public ended;
    
        mapping(address => Bid[]) public bids;
    
        address public highestBidder;
        uint public highestBid;
    
        // Retiradas permitidas de pujas previas
        mapping(address => uint) pendingReturns;
    
        event AuctionEnded(address winner, uint highestBid);
    
        /// Los modificadores son una forma cómoda de validar los
        /// inputs de las funciones. Abajo se puede ver cómo
        /// `onlyBefore` se aplica a `bid`.
        /// El nuevo cuerpo de la función pasa a ser el del modificador,
        /// sustituyendo `_` por el anterior cuerpo de la función.
        modifier onlyBefore(uint _time) { require(now < _time); _; }
        modifier onlyAfter(uint _time) { require(now > _time); _; }
    
        function BlindAuction(
            uint _biddingTime,
            uint _revealTime,
            address _beneficiary
        ) {
            beneficiary = _beneficiary;
            auctionStart = now;
            biddingEnd = now + _biddingTime;
            revealEnd = biddingEnd + _revealTime;
        }
    
        /// Efectúa la puja de manera oculta con `_blindedBid`=
        /// keccak256(value, fake, secret).
        /// El ether enviado sólo se recuperará si la puja se revela de
        /// forma correcta durante la fase de revelacin. La puja es
        /// válida si el ether junto al que se envía es al menos "value"
        /// y "fake" no es cierto. Poner "fake" como verdadero y no enviar
        /// la cantidad exacta, son formas de ocultar la verdadera puja
        /// y aún así realizar el depósito necesario. La misma dirección
        /// puede realizar múltiples pujas.
        function bid(bytes32 _blindedBid)
            payable
            onlyBefore(biddingEnd)
        {
            bids[msg.sender].push(Bid({
                blindedBid: _blindedBid,
                deposit: msg.value
            }));
        }
    
        /// Revela tus pujas ocultas. Recuperarás los fondos de todas
        /// las pujas inválidas ocultadas de forma correcta y de
        /// todas las pujas salvo en aquellos casos en que sea la más alta.
        function reveal(
            uint[] _values,
            bool[] _fake,
            bytes32[] _secret
        )
            onlyAfter(biddingEnd)
            onlyBefore(revealEnd)
        {
            uint length = bids[msg.sender].length;
            require(_values.length == length);
            require(_fake.length == length);
            require(_secret.length == length);
    
            uint refund;
            for (uint i = 0; i < length; i++) {
                var bid = bids[msg.sender][i];
                var (value, fake, secret) =
                        (_values[i], _fake[i], _secret[i]);
                if (bid.blindedBid != keccak256(value, fake, secret)) {
                    // La puja no ha sido correctamente revelada.
                    // No se recuperan los fondos depositados.
                    continue;
                }
                refund += bid.deposit;
                if (!fake && bid.deposit >= value) {
                    if (placeBid(msg.sender, value))
                        refund -= value;
                }
                // Hace que el emisor no pueda reclamar dos veces
                // el mismo depósito.
                bid.blindedBid = 0;
            }
            msg.sender.transfer(refund);
        }
    
        // Esta función es "internal", lo que significa que sólo
        // se podrá llamar desde el propio contrato (o contratos
        // que deriven de él).
        function placeBid(address bidder, uint value) internal
                returns (bool success)
        {
            if (value <= highestBid) {
                return false;
            }
            if (highestBidder != 0) {
                // Devolverle el dinero de la puja
                // al anterior pujador con la puja más alta.
                pendingReturns[highestBidder] += highestBid;
            }
            highestBid = value;
            highestBidder = bidder;
            return true;
        }
    
        /// Retira una puja que ha sido superada.
        function withdraw() returns (bool) {
            var amount = pendingReturns[msg.sender];
            if (amount > 0) {
                // Es importante poner esto a cero porque el receptor
                // puede llamar a esta función de nuevo como parte
                // de la recepción antes de que `send` devuelva su valor.
                // (ver la observacin de arriba sobre condiciones -> efectos
                // -> interacción).
                pendingReturns[msg.sender] = 0;
    
                if (!msg.sender.send(amount)){
                    // Aquí no es necesario lanzar una excepción.
                    // Basta con reiniciar la cantidad que se debe devolver.
                    pendingReturns[msg.sender] = amount;
                    return false;
                }
            }
            return true;
        }
    
        /// Finaliza la subasta y envía la puja más alta
        /// al beneficiario.
        function auctionEnd()
            onlyAfter(revealEnd)
        {
            require(!ended);
            AuctionEnded(highestBidder, highestBid);
            ended = true;
            // Enviamos todo el dinero que tenemos, porque
            // parte de las devoluciones pueden haber fallado.
            beneficiary.transfer(this.balance);
        }
    }
    

购买安全距离

pragma solidity ^0.4.11;

    contract Purchase {
        uint public value;
        address public seller;
        address public buyer;
        enum State { Created, Locked, Inactive }
        State public state;
    
        function Purchase() payable {
            seller = msg.sender;
            value = msg.value / 2;
            require((2 * value) == msg.value);
        }
    
        modifier condition(bool _condition) {
            require(_condition);
            _;
        }
    
        modifier onlyBuyer() {
            require(msg.sender == buyer);
            _;
        }
    
        modifier onlySeller() {
            require(msg.sender == seller);
            _;
        }
    
        modifier inState(State _state) {
            require(state == _state);
            _;
        }
    
        event Aborted();
        event PurchaseConfirmed();
        event ItemReceived();
    
        /// Aborta la compra y reclama el ether.
        /// Sólo puede ser llamado por el vendedor
        /// antes de que el contrato se cierre.
        function abort()
            onlySeller
            inState(State.Created)
        {
            Aborted();
            state = State.Inactive;
            seller.transfer(this.balance);
        }
    
        /// Confirma la compra por parte del comprador.
        /// La transacción debe incluir la cantidad de ether
        /// multiplicada por 2. El ether quedará bloqueado
        /// hasta que se llame a confirmReceived.
        function confirmPurchase()
            inState(State.Created)
            condition(msg.value == (2 * value))
            payable
        {
            PurchaseConfirmed();
            buyer = msg.sender;
            state = State.Locked;
        }
    
        /// Confirma que tú (el comprador) has recibido el
        /// artículo. Esto desbloqueará el ether.
        function confirmReceived()
            onlyBuyer
            inState(State.Locked)
        {
            ItemReceived();
            // Es importante que primero se cambie el estado
            // para evitar que los contratos a los que se llama
            // abajo mediante `send` puedan volver a ejecutar esto.
            state = State.Inactive;
    
            // NOTA: Esto permite bloquear los fondos tanto al comprador
            // como al vendedor - debe usarse el patrón withdraw.
    
            buyer.transfer(value);
            seller.transfer(this.balance);
        }
    }
    

微支付渠道

写作。

彻底的坚定

本节将向您提供有关Solidity所需的一切信息。 如果缺少某些东西,请通过 Gitter 与我们联系, 或者在 Github上 提出请求 .

Solidity源文件的组成

源文件可以包含任意数量的合约定义,包括杂注指令和指令。

Pragma版本

强烈建议使用pragma版本记录源文件,以防止使用可能引入不兼容更改的编译器的更高版本进行编译。 我们试图尽量减少这种变化,并且引入和修改语义的变化也需要语法的改变,但这并不总是可能的。 出于这个原因,至少为包含中断更改的新版本检查更改日志总是一个好主意。 这些新版本将始终具有类型 0.x.0 o 的版本号 x.0.0 .

编译指示的版本使用如下:

杂注坚实度^ 0.4.0;

像这样的文件将不会与编译器一起使用较早的版本进行编译, 0.4.0 并且不会与具有更高版本的编译器一起使用 0.5.0 (第二个条件是使用指定的 ^ )。 这个背后的想法是,在发布之前不会有任何改变 0.5.0 ,所以我们可以肯定,我们的代码将按照我们期望的方式进行编译。 我们不修复编译器的确切版本,所以我们可以发布修正错误的新版本。

您可以为编译器版本指定更复杂的规则,表达式遵循 npm 使用的规则 .

导入其他源文件

语法和语义

Solidity支持声明的导入,这与JavaScript中的声明非常类似(ES6),但是Solidity并不知道“默认导出”的概念。

在全球范围内,您可以按如下方式使用声明导入:

import "filename";
  

此声明将所有全局“文件名”符号(以及从那里导入的符号)导入当前全局范围(不同于ES6中,但与先前版本的Solidity兼容)。

import * as symbolName from "filename";
                                                                    

...创造一个新的全球符号, symbolName 其成员都是全球的象征 "filename" .

import {symbol1 as alias, symbol2} from "filename";
                                                                    

...创建一个新的全球性符号 alias ,并 symbol2 分别引用 symbol1 symbol2 from "filename" .

这个其他语法不是ES6的一部分,但可能很方便:

import “filename”  as symbolName ;
  

相当于什么 . import * as symbolName from "filename";

路线

在上面我们所看到的, filename 它总是 / 作为一个目录分隔符, . 作为当前目录和 .. 父目录来处理。 . .. 之后是一个字符除外 / ,它不被视为当前目录或父目录。 所有路径名都被视为绝对路径,除非它们以 . 父目录 开头 .. .

要从 x 当前文件的相同目录 import 文件, 请将“./x”作为x;`导入 . 如果不使用该表达式 ,则可能是引用了不同的文件(在全局“包含目录”中)。 import "x" as x;

如何解决路线取决于编译器(见下文)。 通常,目录层次结构不需要严格映射其本地文件系统,也可以映射使用例如ipfs,http或git发现的资源。

在当前的编译器中使用

调用编译器时,不仅可以指定发现路由的第一个元素的方式,还可以指定重新映射路径前缀,以便例如 github.com/ethereum/dapp-bin/library 重新映射,并由 /usr/local/dapp-bin/library 编译器读取文件从那里。 虽然可以完成多个重新映射,但是我们将首先尝试使用最长的密钥重新映射。 这允许例如 "" 映射到 “fallback-remapping” /usr/local/include/solidity . 另外,这些重映射可以依赖于上下文,这允许您配置包来导入,例如,具有相同名称的不同版本的库。

solc :

对于solc(命令行编译器),重新映射作为参数提供 context:prefix=target ,其中部分 context: 和部分 =target 都是可选的(在这种情况下,target将默认值作为默认值)。 编译标准文件的所有重映射值(包括它们的依赖关系)。 这个机制是完全兼容以前的版本(只要没有文件名包含=或:),因此它不是破裂的变化。 context 导入以a开头 的文件的目录 (或其下的文件)中的所有导入 prefix 都将 prefix 通过 替换而重定向 target .

举个例子,如果你 github.com/ethereum/dapp-bin/ 在本地 clone 一个 /usr/local/dapp-bin ,你可以在源代码中使用以下代码:

import “github.com/ethereum/dapp-bin/library/iterable_mapping.sol”  as it_mapping ;
  

然后编译器就像这样运行:

solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol
                                                                    

作为一个稍微复杂的例子,让我们假设我们依赖于使用一个非常旧版本的dapp-bin的模块。 先前版本的dapp-bin已签入 /usr/local/dapp-bin_old ,然后您可以使用:

solc module1:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ \
    module2:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin_old/ \
    source.sol
                                                                   

因此,所有导入 module2 指向以前的版本,但导入 module1 获取新版本。

请注意,solc只允许包含来自某些目录的文件。 它们必须位于显式指定的源文件的目录(或子目录)中或重映射目标的目录(或子目录)中。 如果你想允许绝对直接包含,你只需要添加 =/ 重新映射。

如果有多个重新映射导致有效的文件,则选择具有最长公共前缀的重新映射。

Remix :

Remix 为github提供了一个自动重新映射,并自动从网络中检索文件:例如,您可以导入可迭代映射 . import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;

未来,可以添加其他源代码提供者。

评论

简单的行注释( // )和多行注释( /*...*/ ) 被接受 .

//这是一个简单的线条评论。
  
  / * 
  这是一个
  多行的
  评论.* /
  

此外,还有另一种名为natspec的注释,但文档尚未编写。 这些注释是用三个 *斜杠(或``///``)或者一个双星号注释(``/ ... /``) 编写的, 并且应该在函数语句之上使用,或者说明。 为了记录功能,记录形式验证的条件,并 在尝试调用功能时向用户 提供**确认文本* ,可以 在这些注释中 使用 Doxygen 类型的标签 .

在下面的例子中,我们记录合同的标题,两个输入参数和两个返回值的解释。

   pragma solidity ^0.4.0;
  
  / ** @title 形状计算器* / 
  contract  shapeCalculator { 
      / ** @dev 计算一个矩形的表面和周长。
       * @param w矩形的宽度。
       * @ 参数h矩形的高度。
       * @return s计算的曲面。
       * @return p计算的周长。
       * / 
      function  rectangle (uint w , uint h ) returns  (uint s , uint p ) {
          s  =  w  *  h ; 
          p  =  2  *  (w  +  h ); 
      } 
  }
  

合同的结构

Solidity中的契约类似于面向对象语言的类。 任何合约都可以包含 状态 类型 变量 , function , function 修饰符 , event , 结构 枚举的声明 . 另外,合同可以继承其他合同。

状态变量

状态变量是永久存储在称为合同存储的合同的一部分中的值。

pragma solidity ^0.4.0;
  
  contract SimpleStorage  { 
      uint storedData ;  //状态变量
      // ... 
  }
  

请参阅 类型 部分 以了解状态变量和 可见性与获取者 的不同有效类型, 以了解状态变量可能具有的不同可见性可能性。

function

这些函数是合约内代码的可执行单元。

pragma solidity ^0.4.0;

contract SimpleAuction {
       function bid() payable { // Función
        // ...
    }
}
                                                                    

函数调用 可以内部或外部同样发生。 相对于其他合同, 功能可以具有多个 可见性 级别 .

功能修饰符

函数修饰符用于以声明方式声明函数的语义(请参阅 合同章节中的 修饰符 )。

pragma solidity ^0.4.11;

    contract Purchase {
        address public seller;
    
        modifier onlySeller() { // Modificador
            require(msg.sender == seller);
            _;
        }
    
        function abort() onlySeller { // Uso de modificador
            // ...
        }
    }
    

活动

这些事件是EVM(以太坊虚拟机)注册服务方便的接口。

pragma solidity ^0.4.0;

    contract SimpleAuction {
        event HighestBidIncreased(address bidder, uint amount); // Evento
    
        function bid() payable {
            // ...
            HighestBidIncreased(msg.sender, msg.value); // Lanzamiento del evento
        }
    }
    

event 如何事件的声明,以及它们如何能在青涩中使用在进一步的信息,合同的部分。

结构的类型

结构是由用户定义的类型,可以将多个变量分组(参见 类型部分的 结构 )。

pragma solidity ^0.4.0;

                                                                    contract Ballot {
                                                                        struct Voter { // Structs
                                                                            uint weight;
                                                                            bool voted;
                                                                            address delegate;
                                                                            uint vote;
                                                                        }
                                                                    }
                                                                    

枚举的类型

枚举(Enums)用于创建具有一组有限值的类型,并由用户定义(请参见 类型部分中的 枚举 )。

pragma solidity ^0.4.0;

    contract Purchase {
        enum State { Created, Locked, Inactive } // Enum
    }
    

s .. index :: type

类型

Solidity是一种静态的键入语言,这意味着每个类型的变量(状态和本地)必须 在编译时 指定(或者至少已知 - 参见 下面的 类型推导 )。 Solidity提供了几种基本类型,可以组合起来创建更复杂的类型。

除此之外,类型可以在包含运算符的表达式中相互交互。 有关运营商的快速参考列表,请参阅 运营商优先级顺序 .

值类型

以下类型也被称为值类型,因为此类型的变量将始终作为值传递,例如。 当它们用作函数参数或赋值时,它们将始终被复制。

bool

bool :可能的值是常量 true y false .

运营商:

  • ! (逻辑否定)
  • && (逻辑连词“和”)
  • || (逻辑和,“或”)
  • == (等式)
  • != (不等式)

运营商 || && 应用短路的共同规则。 这意味着,在表达中 ,如果你 评估 , 即使有副作用也不会被评估。 f(x) || g(y) f(x) true g(y)

冲压

int / uint :具有和不具有各种尺寸标志的整数。 关键字 uint8 a uint256 的步骤 8 (不带8至256位的符号)和 int8 a int256 . uint 他们 int 分别是 uint256 别名 int256 .

运营商:

  • 比较: <= , < , == , != , >= , > (求值 bool )
  • 位运算符: & , | , ^ (或专用于位水平), ~ ( 按位取反)
  • 算术运算符: + , - , - 一元, + 一元, * , / , % (剩余), ** ( 指数), << ( 左移位), ( >> 右移位)

除法总是截断(它被编译为EVM的DIV操作码),但是如果两个操作符是 文字 (或文字表达式) ,它将不会被截断 .

在运行时由零和模块分区零抛出异常。

移位操作的结果是左操作符类型。 这个表达式 相当于 相当于 . 这意味着做一个负数滚动扩展到一个标志。 滚动浏览负数会在运行时引发异常。 x << y x * 2**y x >> y x / 2**y

警告

由有符号整数的负值右移产生的结果与其他编程语言产生的结果不同。 在Solidity中,向右移动将划分映射,以便向右移动的负值舍入为零(截断)。 在其他编程语言中,向右移动负值的作用是向下舍入(朝向负无穷)。

address

address :包含20个字节的值(以太坊地址的大小)。 地址类型也有成员,并作为所有合同的基础。

运营商:

  • <= , < , == , != , >= >
地址的成员
  • balance transfer

有关快速参考,请参阅 关于方向 .

可以使用该属性查询地址的数量, balance 并使用以下函数将以太网(以wei为单位)发送到地址 transfer :

address x = 0x123;
    address myAddress = this;
    if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
    

nota

if x 是合同地址,则其代码(特别是其后备功能,如果存在)将随呼叫执行 transfer (这是EVM的限制,无法阻止)。 如果执行耗尽天然气或者以任何方式失败,以太转移将被撤销,目前的合同将被停止,但有一个例外。

  • send

Send是低级别对应的 transfer . 如果执行失败,则当前合同不会停止,但将 send return false .

警告

使用中有一些危险 send :如果调用的深度是1024(这可以由调用者强制执行),则调用失败,并且如果容器中的气体用完,也将失败。 然后为了安全的乙醚传输,总是检查返回的价值 send ,使用 transfer ,甚至更好:使用模式,收件人提款。

  • call , callcode delegatecall

此外,为了与不符合ABI的合约进行交互,该函数 call 预计会采取任意类型的任意数量的参数。 这些参数被填充到32个字节并连接。 第一个参数编码为4个字节的情况是个例外。 在这种情况下,不允许使用功能签名进行填充。

address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
    nameReg.call("register", "MyName");
    nameReg.call(bytes4(keccak256("fun(uint256)")), a);
    

call 返回一个布尔值,指示被调用函数是否终止( true )或导致EVM( false ) 的异常 . 不可能访问返回的实际数据(为此我们需要事先知道编码大小)。

delegatecall 可以以类似的方式使用:不同之处在于只使用给定地址的代码,所有其他方面(存储,余额...)直接来自当前合同。 目的 delegatecall 是使用存储在另一个合同中的库代码。 用户必须确保两个合同中的存储布局是正确的 delegatecall . 在家园之前,只有一个有限的版本 callcode 可用,但没有提供价值 msg.sender msg.value 原件的 访问权限 .

这三个功能 call , delegatecall 它们 callcode 是非常低级的功能,只能作为最后的手段,因为它们破坏了Solidity类型的安全性。

该选项 .gas() 可用于所有3种方法,而该选项 .value() 不受支持 delegatecall .

nota

所有合同都继承了地址成员,因此可以使用当前的合同余额查询 this.balance .

警告

所有这些功能都是低级功能,应小心使用。 具体而言,任何未知的合同都可能是恶意的,如果被调用,则控制权被赋予该合同,然后可以回复您的合同​​,因此当调用返回值时,准备更改状态变量。

固定大小的字节数组

bytes1 , bytes2 , bytes3 ,..., bytes32 . byte 这是一个别名 bytes1 .

运营商:

  • 比较: <= , < , == , != , >= , > (求值 bool )
  • 位运算符: & , | , ^ (异或按位), ~ ( 按位取反), << ( 左移), >> ( 右移)
  • 普及指数:如果 x 您键入 bytesI ,然后 x[k] to 返回的字节 (读 - 只)。 0 <= k < I k

移位运算符使用任何整数作为正确的运算符(但会返回左侧的运算符类型,表示要移动的位数)。滚动负数会在运行时抛出异常。

成员:

  • .length 返回数组字节的固定长度(只读)。
动态大小字节数组
bytes :
数组字节的动态大小,请参阅 数组 . 不是一种价值!
string :
UTF-8编码的动态大小的字符串,参见 数组 . 不是一种价值!

作为一般规则,使用 bytes 任意大小的数据原始字节和任意大小 string 的字符串(UTF-8)。 如果你能大小限制在一定的字节数,总是用一个 bytes1 bytes32 ,因为他们是一个 便宜很多。

定点数字

即将来临...

文字地址

例如,通过校验和测试的十六进制文字 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF 是类型的 address . 长度在39到41位之间的十六进制文字不通过校验和测试会产生警告,并被视为正则文字有理数。

有理和整数文字

整个文字由0-9范围内的一系列数字组成。 它们被解释为小数。 例如,这 69 意味着六十九。 在Solidity中不存在口头文字,前导零是无效的。

小数部分的文字由 . 一边至少有一个数字组成。 例子包括 1. , .1 1.3 .

科学记谱法也是支持的,其中基数可以有分数,而指数则不能。 实例包括 2e10 , -2e10 , 2e-10 , 2.5e1 .

字面数字的表达式保持任意精度,直到它们被转换为非文字类型(例如,将它们与非文字表达式一起使用)。 这意味着计算不会溢出,并且在文字数字的表达式中分割不会被截断。

例如,它会 导致常量 (类型 ),尽管中间结果甚至不会是单词的大小。 此外,它的 结果是整体 (虽然整数之间没有使用)。 (2**800 + 1) - 2**800 1 uint8 .5 * 8 4

如果结果不是一个整数, 则使用 一个合适的类型, ufixed 或者 fixed 使用其中小数位的数量尽可能大(在最坏的情况下近似于有理数)。

, 您将收到的类型 ,而 收到的类型 不以二进制所能表述的有限然后近似。 var x = 1/4; x ufixed0x8 var x = 1/3 ufixed0x256 1/3

只要运算符是整数,任何可以应用于整数的运算符也可以应用于文字数字表达式。 如果其中一个是小数,则不允许位操作,并且如果指数是小数(因为这可能导致非有理数),则不允许指数运算。

nota

对于每个有理数,Solidity都有一个字面类型的数字。 文字整数和文字有理数属于文字数字的类型。 另一方面,所有文字表达式(例如,只包含文字数字和运算符的表达式)都属于文字数字类型。 然后,字面数的表达式 两者都属于同样类型的有理数三的字面数。 1 + 2 2 + 1

nota

大多数有限小数部分,因为 5.3743 它们不是二进制有限的。 正确的类型 5.3743 ufixed8x248 因为它允许数字的最佳近似值。 如果你想以 使用与球员像数 ufixed (例如 ufixed128x128 ),你必须明确地指定所需的精度: . x + ufixed(5.3743)

警告

在以前的版本中,文字整数的分割曾被截断,但现在它将成为一个有理数,例如。 这不等于 ,而是一个 . 5 / 2 1 2.5

nota

文字数字表达式一旦与非文字表达式一起使用,就会转换为非文字类型。 虽然我们知道 b 在下面的例子中 赋值的表达式的值是 一个整数,但它仍然使用定点类型(而不是合理的字面数),然后代码不能编译。

uint128  a  =  1 ; 
  uint128  b  =  2.5  +  a  +  0.5 ;
  
字符串文字

文字字符串用单引号或双引号( "foo" 'bar' ) 编写 . 在C中没有隐式的零; "foo" 它代表三个字节,而不是四个。 与整数文字的类型可能会有所不同,但隐式转换 bytes1 ..., bytes32 如果适合 bytes 较长 string .

文字字符串支持转义字符,如 \n , \xNN \uNNNN . \xNN 取一个值并插入适当的字节,同时 \uNNNN 取一个Unicode代码点并插入一个UTF-8序列。

十六进制文字

十六进制文字是带关键字的前缀, hex 并用单引号或双引号( hex"001122FF" ) 关闭 . 其内容必须是十六进制字符串,其值将是这些值的二进制表示。

十六进制文字的行为类似于文字字符串,并具有相同的可转换性限制。

枚举

枚举是用户在Solidity中创建自己类型的一种方法。 它们可以显式转换为所有整数类型,但是不允许隐式转换。 显式转换在运行时检查范围的值,失败会导致异常。 枚举至少需要一个成员。

pragma solidity ^0.4.0;

    contract test {
        enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
        ActionChoices choice;
        ActionChoices constant defaultChoice = ActionChoices.GoStraight;
    
        function setGoStraight() {
            choice = ActionChoices.GoStraight;
        }
    
        // Ya que los tipos enum no son parte del ABI, la firma de "getChoice"
        // será automáticamente cambiada a "getChoice() returns (unit8)"
        // para todo lo externo a Solidity. El tipo entero usado es apenas
        // suficientemente grande para guardar todos los valores enum, p.ej. si
        // tienes más valores, `unit16` será utilizado y así sucesivamente.
        function getChoice() returns (ActionChoices) {
            return choice;
        }
    
        function getDefaultChoice() returns (uint) {
            return uint(defaultChoice);
        }
    }
    
function

函数类型是函数类型。 函数类型变量可以从函数中分配,函数类型的函数参数可以用来传递函数并返回函数调用函数。 功能的类型有两种: 内部 外部 :

内部函数只能在当前合同中使用(特别是在当前代码单元内,也包括内部函数库和继承函数),因为它们不能在当前合同的上下文之外执行。 对内部函数的调用是通过跳转到其输入标签来完成的,比如当前合同的内部函数被调用时。

外部函数由地址和函数签名组成,可以从外部函数调用传递和返回。

功能的类型如下所示:

function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)]

与参数类型不同,返回类型不能为空 - 如果函数类型不返回任何内容,则 必须忽略 该部分 . returns (<return types>)

默认情况下,这些功能是内部的,所以关键字 internal 可以省略。

在当前合同中有两种访问函数的方法:直接使用您的名字 f ,或者使用 this.f . 使用这个名字将会产生一个内部函数,并且 this 会有一个外部函数。

如果函数类型的变量没有被初始化,调用它将导致异常。 如果在使用 delete 之后调用一个函数,也会发生同样的事情 .

如果外部函数在Solidity的上下文之外使用,则将它们视为一个类型 function ,它将对地址进行编码,然后将函数的标识符与类型一起编码 bytes24 .

请注意,当前合同的公共职能既可以作为内部职能,也可以作为外部职能使用。 f 作为内部函数使用,只能称为 f ,如果要将其用作外部 function ,请使用 this.f .

显示如何使用内部函数类型的示例:

pragma solidity ^0.4.5;

    library ArrayUtils {
        // las funciones internas pueden ser usadas en funciones de librerías
        // internas porque serán parte del mismo contexto de código.
        function map(uint[] memory self, function (uint) returns (uint) f)
        internal
        returns (uint[] memory r)
        {
        r = new uint[](self.length);
        for (uint i = 0; i < self.length; i++) {
            r[i] = f(self[i]);
        }
        }
        function reduce(
        uint[] memory self,
        function (uint x, uint y) returns (uint) f
        )
        internal
        returns (uint r)
        {
        r = self[0];
        for (uint i = 1; i < self.length; i++) {
            r = f(r, self[i]);
        }
        }
        function range(uint length) internal returns (uint[] memory r) {
        r = new uint[](length);
        for (uint i = 0; i < r.length; i++) {
            r[i] = i;
        }
        }
    }
    
    contract Pyramid {
        using ArrayUtils for *;
        function pyramid(uint l) returns (uint) {
        return ArrayUtils.range(l).map(square).reduce(sum);
        }
        function square(uint x) internal returns (uint) {
        return x * x;
        }
        function sum(uint x, uint y) internal returns (uint) {
        return x + y;
        }
    }
    

另一个使用外部函数类型的例子:

pragma solidity ^0.4.11;

contract Oracle {
    struct Request {
    bytes data;
    function(bytes memory) external callback;
    }
    Request[] requests;
    event NewRequest(uint);
    function query(bytes data, function(bytes memory) external callback) {
    requests.push(Request(data, callback));
    NewRequest(requests.length - 1);
    }
    function reply(uint requestID, bytes response) {
    // Aquí se revisa que la respuesta viene de una fuente de confianza
    requests[requestID].callback(response);
    }
}

contract OracleUser {
    Oracle constant oracle = Oracle(0x1234567); // contrato conocido
    function buySomething() {
    oracle.query("USD", this.oracleResponse);
    }
    function oracleResponse(bytes response) {
    require(msg.sender == address(oracle));
    // Usar los datos
    }
}

请注意,lambda或内联函数是计划的,但尚未实现。

引用类型

复杂类型,例如 不能总是适合256位的类型必须比我们已经看到的值更加谨慎。 由于复制它们可能非常昂贵,我们不得不考虑是否要将它们存储在 内存中 (不是持久的)或 存储器中 (在哪里 存储 状态变量)。

数据的位置

每个复杂类型,例如。 数组 结构体 ,还有额外的注释,“数据位置”,关于它是存储在内存还是存储。 根据上下文,总是有一个默认值,但可以通过 在类型中 添加“ storage 或”或“ 内存” 替换 . 默认情况下,对于函数参数类型(包括返回参数)来说 memory ,默认情况下是局部变量是, storage 并且该位置被强制 storage 为状态变量(显然)。

还有第三个数据位置“calldata”,这个区域在存储函数参数的地方是不可修改或持久的。 外部函数的函数参数(不返回参数)被强制为“calldata”,表现得和内存几乎一样。

数据位置很重要,因为它们改变了赋值的行为方式:存储和内存之间的分配以及状态变量(甚至来自其他状态变量)总是创建一个独立的副本。 本地存储变量存储分配只分配一个引用,并且该引用始终指向状态变量,即使引用同时发生变化。 相反,将存储在内存中的引用分配给另一个引用类型不会创建副本。

pragma solidity ^0.4.0;

contract C {
    uint[] x; // la ubicación de los datos de x es storage

    // la ubicación de datos de memoryArray es memory
    function f(uint[] memoryArray) {
        x = memoryArray; // funciona, copia el array entero al almacenamiento
        var y = x; // funciona, asigna una referencia, ubicación de datos de y es almacenamiento
        y[7]; // bien, devuelve el octavo elemento
        y.length = 2; // bien, modifica x a través de y
        delete x; // bien, limpia el array, también modifica y
        // Lo siguiente no funciona; debería crear un nuevo array temporal/sin nombre
        // en almacenamiento, pero el almacenamiento es asignado "estáticamente":
        // y = memoryArray;
        // Esto no funciona tampoco, ya que resetearía el apuntador, pero no hay
        // ubicación donde podría apuntar
        // delete y;
        g(x); // llama g, dando referencia a x
        h(x); // llama h y crea una copia independiente y temporal en la memoria
    }

    function g(uint[] storage storageArray) internal {}
    function h(uint[] memoryArray) {}
}
摘要
强制数据位置:
  • 外部函数的参数(不返回):calldata
  • 状态变量:存储
默认数据位置:
  • 参数(也返回)的函数:内存
  • 所有其他变量:存储
阵列

数组在编译时可以有固定的大小,也可以是动态的。 对于存储阵列,元素的类型可以是任意的(例如其他数组,映射或结构)。 对于内存数组,它不能是一个映射,如果它是一个公开可见的函数的参数,它必须是一个ABI类型。

一个固定的大小 k 和类型的元素 数组 T 被写成 T[k] 一个像动态大小的数组 T[] . 作为一个例子,一个5个动态数组的数组 uint uint[][] (注意与其他语言相比,这个记号是反转的)。 要访问第三个动态数组中的第二个uint,将使用它 x[2][1] (索引从0开始,访问以与声明相反的方式工作,即 x[2] 从右侧减少类型的级别)

类型变量 bytes string 特殊的阵列。 A bytes 是类似的 byte[] ,但一起在calldata。 string 等于 bytes 但不允许访问长度或索引(现在)。

所以它 bytes 总是 byte[] 比较便宜 , 因为它比较便宜。

nota

如果要访问字符串的字节表示形式 s ,请使用 bytes(s).length / . 请注意,您正在使用UTF-8访问表示的低级字节,而不是单个字符! bytes(s)[7] = 'x';

可以将数组标记为 public 并让Solidity创建一个getter。 数字索引将成为getter所需的参数。

内存分配在数组中

在内存中创建具有可变长度的数组可以使用关键字来完成 new . 与存储阵列不同,通过将阵列分配给成员来调整阵列的大小是不可能的 .length .

pragma solidity ^0.4.0;

contract C {
    function f(uint len) {
        uint[] memory a = new uint[](7);
        bytes memory b = new bytes(len);
        // Aquí tenemos a.length == 7 y b.length == len
        a[6] = 8;
    }
}
文字数组/在线数组

文字数组是指作为表达式写入的数组,并且此时不会被分配给变量。

pragma solidity ^0.4.0;

contract C {
    function f() {
        g([uint(1), 2, 3]);
    }
    function g(uint[3] _data) {
        // ...
    }
}

字面数组类型是一个固定大小的内存数组,其基类型是给定元素的常见类型。 类型 ,因为这些常量的类型是 . 因此,有必要将上例中的第一个元素转换为 . 请注意,目前,固定大小的内存数组不能分配给动态大小的内存数组,例如。 以下是不可能的: [1, 2, 3] uint[3] memory uint8 uint

pragma solidity ^0.4.0;

contract C {
    function f() {
        // La próxima línea crea un tipo error porque uint[3] memory
        // no puede ser convertido a uint[] memory.
        uint[] x = [uint(1), 3, 4];
}

这个限制计划在将来被淘汰,但是由于数组在ABI中的传递方式,目前造成了一些复杂性。

成员
长度 :
数组有一个成员 length 来存储他们的元素数量。 动态数组可以通过更改成员在存储中(而不是在内存中)进行修改 .length . 当您尝试访问当前长度以外的元素时,这不会自动发生。 内存数组的大小在创建时是固定的(但是动态的,例如可能取决于运行时的参数)。
:
动态存储阵列和 bytes (否 string )有一个命名的成员函数 push ,可以用来添加一个元素到数组的末尾。 该函数返回新的长度。

警告

在外部函数中使用数组还是不可能的。

警告

由于EVM的限制,无法从外部功能返回动态内容。 如果从web3.js中调用 function f ,将会 返回一些东西,但是如果它是从Solidity调用的 ,则 返回一些东西。 contract C { function f() returns (uint[]) { ... } }

现在唯一的选择是使用静态大小的大数组。

pragma solidity ^0.4.0;

contract ArrayContract {
    uint[2**20] m_aLotOfIntegers;
    // Nótese que el siguiente no es un par de arrays dinámicos, sino un
    // array dinámico de pares (ej. de arrays de tamaño fijo de length 2).
    bool[2][] m_pairsOfFlags;
    // newPairs es almacenado en memoria - por defecto para argumentos de función

    function setAllFlagPairs(bool[2][] newPairs) {
        // asignación a un array de almacenamiento reemplaza el array completo
        m_pairsOfFlags = newPairs;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) {
        // acceso a un index que no existe arrojará una excepción
        m_pairsOfFlags[index][0] = flagA;
        m_pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) {
        // si el tamaño nuevo es más pequeño, los elementos eliminados del array serán limpiados
        m_pairsOfFlags.length = newSize;
    }

    function clear() {
        // éstos limpian los arrays completamente
        delete m_pairsOfFlags;
        delete m_aLotOfIntegers;
        // efecto idéntico aquí
        m_pairsOfFlags.length = 0;
    }

    bytes m_byteData;

    function byteArrays(bytes data) {
        // byte arrays ("bytes") son diferentes ya que no son almacenados sin padding,
        // pero pueden ser tratados idénticamente a "uint8[]"
        m_byteData = data;
        m_byteData.length += 7;
        m_byteData[3] = 8;
        delete m_byteData[2];
    }

    function addFlag(bool[2] flag) returns (uint) {
        return m_pairsOfFlags.push(flag);
    }

    function createMemoryArray(uint size) returns (bytes) {
        // Arrays de memoria dinámicos son creados usando `new`:
        uint[2][] memory arrayOfPairs = new uint[2][](size);
        // Crear un byte array dinámico:
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(i);
        return b;
    }
}
结构

Solidity提供了一种用结构定义新类型的方法,如下例所示:

pragma solidity ^0.4.11;

contract CrowdFunding {
    // Define un nuevo tipo con dos campos.
    struct Funder {
        address addr;
        uint amount;
    }

    struct Campaign {
        address beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID es variable de retorno
        // Crea un nuevo struct y lo guarda en almacenamiento. Dejamos fuera el tipo mapping.
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    function contribute(uint campaignID) payable {
        Campaign c = campaigns[campaignID];
        // Crea un nuevo struct de memoria temporal, inicializado con los valores dados
        // y lo copia al almacenamiento.
        // Nótese que también se puede usar Funder(msg.sender, msg.value) para inicializarlo
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) returns (bool reached) {
        Campaign c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

合同没有提供众筹合同的全部功能,但它确实包含了理解结构所必需的基本概念。 结构类型可以在映射和数组中使用,它们本身可以包含映射和数组。

尽管结构可以是映射成员的值类型,但结构不可能包含它自己类型的成员。 这个限制是必须的,因为struct的大小必须是有限的。

请注意,在所有函数中,都将结构类型分配给本地变量(从默认存储位置)。 这不会复制结构,而是保存一个引用,以便分配给本地变量的成员实际写入状态。

当然,你可以直接访问结构的成员,而不必将它们分配给局部变量,如 . campaigns[campaignID].amount = 0

mapping

地图类型被声明为 . 在这里,它 几乎可以是任何类型的映射,一个动态大小的数组,一个约定,一个枚举和一个结构。 它可以是任何类型,包括映射。 mapping(_KeyType => _ValueType) _KeyType _ValueType

映射可以看作是“哈希表< https://en.wikipedia.org/wiki/Hash_table >”,它们被虚拟初始化,因为每个可能的类都存在,并被映射到一个字节表示全部为零的值: 默认情况下 是一个类型。 尽管相似性在这里结束:关键数据并不真正存储在映射中,只有它的哈希值 keccak256 用于查找值。

因此,映射没有长度或“修复”键或值的概念。

映射只能用于状态变量(或作为内部函数的引用类型)。

可以标记映射 public 并使Solidity创建一个getter。 _KeyType 将是一个必要的参数为getter并将返回 _ValueType .

_ValueType 也可以是一个映射。 getter将 _KeyType 递归地 为每个参数赋予一个参数 .

pragma solidity ^0.4.0;

contract MappingExample {
    mapping(address => uint) public balances;

    function update(uint newBalance) {
        balances[msg.sender] = newBalance;
    }
}

contract MappingUser {
    function f() returns (uint) {
        return MappingExample(<address>).balances(this);
    }
}

nota

映射不是可迭代的,但是可以在其上实现数据结构。 例如,请参阅 可迭代映射 .

带LValues的操作符

如果它 a 是一个LValue(例如一个变量或可以分配的东西),下面的操作符是可能的缩写:

a += e 相当于 . 运营商 , , , , , 都被定义这种方式。 他们 相当于 / 但表达式本身仍然有以前的价值 . 相比之下, 他们有同样的效果 ,但改变后返回值。 a = a + e -= *= /= %= a |= &= ^= a++ a-- a += 1 a -= 1 a --a ++a a

删除

delete a 为类型a分配初始值 a . 例如对于整数,等价的是 ,但它可以用在数组中,其中一个零长度的动态数组或一个相同长度的静态数组被赋值,所有元素都被重置。 对于结构体,它被赋值为所有成员重置的结构体。 a = 0

delete 它对整个映射没有影响(因为映射键可以是任意的,一般是未知的)。 所以,如果你删除了一个结构体,那么它将会重置所有不是映射的成员,并且还会递归地向成员递归,除非它们是映射关系。 但是,可以消除各个键和他们可以映射的内容。

重要的是要注意,它 实际上就像一个赋值 ,例如。 存储一个新的对象 . delete a a a

pragma solidity ^0.4.0;

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() {
        uint x = data;
        delete x; // setea x to 0, no afecta a los datos
        delete data; // setea data a 0, no afecta a x que aún tiene una copia
        uint[] y = dataArray;
        delete dataArray; // esto setea dataArray.length a cero, pero como uint[] es un objecto complejo,
        // también y es afectado que es un alias al objeto de almacenamiento
        // Por otra parte: "delete y" no es válido, ya que asignaciones a variables locales
        // haciendo referencia a objetos de almacenamiento sólo pueden ser hechas de
        // objetos de almacenamiento existentes.
    }
}

基本类型之间的转换

隐式转换

如果运算符应用于不同的类型,编译器会尝试隐式地将其中一个运算符转换为另一个运算符(对于赋值也是如此)。 一般来说,如果语义上有意义,并且没有信息丢失,则可以在类型值之间进行隐式转换:它 uint8 可以转换为 uint16 int128 a int256 ,但 int8 不能转换 uint256 (因为 uint256 它不能包含 -1 )。 另外,无符号整数可以转换为相同或更大的字节,反之亦然。 任何可以转换的类型 uint160 也可以转换为 address .

显式转换

如果编译器不允许隐式转换,但是您知道自己在做什么,则有时可以使用显式类型转换。 请注意,这可以给你意想不到的行为,所以一定要证明结果是你想要的! 这个例子是从一个负面转换 int8 as uint :

int8 y = -3;
uint x = uint(y);

在这段代码的末尾,它将 x 具有值 0xfffff..fd (64个十六进制字符),在二进制补码的256位表示中为-3。

如果某个类型明确转换为较小的类型,则删除较高的位:

uint32 a = 0x12345678;
uint16 b = uint16(a); // b será 0x5678 ahora

类型扣除

为了方便起见,并不总是需要明确指定变量的类型,编译器会自动推断变量分配到的第一个表达式的类型:

uint24 x = 0x123;
var y = x;

在这里,类型 y 将是 uint24 . 无法使用 var 函数参数或返回参数。

警告

这个类型只是从第一个赋值中推导出来的,所以下面的代码片段的循环是无限的,因为它 i 有类型, uint8 而且这个类型的任何值都小于 2000 for (var i = 0; i < 2000; i++) { ... }

单位和变量全球可用

醚单位

一个文字数可以采取后缀的相同 wei ,所述 finney 的 , szabo ether 以subdenominaciones以太之间进行转换。 假设一个没有后缀代表以太币的数字在魏中表示,例如 回报 . 2 ether == 2000 finney true

时间单位

后缀 seconds , minutes , hours , days , weeks years 使用文字数字后可以用于转换的时间单元,其中后者是基座单元。 等价如下:

  • 1 == 1 seconds
  • 1 minutes == 60 seconds
  • 1 hours == 60 minutes
  • 1 days == 24 hours
  • 1 weeks == 7 days
  • 1 years == 365 days

如果您使用这些单位执行日历计算, 请小心 ,因为不是所有的年份都有365天,而且所有的日子都不是24小时,因为 插入的秒数 . 由于闰秒不可预测,现存的日历库必须由外部的oracle更新。

这些后缀不能应用于变量。 如果你想解释一些输入变量,如天,你可以这样做,如下所示:

function f(uint start, uint daysAfter) {
    if (now >= start + daysAfter * 1 days) { ... }
}

变量和特殊功能

全局范围的变量和特殊功能始终可用,主要用于提供有关区块链的信息。

交易的块和属性
  • block.blockhash(uint blockNumber) returns (bytes32) :给定块的散列 - 仅适用于最近的256个块,不包括当前块
  • block.coinbase ( address ):返回正在处理当前块的矿工的地址
  • block.difficulty ( uint ):返回当前块的难度
  • block.gaslimit ( uint ):返回当前块的气体限制
  • block.number ( uint ):返回当前块的编号
  • block.timestamp ( uint ):以Unix通用时间(Unix纪元)之后的秒数形式返回当前块的时间戳
  • msg.data ( bytes ):在事务中发送的数据(calldata)
  • msg.gas ( uint ):返回剩余的气体
  • msg.sender ( address ):返回当前调用的发送者
  • msg.sig ( bytes4 ):返回事务中发送的数据的前四个字节(即函数的标识符)
  • msg.value ( uint ):返回随呼叫发送的卫号
  • now ( uint ):返回当前块的时间戳(它是一个别名 block.timestamp )
  • tx.gasprice ( uint ):返回交易的天然气价格
  • tx.origin ( address ):返回事务的原始发件人

nota

每个调用的 外部 function 的所有元素的值都 msg 包含在内, msg.sender 并且 msg.value 可以更改 . 包括对图书馆功能的调用。

如果要使用库函数实现访问限制 msg.sender ,则必须手动提供 msg.sender 作为参数 的值 .

nota

出于可伸缩性原因,块哈希不可用于所有块。 您只能访问最近256个块的哈希值。 旧块的散列值将为零。

数学和密码学功能

assert(bool condition) :如果条件不满足,则抛出异常。 :计算 总和完成的任意精度,不溢出 :计算 乘法以任意精度完成,不溢出 :计算参数(压缩)的Ethereum-SHA-3(Keccak-256)的方式。 :相当于 :计算参数的联合(压缩)的SHA-256散列。 :计算参数的联合(压缩)的RIPEMD-160散列。 :检索与椭圆曲线类型签名的公钥相关的地址,如果有错误( 使用示例 ) ,则返回零 addmod(uint x, uint y, uint k) returns (uint) (x + y) % k 2**256 mulmod(uint x, uint y, uint k) returns (uint) (x * y) % k 2**256 keccak256(...) returns (bytes32) sha3(...) returns (bytes32) keccak256() sha256(...) returns (bytes32) ripemd160(...) returns (bytes20) ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) revert() :中止执行并按原样颠倒状态更改。

在上面,“压缩”意味着参数连接而没有填充。 这意味着下面的调用都是一样的:

keccak256("ab", "c")
keccak256("abc")
keccak256(0x616263)
keccak256(6382179)
keccak256(97, 98, 99)

如果需要填充,可以使用类型的显式转换: keccak256("\x00\x12") keccak256(uint16(0x12)) . 相同 .

请注意,常量将使用存储它们所需的最小字节数进行压缩。 这意味着例如 y . keccak256(0) == keccak256(uint8(0)) keccak256(0x12345678) == keccak256(uint32(0x12345678))

他可能会发生这样的情况:他缺乏调用职能的天然气 sha256 , ripemd160 或者 ecrecover 私人区块链中 . 这是因为这些函数是作为预编译的契约来实现的,只有在接收到第一条消息(即使合约是硬编码的)之后,这些契约才存在。 发送给尚不存在的合同的消息比较昂贵,因此由于缺乏气体(Out-of-Gas错误),它们的执行可能导致错误。 解决这个问题的一个办法是,例如,在你开始使用它之前,把1Wei发送给所有的合同。 请注意,这不会导致官方网络或测试网络中的错误。

关于方向

<address>.balance ( uint256 ): 地址的魏Balances :将所需的金额发送给 address ,如果失败则抛出异常。 :将所需的金额发送给 address , 如果失败则 return :创建一个 低级的 类型指令 , 如果失败则 return :创建一个 低级的 类型指令 , 如果失败则 return :创建一个 低级的 类型指令 , 如果失败则 return . <address>.transfer(uint256 amount) <address>.send(uint256 amount) returns (bool) false <address>.call(...) returns (bool) CALL false <address>.callcode(...) returns (bool) CALLCODE false <address>.delegatecall(...) returns (bool) DELEGATECALL false

有关更多信息,请参阅部分 address .

警告

使用时存在危险 send :如果调用堆栈的深度为1024(调用程序始终强制执行此操作),则调用将失败; 如果收件人用完了,也会失败。 因此,为了确保您在Ether中进行安全传输,请始终查看返回的值 send ,使用 transfer 而不是 send 更好,或者使用收款人提取资金的模式。

关于合同

this (当前合同类型):当前合同,明确地转换为 address :摧毁当前的合同,并把它的资金发送到 指定的 address . selfdestruct(address recipient)

另外,可以直接调用当前合同的所有功能,包括当前的功能。

表达式和控制结构

输入和输出参数

正如在Javascript中,函数获取参数作为输入。 另一方面,不像Javascript和C,这些也应该返回随机数量的参数作为输出。

输入参数

输入参数的声明方式与变量相同。 作为例外,未使用的参数可能会忽略变量的名称。 例如,如果我们希望我们的合同接受一个具有两个整数的外部调用,那么代码将类似于这个:

contract Simple {
    function taker(uint _a, uint _b) {
        // hace algo con _a y _b.
    }
}
输出参数

输出参数可以在保留字之后用相同的语法声明 returns . 例如,假设我们要返回两个结果:两个给定值的总和和乘积。 然后,我们会写这样的代码:

contract Simple {
    function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) {
        o_sum = _a + _b;
        o_product = _a * _b;
    }
}

输出参数的名称可以省略。 输出值也可以使用声明来指定 return . 声明 return 也能够返回多个值,请参阅 返回多个值 . 返回参数初始化为零; 如果它们的值没有明确指定,则它们保持为所述值零。

输入和输出参数可以用作函数主体中的表达式。 在这种情况下,他们也可以在任务的左侧进行。

控制结构

除了 switch and 之外,JavaScript中大部分可用的控制结构也都在Solidity中 goto . 这意味着我们有: if , else , while , do , for , break , continue , return , ,已知C或JavaScript中的常用语义。 ? :

括号不能省略,但简单语句的主体周围的键可以省略。

请记住,在C和JavaScript 存在从非布尔型到布尔型的类型转换,所以 在Solidity中无效。 if (1) { ... }

返回多个值

当一个函数有多个输出参数时,它 可以返回多个值。 组件的数量必须与输出参数的数量相同。 return (v0, v1, ..., vn)

函数调用

呼叫内部功能

当前契约的功能可以直接调用(“内部”),也可以递归地调用,就像你在这个例子中看到的那样,没有功能意义:

contract C {
    function g(uint a) returns (uint ret) { return f(); }
    function f() returns (uint ret) { return g(7) + f(); }
}

这些函数调用被转换为以太坊虚拟机(EVM)中的简单跳转。 这样做的结果是当前内存不被清理,所以将内存引用传递给内部调用的函数是非常有效的。 只有同一合同的功能可以在内部调用。

呼叫外部功能

表达式 this.g(8); c.g(2); (其中 c 是合同的实例)也是有效的调用,但是这次函数将通过消息调用“外部”调用,而不是直接由跳转调用。 请记住,函数的调用 this 不能在构造 函数中 使用,因为有问题的合同还没有被创建。

其他合同的功能必须从外部调用。 对于外部调用,函数的所有参数都必须复制到内存中。

当你调用函数的其他合同,伟量随呼叫发送和气体可以用特殊的选项来指定 .value() ,并 .gas() 分别:

contract InfoFeed {
    function info() payable returns (uint ret) { return 42; }
}


contract Consumer {
    InfoFeed feed;
    function setFeed(address addr) { feed = InfoFeed(addr); }
    function callFeed() { feed.info.value(10).gas(800)(); }
}

payable 必须使用 修饰符 info ,否则选项 .value() 将不可用。

请注意,该表达式 InfoFeed(addr) 执行显式类型转换,指出“我们知道给定地址中的合约类型是 InfoFeed ”,而不执行构造函数。 显式类型转换必须非常小心。 你永远不应该在合同中调用一个你不知道你的类型是什么的函数。

它也可以 直接 使用 . 要小心这样一个事实,即 只有(本地)设置随功能调用发送的气体的数量和数量。 只有在最后一个括号之后才是实际的呼叫。 function setFeed(InfoFeed _feed) { feed = _feed; } feed.info.value(10).gas(800)

如果被调用的合约不存在(在帐户不包含代码的意义上),或者由自己调用的合约触发异常或耗尽气体,则函数调用会导致异常。

警告

与其他合同的任何交互都是潜在的损害,特别是如果合同的源代码未知。 当前合同将控制权交给被援引的合同,这可能意味着它执行任何操作。 即使被调用的合同继承自已知的父合同,与继承合同的合同也只需要具有正确的接口。 然而,合同的执行可以是完全随机的,因此造成损失。 另外,如果您在其他系统合同中打电话,或者在第一个电话回复之前,甚至返回到打电话给您的合同,您必须做好准备。 这意味着发票合同可以改变通过其功能调用它的合同的状态变量。 写你的功能,以便他们执行,

命名的呼叫和匿名功能参数

调用函数的参数可以按名称给出,如果它们之间存在任何次序, 可以在下面的例子中看到。 参数列表必须与名称与函数声明的参数列表匹配,但是它们可以是随机的。 { }

pragma solidity ^0.4.0;

    contract C {
        function f(uint key, uint value) { ... }
    
        function g() {
            // argumentos con nombre
            f({value: 2, key: 3});
        }
    }
    
功能参数名称省略

未使用的参数名称(特别是返回值)可以省略。 这些名字将出现在堆栈中,但是它们将不可访问。

pragma solidity ^0.4.0;

contract C {
    // Se omite el nombre para el parámetro
    function func(uint k, uint) returns(uint) {
        return k;
    }
}

通过创建合同 new

合同可以使用保留字创建新的合同 new . 正在创建的合同的完整代码必须事先知道,所以递归创建依赖关系是不可能的。

pragma solidity ^0.4.0;

contract D {
    uint x;
    function D(uint a) payable {
        x = a;
    }
}


contract C {
    D d = new D(4); // Se ejecutará como parte del constructor de C

    function createD(uint arg) {
        D newD = new D(arg);
    }

    function createAndEndowD(uint arg, uint amount) {
        // Envía Ether junto con la creación
        D newD = (new D).value(amount)(arg);
    }
}

如示例所示,可以使用选项将乙醚转移到创建中 .value() ,但不可能限制气体的数量。 如果创建失败(由于电池溢出,缺乏Balances或任何其他问题),将触发异常。

表达式评估的顺序

表达式评估的顺序没有被规定(更正式地说,表达式树中节点的子节点的评估顺序没有被指定,但是它们在节点本身之前被评估)。 只能保证语句按顺序执行,并且布尔表达式会短路。 参见 运营商的层次 以获取更多信息。

分配

分配解构和返回多个值

Solidity在内部允许元组类型,例如:在编译时可能有大小不变的不同类型的对象列表。 这些元组可以同时返回多个值,也可以同时将它们分配给多个变量(或一般值的列表):

contract C {
    uint[] data;

    function f() returns (uint, bool, uint) {
        return (7, true, 2);
    }

    function g() {
        // Declara y asigna variables. No es posible especificar el tipo de forma explícita.
        var (x, b, y) = f();
        // Asigna a una variable pre-existente.
        (x, y) = (2, 7);
        // Truco común para intercambiar valores -- no funciona con tipos de almacenamiento sin valor.
        (x, y) = (y, x);
        // Los componentes se pueden dejar fuera (también en declaraciones de variables).
        // Si la tupla acaba en un componente vacío,
        // el resto de los valores se descartan.
        (data.length,) = f(); // Establece la longitud a 7
        // Lo mismo se puede hacer en el lado izquierdo.
        (,data[3]) = f(); // Sets data[3] to 2
        // Los componentes sólo se pueden dejar en el lado izquierdo de las asignaciones, con
        // una excepción:
        (x,) = (1,);
        // (1,) es la única forma de especificar una tupla de un componente, porque (1)
        // equivale a 1.
    }
}
阵列和结构中的并发症

赋值语法对于诸如数组和结构等无价值的类型来说更为复杂。 分配 to 状态变量总是创建一个独立的副本。 另一方面,分配一个局部变量只为基本类型创建一个独立的副本,比如适合32个字节的静态类型。 如果将结构或数组(包括 bytes y string )从状态变量分配给局部变量,则局部变量将保留对原始状态变量的引用。 局部变量的第二个赋值不会改变状态,只有引用的变化。 分配给成员(或元件)的局部变量的 使 更改状态。

范围界定和陈述

声明的变量默认情况下有一个初始值,用字节表示,将全部为零。 无论类型如何,变量的默认值都是典型的“零状态”。 例如,一个 bool is 的默认值 false . a uint int is 的默认值 0 . 对于静态的大小和数组 bytes1 to bytes32 每个单独的元件将被初始化为默认值作为它的类型。 最后,对于动态大小的数组, 默认值是一个空数组或字符串。 bytes``y ``string

无论声明在哪里, function 中任何一个声明的变量都将在 整个函数 的范围内 . 发生这种情况是因为Solidity继承了其JavaScript范围规则。 这与许多语言不同,变量只在声明范围内,直到语义块结束。 作为一个 结果,下面的代码是非法的,并且使由于标识符已经被先前声明的编译器将返回一个错误 : Identifier already declared

pragma solidity ^0.4.0;

contract ScopingErrors {
    function scoping() {
        uint i = 0;

        while (i++ < 1) {
            uint same1 = 0;
        }

        while (i++ < 2) {
            uint same1 = 0; // Ilegal, segunda declaración para same1
        }
    }

    function minimalScoping() {
        {
            uint same2 = 0;
        }

        {
               uint same2 = 0; // Ilegal, segunda declaración para same2
        }
    }

    function forLoopScoping() {
        for (uint same3 = 0; same3 < 1; same3++) {
        }

           for (uint same3 = 0; same3 < 1; same3++) { // Ilegal, segunda declaración para same3
        }
    }
}

除此之外,如果变量被声明,它将在函数的开始被默认值初始化。 这意味着下面的代码是合法的,尽管它的写法有点糟糕:

function foo() returns (uint) {
    // baz se inicializa implícitamente a 0
    uint bar = 5;
    if (true) {
        bar += baz;
    } else {
        uint baz = 10;// Nunca se ejecuta
    }
    return bar;// devuelve 5
}

例外

有些情况下会自动抛出异常(见下文)。 您可以使用该指令 throw 手动启动它们。 异常的结果是当前正在执行的调用被停止并反转(状态和余额中的所有更改都被撤消),并且由Solidity函数调用(异常 send 和低 - 级功能 call , delegatecall 并且 callcode ,所有的返回 false 如果一个例外)。

目前尚不可能捕获异常情况。

在下面的例子中,教会了如何使用 throw 它来轻松地反转以太转换,另外还教会了如何检查以下的返回值 send :

pragma solidity ^0.4.0;

contract Sharer {
    function sendHalf(address addr) payable returns (uint balance) {
        if (!addr.send(msg.value / 2))
            throw; // También revierte la transferencia de Sharer
        return this.balance;
    }
}

目前,在以下情况下,Solidity会自动生成运行时异常:

  1. 如果在索引中访问数组太长或负数(例如: x[i] where o )。 i >= x.length i < 0
  2. 如果您访问 bytesN 固定长度索引太长或负面。
  3. 如果你调用一个消息调用的函数,但是未正常完成(如汽油耗尽,具有匹配的功能,或触发例外本身),除了在使用操作的情况下低电平 call , send , delegatecall callcode . 低级操作从不触发异常,但通过返回指示失败 false .
  4. 如果使用保留字 new 创建合同,但合同的创建不能正确结束(请参阅上面的“不要正确结束”的定义)。
  5. 如果它被划分或由零模块(例子: o )。 5 / 0 23 % 0
  6. 如果移动是负数。
  7. 如果一个非常大或负的值被转换为一个枚举类型。
  8. 如果通过指向不包含代码的合同进行外部函数调用。
  9. 如果合同通过一个没有修饰符 payable (包括构造函数和后备功能) 的函数接收Ether .
  10. 如果合同通过公共的getter函数接收Ether。
  11. 如果从内部函数类型调用初始化为零的变量。
  12. 如果一个 .transfer() 失败。
  13. 如果调用 assert 的参数的计算结果为false。

用户在以下情况下生成异常:

  1. 打电话 throw .
  2. require 与评估a的论点一起 调用 false .

在内部, 0xfd 当用户提供的异常被抛出或通话条件 require 不满足 ,Solidity执行反向操作(回复,指令 ) . 另一方面, 0xfe 如果运行时异常出现或呼叫条件 assert 不满足 ,则执行无效操作(指令 ) . 在这两种情况下,这都会导致EVM反转发生的所有状态更改。 所有这一切的原因是没有办法继续执行,因为预期的效果没有发生。 如果要保持交易的原子性,最好是将所有的变更都撤消,并使交易完全无效,或者至少在通话中不起作用。

如果合同的写法 assert 只能用于测试内部条件, require 并在出现格式错误的情况下使用,那么验证无效操作码永远无法到达的正式分析工具可能是假设输入有效,用于检查是否存在错误。

contract

Solidity中的契约类似于面向对象语言中的类。 合同包含存储在状态变量中的持久数据和可以修改这些变量的函数。 调用不同协议(实例)的函数将调用EVM(以太坊虚拟机)的函数,以便更改上下文,以便状态变量不可访问。

创建合同

合同可以从“外部”创建,也可以从Solidity合同中创建。 创建合同时,其构造函数(与合同名称相同的函数)仅执行一次。

构造函数是可选的。 支持单个构造函数,这意味着不支持超载。

from web3.js JavaScript API开始,这个过程如下:

// Es necesario especificar alguna fuente, incluido el nombre del contrato para los parametros de abajo
var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }";

// El array json del ABI generado por el compilador
var abiArray = [
    {
        "inputs":[
            {"name":"x","type":"uint256"},
            {"name":"y","type":"uint256"}
        ],
        "type":"constructor"
    },
    {
        "constant":true,
        "inputs":[],
        "name":"x",
        "outputs":[{"name":"","type":"bytes32"}],
        "type":"function"
    }
];

var MyContract_ = web3.eth.contract(source);
MyContract = web3.eth.contract(MyContract_.CONTRACT_NAME.info.abiDefinition);

// Desplegar el nuevo contrato
var contractInstance = MyContract.new(
    10,
    11,
    {from: myAccount, gas: 1000000}
);

在内部,构造函数的参数是在合同代码之后传递的,但是如果使用它,则不必担心 web3.js .

如果合同要创建其他合同,创建者必须知道要创建的合同的源代码(和二进制)。 这意味着创造周期性的依赖是不可能的。

pragma solidity ^0.4.0;

contract OwnedToken {
       // TokenCreator es un contrato que está definido más abajo.
       // No hay problema en referenciarlo, siempre y cuando no esté
    // siendo utilizado para crear un contrato nuevo.
    TokenCreator creator;
    address owner;
    bytes32 name;

    // Esto es el constructor que registra el creador y el nombre
    // que se le ha asignado
    function OwnedToken(bytes32 _name) {
        // Se accede a las variables de estado por su nombre
           // y no, por ejemplo, por this.owner. Eso también se aplica
        // a las funciones y, especialmente en los constructores,
        // solo está permitido llamarlas de esa manera ("internal"),
        // porque el propio contrato no existe todavía.
        owner = msg.sender;
           // Hacemos una conversión explícita de tipo, desde `address`
        // a `TokenCreator` y asumimos que el tipo del contrato que hace
        // la llamada es TokenCreator, ya que realmente no hay
        // formas de corroborar eso.
        creator = TokenCreator(msg.sender);
        name = _name;
    }

    function changeName(bytes32 newName) {
        // Solo el creador puede modificar el nombre --
           // la comparación es posible ya que los contratos
        // se pueden convertir a direcciones de forma implícita.
        if (msg.sender == address(creator))
            name = newName;
    }

    function transfer(address newOwner) {
        // Solo el creador puede transferir el token.
        if (msg.sender != owner) return;
           // También vamos a querer preguntar al creador
        // si la transferencia ha salido bien. Note que esto
           // tiene como efecto llamar a una función del contrato
           // que está definida más abajo. Si la llamada no funciona
           // (p.ej si no queda gas), la ejecución se para aquí inmediatamente.
        if (creator.isTokenTransferOK(owner, newOwner))
            owner = newOwner;
    }
}

contract TokenCreator {
    function createToken(bytes32 name)
        returns (OwnedToken tokenAddress)
    {
        // Crea un contrato para crear un nuevo Token.
        // Del lado de JavaScript, el tipo que se nos devuelve
           // simplemente es la dirección ("address"), ya que ese
           // es el tipo más cercano disponible en el ABI.
        return new OwnedToken(name);
    }

    function changeName(OwnedToken tokenAddress, bytes32 name) {
        // De nuevo, el tipo externo de "tokenAddress"
        // simplemente es "address".
        tokenAddress.changeName(name);
    }

    function isTokenTransferOK(
        address currentOwner,
        address newOwner
    ) returns (bool ok) {
           // Verifica una condición arbitraria
        address tokenAddress = msg.sender;
        return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
    }
}

可见性和获得者

由于Solidity只知道两种类型的函数调用(不产生调用EVM的内部调用(也称为“消息调用”)和外部函数调用EVM),因此有四种类型的可见性函数和状态变量。

函数可以被指定为 external , public , internal private . 默认情况下,一个函数是 public . 对于状态变量,类型 external 是不可能的,默认类型是 internal .

external :外部函数是合同界面的一部分,这意味着可以从其他合同和交易中调用它们。 外部函数 f 不能在内部调用(例如,它 f() 不起作用,但是它的 this.f() 工作原理)。 外部函数在接收大量数据时有时更有效率。

public :公共职能是合同接口的一部分,可以在内部或通过消息进行调用。 对于公共状态变量,会自动生成一个getter函数(见下文)。

internal :这些函数和状态变量只能在内部调用(即从当前的合同内或从它派生的合同),而不能被使用 this .

private :私有函数和状态变量只对已经定义的合约而不是从它派生的合约是可见的。

nota

所有外部观察者都可以看到合同中定义的所有东西。 定义一些东西 private 只会阻止其他合约访问和修改信息,但这些信息对于所有人都是可见的,即使在区块链以外也是如此。

可见性说明符设置在状态变量的类型之后,参数列表和函数的返回参数列表之间。

pragma solidity ^0.4.0;

contract C {
    function f(uint a) private returns (uint b) { return a + 1; }
    function setData(uint a) internal { data = a; }
    uint public data;
}

在以下示例中,您 D 可以调用以 c.getData() 检索 data 状态存储中 的值 ,但不能调用 f . contract E 来自于 C ,因此,你可以打电话 compute .

pragma solidity ^0.4.0;

contract C {
    uint private data;

    function f(uint a) private returns(uint b) { return a + 1; }
    function setData(uint a) { data = a; }
    function getData() public returns(uint) { return data; }
    function compute(uint a, uint b) internal returns (uint) { return a+b; }
}


contract D {
    function readData() {
        C c = new C();
        uint local = c.f(7); // error: el miembro "f" no es visible
        c.setData(3);
        local = c.getData();
        local = c.compute(3, 5); // error: el miembro "compute" no es visible
    }
}


contract E is C {
    function g() {
        C c = new C();
        uint val = compute(3, 5);  // acceso a un miembro interno (desde un contrato derivado a su contrato padre)
    }
}
Getter函数

编译器自动为所有 public 状态变量创建getter函数 . 在下面显示的合同中,编译器将生成一个调用的函数 data ,它不读取任何参数并返回 uint 状态变量的值 data . 状态变量的初始化可以在声明的时候完成。

pragma solidity ^0.4.0;

contract C {
    uint public data = 42;
}


contract Caller {
    C c = new C();
    function f() {
        uint local = c.data();
    }
}

吸气剂功能具有外部可见性。 如果符号是内部访问的(即没有 this. ),那么它被评估为一个状态变量。 如果符号是外部访问的(即带有 this. ),那么它被评估为一个函数。

pragma solidity ^0.4.0;

contract C {
    uint public data;
    function x() {
        data = 3; // acceso interno
        uint val = this.data(); // acceso externo
    }
}

下面的例子稍微复杂一些:

pragma solidity ^0.4.0;

contract Complex {
    struct Data {
        uint a;
        bytes3 b;
        mapping (uint => uint) map;
    }
    mapping (uint => mapping(bool => Data[])) public data;
}

它将以下列方式生成一个函数:

function data(uint arg1, bool arg2, uint arg3) returns (uint a, bytes3 b) {
    a = data[arg1][arg2][arg3].a;
    b = data[arg1][arg2][arg3].b;
}

请注意,结构中的映射已被省略,因为没有一个很好的方法来给键进行映射。

功能修饰符

修饰符可以用来以灵活的方式改变函数的行为。 例如,修饰符能够在执行一个函数之前自动检查一个条件。 修饰符是合约的可继承属性,可以被衍生合约覆盖。

pragma solidity ^0.4.11;

contract owned {
    function owned() { owner = msg.sender; }
    address owner;

       // Este contrato sólo define un modificador pero no lo usa, se va a utilizar en un contrato derivado.
       // El cuerpo de la función se inserta donde aparece el símbolo especial "_;" en la definición del modificador.
       // Esto significa que si el propietario llama a esta función, la función se ejecuta, pero en otros casos devolverá una excepción.
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
}


contract mortal is owned {
       // Este contrato hereda del modificador "onlyOwner" desde "owned" y lo aplica a la función "close", lo que tiene como efecto que las llamadas a "close" solamente tienen efecto si las hace el propietario registrado.
    function close() onlyOwner {
        selfdestruct(owner);
    }
}


contract priced {
    // Los modificadores pueden recibir argumentos:
    modifier costs(uint price) {
        if (msg.value >= price) {
            _;
        }
    }
}


contract Register is priced, owned {
    mapping (address => bool) registeredAddresses;
    uint price;

    function Register(uint initialPrice) { price = initialPrice; }

       // Aquí es importante facilitar también la palabra clave "payable", de lo contrario la función rechazaría automáticamente todos los ethers que le mandemos.
    function register() payable costs(price) {
        registeredAddresses[msg.sender] = true;
    }

    function changePrice(uint _price) onlyOwner {
        price = _price;
    }
}

contract Mutex {
    bool locked;
    modifier noReentrancy() {
        require(!locked);
        locked = true;
        _;
        locked = false;
    }

       /// Esta función está protegida por un mutex, lo que significa que llamadas reentrantes desde dentro del msg.sender.call no pueden llamar a f de nuevo.
       /// La declaración `return 7` asigna 7 al valor devuelto, pero aún así ejecuta la declaración `locked = false` en el modificador.
    function f() noReentrancy returns (uint) {
        require(msg.sender.call());
        return 7;
    }
}

可以通过在由空格分隔的列表中指定几个修饰符来应用相同的功能。 他们将按列表中列出的顺序进行评估。

警告

在早期版本的Solidity中, return 包含修饰符的函数 中的类型语句 表现不同。

显式地从一个修饰符或函数体返回的内容只留下当前修饰符或当前函数的主体。 返回的变量被赋值,并且流量控制在前面的修饰符中的“_”之后继续。

任意表达式被接受为修饰符参数,并且在该上下文中,从该功能可见的所有符号在修饰符中是可见的。 在修饰符中输入的符号在函数中是不可见的(因为它们可以通过覆盖来改变)。

常量状态变量

状态变量可以声明为 constant . 在这种情况下,它们必须从编译时的常量表达式中分配。 访问存储数据的blockchain(如表情 now , this.balance block.number )性能数据( msg.gas )或对外部合同呼叫禁止。 在内存分配中可能有副作用的表达式是允许的,但在其他内存对象中可能有副作用的表达式则不是。 默认功能 keccak256 , sha256 , ripemd160 , ecrecover , addmod mulmod 允许(虽然做出外部合同调用)。

在内存调度程序中允许产生间接影响,因为它必须能够构造复杂的对象,如查找表。 此功能仍不能按原样使用。

编译器不会为这些变量保存一个存储空间,并且每个事件都被其各自的常量表达式(可以被优化器编译为一个简单的值)替换。

此时,并不是所有类型的常量都被执行。 现在唯一实现的类型是值类型和文本字符串(字符串)。

pragma solidity ^0.4.0;

contract C {
    uint constant x = 32**22 + 8;
    string constant text = "abc";
    bytes32 constant myHash = keccak256("abc");
}

常量函数

在函数被声明为常量的情况下,它承诺不修改状态。

pragma solidity ^0.4.0;

contract C {
    function f(uint a, uint b) constant returns (uint) {
        return a * (b + 42);
    }
}

nota

getter方法被标记为常量。

警告

编译器仍然没有强加一个常量方法不会修改状态。

回退功能

合同可以有一个没有名字的单一功能。 这个函数不能有参数或返回任何东西。 如果在调用合同时,合同的其他功能都不符合提供的功能标识符(或者没有提供数据),则执行该功能。

另外,只要合同只收到以太(没有数据),这个功能就会被执行。 在这种情况下,通常几乎没有可用于调用函数的气体(准确地说是2300个气体),所以重要的是使后备函数尽可能便宜。

特别是,以下操作会消耗比作为回退功能的津贴更多的气体。

  • 写入存储
  • 创建一个合同
  • 调用消耗大量气体的外部函数
  • 发送Ether

请务必在部署合同之前仔细测试您的后备功能,以确保您的执行成本低于2300瓦斯。

警告

直接接收以太网的协议(没有调用一个函数,例如使用 send o transfer )但是没有定义一个回退函数,会抛出一个异常,返回以太(请注意,在版本v0之前,这是不同的。 4.0的Solidity)。 因此,如果您希望您的合同获得以太网,则必须实施回退功能。

pragma solidity ^0.4.0;

contract Test {
    // Se llama a esta función para todos los mensajes enviados a este contrato (no hay otra función). Enviar Ether a este contrato lanza una excepción, porque la función fallback no tiene el modificador "payable".
    function() { x = 1; }
    uint x;
}


// Este contrato guarda todo el Ether que se le envía sin posibilidad de recuperarlo.
contract Sink {
    function() payable { }
}


contract Caller {
    function callTest(Test test) {
        test.call(0xabcdef01); // el hash no existe
        // resulta en que test.x se vuelve == 1.

        // La siguiente llamada falla, devuelve el Ether y devuelve un error:
        test.send(2 ether);
    }
}

活动

这些事件允许方便地使用EVM的注册功能,而这又可以在监听这些事件的dapp的用户界面中“调用”JavaScript回调。

这些事件是合同的可继承成员。 当它们被调用时,它们将参数存储在事务日志中 - 区块链中的特殊数据结构。 这些记录与合同管理有关的,将被纳入blockchain而且将永远是块可访问(即永远边境宅基地,但可能与太湖改变)。 记录和事件数据不能从合同中得到(甚至不是从创建它们的合同)。

您可以对记录进行SPV测试,因此如果外部实体提供了该测试的合同,则可以验证该记录确实存在于区块链中。 也就是说,请记住必须提供块头,因为契约只读取最后的256块散列。

最多三个参数可以接收该属性 indexed ,这将使其查找各自的参数。 在用户界面中,可以通过索引参数的特定值进行过滤。

如果数组被用作索引参数(包括 string y bytes ),那么Keccak-256哈希将被保存为一个问题。

事件签名的散列是其中一个问题,除非你用说明符声明事件 anonymous . 这意味着不可能按名称过滤特定的匿名事件。

所有非索引参数将被保存在记录的数据部分。