转载微信订阅号(区块链之录)文章:
“本章主要介绍DAPP合约开发基础,solidity基本语法、开发、编译和运行环境,以及合约调试,为forever bird合约开发做准备”
Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言,运行在太坊虚拟机(EVM)上。下面我们对solidity相关内容进行讲解。
01
—
配置solidity开发工具
很多编辑器都支持solidity开发,例如:SublimeText、Atom、Visual Studio Code等。可参考如下路径:
http://solidity-cn.readthedocs.io/zh/develop/index.html
我们编写合约选择使用Visual Studio Code,安装Solidity插件可以提供语法高亮、关键字提示、合约编译等功能。
详细配置方法请参考一下路径:
http://juan.blanco.ws/solidity-contracts-in-visual-studio-code/
02
—
运行环境
智能合约是运行在以太链的虚拟机上面,合约编译通过之后首先部署到以太链,可以使用testrpc或者geth搭建以太坊私链,然后使用以太坊钱包(Ethereum Wallet)、trffule框架或者remix进行部署到私链。
Ethereum Wallet下载路径:
https://github.com/ethereum/mist/releases
trffule参考文档:
http://truffle.tryblockchain.org
Remix参考文档:
https://github.com/ethereum/remix
Ethereum Wallet提供了友好的界面,可以通过界面完成部署合约、执行合约、转账等操作。Trffule提供了nodejs的程序开发框架,内置的智能合约编译、链接、部署等,简化合约的开发流程。remix是基于浏览器的 IDE,集成了编译器和 Solidity运行时环境,可以使用内置的JavaScript VM作为运行环境,也使用当前浏览器的web3 Provider或自定义web3 Provider连接其他私链的node。
我们现在选择使用remix,使用其内置的JavaScript VM作为运行环境。remix除了提供运行环境,还集成了合约的调试功能,可以追溯合约执行逻辑、跟踪合约状态、查看内存数据等,可以方便检测和验证合约的正确性。
03
—
solidity语法简介
智能合约是存储于以太链的代码和数据,每个部署以太坊的合约都有一个特定的地址,可以通过合约地址调用代码函数更新合约中数据。关于solidity的详细介绍,请参考文档:
http://solidity-cn.readthedocs.io/zh/develop/index.html
源文件结构
版本标注
合约开头要有版本标注,如下所示:
pragmasolidity^0.4.11;
该标注说明源文件只允许编译器版本[0.4.11,0.5.0)
合约声明的关键字contract,例如:
contractSimple{...}
注释
可以使用单行注释(//)和多行注释(/*...*/)
状态变量
永久存储在合约存储中的值。
contractSimple{ uintstoredData;// 状态变量 ... }
Solidity常见类型:
bool:可能的取值为字面常数值 true和 false。
int / uint :分别表示有符号和无符号的不同位数的整型变量。支持关键字 uint8 到 uint256(无符号,从 8 位到 256 位)以及 int8 到 int256,以 8 位为步长递增。 uint 和 int 分别是 uint256 和 int256 的别名。
address:地址类型存储一个 20 字节的值(以太坊地址的大小)。地址类型也有成员变量,并作为所有合约的基础。
地址类型成员变量(如 balance、transfer)请参考如下文档:
http://solidity-cn.readthedocs.io/zh/develop/units-and-global-variables.html#address-related
bytes1, bytes2, bytes3, ..., bytes32,标识定长字节数组。
bytes 标识变长数组
string 标识变长UTF-8编码字符串类型。
enum 枚举类型
enum ActionChoices { Red, Blue}
函数类型
function()
{internal|external}
[pure|constant|view|payable]
[returns ()]
数据位置
所有的复杂类型,即数组和结构类型,都有一个额外属性:数据位置,说明数据是保存在内存memory中还是存储storage 中。
强制指定的数据位置:
外部函数的参数(不包括返回参数):calldata
状态变量:storage
默认数据位置:
函数参数(包括返回参数):memory
所有其它局部变量: storage
存储和内存之间两两赋值,或存储向状态变量赋值都会创建一份独立的拷贝。 状态变量向局部变量赋值时仅传递一个引用,而且这个引用总是指向状态变量,因此后者改变的同时前者也会发生改变。
关于存储的详细内容请参考一下文档:
http://solidity-cn.readthedocs.io/zh/develop/introduction-to-smart-contracts.html#index-10
结构体 形式如下:
structFunder{addressaddr;uintamount;}
映射
映射类型在声明时的形式为 mapping(key=>value)。 其中key可以是除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。value可以是包括映射类型在内的任何类型。
以上是对solidity的简介,关于solidity深入理解,请参考以下文档:
http://solidity-cn.readthedocs.io/zh/develop/solidity-in-depth.html
04
—
solidity编写和运行
我们首先编写一个简单的合约,用来记录用户账本,创建一个结构体User,User.addr标识用户地址,User.count标识用户存款次数,User.amount标识用户当前金额。add函数用户存款,get函数用户查看存款次数和金额。合约代码如下:
pragma solidity ^0.4.11;
contract Sample{
struct User {
address addr;
int64 count;
int64 amount;
}
mapping(address=>User) users;
function add(int64 amount) external
returns (bool) {
if(users[msg.sender].addr == 0) {
users[msg.sender] = User({
addr:msg.sender,
count:0,
amount:0
});
}
users[msg.sender].count += 1;
if(amount < 0) {
return false;
}
users[msg.sender].amount += amount;
return true;
}
function get() public view
returns (int64, int64) {
return (users[msg.sender].count,users[msg.sender].amount);
}
}
我们把智能合约代码贴到remix代码区,如下图:

勾选"Auto compile",如果内容发生改变编译器会立刻编译,如下图,我们去掉一个分号,右边会立刻显示合约错误。

点击“Details”按钮,我们可以查看合约的详细内容,比如ABI、汇编代码等。我们查看编译没有问题,这时候就可以部署运行,切换Tab "Run",如下图所示。

运行环境默认选择当前浏览器提供的web3 provider,因为我的浏览器安装了Metamask钱包,并且连接了rinkeby测试网络,所以图中Environment显示的是Metamask提供的web3 provider。我们现在切换环境为remix内置的JS VM,会创建一些测试地址,每个地址分配100eth,切换之后如下图所示:

切换之后我们就点击按钮"deploy"发布合约,发布合约会消耗gas,所以我们的账户ether会变化,合约发布成功之后会展示到“Depolyed Contracts” 区域,合约源码下方的console区域会展示交易凭证(transaction receipt)信息,还可以点击“Debug”调试追溯本次交易,如下图:

展开合约函数,我们依次调用add和get函数,如下图所示。

执行add函数,就会给本地址账户添加金额111,交易执行打包完成之后,合约的状态存储区数据就发生了改变,我们点击get的debug,查看当前状态数据,如下图:

这时我们的get函数只能查看自己的金额,如果想要查看其他用户金额,我们再添加一个函数getAmount函数,如下:
function getAmount(address addr) public view
returns int64
{
return users[addr].count;
}
我们就可以调用getAmount获取其他用户的账户金额。
添加该函数之后就存在一个问题,所有人都可以查看其他账户的金额,显然这是不合理的,需要添加一个限制:必须是合约的创建者才可以调用该函数,该如何实现?
方法一:我们可以在每个需要限制的函数开头加入地址判断,如果调用者不是合约创建者则退出。
方法二:我们可以利用函数修饰器,在每个函数执行前检查是否符合执行条件。函数修饰器的参考资料如下:
http://solidity-cn.readthedocs.io/zh/develop/contracts.html#modifiers
我们使用函数修饰器,首先定义一个contract owned,智能合约的部署会调用合约的构造函数constructor,构造函数的调用者就是合约的拥有者,函数使用中使用了require,如果不符合条件该函数会抛出异常,还原合约状态,owned代码如下所示:
contract owned {
constructor() public { owner = msg.sender; }
address owner;
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
其次,Sample继承owned,如下所示:
contract Sample is owned{...}
最后,在要限制的函数定义上面加上修饰器,如下所示:
function getAmount(address addr) public view onlyOwner
returns (int64, int64) {
return (users[addr].count,users[msg.sender].amount);
}
修改完成之后我们重新部署合约进行测试,
(1)我们选择0xca3...账户进行部署,然后对本账号执行add函数,如下图:
(2)利用debug查看当前合约状态,如下图:
(3)切换账户0x147...,如下图:
(3)调用getAmount函数获取0xca3...的金额。如下图:

可以通过console区看到,执行getAmount的时候抛出了异常。这是因为当前的账户非owner,所以require抛出了异常,关于异常的相关文档请参考:
http://solidity-cn.readthedocs.io/zh/develop/control-structures.html#assert-require-revert-and-exceptions
(4)使用0x147...账户,调用add函数,添加金额123。如图:

(5)我们切换到owner账户0xca3...,然后查看0x147...的金额。如图:
以上步骤验证了函数修饰器的作用,我们利用这个简单的账户存款合约,给大家演示了合约语法、编写、编译、部署和运行过程,本章就先简单介绍这里,如果有什么疑问请留言。
下一章讲解智能合约常见问题和调试,敬请关注!
喜欢区块链之录,请长按二维码加入我们
一起奋斗成长
网友评论