理解Ethereum智能合约开发

April 11, 2019

[TOC]

背景知识

为什么是分布式账本?

从第一台计算机的诞生开始,地球的计算能力就一直在按照摩尔定律 (每隔两年计算能力翻倍)所指示的那样,持续地在增长(近年有放缓)。计算早已经不再是一种稀缺的资源,个人计算机的普及乃至泛滥,也说明了这一现象。

另一方面,我们处在飞速发展的信息社会,各种媒介充斥在生活里的方方面面,每个人在互联网上留下的一切痕迹,统称为数据,也是信息社会最有价值的。如果你看过《西部世界》,你就能够明白数据的力量是有多强大和危险。越来越多的人,意识到这一点,并且不希望自己的数据尤其是私密数据被企业或组织控制。

data_is_gold

这些企业包含但不局限于支付宝、微信等等独角兽企业。分布式账本技术去除了对中心化的服务或组织的依赖,通过提供计算资源,每一个计算单元或者节点(简单的说就是一台计算机)共同维护着一个数据库或者账本,数据库中的数据可以被所有人读,但只有数据的拥有人才能进行转移或者处理,甚至可以进一步通过密码学对数据加密存储,只有持有秘钥的数据拥有人才能读取和处理数据。

node_connection

数据的价值在于流动。不论在什么系统中,交易都是极为重要的,但是交易必须是可控和合理的。任何交易的促成都需要建立在一定的共识基础上。比如中心化的服务支付宝,交易成功的标识是支付宝后台数据库完成价值的转移,并返回成功消息给用户,支付宝是整个交易周期的信任基础。种种原因导致我们并不是百分百地信任支付宝。而去中心化的系统不存在这样的信任中心角色,交易的最终确定是由整个系统基于一定的共识算法完成的,这些算法包括但不局限于工作量证明、权益证明、代理权益证明等。

在分布式账本技术中,我们通过移除信任中心,实现了数据自由;通过开源代码,建立了另一个维度的信任关系。这项技术,还处在很早期的阶段,需要通过减缓交易的完成时间来提高安全性;需要通过花费大量的计算资源来达到彻底的去中心化

到底什么是智能合约?

日常生活中充满了各种各样的合约机制,比如房屋租赁合约、银行存取款合约、商务合作合约等等,这些合约的目的都只有一个,就是保证合约的参与方正常履行自己的义务

智能合约的作用也是同样的,只不过合约的形式发生了改变,不再是纸质签名,而是存储在分布式账本中的代码。

contract_in_code

为什么要选择智能合约而不是传统合约形式呢?原因我总结大致有以下几个:

  • 去中心化的合约形式,摆脱了对代理人的依赖,解决了对于代理人的信任问题。
  • 分布式账本这个跨越一切国家和地区的技术,使得自由的全球化经济更进一步发展,智能合约能够方便快速地满足多种行业全球化经济的需要。
  • 智能合约以开源代码的形式展示,使得合约更加可信、透明
  • 新的组织、文化、工作形式,如远程协作办公,开源社团,需要一个更加灵活地协作方式,这里应用智能合约恰到好处。
  • 正是由于代码运行在公共的分布式账本上,满足条件的合约可以随时执行,永不离线

合约解决了现实生活中的协作问题,智能合约则将协作的广度和深度推向了另一个高度。

以太坊和Solidity

以太坊

分布式账本和智能合约的结合,创造了一种新的协作方式。以太坊作为一个公共的分布式账本,提供了一个可复制的虚拟机环境,用来执行智能合约代码,保证了每个节点执行相同的代码和交易请求会得到相同的结果。

当应用程序的功能由智能合约来提供时,也常被认为是去中心化应用 (DAPP)。DApp前端依然是传统的HTML、CSS、JavaScript “三剑客”,通过发送请求到以太坊节点,上传数据到链上,响应链上的事件如智能合约代码的执行结果。对于DApp的后端,从传统中心化的服务器转变为链上可执行的智能合约,通过执行链上的交易请求完成用户账户或者合约账户状态的改变。

越来越多的领域采用DApp的解决方案,例如:

  • 知识产权保护
  • 金融衍生品交易
  • 去中心化的自治组织

Solidity编程语言

所有上面那些复杂的应用场景,都是用Solidity编写的智能合约来完成的。和大多数高级编程语言类似,Solidity支持面向对象的编程范式,有完善的类型系统,是一种静态编程语言,语法和Javascript很类似。你可以通过交互式编程环境熟悉Solidity的基本用法

基础数据类型

基于按值传递的特性,通常也叫以下类型为值类型

举例 解释 操作
bool true / false !, &&, ||, ==, !=
int / uint(8~256) 8 默认是256位 比较,位移,算数加减乘除、取模**,位操作
address 0x7fD0030D3D21d17Fb4056DE319faD67A853b3C20 20字节,也即160位二进制码组成,代表以太坊的地址 transfer, call
contract contract MyContract {

}
合约类,是对函数和数据的封装 new
bytes(1~32) “foo” 定长字节数组
bytes “bar” 变长字节数组
string “this is a string” UTF-8字符串

枚举类型

例如:

enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }

函数

函数可以是publicinternal的,internal函数只能在合约类内部调用,public函数可以被其他合约类调用。

可以通过pure, view, payable等关键字对函数进行约束:

  • view: 函数对于状态可读不可写
  • pure: 函数不会与外界状态发生交互,即不会读写状态
  • payable: 允许函数接收ether

    function f(uint num) public pure returns (uint) {
    return num + 1;
    }
    

引用类型

由于不同的变量名可以指向同一个引用类型的数据,使用引用类型的时候需要格外小心。常用的引用类型有:

  • 数组,如 [uint(1), 2, 3]

  • 结构体,

    struct Funder {
    address addr;
    uint amount;
    }
      
    Funder({addr: msg.sender, amount: msg.value});
    
  • 哈希,

    mapping(address => uint) public balances;
      
    balances[msg.sender] = newBalance;
    

引用类型通过关键字memory, storage,calldata来确定存储位置

条件控制

if else

function max(uint a, uint b) internal pure returns (uint) {
  if (a > b) {
  	return a;
	} else {
  	return b;
	}
}

循环控制

for, while, break, continue

function sum(uint[] numbers) internal pure returns (uint) {
  uint temp;
  for (i=0; i<numbers.length; i++) {
    temp += numbers[i];
  }
}

异常处理

Solidity使用异常来回退状态,常用的方式有assert, aequire, revert,抛出的异常不能由代码捕获处理。

require(msg.value % 2 == 0, "Even value required.");

assert(address(this).balance == balanceBeforeTransfer - msg.value / 2);

function buy(uint amount) public payable {
  if (amount > msg.value / 2 ether)
    revert("Not enough Ether provided.");
  // Alternative way to do it:
  require(
    amount <= msg.value / 2 ether,
    "Not enough Ether provided."
  );
  // Perform the purchase.
}

面向对象特性

抽象合约类:内部存在没有实现的方法。

contract Feline {
    function utterance() public returns (bytes32);
}

接口:和其他语言类似,接口内的方法不能有实现。

interface Token {
    enum TokenType { Fungible, NonFungible }
    struct Coin { string obverse; string reverse; }
    function transfer(address recipient, uint amount) external;
}

继承和多态:

contract X {}
contract A is X {}

Libraries: 请参考这里.

常用开发框架

Truffle: 基于以太坊平台的开源开发、测试、部署框架,可以一站式完成以太坊智能合约的开发。

快速练习参考官方资料

常用命令:

# install
npm install -g truffle

# create project from template
truffle unbox metacoin

# run all the test
truffle test

# compile solidity to abi json file
truffle compile

# start personal ethereum like blockchain
truffle develop

# migrate the contract on blockchain
turffle migrate

# install dependency in ethpm.json
truffle install

# publish your own package
truffle publish

# user debugger
truffle debug <transaction hash>

Ganache: 本地以太坊私有链客户端,用来本地模拟线上环境,用来本地部署、测试合约。

Drizzle:DApp前端开发框架

  • 对合约数据响应式编程,包含state,event和transaction
  • 声明式,可以减少处理无用数据时的资源浪费。
  • 封装底层接口。

应用场景练习

交易

代码源自metacoin template.

ConvertLib.sol:

pragma solidity >=0.4.25 <0.6.0;

library ConvertLib{
	function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
	{
		return amount * conversionRate;
	}
}

MetaCoin.sol

pragma solidity >=0.4.25 <0.6.0;

import "./ConvertLib.sol";

// This is just a simple example of a coin-like contract.
// It is not standards compatible and cannot be expected to talk to other
// coin/token contracts. If you want to create a standards-compliant
// token, see: https://github.com/ConsenSys/Tokens. Cheers!

contract MetaCoin {
	mapping (address => uint) balances;

	event Transfer(address indexed _from, address indexed _to, uint256 _value);

	constructor() public {
		balances[tx.origin] = 10000;
	}

	function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
		if (balances[msg.sender] < amount) return false;
		balances[msg.sender] -= amount;
		balances[receiver] += amount;
		emit Transfer(msg.sender, receiver, amount);
		return true;
	}

	function getBalanceInEth(address addr) public view returns(uint){
		return ConvertLib.convert(getBalance(addr),2);
	}

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

参考

comments powered by Disqus