初探Solidity
基础概念
没有浮点数运算
为了弥补这个缺陷,如何表示带小数的数字呢?以太坊将自身的数切分为最小18位,我们称为wei,任何数字都是wei的整数倍
变量类型
智能合约的变量分为两种,存在区块链上的和不存在区块链上的。存在区块链上的我们称为状态变量(state variable)。这类变量将永久记录在区块链上,写入读取它们就仿佛操作一个数据库一样,修改和赋值都会造成巨额的开销。不存在于区块链上的变量则是程序中的内存变量(memory variable),程序运行完毕就从内存中释放,相对开销较小
internal/external修饰符
- internal修饰符可以让合约继承后子合约访问该函数
- external 修饰符让该函数只能被外部调用者调用
Solidity的函数也有修饰符,称为modifiers,这标明了函数可能对区块链状态有无修改/读取的标记。一般都会标记该值让编译器帮我们执行代码的优化
内置函数
- keccak256 散列函数算法,可以根据任意长度的明文产生固定长度256位的哈希值
数据结构:Map
映射数据结构:mapping
例如我们可以存储账号地址以及它对应的合约内的token数量的关系(假设是一个代币合约)
环境变量:msg.sender
当前合约调用者的地址
require / assert
在合约中进行权限检查或者条件满足检查
- require 条件检查语句如果不通过,则扣除运行到当前语句时,程序执行所话费的gas,终止程序执行,并返回
- assert 条件检查语句如果不通过,则视为严重错误,扣除所有的gas,终止程序执行,并返回
继承和引入
智能合约的代码可以来源于自身项目内,也可以来源于外部早已部署完毕的链上合约
内存变量
不同的存储类型,花费的gas数额不同。它们的最终存储地方也不同。有时候为了省钱,我们会把临时变量留在内存里,而不是保存在区块链上。随着程序执行,内存里的变量会消亡,而区块链上的会永存。由于没有改变区块链状态,内存变量(memory)的花费会比状态变量(storage)的花费少很多
接口与合约调用
合约的接口就是合约的抽象。我们可以通过定义合约接口,并指定合约地址,来调用另外一个在以太坊上早已经部署好的合约
多返回值
Ownable控制
Pausable控制
时间单位表示
这里now和5 minutes都属于时间表示,我们甚至可以直接将时间单位赋值给uint类型的变量。以下的语句是完全合法的。
uint lastUpdated = now;
uint one_day = 1 days;
uint five_minutes = 5 minutes;
uint now_time = now;
uint two_hours = 2 hours;
带参数的函数修饰符
// ID 和年龄的映射
mapping (uint => uint) public age;
// 一个函数修饰
modifier olderThan(uint _age, uint _userId) {
require(age[_userId] >= _age);
_;
}
// 限制年龄大于16岁才能开车
function driveCar(uint _userId) public olderThan(16, _userId) {
}
for循环
环在智能合约中使用范围不是很明显。因为循环消耗大量的计算步骤,智能合约编纂者都尽量减少循环次数,甚至从根本上避免循环的产生
function getOdds() pure external returns(uint[]) {
uint[] memory odds = new uint[](5);
uint counter = 0;
for (uint i = 1; i <= 10; i++) {
if (i % 2 != 0) {
odds[counter] = i;
counter++;
}
}
return odds;
}
合约收款:payable修饰符
contract OnlineStore {
function buySomething() external payable {
// 查看付款金额
require(msg.value == 0.001 ether);
// 将对应金额的物品转移给调用方
transferSomething(msg.sender);
}
}
上面多了一个关键字payable和两个环境变量:msg.value和msg.sender。payable表示调用者可以在调用该方法时附上想发送的以太币数量;msg.value则表示实际使用中调用者支付的以太币,这里要和gas费用相区别,gas费用是付给矿工来执行合约的,而 msg.value则是直接付给智能合约的费用。在智能合约收到以太币后,可以直接调用其他函数进行等价交换。这种函数经常在ICO的代币发行中使用,让广大用户可以直接通过合约交换以太坊为代币,无需人工干预,公平、公正、公开
支付费用:transfer方法
智能合约的作者往往能获得报酬。怎么获得呢?就是通过用户不断向智能合约打入以太币,并积累在智能合约的balance余额中。当智能合约所有者想提取的时候,调用一个函数就可以执行
Truffle
Truffle内置了一个默认的测试底层网络实现 Ganache。Ganache 原名 Test-RPC,是以太坊开源项目中采用JavaScript编写的模拟区块链运行的节点。它本身并不联网产生区块,而是一个测试环境中的高度仿真模拟器。它的Web3命令支持丰富,紧跟社区的最新提案和开发进度,因为没有实际挖矿(仅仅模拟挖矿行为),所以它的运行速度是惊人的,非常利于测试环境下的高速使用,不需要等实际区块链的出块时间间隔
命令
$ mkdir truffle-project
$ cd truffle-project
$ truffle unbox metacoin
每个目录具体解释如下:
- contracts目录包含了主要的合约代码,示例中包含了MetaCoin.sol(主要合约),ConvertLib.sol (库文件)和部署时记录地址的合约 Migration.sol
- migrations目录包含了两个部署文件,先会部署Migration.sol, 再会链接ConvertLib.sol与MetaCoin.sol部署到链上,两者顺序不可颠倒。代码中aritfacts.require()函数相当于node.js中的require()函数,指明了我们希望引用哪个合约的抽象。这个引用名必须与合约中的类名相同
- test目录包含了两种不同的测试文件,智能合约可以通过sol结尾的合约进行测试写作,也可以通过js文件写作,两者功能几乎一致,笔者在开发组多时,考虑到语言的通用性与灵活程度,平时一直使用js的格式书写测试。Truffle的测试都是Mocha测试框架格式的语法
- truffle-config.js和truffle.js都保存了控制truffle行为定义的全局变量,当想调换测试网络时,可以编辑truffle.js指定网络和端口
ERC20合约
\