初探Solidity

86 阅读6分钟

初探Solidity

基础概念

没有浮点数运算

为了弥补这个缺陷,如何表示带小数的数字呢?以太坊将自身的数切分为最小18位,我们称为wei,任何数字都是wei的整数倍

44A06DF3-DABB-4BD0-86EE-86A0726C014C

变量类型

智能合约的变量分为两种,存在区块链上的和不存在区块链上的。存在区块链上的我们称为状态变量(state variable)。这类变量将永久记录在区块链上,写入读取它们就仿佛操作一个数据库一样,修改和赋值都会造成巨额的开销。不存在于区块链上的变量则是程序中的内存变量(memory variable),程序运行完毕就从内存中释放,相对开销较小

internal/external修饰符

  • internal修饰符可以让合约继承后子合约访问该函数
  • external 修饰符让该函数只能被外部调用者调用

Solidity的函数也有修饰符,称为modifiers,这标明了函数可能对区块链状态有无修改/读取的标记。一般都会标记该值让编译器帮我们执行代码的优化

672641FC-3F0D-45C1-921E-05293A6D99FD

内置函数

  • keccak256 散列函数算法,可以根据任意长度的明文产生固定长度256位的哈希值

数据结构:Map

映射数据结构:mapping

例如我们可以存储账号地址以及它对应的合约内的token数量的关系(假设是一个代币合约)

1CC08B0E-986A-4DDA-9732-C7A7DAB23454

环境变量:msg.sender

当前合约调用者的地址

FD0D5090-B4DB-40B3-AE34-36C17DB7A106

require / assert

在合约中进行权限检查或者条件满足检查

  • require 条件检查语句如果不通过,则扣除运行到当前语句时,程序执行所话费的gas,终止程序执行,并返回
  • assert 条件检查语句如果不通过,则视为严重错误,扣除所有的gas,终止程序执行,并返回

6CFC6B7B-5EAF-4AE4-8D89-4514B8C65B9E

继承和引入

智能合约的代码可以来源于自身项目内,也可以来源于外部早已部署完毕的链上合约

CDE78801-35DE-4599-BBA1-7F536A4F5C6A

C77EB3A9-9CD3-463A-9A1A-787056410F6F

内存变量

不同的存储类型,花费的gas数额不同。它们的最终存储地方也不同。有时候为了省钱,我们会把临时变量留在内存里,而不是保存在区块链上。随着程序执行,内存里的变量会消亡,而区块链上的会永存。由于没有改变区块链状态,内存变量(memory)的花费会比状态变量(storage)的花费少很多

9047DF90-02CA-48D2-B48D-CE2B597A5BD9

接口与合约调用

合约的接口就是合约的抽象。我们可以通过定义合约接口,并指定合约地址,来调用另外一个在以太坊上早已经部署好的合约

ECC942AC-4156-445B-9542-F6379B490E37

8CDE7C2D-CB8D-41E3-833F-1446E07D5FDE

多返回值

09363FC1-2333-472A-8116-1ABDB5BF929F

Ownable控制

753431E0-28EA-4C7B-9C26-DDE534A6B4AE

Pausable控制

367FDA31-4F30-4E7F-8E41-BF0F9190CF36

时间单位表示

86C34667-D059-4B79-8AB4-6F6ABA3AAD58

这里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 => uintpublic age;
​
// 一个函数修饰
modifier olderThan(uint _age, uint _userId) {
  require(age[_userId] >= _age);
  _;
}
​
// 限制年龄大于16岁才能开车
function driveCar(uint _userIdpublic 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余额中。当智能合约所有者想提取的时候,调用一个函数就可以执行

A984B0D7-FBB5-4CA0-9FC7-DB47FB2BC256

Truffle

Truffle内置了一个默认的测试底层网络实现 Ganache。Ganache 原名 Test-RPC,是以太坊开源项目中采用JavaScript编写的模拟区块链运行的节点。它本身并不联网产生区块,而是一个测试环境中的高度仿真模拟器。它的Web3命令支持丰富,紧跟社区的最新提案和开发进度,因为没有实际挖矿(仅仅模拟挖矿行为),所以它的运行速度是惊人的,非常利于测试环境下的高速使用,不需要等实际区块链的出块时间间隔

EFD042A4-D6FD-4DD6-A83C-15F52C7E3FF8.png

命令

91F22119-838F-4434-9836-83983AAB103D

$ mkdir truffle-project
$ cd truffle-project
$ truffle unbox metacoin

2E0AF910-243F-4C8F-8013-A6376F777513

453E6230-9C76-4E60-9078-95117039817D

每个目录具体解释如下:

  • 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合约

3C20E549-C0C0-4A14-9142-6E565F46A871


\