第三章:以太坊智能合约单位、表达式、全局变量及函数、控制结构
第三章:以太坊智能合约单位、表达式、全局变量及函数、控制结构
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
Solidity 中的单位
货币单位(Ether Unit)
一个数字常量(字面量) 后面跟随一个后缀 wei, finney、 szabo 或 ether, 这个后缀就是货币单位。
• 1 ether == 10^3 finney == 1000 finney
• 1 ether == 10^6 szabo
• 1 ether == 10^18 wei
时间单位( Time Unit)
seconds、minutes、hours、days、weeks、years
由于无法预测闰秒,所以必须由外部的预言 Coracle)来更新从而得到一个精确的日历库。
如果在合约中需要使用年、月、日等时间单位,则可以引入 DateTime 库。 比如可以通过时间戳解析出对应的年、月、日信息,DateTime 库的 GitHub 地址为 https://github.com/pipermerriam/ethereum-datetime。
Solidity 全局变量及函数
区块和交易的属性
Solidity 中有一些全局变量用来提供区块(或链)当前的信息。
- blockhash (uint blockNumber) returns (bytes32):返回给定区块号的哈希值,只支持最近的 256 个区块且不包含当前区块。 在 Solidity 0.4.22 之前这个属性是 block.Blockhash(uint blockNumber)。
- block.coinbase (address): 当前块矿工的地址,括号中表示返回值的类型。
- block.difficulty (uint): 当前块的难度。
- block.gaslimit (uint): 当前块的 gaslimit。
- block.number (uint): 当前区块的块号。
- block.timestamp (uint): 当前块的 Unix 时间戳(从 1970/1/1 00:00:00 UTC 开始所经过的秒数)。
- gasleft() (uint256): 获取剩余 gas。
- msg.data (bytes): 完整地调用数(calldata)。
- msg.gas (uint): 当前还剩的 gas。
- msg.sender (address): 当前调用发起人的地址。
- msg.sig (bytes4): 调用数据(calldata)的前四个字节(例如,函数标识 符)。
- msg.value (uint): 这个消息所附带的以太币, 单位为 wei。
- now (uint): 当前块的时间戳(block.timestamp 的别名)。
- tx.gasprice (uint): 交易的 gas 价格。
- tx.origin (address): 交易的发送者(全调用链)。
**msg 的所有成员值,如 msg.sender、 msg.value 的值可以因为每一次外部函数调用或库函数调用发生变化(因为 msg 就是和调用相关的全局变量)。对于同一个链上连续的区块来说, 当前区块的时间戳会大于上一个区块的时间戳。 为了可扩展性的原因,只能查最近的 256 个块, 其他的将返回 0。
**
ABI 编码函数
- abi.encode(…) returns (bytes): 计算参数的 ABI 编码。
- abi.encodePacked(…) returns (bytes): 计算参数的紧密打包编码。
- abi. encodeWithSelector(bytes4 selector, … )returns (bytes): 计算函数选择器和参数的 ABI 编码。
- abi.encodeWithSignature(string signature, … ) returns (bytes): 等价于abi.encodeWithSelector(bytes4(keccak256(signature ), …)。
错误处理函数
- assert (bool condition): 用于判断内部错误, 条件不满足时抛出异常。
- require (bool condition): 用于判断输入或外部组件错误,条件不满足 时抛出异常。
- require(bool condition, string message): 同上, 只是多提供了一个错误信息。
- revert():终止执行并还原改变的状态。
- revert(string reason): 同上,只是多提供了一个错误信息。
数学及加密功能
Solidity 提供的有数学与加密功能的函数有:
- addmod(uint x, uint y, uint k) returns (uint): 计算(x + y) % k,加法支持任 意的精度且不会在2**256 处溢出,从 Solidity 0.5.0版本开始断言 k != 0。
- mulmod(uint x, uint y, uint k) returns (uint): 计算(x * y) % k,乘法支持任意的精度且不会在 2**256 处溢出,从 Solidity 0.5.0 版本开始断言 k!= 0。
- keccak256(…) returns (bytes32): 使用 Keccak-256 计算 hash 值,为紧密打包参数。
- sha256( … ) returns (bytes32): 使用 SHA-256 计算 hash 值,为紧密打包参数。
- sha3( … ) returns (bytes32): keccak256 的别名。
- ripemd160( … ) returns (bytes20): 使用 RJPEMD-160 计算 hash 值,为紧密打包参数。
- ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address): 通过椭圆曲线签名来恢复与公钥关联的地址,或者在错误时返回零。 可用于签名数据的校验,如果返回结果是签名者的公匙地址,那么说明数据是正确的。
ecrecover 函数需要四个参数,需要被签名数据的晗希结果值, r、 5、 v 分别来 自签名结果串。
r= signature[0:64]
s= signature[64:128]
v= signature[128:130] 其中 v 取出来的值或者是 00 或者是 01。 要使用时,我们先要将其转为整型, 再加上 27 ,所以我们将得到 27 或 28。 在调用函数时 v 将填入 27 或 28。
在私链(private blockchain)上运行 sha256、 ripemd160 或 ecrecover 可能会出现 out-of-gas 报错。 因为私链实现了一种预编译合约,合约要在收到第一个消息后才会真正存在(虽然他们的合约代码是硬编码的)。 而向一个不存在的合约 发送消息,是导致 out-of-gas 问题的原因。 另一种解决办法(work around)是, 在你真正使用它们之前先发送 1 wei 到这些合约上来完成初始化。在官方和测 试链上没有 out-of-gas 问题。
相关属性和函数
- .balance (uint256): address 的余额,以 wei 为单位。
- .transfer(uint256 amount): 发送给定数量的 Ether 到某个地址, 以 wei 为单位。 失败时抛出异常。
- .send(uint256 amount) returns (bool): 发送给定数量的 Ether 到 某个地址,以 wei 为单位,失败时返回 false。
- .call( ... ) returns (bool): 发起底层的 call 调用, 参数为函数选择 器。 失败时返回 false。
- .callcode( ... ) returns (bool): 发起底层的 callcode 调用, 参数为函数选择器,失败时返回 false。 不鼓励使用,未来可能会移除。
- .delegatecall( ... ) returns (bool): 发起底层的委托调用 delegatecall 调用,参数为函数选择器, 失败时返回 false。由于委托调 用的被调合约是在当前合约的环境下执行的,如果我们不知道被调合约具体做了什么事,委托调用将是一个很危险的操作。
**注意: 执行 send() 函数有一些风险。 如果调用栈的深度超过 1024 或 gas 耗光,交易都会失败。如果交易失败,会退回以太币 。而 transfer()在失败时会抛出异常。 实际上 addrA. transfer(addrB)和 require(addrA.send(y))是等价的。 **。
相关属性和函数
合约相关属性和函数有:
- this(当前合约的类型): 表示当前合约可以显式地转换为 Address。
- selfdestruct(address recipient): 销毁当前合约(也就是在第 2 章介绍的 “自毁” ),并把它的所有资金发送到给定的地址。
- suicide(address recipient): selfdestruct 的别名。
另外,当前合约里的所有自定义的函数均可支持调用 ,包括当前函数本身。 来看一个例子,了解如何使用 selfdestruct():
pragma solid ^0.4.2;
contract Steal {
address owner;
function Steal() {
owner = msg.sender;
}
function kill() { // 这是销毁的标准做法
if (msg.sender == owner) {
selfdestruct(owner);
}
}
function innocence() {
selfdestruct(owner); // 销毁合约
}
}
contract Mark {
function Deposit() payable {}
function call(address a) {
a.delegatecall(byte4(sha3("innocence()")));
}
}
在这段代码中, Steal 合约有一个 innocence 方法,这个函数虽然命名为“清白 ”,但如果 Mark 通过委托调用 innocence 方法,则 Mark 会把自己销毁掉,并且把存款发送到 Steal 合约的创建者那里。 所以我们在委托调用其他合约的函数 时, 一定要特别小心。
Solidity 表达式及控制结构
控制结构
注意,在 Solidity 中没有像 C 和JavaScript 那样从非布尔类型到布尔类型的转换, 因此不能在条件语句中使用非布尔类型, 注意:所以 if(1){…}在 Solidity 中是不合法的。
函数调用表达式
内部函数调用(Internal Function Call)
内部函数调用不会创建 EVM 消息调用 。 仅仅是在同一个合约的函数之间才可以通过内部函 数调用的方式进行调用。
外部函数调用(External Function Call)
表达式的形式为: this.g(8);和 c.g(2); (这里的 c 是一个合约实例)通过 this 或使用一个合约实例来调用是外部调用函数 的方式。它是一个消息调用,而不是 EVM 的指令跳转。 在合约的构造器中不能使用 this 来调用函数,其他合约的函数必须通过外部的方式调用 。 对于一个外部函数调用,所有函数的参数必须拷贝到内存中。可以通过选项.value()和.gas()来分别指定要发送 的以太币(以 wei 为单位)和 gas 值。
pragma solidity ^0.4.0;
contract InfoFeed {
function info() public payable returns (uint ret) {
return 42;
}
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) public {
feed = InfoFeed(addr);
}
function callFeed() public {
feed.info.value(10).gas(800)();
}
}
表达式 InfoFeed(addr)进行了一个显示的类型转换,表示给定的地址是合约 lnfoFeed 类型。
注意 feed.info.value(10).gas(800)仅仅是对发送的以太币和 gas 值进行了设置。调用callFeed 时, 需要预先存入一定量的以太币,否则会因余额不足报错。
如果我们不知道被调用的合约源代码,那么和它们交互会有潜在的风险。 虽然被调用的合约继承自一个已知的父合约 (继承仅仅要求正确实现接口,而不关注实现的内容),但是和他们交互就相当于把自己的控制权交给了被调用的合约,所以对方可以利用它做任何事。 此外,被调用的合约可以改变调用合约的状态变量( state variable ),在编写函数时我们要注意可重入性漏洞问题。
赋值表达式
解构及返回多个值
它是由一个数量固定、类型可以不同的元 素组成的一个列表。
变量声明与作用范围
函数内定义的变量,其作用域是整个函数,不管它定义的位置。 因为 Solid即使用了JavaScript 的变量作用域的规则。
错误处理
什么是错误处理
Solidity 是通过回退状态的方式来处理错误的。发生异常时会撤销当前调用(及其所有子调用)所改变的状态。 注意捕捉异常是不可能的, 因此没有 try … catch…。
我们可以把区块链理解为全球共享的分布式事务性数据库。 全球共享意味着参与这个网络的每一个人都可以读写其中的记录。 如果想修改这个数据库中的内容,就必须创建一个事务。 事务意味着要做的修改只能被全部应用, Solidity 错误处理就是要保证每次 调用都是事务性的。
如何处理
Solidiy 提供了两个函数 assert 和 require 来进行条件检查。 assert函数通常用来检查(测试)内部错误,而 require 函数用来检查输入变量或合同状态变量 是否满足条件,以及验证调用外部合约的返回值。 Solidity 分析工具(如实现中的 SMTChecker)就可以帮我们分析出智能合约中的错误。
除可以用两个函数 assert 和 require 来进行条件检查以外,还有两种方式可以触发异常:
- revert 函数可以用来标记错误并回退当前调用,当前剩余的 gas 会返回 给调用者。
- 使用 throw 关键字抛出异常(从Solidity 0.4.13 版本开始, throw 关键字已被弃用)。
注意: 在一个不存在的地址上调用底层的函数 call、 delegatecall、 calicode 会成功返回,所以我们在进行调用时,应该优先进行函数存在性检查。
pragma solidity ^0.4.0;
contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) {
require(msg.value % 2 == 0);
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
}
运行测试1:附加 1wei (奇数)去调用 sendHalf, 这时会发生异常,如下图所示。
运行测试 2:附加 2wei 去调用 sendHalf, 运行正常。
运行测试 3:附加 2wei 及 sendHalf参数为当前合约本身,在转账时发生异常,因为合约无法接收转账,错误提示与上图类似。
assert 类型异常
在下述场景中自动产生 assert 类型的异常:
1.越界或负的序号值访问数组, 如 i >= x.length 或 i<0 时访问 x[i]。
2. 序号越界或负的序号值访问一个定长的 bytesN。
3. 被除数为 0,如 5/0 或 23%0。
4. 对一个二进制数移动一个负的值,如 5<<i; i为 -1。
5. 整数进行显式转换为枚举时,将过大值、负值转为枚举类型并抛出异常。
6. 调用未初始化内部函数类型(参考函数类型章节)的变量。
7. 调用 assert的参数为 false。
require 类型异常
- 调用 throw。
- 调用 require 的参数为 false。
- 通过消息调用一个函数,但在调用的过程中并没有正确结束(例如 gas 不足没有匹配到对应的函数,或是被调用的函数出现异常)。 底层操作如 call、 send、 delegatecall 或 callcode 除外,它们不会抛出异常, 但它们会通过返回 false 来表示失败。
- 在使用 new 创建一个新合约时因为第 3 条的原因而没有正常完成。
- 调用外部函数时,被调用的对象不包含代码。
- 合约没有 payable 修饰符的 public 的函数在接收以太币时(包括构造函 数和回退函数)会出现异常。
- 合约通过一个 public 的 getter 函数接收以太币。
- transfer()执行失败。
assert 与 require 的详细对比
两种类型的相同点是异常都会撤销所有操作,这是为了保证交易的原子性(即一致性,要么全部执行,要么一点都不执行,不能只改变一部分)。
两种类型的不同点:
- assert 类型的异常会消耗掉所有剩余的 gas, 而 require 不会消耗剩余 gas(剩余 gas 会返还给调用者)。
- 操作符不同
当发生 require 类型的异常时, Solidity 会执行一个回退操作 (REVERT 指令 0xfd)。
当发生 assert 类型的异常时,Solidity 会执行一个无效操作(无效指令 0xfe)。
该使用哪一个?
下面是使用 require()的一些经验总结:
- 用于检查用户输入。
- 用于检查合约调用返回值, 如 require(external.send( amount))。
- 用于检查状态, 如 msg.send == owner。
- 通常用于函数的开头。
- 不知道使用哪一个的时候,就使用 require。
下面是使用 assert()的一些经验总结:
- 用于检查溢出错误,如“z = x + y ; assert(z >= x);”。
- 用于检查不应该发生的异常情况。
- 用于在状态改变之后,检查合约状态。
- 尽量少使用 assert;。
- 通常用于函数中间或结尾。
第二章:以太坊智能合约内容、数据类型的认识:https://blog.csdn.net/qq_44423523/article/details/108296586
第四章: 以太坊智能合约 - 合约: https://blog.csdn.net/qq_44423523/article/details/108357474