Solidity 0.5.8官方文档

智能合约简介
实例 - 简单存储合约 实例 - 简单子货币合约 区块链基础知识
以太坊虚拟机简介
EVM - 账户 EVM - 交易 EVM - Gas EVM - 内存/存储/栈 EVM - 指令集 EVM - 消息调用 EVM - 委托调用/调用代码/库 EVM - 日志 EVM - 创建其他合约 EVM - 合约停用与自毁
安装Solidity编译器
Solidity编译器版本概述 使用在线Solidity IDE - Remix 使用npm安装Solidity编译器 使用Solidity编译器Docker镜像 安装预构建Solidity编译器 使用源代码构建Solidity编译器
利用实例学习Solidity
实例 - 投票合约 实例 - 盲拍合约 实例 - 安全远程购买合约 实例 - 微支付通道 实例 - 模块化合约
Solidity源文件布局
Pragma - Solidity编译控制指令 导入其他Solidity文件 在Solidity中使用注释
Solidity智能合约的结构
合约 - 状态变量 合约 - 函数 合约 - 函数修饰符 合约 - 事件 合约 - 结构类型 合约 - 枚举类型
Solidity值数据类型
布尔类型 整型 定长浮点数 地址类型 定长字节数组 变长字节数组 地址字面量 数值字面量 字符串字面量 十六进制字面量 枚举类型 数类型
Solidity其他数据类型
引用类型 映射类型 LValue相关的操作符 基本类型之间的转换 字面量类型推断
Solidity内置的计量单位及全局变量
以太币计量单位 时间单位 特殊变量与特殊函数
Solidity表达式和逻辑控制结构
逻辑控制结构 函数调用 使用new创建合约实例 表达式执行顺序 赋值 变量声明与作用域
Solidity智能合约
创建合约 可视性与getter函数 函数修饰符 状态常量 函数参数与返回值 只读函数 纯函数 回退函数 函数重载 使用日志 合约的构造函数 基类构造函数的参数 多重继承与线性化 同名不同类型成员的继承 抽象合约 接口 using for指令
Solidity汇编
内联汇编 - 语法 内联汇编 - 示例 内联汇编 - 操作符 内联汇编 - 字面量 内联汇编 - 函数式风格 内联汇编 - 访问外部变量/函数/库 内联汇编 - 标签 内联汇编 - 声明本地变量 内联汇编 - 赋值 内联汇编 - 条件语句 内联汇编 - 分支语句 内联汇编 - 循环语句 内联汇编 - 函数 使用内联汇编需要避免的错误 Solidity中的惯例 独立汇编的语法
Solidity其他问题
存储中的状态变量布局 内存中的布局 调用数据的布局 变量清理的内部机制 优化器的内部机制 源代码映射 技巧及小窍门
Solidity速查表
操作符优先级 全局变量 函数可见性声明符 修饰符 保留关键字 Solidity语法
NatSpec格式
NatSpec文档示例 使用NatSpec标签 NatSpec文档输出
Solidity安全陷阱
安全陷阱 - 私有信息与随机性 安全陷阱 - 重入问题 安全陷阱 - Gas限量与循环 安全陷阱 - 以太币的发送与接收 安全陷阱 - 调用栈的深度 安全陷阱 - 谨慎使用tx.origin 安全陷阱 - 2的补数/下界溢出/上界溢出 安全相关的小细节
Solidity安全推荐做法
安全推荐 - 认真对待警告信息 安全推荐 - 限定以太币的数量 安全推荐 - 保持合约的简洁与模块化 安全推荐 - 使用检查/生效/交互模式 安全推荐 - 引入故障安全运行模式 安全推荐 - 提请他人审核代码
Solidity安全的正式验证
安全验证 - 抽象与误报 安全验证 - 引用类型与别名
Solidity资源
常规资源 支持Solidity集成的在线服务 Solidity工具 第三方Solidity解析器和语法器
使用Solidity编译器
使用命令行编译器 设置目标EVM版本 编译器输入/输出JSON格式说明
Solidity合约元数据
在合约字节码中嵌入元数据 使用元数据自动生成接口及NatSpec文档 使用元数据进行源代码验证
Solidity合约ABI规范
ABI - 基本设计 ABI - 函数选择符 ABI - 参数编码 ABI - 数据类型 ABI - 编码设计条件 ABI - 编码的形式化约定 ABI - 函数选择符与参数编码 ABI - 示例 ABI - 使用动态类型 ABI - JSON格式 ABI - 严格编码格式 ABI - 非标压缩格式 ABI - 有索引事件参数的编码
Yul规范
Yul - 语法的局限性 Yul - 作用域规则 Yul - 形式化定义 Yul - 类型转换函数 Yul - 底层函数 Yul - 后端 Yul - 对象规范
Solidity风格指南
风格指南简介 代码布局指南 代码布局的顺序 命名惯例 NatSpec风格
Solidity常见模式
模式 - 从合约提款 模式 - 访问限制 模式 - 状态机
在线工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器

示例 - 子货币合约

下面的合约实现了一个最简单的加密货币。这里,币确实可以无中生有地产生,但是 只有创建合约的人才能做到(实现一个不同的发行计划也不难)。而且,任何人都可 以给其他人转币,不需要注册用户名和密码 —— 所需要的只是以太坊密钥对。

pragma solidity ^0.4.21;

contract Coin {
    // 关键字“public”让这些变量可以从外部读取
    address public minter;
    mapping (address => uint) public balances;

    // 轻客户端可以通过事件针对变化作出高效的反应
    event Sent(address from, address to, uint amount);

    // 这是构造函数,只有当合约创建时运行
    function Coin() public {
        minter = msg.sender;
    }

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

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

这个合约引入了一些新的概念,让我们逐一解读。

address public minter; 这一行声明了一个可以被公开访问的 address 类型的状态变量。 address 类型是一个160位的值,且不允许任何算数操作。这种类型适合存储合约地址或外部人员的密钥对。 关键字 public 自动生成一个函数,允许你在这个合约之外访问这个状态变量的当前值。如果没有这个关键字, 其他的合约没有办法访问这个变量。由编译器生成的函数的代码大致如下所示:

function minter() returns (address) { return minter; } 当然,加一个和上面完全一样的函数是行不通的,因为我们会有同名的一个函数和一个变量, 这里,主要是希望你能明白——编译器已经帮你实现了。

下一行, mapping (address => uint) public balances; 也创建一个公共状态变量, 但它是一个更复杂的数据类型。 该类型将address映射为无符号整数。 Mappings 可以 看作是一个 哈希表 它会执行虚拟初始化,以使所有可能存在的键都映射到一个字节表示 为全零的值。 但是,这种类比并不太恰当,因为它既不能获得映射的所有键的列表, 也不能获得所有值的列表。 因此,要么记住你添加到mapping中的数据(使用列表或 更高级的数据类型会更好),要么在不需要键列表或值列表的上下文中使用它,就如本例。 而由 public 关键字创建的getter函数 getter function 则是更复杂一些的情况, 它大致如下所示:

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

正如你所看到的,你可以通过该函数轻松地查询到账户的余额。

event Sent(address from, address to, uint amount);这行声明了一个所谓的“事件(event)”,它会在 send 函数的最后一行被发出。用户界面(当然也包括服务器应用程序)可以监听区块链上正在发送的事件,而不会花费太多成本。一旦它被发出,监听该事件的listener都将收到通知。而所有的事件都包含了 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 和 block ) 是一个神奇的全局变量,其中包含一些允许访问区块链的属性。 msg.sender 始终是当前(外部)函数调用的来源地址。

最后,真正被用户或其他合约所调用的,以完成本合约功能的方法是 mint 和 send。 如果 mint 被合约创建者外的其他人调用则什么也不会发生。 另一方面, send 函数可被任何人用于向他人发送币 (当然,前提是发送者拥有这些币)。记住,如果你使用合约发送币给一个地址,当你在区块链浏览器上查看该地址时是看不到任何相关信息的。因为,实际上你发送币和更改余额的信息仅仅存储在特定合约的数据存储器中。通过使用事件,你可以非常简单地为你的新币创建一个“区块链浏览器”来追踪交易和余额。