上文介绍了区块链生态发展,我们知道以太坊的到来可以使开发人员基于区块链开发DApp,本文介绍 Solidity 编程语言的使用,然后基于 Solidity 编写一个简单的智能合约。
Solidity 是以太坊开发人员使用的编程语言,用来编写智能合约,运行在以太坊虚拟机(EVM)上。
有开发经验的同学上手应该是比较容易的,所有编程语言无非就是变量、分支、函数,变量值之所以称之为变量,是因为它是用来存储临时值,是可变化的;而分支是在检查某个事件是否为真后决定是否执行;Solidity 编程语言中函数是与区块链交互的主要方式,前期可以使用在线编程工具 remix 进行开发。
如果要开发一个去中心化的DApp,需要用 React、HTML、CSS 等前端技术将用户页面和智能合约结合。
一个 Solidity 文件后缀为 .sol,文件中包含许可声明、文件编译指示、文件导入标识以及任意数量的合约定义,包括变量、常量、构造、函数、事件、结构体、错误处理等。
由于提供源代码总是涉及版权方面的法律问题,Solidity 编译器鼓励使用机器可读的 SPDX 许可证标识符,每个源文件都应以说明其许可证的注释开头:
// SPDX-License-Identifier: MIT
如果不想指定许可证或者源代码不是开源的,可以使用特殊值 UNLICENSED
,其他值可以参考 SPDX licenses
通过 pragma
关键字指定源代码使用哪个版本的编译器进行编译,版本编译指示使用如下:
pragma solidity ^0.5.2;
意思是该源文件不能使用 0.5.2 版本之前的编译器进行编译。
声明变量的数据类型,跟很多编程语言都相似。
address
:地址类型,用来转账,区块链独有类型。代码中 address(this) 表示当前合约的地址。
string
:文本类型。
int
:数值类型,显示范围是-2255 到 2255 - 1。
uint
:无符号整数(指的正整数),最大是 2**256 -1。
bool
:布尔值,true 或 false。
bytes
:字节类型。
mapping
:映射类型,可以理解为 Map。
函数声明跟 JS 一样都是 function
,Solidity 函数格式如下:
function (
示例: function fn() public view returns (address){return address(this);}
其中 internal|external|public|private
为可见性和可变性,比较容易理解。pure|view|payable
用来控制函数权限,其中:
pure
表示该函数内只能拥有局部变量,不能读不能写外部变量,不对链上有任何的读写操作。view
表示该函数内只能查看变量、链上内容,不能修改变量、链上的内容。payable
表示该函数内可以修改变量及链上内容,通常用来给合约地址转账或着接收ETH。事件我们可以理解成通知订阅,当合约中发生某些事,比如谁给谁转账,那么可以声明一个事件,客户端(用户界面)对其监听后,可以接收到这个时间的消息从而 do something。示例:
event Deposit(address from,address to,unit amount);
function deposit(address to,unit amount) public payable {
// do something
emit Deposit(address(this), to, amount);
}
然后在 JS(Web3.js) 代码中监听这个事件,示例如下:
var abi = /* abi as generated by the compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);
var depositEvent = clientReceipt.Deposit();
depositEvent.watch(function(error, result){
if (!error)
console.log(result);
});
// Or pass a callback to start watching immediately
var depositEvent = clientReceipt.Deposit(function(error, result) {
if (!error)
console.log(result);
});
我们知道只能合约是运行在区块链中的,所以在智能合约中可以访问区块链的内置数据,比如 block 区块信息、调用合约的上下文(msg)信息。
block区块信息:
block.coinbase
(address): 当前块的矿工的地址block.difficulty
(uint):当前块的难度系数block.gaslimit
(uint):当前块gas的上限block.number
(uint):当前块编号block.blockhash
(function(uint) returns (bytes32)):函数,返回指定块的哈希值,已经被内建函数blockhash所代替block.timestamp
(uint):当前块的时间戳msg调用上下文信息:
msg.data
(bytes):完整的calldatamsg.gas
(uint):剩余的gas量msg.sender
(address):消息的发送方(调用者)msg.sig
(bytes4):calldata的前四个字节(即函数标识符)msg.value
(uint):所发送的消息中wei的数量// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
address public minter;
mapping (address => uint) public balances;
// 声明一个合约事件,在函数send的最后一行触发,使得客户端监听在区块链上发出的这些事件。一旦它发出,侦听器就会收到from, to 和 amount 参数
event Sent(address from, address to, uint amount);
// 创建合约时执行,仅一次。
constructor() {
minter = msg.sender;
}
// 将一定数量的ETH发送到该地址
// 只能由合同创建者调用
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
balances[receiver] += amount;
}
// 声明一个错误类型可以提供详细的信息
error InsufficientBalance(uint requested, uint available);
// 调用者向接收者转账
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender])
# revert语句无条件中止对链上数据的更改,同时使用 InsufficientBalance 错误向发送者提供错误详细信息。
revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}