第三章:以太坊智能合约单位、表达式、全局变量及函数、控制结构

4 篇文章 0 订阅
3 篇文章 0 订阅

第三章:以太坊智能合约单位、表达式、全局变量及函数、控制结构


第三章:以太坊智能合约单位、表达式、全局变量及函数、控制结构


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


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 类型异常

  1. 调用 throw。
  2. 调用 require 的参数为 false。
  3. 通过消息调用一个函数,但在调用的过程中并没有正确结束(例如 gas 不足没有匹配到对应的函数,或是被调用的函数出现异常)。 底层操作如 call、 send、 delegatecall 或 callcode 除外,它们不会抛出异常, 但它们会通过返回 false 来表示失败。
  4. 在使用 new 创建一个新合约时因为第 3 条的原因而没有正常完成。
  5. 调用外部函数时,被调用的对象不包含代码。
  6. 合约没有 payable 修饰符的 public 的函数在接收以太币时(包括构造函 数和回退函数)会出现异常。
  7. 合约通过一个 public 的 getter 函数接收以太币。
  8. transfer()执行失败。

assert 与 require 的详细对比

两种类型的相同点是异常都会撤销所有操作,这是为了保证交易的原子性(即一致性,要么全部执行,要么一点都不执行,不能只改变一部分)。
两种类型的不同点:

  1. assert 类型的异常会消耗掉所有剩余的 gas, 而 require 不会消耗剩余 gas(剩余 gas 会返还给调用者)。
  2. 操作符不同
    当发生 require 类型的异常时, Solidity 会执行一个回退操作 (REVERT 指令 0xfd)。
    当发生 assert 类型的异常时,Solidity 会执行一个无效操作(无效指令 0xfe)。

该使用哪一个?
下面是使用 require()的一些经验总结:

  1. 用于检查用户输入。
  2. 用于检查合约调用返回值, 如 require(external.send( amount))。
  3. 用于检查状态, 如 msg.send == owner。
  4. 通常用于函数的开头。
  5. 不知道使用哪一个的时候,就使用 require。

下面是使用 assert()的一些经验总结:

  1. 用于检查溢出错误,如“z = x + y ; assert(z >= x);”。
  2. 用于检查不应该发生的异常情况。
  3. 用于在状态改变之后,检查合约状态。
  4. 尽量少使用 assert;。
  5. 通常用于函数中间或结尾。

第二章:以太坊智能合约内容、数据类型的认识:https://blog.csdn.net/qq_44423523/article/details/108296586
第四章: 以太坊智能合约 - 合约: https://blog.csdn.net/qq_44423523/article/details/108357474

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以太坊一个平台,它上面提供各种模块让用户来搭建应用,如果将搭建应用比作造房子,那么以太坊就提供了墙面、屋顶、地板等模块,用户只需像搭积木一样把房子搭起来,因此在以太坊上建立应用的成本和速度都大大改善。具体来说,以太坊通过一套图灵完备的脚本语言(Ethereum Virtual Machinecode,简称EVM语言)来建立应用,它类似于汇编语言。我们知道,直接用汇编语言编程是非常痛苦的,但以太坊里的编程并不需要直接使用EVM语言,而是类似C语言、python、Lisp等高级语言,再通过编译器转成Evm语言。上面所说的平台之上的应用,其实就是合约,这是以太坊的核心。合约是一个活在以太坊系统里的自动代理人,他有一个自己的以太币地址,当用户向合约的地址里发送一笔交易后,该合约就被激活,然后根据交易中的额外信息,合约会运行自身的代码,最后返回一个结果,这个结果可能是从合约的地址发出另外一笔交易。需要指出的是,以太坊中的交易,不单只是发送以太币而已,它还可以嵌入相当多的额外信息。如果一笔交易是发送给合约的,那么这些信息就非常重要,因为合约将根据这些信息来完成自身的业务逻辑。合约所能提供的业务,几乎是无穷无尽的,它的边界就是你的想象力,因为图灵完备的语言提供了完整的自由度,让用户搭建各种应用。白皮书举了几个例子,如储蓄账户、用户自定义的子货币等。 2013年年末,以太坊创始人Vitalik Buterin发布了以太坊初版白皮书,启动了项目。2014年7月24日起,以太坊进行了为期42天的以太币预售。2016年初,以太坊的技术得到市场认可,价格开始暴涨,吸引了大量开发者以外的人进入以太坊的世界。中国三大比特币交易所之二的火币网及OKCoin币行都于2017年5月31日正式上线以太坊。 [1] 自从进入2016年以来,那些密切关注数字货币产业的人都急切地观察着第二代加密货币平台以太坊的发展动向。作为一种比较新的利用比特币技术的开发项目,以太坊致力于实施全球去中心化且无所有权的的数字技术计算机来执行点对点合约。简单来说就是,以太坊一个你无法关闭的世界计算机。加密架构与图灵完整性的创新型结合可以促进大量的新产业的出现。反过来,传统行业的创新压力越来越大,甚至面临淘汰的风险。比特币网络事实上是一套分布式的数据库,而以太坊则更进一步,她可以看作是一台分布式的计算机:区块链是计算机的ROM,合约是程序,而以太坊的矿工们则负责计算,担任CPU的角色。这台计算机不是、也不可能是免费使用的,不然任何人都可以往里面存储各种垃圾信息和执行各种鸡毛蒜皮的计算,使用它至少需要支付计算费和存储费,当然还有其它一些费用。最为知名的是2017年初以摩根大通、芝加哥交易所集团、纽约梅隆银行、汤森路透、微软、英特尔、埃森哲等20多家全球top金融机构和科技公司成立的企业以太坊联盟。而以太坊催生的加密货币以太币近期又成了继比特币之后受追捧的资产。  智能合约的潜在应用很多。彭博社商业周刊称它是“所有人共享但无法篡改的软件”。更高级的软件有可能用以太坊创建网络商店。  以太坊一个平台,它上面提供各种模块让用户来搭建应用,如果将搭建应用比作造房子,那么以太坊就提供了墙面、屋顶、地板等模块,用户只需像搭积木一样把房子搭起来,因此在以太坊上建立应用的成本和速度都大大改善。具体来说,以太坊通过一套图灵完备的脚本语言(Ethereum Virtual Machinecode,简称EVM语言)来建立应用,它类似于汇编语言。我们知道,直接用汇编语言编程是非常痛苦的,但以太坊里的编程并不需要直接使用EVM语言,而是类似C语言、python、Lisp等高级语言,再通过编译器转成Evm语言。上面所说的平台之上的应用,其实就是合约,这是以太坊的核心。合约是一个活在以太坊系统里的自动代理人,他有一个自己的以太币地址,当用户向合约的地址里发送一笔交易后,该合约就被激活,然后根据交易中的额外信息,合约会运行自身的代码,最后返回一个结果,这个结果可能是从合约的地址发出另外一笔交易。需要指出的是,以太坊中的交易,不单只是发送以太币而已,它还可以嵌入相当多的额外信息。如果一笔交易是发送给合约的,那么这些信息就非常重要,因为合约将根据这些信息来完成自身的业务逻辑。合约所能提供的业务,几乎是无穷无尽的,它的边界就是你的想象力,因为图灵完备的语言提供了完整的自由度,让用户搭建各种应用。白皮书举了几个例子,如储蓄账户、用户自定义的子货币等。 2013年年末,以太坊创始人Vitalik Buterin发布了以太坊初版白皮书,启动了项目。2014年7月24日起,以太坊进行了为期42天的以太币预售。2016年初,以太坊的技术得到市场认可,价格开始暴涨,吸引了大量开发者以外的人进入以太坊的世界。中国三大比特币交易所之二的火币网及OKCoin币行都于2017年5月31日正式上线以太坊。 [1] 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值