当前位置:   article > 正文

《深入掌握以太坊核心技术》--11-深入理解Solidity

《深入掌握以太坊核心技术》--11-深入理解Solidity

Solidity源文件布局

pragma(版本杂注)

  • 源文件可以被版本杂注pragma所注解,表明要求的编译器版本
  • 例如:pragma solidity ^0.4.0;
  • 源文件将既不允许低于0.4.0版本的编译器编译,也不允许高于(包含)0.5.0版本的编译器编译(第二个条件因使用^被添加)
    • import(导入其它源文件)
  • Solidity所支持的导入语句import,语法同JavaScript(从ES6起)非常类似

import
import “filename”;

  • 从“filename”中导入所有的全局符号到当前全局作用域中

import * as symbolName from “filename”;

  • 创建一个新的全局符号symbolName,其成员均来自“filename中全局符号

import {symbol1 as alias, symbol2} from “filename”;

  • 创建新的全局符号alias和symbol2,分别从"filename"引用symbol1 和 symbol2

import “filename” as symbolName;

  • 这条语句等同于import*as symbolName from"filename";

Solidity值类型

  • 布尔(bool):可能的取值为字符常量值true或false

  • 整型(int/uint):分别表示有符号和无符号的不同位数的整型变量:支持关键字 uint8到uint256(无符号,从8位到256位)以及int8 到int256,以8位为步长递增

  • 定长浮点型(fixed/ufixed):表示各种大小的有符号和无符号的定长浮点型:在关键字ufixedMxN和fixedMxN中,M表示该类型占用的位数,N表示可用的小数位数(M需被8整除,N限于0-80)

  • 地址(address):存储一个20字节的值(以太坊地址大小)

  • 定长字节数组:关键字有bytes1,bytes2,bytes3,…,bytes32

  • 枚举(enum):一种用户可以定义类型的方法,与C语言类似,默认从0开始递增,一般用来模拟合约的状态

  • 函数(function):一种表示函数的类型

Solidity引用类型

数组(Array)

  • 数组可以在声明时指定长度(定长数组),也可以动态调整大小(变长数组、

  • 动态数组)

    对于存储型(storage)的数组来说,元素类型可以是任意的(即元素也可以是数组类型,映射类型或者结构体):对于内存型(memory)的数组来说,元素类型不能是映射(mapping)类型

结构(Struct)

  • Solidity 支持通过构造结构体的形式定义新的类型映射(Mapping)
  • 映射可以视作,在实际的初始化过程中创建每个可能的key,并将其映射到字节形式全是零的值(类型默认值)

Solidity地址类型

address

  • 地址类型存储一个20字节的值(以太坊地址的大小):地址类型也有成员变量,并作为所有合约的基础

address payable(v0.5后引入)

  • 与地址类型基本相同,不过多出了transfer和send 两个成员变量

两者区别和转换

  • Payable 地址是可以发送ether 的地址,而普通 address 不能
  • 允许从 payable address 到address的隐式转换,而反过来的直接转换是不可能的(唯一方法是通过uint160来进行中间转换)
  • 从0.5.0版本起,合约不再是从地址类型派生而来,但如果它有payable的回退函数,那同样可以显式转换为address或者address payable 类型

地址类型成员变量

<address>.balance (uint256)

  • 该地址的 ether 余额,以Wei为单位

<address payable>.transfer(uint256 amount)

  • 向指定地址发送数量为amount的ether(以Wei为单位),失败时抛出异常,发送 2300 gas的矿工费,不可调节,即发给address payable

<address payable>.send(uint256 amount) returns (bool)

  • 向指定地址发送数量为 amount的ether(以Wei为单位),失败时返回false,发送 2300 gas的矿工费用,不可调节,默认是执行成功的,一般不用这个。

<address>.call(bytes memory) returns (bool, bytes memory)

  • 发出底层函数CALL,失败时返回false,发送所有可用gas,可调节(call bytes memory,假设调用这里面的合约,则发送所有可用gas)

<address>.delegatecall(bytes memory) returns (bool, bytes memory)

  • 发出底层函数DELEGATECALL,失败时返回false,发送所有可用gas,可调节(代理调用,不是把自己的合约的地址来调用,而是本来调用该合约的地址来调用,即A->B,B->C,用delegatecall的话,调用地址为A调用C)

<address>.staticcall(bytes memory) returns (bool, bytes memory)

  • 发出底层函数STATICCALL,失败时返回false,发送所有可用8as,可调节(调用过程中没有任何状态改变,有则报错)

地址成员变量用法

balance 和transfer

  • 可以使用 balance 属性来查询一个地址的余额,可以使用transfer 函数向一个payable地址发送以太币Ether(以
    wei为单位)
address payable x= address(0x123);
address myAddress = address(this);
if(x.balance <10&& myAddress.balance >= 10)
x.transfer(10);
  • 1
  • 2
  • 3
  • 4

send

  • send 是transfer的低级版本。如果执行失败,当前的合约不会因为异常而终止,但 send 会返回 false

call

  • 也可以用call来实现转币的操作,通过添加,gas()和.value()修饰器:
nameReg.call.gas(1000000).value(1ether)(abi.encodeWithSignature("register(string)","MyName"));
  • 1

字符数组(Byte Arrays)

定长字符数组

  • 属于值类型,bytes1,bytes2,…,bytes32分别代表了长度为1到32的字节序列
  • 有一个 .length 属性,返回数组长度(只读)
    变长字符数组
  • 属于引用类型,包括bytes和string,不同的是bytes是Hex字符串,而string是UTF-8编码的字符串

枚举(Enum)

  • 枚举类型用来用户自定义一组常量值与C语言的枚举类型非常相似,对应整型值
pragma solidity >=0.4.0 <0.6.0;
contract Purchase {
	enum State { Created, Locked, inactive }
}
  • 1
  • 2
  • 3
  • 4

数组(Array)

  • 固定大小k和元素类型T的数组被写为T[k],动态大小的数组为T[]。例如,一个由5个uint动态数组组成的数组是uint[][5]
  • 要访问第三个动态数组中的第二个uint,可以使用x[2][1]
  • 越界访问数组,会导致调用失败回退
  • 如果要添加新元素,则必须使用.push()或将.length增大(storage可变与memory实际上不可增大,固定长度)
  • 变长的storage数组和bytes(不包括string)有一个push()方法。可以将一个新元素附加到数组末端,返回值为当前长度,.length直接指定长度或者++

数组示例


pragma solidity >=0.4.16 <0.6.0;
contract C {
		function f(uint len) public pure {
					uint[] memory a = new uint[](7); //一开始得指定大小,7为大小
					bytes memory b = new bytes(len);//一样,没赋值,默认全为0
					assert(a.length == 7);  //assert 后面判断一定得为true否则报错
					assert(b.length == len);
					a[6]= 8;
			}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

结构(Struct)

  • 结构类型可以在映射和数组中使用,它们本身可以包含映射和数组。
  • 结构不能包含自己类型的成员,但可以作为自己数组成员的类型,也可以作为自己映射成员的值类型
pragma solidity >=0.4.0 <0.6.0;contract Ballot {
struct Voter {
		uint weight;
		bool voted;
		uint vote;
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

映射(Mapping)

  • 声明一个映射:mapping(KeyType=>ValueType)
  • KeyType可以是任何基本类型。这意味着它可以是任何内置值类型加上字符数组和字符串。不允许使用用户定义的或复杂的类型,如枚举,映射,结构以及除bytes和string之外的任何数组类型。
  • ValueType可以是任何类型,包括映射。
pragma solidity >=0.4.0 <0.6.0;
contract MappingExample {
	mapping(address => uint) public balances;
	function update(uint newBalance) public {
				balances[msg.sender] = newBalance;
	}}
	
contract MappingUser {
	function f() public returns (uint) {
				MappingExample m = new MappingExample();
				m.update(100);
				return m.balances(address(this));
	}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Solidity数据位置

  • 所有的复杂类型,即数组、结构和映射类型,都有一个额外属性,“数据位置”,用来说明数据是保存在内存memory中还是存储storage中
  • 根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后增加关键字 storage 或 memory 进行修改
  • 函数参数(包括返回的参数)的数据位置默认是memory,局部变量的数据位置默认是 storage,状态变量的数据位置强制是 storage
  • 另外还存在第三种数据位置,calldata,这是一块只读的,且不会永久存储的位置,用来存储函数参数。外部函数的参数(非返回参数)的数据位置被强制指定为calldata,效果跟 memory 差不多

数据位置总结

强制指定的数据位置

  • 外部函数的参数(不包括返回参数):calldata;
  • 状态变量:storage

默认数据位置

  • 函数参数(包括返回参数):memory;
  • 引用类型的局部变量:storage(要求一个很大存储空间,用来保证不发生哈希碰撞,这也导致无法遍历,mapping就是,同时memory内存分配时就要说明存储大小,而storage不用,故可用mapping)
  • 值类型的局部变量:栈(stack)

特别要求

  • 公开可见(publicly visible)的函数参数一定是memory类型,如果要求是storage类型则必须是 private或者internal 函数,这是为了防止随意的公开调用占用资源

实例
实例1

/一个简单的例子
pragma solidity ^0.4.0;
contract C {
				uint[] data1;
				uint[] data2;
				function appendOne() public {
				append(data1);
				}
				function appendTwo() public {
				append(data2);
				}
function append(uint[] storage d) internal{ //这里d是对data1的引用,而不是复制一份数据
				d.push(1);
				}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

实例2

///下面代码包含一个错误
pragma solidity ^0.4.0;
contract C {
			uint someVariable;//默认值为0
			uint[] data;
			function f() public {
						uint[] x;//长度可变的数组(局部变量,未初始,指向全局,storage(局部变量默认))
						x.push(2);//这里面会改变someVariable的值,这是由于这里的x是一个指针,没有初始化,只会指向存储空间的0位置,即指向someVariable,每次push,someVariable+1,而不是2,是由于由于这里的Push不是简单放值,而是长度,然后根据这个长度和x的位置进行哈希得到x对应的值,即hash(位置+长度)=2
						data = x;
				}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

实例3
push和.length用法

//下面代码编译错误,push和.length用法
pragma solidity ^0.4.0;
contract C {
			uint[] x;
			function f(uint[] memoryArray) public {
					x= memoryArray; //memory类型和storage类型之间可以互相传值,
					//但memory向storage赋值,则是完整的拷贝,storage向状态变量(强制要求storage)
					//,只有赋值,则会改变里面内容,相当于拷贝,storage向局部变量(默认storage),则会相当于引用,
					//memory到Memory一般是引用,但只要涉及到状态变量,就一定是拷贝
					uint[] y = x; //引用,y是指针
					y[7];
					y.length = 2;//令长度为2
					delete x;
					y= memoryArray;//y是指针,不能直接指向,
					delete y; 
					g(x);
					h(x);
			}
		function g(uint[] storage storageArray) internal {}
		function h(uint[] memoryArray) public {} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Solidity函数声明和类型

在这里插入图片描述

Solidity函数可见性

函数的可见性可以指定为external,public,internal或者private;对于状态变量,不能设置为external,默认是internal。

  • external:外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。一个外部函数f不能从内部调用(即f不起作用,但
    this.f()可以)。当收到大量数据的时候,外部函数有时候会更有效率。
  • public:public函数是合约接口的一部分,可以在内部或通过消息调用对于 public 状态变量,会自动生成一个 getter函数。
  • internal:这些函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不使用this调用。
  • private:private函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。

Solidity函数状态可变性

函数状态可变性

  • pure:纯函数(给定输入,则固定输出),不允许修改或访问状态
  • view:不允许修改状态(只读,不写)
  • payable:允许从消息调用中接收以太币Ether
  • constant:与view相同,一般只修饰状态变量,不允许赋值(除 初始化以外)

以下情况被认为是修改状态:

  • 修改状态变量。
  • 产生事件。(比如写日志)
  • 创建其它合约。
  • 使用 selfdestruct.
  • 通过调用发送以太币,
  • 调用任何没有标记为view或者pure 的函数。
  • 使用低级调用。(之前讲过的call之类的)
  • 使用包含特定操作码的内联汇编。

以下被认为是从状态中进行读取:

  • 读取状态变量。
  • 访问 this,balance 或者
    .balance。
  • 访问 block,tx,msg中任意成员(除msg.sig和msg.data之外)
  • 调用任何未标记为 pure 的函数。
  • 使用包含某些操作码的内联汇编

函数修饰器(modifier)

  • 使用 修饰器modifier 可以轻松改变函数的行为。例如,它们可以在执行函数之前自动检查某个条件。修饰器modifier是合约的可继承属性,并可能被派生合约覆盖(把一个操作固定在某个函数上,可被子合约继承)
  • 如果同一个函数有多个修饰器modifier,它们之间以空格隔开,修饰 器modifier 会依次检查执行

Modifier示例

pragma solidity >=0.4.22 <0.6.0;
contract Purchase {
			address public seller;
			modifier onlySeller() {//Modifier,执行某函数前先执行modifier
			require( msg.sender == seller, "Only seller can call." );//require要求为真,若假,则抛出异常
			_;//占位符,代表函数执行的位置,可放在require上面
	}
			function abort() public view onlySeller {
			//Modifier usage
			//...
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

回退函数(fallback)

  • 回退函数(fallback function)是合约中的特殊函数;没有名字,不能有参数也不能有返回值
  • 如果在一个到合约的调用中,没有其他函数与给定的函数标识符匹配(或没有提供调用数据),那么这个函数(fallback函数)会被执行(之前激讲过的函数选择器,在该合约没匹配上,则会执行这个,即默认选项)
  • 每当合约收到以太币(没有任何数据),回退函数就会执行。此外,为了接收以太币,fallback函数必须标记为payable。如果不存在这样的函数,则合约不能通过常规交易接收以太币()
  • 在上下文中通常只有很少的 gas 可以用来完成回退函数的调用,所以使fallback函数的调用尽量廉价很重要

事件(event)

  • 事件是以太坊EVM提供的一种日志基础设施。事件可以用来做操作记录,存储为日志。也可以用来实现一些交互功能,比如通知U1,返回函数调用结果等
  • 当定义的事件触发时,我们可以将事件存储到EVM的交易日志中,日志是区块链中的一种特殊数据结构:日志与合约关联,与合约的存储合并存人区块链中:只要某个区块可以访问,其相关的日志就可以访问:但在合约中,我们不能直接访问日志和事件数据
  • 可以通过日志实现简单支付验证 SPV(Simplified
    PaymentVerification),如果一个外部实体提供了一个带有这种证明的合约,它可以检查日志是否真实存在于区块链中

Solidity异常处理

  • Solidity使用“状态恢复异常”来处理异常。这样的异常将撤消对当前调用(及其所有子调用)中的状态所做的所有更改,并且向调用者返回错误。
  • 函数assert和require可用于判断条件,并在不满足条件时抛出异常assert()一般只应用于测试内部错误,并检查常量
  • require()应用于确保满足有效条件《如输入或合约状态变量)或验证调用外部合约的返回值
  • revert()用于抛出异常,它可以标记一个错误并将当前调用回退

Solidity中的单位

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/607488
推荐阅读
相关标签
  

闽ICP备14008679号