浅入浅出智能合约 - 概述(一)

智能合约(Smart Contract)是时下非常热门的概念,但是它在 20 多年前就已经被非著名计算机科学家 Nick Szabo 提出了,它将智能合约描述为一种以信息化方式传播、验证或者执行合约的计算机协议,能够允许在没有第三方的情况下进行可信的交易,并且这些交易是无法被追踪、同时也是不可逆的。

A smart contract is a computer protocol intended to digitally facilitate, verify, or enforce the negotiation or performance of a contract. Smart contracts allow the performance of credible transactions without third parties. These transactions are trackable and irreversible.

smart-contract

作为区块链鼻祖的 Bitcoin 虽然通过 POW 实现了 分布式一致性,同时使用 UTXO 模型 存储和管理底层数据结构,实现了去中心化的分布式账本,并且在一定程度上实现了『可编程』这一特点,但是它的脚本机制非常简单,只是一个基于堆栈式的脚本语言,它不仅没有函数的功能,同时也不是图灵完备的,无法实现复杂的逻辑。

Ethereum 在今天一般被视为区块链 2.0 项目,与 Bitcoin 不同,Ethereum 平台实现了图灵完备的编程语言,这样我们就能够在 Ethereum 上编写和部署智能合约,利用 Ethereum 支持的编程语言 Solidity 以及 API 实现一些复杂的功能。

Solidity

在 Ethereum 上提到编写智能合约时,很难离开 Solidity 这门编程语言。想要系统、详细地学习 Solidity 建议直接阅读相关的 官方文档,文章并不会对详细展开介绍如何编写智能合约,而是会重点介绍这门编程语言是如何设计的。

solidity

虽然目前很多的基础链都使用 Solidity 作为平台支持的编程语言,但是也有一些基础链,例如 EOS 提供了 C++ 的 API 用于编写智能合约,这只是不同的平台基于不同目的之后做出的选择和权衡。不过在这篇文章中,我们会以 Ethereum 上的 Solidity 为例展开介绍。

组成

Solidity 是一门面向合约并且图灵完备的编程语言,这门编程语言总共包含四种不同的重要元素,ContractVariableFunctionEvent

solidity-composition

其中合约(Contract)是 Solidity 中的核心概念,我们都知道 Ethereum 遵循 账户余额模型 实现底层的逻辑和设计数据结构,其中的账户总共分为两种,一种是被私钥控制的外部账户,这与 Bitcoin 中的地址非常相似,另一种就是被合约代码控制的账户,这些部署在 Ethereum 合约中的代码都使用 Solidity 进行编写,最终部署到整个网络中。

每一个合约账户中的代码都是一个 Contract,它与面向对象编程中类的概念非常类似,无论是合约还是类都可以有变量和函数,但是类是可以实例化的,合约并没有实例化这一功能,它的变量和函数可以直接在合约本身上访问或者调用。

solidity-contract

变量、函数和事件其实都是属于某一个合约的,这些元素共同组成了一个合约的全部内容,其中变量和函数的功能相信也不需要过多解释,稍微有一些编程经验的人都会知道它们的作用,而事件就是面向合约编程语言中比较特殊的元素了。

事件

在区块链应用出现之前,绝大多数的代码都是运行在一个实例或者节点中的,如果一个服务想要在发生某些事件时对外界发出通知,往往都需要通过向消息队列中发送消息,订阅者可以订阅对应的主题并进行处理。

message-queue

Ethereum 中对 Pub/Sub 的设计就是围绕事件进行的,事件是面向合约编程范式中语言层面就支持的元素,在我们使用其他编程语言,例如 C++、Golang 时,我们也可以在语言内部通过已有的元素定义一些事件,并向一些事件的相关方发出通知,由于合约需要保持逻辑尽可能简单、减少计算量消耗的特点,很多操作其实并不适合在链上直接执行,所以合约就在语言层面支持了事件,能够在期望的事件发生时直接通知相关方进行处理,不需要合约的开发者重复实现相同的逻辑。

在如下的合约中,我们定义了一个新的事件 Deposit

pragma solidity ^0.4.0;

contract ClientReceipt {
    event Deposit(
        address indexed _from,
        bytes32 indexed _id,
        uint _value
    );

    function deposit(bytes32 _id) public payable {
        emit Deposit(msg.sender, _id, msg.value);
    }
}

当交易调用合约 ClientReceipt 中的 deposit 函数时,合约就会发出 Deposit 事件,这些事件就可以被 Javascript 的 API 检测到:

var abi = /* abi as generated by the compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);

var event = clientReceipt.Deposit(function(error, result) {
    if (!error)
        console.log(result);
});

当事件被合约中的函数触发时,事件中的参数就会作为一种特殊的数据结构存储在交易日志中,但是这些日志是无法在智能合约内部访问的。在 Ethereum 由事件组成的 Pub/Sub 模型中,合约本身就像是消息队列,所有调用合约并触发事件的交易(或消息)就是生产者,监听事件的 Javascript API 可以理解为消费者。

ethereum-events

ERC20 合约

Ethereum 中最常见的合约应该就是遵循 ERC20 接口的合约了,ERC20 合约的实现现在也非常成熟,在 Ethereum 发行一个新的 Token 的成本可能不到几十块钱。

erc20-interface

在这里,我们可以简单看一下 ERC20 协议的一个简单实现:


contract EIP20 is EIP20Interface {
    mapping (address => uint256) public balances;
    string public name;
    uint8 public decimals;
    string public symbol;

    function EIP20(uint256 _initialAmount, string, uint8 _decimalUnits, string _tokenSymbol) public {
        balances[msg.sender] = _initialAmount;
        totalSupply = _initialAmount;
        name = _tokenName;
        decimals = _decimalUnits;
        symbol = _tokenSymbol;
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        Transfer(msg.sender, _to, _value);
        return true;
    }
    
    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }

    // ...
}

在这里省略了非常多的 ERC20 接口中规定的函数,我们就来看一下两个最简单、常用的函数 balanceOf 接受一个 address 参数就会从合约里存储余额的变量 balances 地址对应的余额,转账函数的实现也非常简单,通过 require 保证当前交易的发出者有足够的余额,然后在 balances 减去发出者的 value 增加接受者的 value,最后发出 Transfer 事件并返回。

总结

作为面向合约的编程语言 Solidity 的功能非常简单,但是其中的很多概念都是按照区块链网络的特点设计的,对于有经验的开发者来说,学习和编写 Solidity 并不会是一件特别困难和复杂的事情。

需要注意的是作为区块链上合约或者说 DApp,它一旦部署就无法像其他应用一样更新和升级,所以合约一旦出现 bug 就是非常严重的问题,在编写合约期间一定要认真考虑其中的漏洞,避免编写复杂的代码和逻辑,重要的合约一定要通过形式验证保证程序中不存在缺陷才可以部署发布。

Reference

关于图片和转载

知识共享许可协议
本作品采用知识共享署名 4.0 国际许可协议进行许可。 转载时请注明原文链接,图片在使用时请保留图片中的全部内容,可适当缩放并在引用处附上图片所在的文章链接,图片使用 Sketch 进行绘制。

关于评论和留言

如果对本文 浅入浅出智能合约 - 概述(一) 的内容有疑问,请在下面的评论系统中留言,谢谢。

原文链接:浅入浅出智能合约 - 概述(一) · 面向信仰编程

Follow: Draveness · GitHub

Draveness

Rails / Elixir / iOS

Beijing, China draveness.me