起因是Xenc师傅给我截了张图,我日 居然看不懂 ,一搜才知道,之前学的版本有些老了.. 这次学下新一点的记录下
HelloWorld
pragma solidity ^0.6.0; // version
contract One{
// 状态变量
string name;
function setName() public{ // 必须要加权限修饰符了
name = "Muxue";
}
// view修饰的函数 表示访问状态变量, 但是不会修改, 不会消耗任何资源
function getName() public view returns(string memory){ // 返回字符串类型也需要加memory了
return name;
}
// pure修饰的函数 不不修饰也不读取状态变量 不会消耗任何资源
function pureTest() public pure returns(string memory){
return "test";
}
}
变量 常量 标识符 关键字
变量
type name = value;
标识符
需要命名的地方都是标识符
标识符命名规则
1. 字符, 数字, _, $组成
2. 不能以数字开头
3. 区分大小写
数据类型
基本类型
- 布尔(bool)
- 整形(int / uint)
- 地址(address)
- 定长字节数组
- 定长浮点(fixed / ufixed)
- 枚举类型(enum)
- 函数类型(function)
引用类型
- 数组(array) {不定长字节数组, 字符串}
- 结构体(struct)
- 映射(mapping)
区别
值类型一般都是值拷贝传递,但引用类型 有些是地址传递
数组
全局数组
全局的是storage
Array
几种声明 初始化方式
pragma solidity ^0.6.0;
contract ArrayTest{
uint[4] arr1; // 固定数组的声明
uint[] arr2; // 动态长度数组的声明
// 声明并初始化
uint[2] arr3 = [1,2];
uint[] arr4 = [1,4,12,4,2];
// 使用new
uint[] public arr5 = new uint[](3);
}
length和push pop的讲解
length: 长度 修改length 可改变动态长度数组的长度,但是0.6.0以上的版本都不能使用length修改数组长度
push:固定数组不能用 动态数组storage可以用 memory不能用
pop: 把push进来的数据 再顶出去
pragma solidity ^0.6.0;
contract ArrayTest{
uint[] arr4 = [1,4,12,4,2];
// 使用new
uint[] public arr5 = new uint[](3);
function push() public{
arr5.push(1);
}
// 尝试使用修改length
function changeLength() public{
// arr4.length = 12; // browser/Array/1.sol:19:9: TypeError: Member "length" is read-only and cannot be used to resize arrays. arr4.length = 12; // ^---------^
}
function Get() view public returns(uint[] memory){
return arr5;
}
function pop() public{
arr5.pop(); // 把push进来的再推出去
}
}
局部数组
局部的是memory
注意的一点是 新版本的 局部的都要加memory
,包括返回数组 返回值那里
pragma solidity ^0.6.0;
contract ArrayTest{
function test() public returns(uint[] memory){
uint[] memory arr8 = new uint[](3); // 使用new方法创建局部数组
arr8[0] = 1;
return arr8;
}
}
数据存储位置
storage
:是存在区块链 上的,全局变量
哪些数组类型可以使用storage
- 数组
- 结构体
- 映射
memory
:是存在内存上的,函数内的 局部的变量栈
:值类型的局部变量存储在这里calldata
:当函数为外部函数(external
),如果此函数的参数(非返回参数),则参数要求必须用calldata
,(教程里是这样说的,但是我用memory
也没报错)
对于存储型的数组(storage
), 可以放任意的元素类型
对于内存性的数组(memory
), 元素不可以是映射类型mapping
storage和memory的相互转换
pragma solidity ^0.6.0;
contract testFunc{
uint[] public a1Storage = [1,2,3];
function Geta1() public view returns(uint[] memory){
return a1Storage;
}
// sotrage -> memory 值传递 不会修改storage的值
function storageTomemory() public view{
uint[] memory b = a1Storage; // storage 赋值给 memory
b[0] = 100;
}
// memory -> storage 值传递
function memoryTostorage() public{
uint8[4] memory c = [2,4,1,5];
a1Storage = c;
c[0] = 255;
}
// sotrage -> storage 引用传递 修改一个 原数据也会修改数据
function storageTostorage() public{
uint[] storage m = a1Storage;
m[1] = 123;
}
// memory -> memory 引用传递
function memoryTomemory() public view returns(uint8[3] memory){
uint8[3] memory a = [1,2,3];
uint8[3] memory b = a;
b[1] = 12;
return a;
}
}
简单来说两个相同的都是引用传递,不同的都是值传递
字节数组
也分为
- 变长字节数组
- 定长字节数组
bytes
: 变成字节数组bytes+num
:后面跟数字的是定长字节数组
pragma solidity ^0.6.0;
contract Test{
bytes b1; // 变长字节数组 bytes里是以16进制存储值的
bytes b2 = "abc\x22\x25"; // \x后面就代表是十六进制的值
bytes b3 = "ce\u8bd5";
function Get() public view returns( bytes memory){
return b3;
}
bytes4 bt = 0x74657374; // 定长字节数组
}
定长字节数组转变长字节数组
pragma solidity ^0.6.0;
contract Test{
bytes4 bt = 0x74657374; // 定长字节数组
// 把定长字节数组转换成变长字节数组
function To() public view returns(bytes memory){
bytes memory temp = new bytes(bt.length);
for(uint i=0;i<bt.length;i++){
temp[i] = bt[i];
}
return temp;
}
}
string
string没啥好说了吧 我擦..
字节转字符串
pragma solidity ^0.6.0;
contract Test{
bytes public name = "慕雪";
function EchoName() public view returns(string memory){
return string(name);
}
}
Solidity交易和内置对象
提到交易不得不提Gas
了哈,也就是燃料也叫手续费Gas
又分为两个:
-
gas price
每个gas
的价格 -
gas limit
限制gas
的最大值
需要给矿工预付的
gas
=gas price
*gas limit
消息:不修改合约状态,如带有pure
,view
等关键字的
账户和地址
分为外部账户
和合约账户
, 外部账户地址
, 外部合约地址
也就是外部账户和外部合约都各有地址
外部账户:我们钱包的一个用户 给你一个地址 就是一个外部账户
合约账户:我们部署一个合约 都会给我们一个地址
地址address
- 20个字节,160位
- 值类型
address payable
0.5.0以后出的
只能使用它来进行转账
属性
balance
函数
专门用来做交易的
transfer
send
不会提示错误
demo code
pragma solidity ^0.6.0;
contract Test{
function echoAddress() public view returns(uint){
address _address = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
return uint(_address);
}
function getBalance(address _account) public view returns(uint){
return _account.balance;
// return address(this).balance; 返回当前合约账户余额
// this是指当前合约账户
}
function getThis() public view returns(address){
return address(this);
}
function testTransfer() public payable{
address(this).transfer(msg.value); // 前面是要给哪个地址转账的地址
}
function testTransfer2() public payable{
address payable _address = address(this); // 转账必须加payable
_address.transfer(1 ether); // 虽然这里写1了 但是我们还是需要在value那里设置1 ether
}
function testSend() public payable{
address payable _address = address(this);
_address.send(1 ether);
}
fallback() payable external{ // 回退函数
}
receive() payable external{
}
}
transfer
和send
的区别:
transfer假如value和设置的值不同 会报错。而send不会,但会返回一个false,但是错误了 还是会扣钱
Solidity单位和全局变量
具体可以看
https://solidity-cn.readthedocs.io/zh/develop/units-and-global-variables.html?highlight=全局#id3
区块链和交易属性
pragma solidity ^0.6.0;
contract One{
/*
block.blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块;而 blocks 从 0.4.22 版本开始已经不推荐使用,
由 blockhash(uint blockNumber) 代替
block.number (uint): 当前区块号
msg.data (bytes): 完整的 calldata
msg.gas (uint): 剩余 gas - 自 0.4.21 版本开始已经不推荐使用,由 gesleft() 代替
msg.sig (bytes4): calldata 的前 4 字节(也就是函数标识符)
msg.value (uint): 随消息发送的 wei 的数量
tx.gasprice (uint): 交易的 gas 价格
tx.origin (address): 交易发起者(完全的调用链)
*/
function getBlockHash() public view returns(bytes32){
return blockhash(block.number-1); // block.number 当前区块号,得-1 要不然获取不到hash
}
// block.coinbase (address): 挖出当前区块的矿工地址
function getBlockCoinbase() public view returns(address){
return block.coinbase;
}
// block.difficulty (uint): 当前区块难度
function getBlockDifficulty() public view returns(uint){
return block.difficulty;
}
// block.gaslimit (uint): 当前区块 gas 限额
function getBlockLimit() public view returns(uint){
return block.gaslimit;
}
// block.timestamp (uint): 自 unix epoch 起始当前区块以秒计的时间戳
function getBlockTimestamp() public view returns(uint){
return block.timestamp;
}
// gasleft() returns (uint256):剩余的 gas
function getGasleft() public view returns(uint){
return gasleft();
}
// now (uint): 目前区块时间戳(block.timestamp)
function getTime() public view returns(uint){
return now;
}
// msg.sender (address): 消息发送者(当前调用)
function GetMsgsender() public view returns(address){
return msg.sender;
}
}
错误处理
最常用的应该就是require
了吧
pragma solidity ^0.6.0;
contract Two{
uint public money;
address master;
constructor() public{ // 构造函数
master = msg.sender;
}
function changeMoney() public {
require(msg.sender == master); // 如果不是 就直接报错了 不会往下面走
money += 5000;
}
}
函数
也就是function
,已经非常熟悉了
可见性
-
public
:内部 外部都可调用,状态变量和函数都可以用 -
private
:私有的 只有内部可以调用,状态变量和函数都可以用 -
internal
:只有内部可以调用,状态变量和函数都可以用 -
external
:只有外部可以调用,只有函数可以用
可以明显的看出 被
private
和internal
修饰的函数 外部不可以调用
被external
修饰的内部不可以调用
如要调用可以加一个this.
function test3() public view returns(uint) {
return this.testExternal();
}
调用方式
-
external
:外部的 也就是外部点击函数名称那种方式 -
internal
:内部的 就是一个合约方法调用另一个方法呗
可变性 可修改性
-
pure
:不用状态变量的时候 -
view
:需要用到状态变量 -
payable
:需要付款的时候 -
constant
:和view一样的,0.5.0以后舍弃了
返回值
pragma solidity ^0.6.0;
contract Two{
// 返回多个参数
function test1() public pure returns(uint a,uint b){
return (2,4);
}
// 写参数名字
function test2() public pure returns(uint sum){
sum = 1+2; // 不需要加类型 因为上面我们定义了
// 不用reutrn 要是return,以return的值为结果
}
}
get
被public
修饰的变量,系统默认写了一个同名的函数,用来获取值,0.5.0以上的版本 不支持重写了
修改器 modifier
作用:可以修改函数的行为,控制函数的逻辑,代码重用
pragma solidity ^0.6.0;
contract TestModifier{
uint public a;
// create modifier
modifier myModifier(){
a=1;
_; // 函数会到这
a=10;
}
// call modifier
function callModifier() public myModifier{
a=9; // 最终结果为10
}
}
带参数的modifier
来个demo
看看modifier
的强大之处
pragma solidity ^0.6.0;
contract Two{
uint public level;
string public name;
string public sex;
modifier levelRequire(uint _level) {
require(level>=_level);
_;
}
function setLevel(uint _num) public{
level = _num;
}
function setName() public levelRequire(5){ // 相当于level需要大于或者等于五级才可以调用此方法
name = "Muxue"; // 如果modifier的_;后面还有 会在当前代码后面执行
}
function setSex() public levelRequire(7){
sex = "male";
}
}
多重modifier的执行顺序
这个是有点东西的
contract Three{
uint public a = 1;
modifier Test1{
a = 2; // 执行顺序1
_; // 把Test2带进来
a = 3; // 执行顺序5 也就是最后一个
}
modifier Test2{
a = 4; // 执行顺序2
_; // 把test函数带进来
a = 5; // 执行顺序4
}
function test() public Test1 Test2{
a = 6; // 执行顺序3
// a的结果为3
}
}
如果不信的 可以debug一步步的调着看
合约
构造函数
- 合约创建时自动调用,且只调用一次。每个合约只能有一个构造函数
- 构造函数一般是为状态变量初始化
-
constructor
关键字 - 分为有参和无参
pragma solidity ^0.6.0;
contract TestNewContract{
uint age;
constructor(uint _age) public{
age = _age;
}
}
封装性
-
函数合约
-
修饰符(访问修饰符)
-
public
,private
,internal
,external
- 状态变量:
public
,private
,internal
- 函数:
public
,private
,internal
,external
- 状态变量:
-
继承
-
is
关键字
pragma solidity ^0.6.0;
contract Father{
address owner;
string name;
uint money = 100000000000000000000000;
constructor() public{
owner = msg.sender;
name = "Muxue";
}
function changeName(string memory _name ) public{
require(msg.sender == owner);
name = _name;
}
}
contract Son is Father{
function getMoney() public view returns(uint){
return money; // 继承的父亲的
}
}
父合约构造函数的传参
pragma solidity ^0.6.0;
contract Father{
uint private age;
constructor(uint _age) public{
age = _age;
}
}
contract Son is Father(46){ // 继承式
}
// 这两种方法不能同时使用
contract Son2 is Father{
constructor() Father(46) public{ // 修改风格式
}
}
多态
重写父类方法和状态变量
得在父类方法上加上virtual
关键字,子类重写的方法加上override
方法
pragma solidity ^0.6.0;
contract A{
function F() public pure virtual returns(string memory){ // 仅当函数被标记为 virtual 或在接口中定义 时,才可以覆盖
return "A";
}
}
contract B is A{
// 函数重写
function F() public pure override returns(string memory){ // override 代表可重写
return "B";
}
// 函数重载
function F(string memory _test) public pure returns(string memory){
return _test;
}
}
父类:
virtual
,子类:override
重写要保证,函数名,参数,返回值相同
重写状态变量0.6.0
后 不可再重写状态变量
super
pragma solidity ^0.6.0;
contract A{
function F() public pure virtual returns(string memory){ // 仅当函数被标记为 virtual 或在接口中定义 时,才可以覆盖
return "A";
}
function eat() public pure virtual returns(string memory){
return "rou";
}
}
contract B is A{
// 函数重写
function F() public pure override returns(string memory){ // override 代表可重写
return "B";
}
// 函数重载
function F(string memory _test) public pure returns(string memory){
return _test;
}
function eat() public pure override returns(string memory){
return "yu";
}
function test() public view returns(string memory){
// return eat(); // 调用的是自己重写的那个方法
return super.eat(); // 调用父类eat方法
}
}
想要调用父类的,也可以用
父类名.方法名
super是内部调用
多重继承 重写父类方法
pragma solidity ^0.6.0;
contract Father{
function getMoney() public pure virtual returns(uint ){
return 10000;
}
}
contract Mother{
function getMoney() public pure virtual returns(uint ){
return 8000;
}
}
contract Son is Father,Mother{
function getMoney() public pure override(Father,Mother) returns(uint){ // 处理重名函数
return 18000;
}
}
这个在我编译时,一直在报错,但是我还不知道哪错了,我用
0.8.4
编译一次之后,没问题。最后再换回0.6的版本,照样可以编译成功
抽象合约
抽象合约
- 关键字:
abstract
- 抽象合约中,可以有抽象函数,非抽象函数
- 抽象函数 不需要实现函数体
- 抽象合约不能实例化
作用:起到约束,约束继承的抽象合约的子合约,必须重写抽象函数
pragma solidity ^0.6.0;
abstract contract Father{ // 抽象合约 关键字 abstract
function eat() public pure virtual; // 抽象函数
}
contract Son is Father{
// 必须重写函数
function eat() public pure override{
}
}
接口
- 关键字
interface
- 接口中所有的函数都是抽象函数,所有可以省略
virtual
关键字 - 接口函数的修饰符必须使用
external
- 其余限制:不能继承其他合约或接口,不能定义构造函数,不能定义状态变量,不能定义结构体
interface Father{ // 接口
function eat() external pure; // 没有{}
}
contract Son is Father{
function eat() public pure override{ // 重写eat方法
}
}
库
作用
- 代码重要性
- 将多个合约重复的代码提取到一个库中
- 不需要继承 节省gas
关键字及特性
libray
- 库中的函数不能修改状态变量
- 库不可以被销毁
- 不能定义状态变量
- 不可以继承其他元素,也不能被继承
- 库不能接受以太币
使用
有两种方式库名.方法名
pragma solidity ^0.6.0;
library Search{ // 库定义
function indexOf(uint[] storage _data,uint _value) public view returns(uint){
for(uint i=0;i<_data.length;i++){
if(_data[i] == _value){
return i;
}
}
return uint(-1);
}
}
contract TestLibray{ // 第一种
uint[] data;
constructor() public{
data.push(1);
data.push(2);
data.push(3);
data.push(4);
}
// 调用库函数
function indexof(uint _value) public view returns(uint){
return Search.indexOf(data,_value);
}
}
using 库名 for 状态变量
pragma solidity ^0.6.0;
library Search{ // 库定义
function indexOf(uint[] storage self,uint _value) public view returns(uint){ // self也就是传过来的那个类型
for(uint i=0;i<self.length;i++){
if(self[i] == _value){
return i;
}
}
return uint(-1);
}
}
contract TestUsingFor{
using Search for uint[]; // 把search这个库绑定为uint[]类型
uint[] data;
constructor() public{
data.push(1);
data.push(2);
data.push(3);
data.push(4);
}
// 调用库函数
function indexof(uint _value) public view returns(uint){
return data.indexOf(_value); // 会自动把data传给第一个参数
}
}
合约销毁
合约生命周期
- 合约创建(new、sdk)
- 合约操作、使用(调用函数实现功能)
- 合约销毁(区块链上 关于合约的存储和代码都会被删除)
pragma solidity ^0.6.0;
library Search{ // 库定义
function indexOf(uint[] storage self,uint _value) public view returns(uint){ // self也就是传过来的那个类型
for(uint i=0;i<self.length;i++){
if(self[i] == _value){
return i;
}
}
return uint(-1);
}
}
contract TestUsingFor{
using Search for uint[]; // 把search这个库绑定为uint[]类型
uint[] data;
address owner;
constructor() public{
owner = msg.sender;
data.push(1);
data.push(2);
data.push(3);
data.push(4);
}
modifier onlyOwner() {
require(owner == msg.sender);
_;
}
// 调用库函数
function indexof(uint _value) public view returns(uint){
return data.indexOf(_value); // 会自动把data传给第一个参数
}
function kill() public onlyOwner{
selfdestruct(msg.sender); // 销毁合约
}
}
映射
引用数据类型
关键字及特性
mapping
- 存储一对数据,以
key-value
形式的 - key的数据类型是有要求的:动态数组,枚举,
struct
,mapping
都不可以;value基本上都可以 -
mapping
不能作为参数使用
pragma solidity ^0.6.0;
contract TestMapping{
mapping(uint=>string) public uintMapping; // key:uint value:string
mapping(address=>uint) public addressMapping;
mapping(string=>mapping(uint=>address)) public stringMapping;
constructor() public{
uintMapping[1] = "test";
addressMapping[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] = 1;
addressMapping[0x17F6AD8Ef982297579C203069C1DbfFE4348c372] = 2;
stringMapping["Muxue"][1] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; // 复杂的是[key][key] = value;
}
}
结构体
引用数据类型
关键字及特性
- 关键字
struct
- 大部分类型都可以,但是不可以包含自己本身
- 结构体作为函数的返回值类型使用时
- 结构体中有
mapping
类型,函数只能使用internal
,private
- 结构体中没有
mapping
类型,需要添加pragma experimental ABIEncoderV2;
- 结构体中有
contract TestStruct{
struct People{
uint id;
string name;
mapping(string=>uint) grade;
// People test;
}
People public pp;
constructor() public{
// People memory ppt = People(1,"Muxue"); // mapping类型不能直接赋值,因为是storage的
People memory ppt = People({name:"Muxue",id:1});
pp = ppt;
pp.grade["level"] = 1;
}
}
枚举
- 作用:增强代码的可读性
- 基本数据类型
- 关键字:
enum
- 书写时不能有
;
- 不能有
""
- 不能有中文
- 结果可以转为
uint
contract TestEnum{
enum Sex{Man,Woman}
function useEnum() public pure returns(Sex){
return Sex.Man;
}
function useenum() public pure returns(uint){
return uint(Sex.Woman);
}
}
事件与日志
- 合约中不能直接访问日志中的内容,可以通过sdk的方式进行交互 获取
- 日志通过事件来实现
- Solidity中,事件是操作触发行为,日志是触发事件后将数据记录在区块链中
事件
事件可以用来做操作记录,存储为日志。主要就是用来记录日志的
关键字:
-
event
:创建事件 -
emit
:触发事件
pragma solidity ^0.6.0;
contract TestEvent{
event LogEvent(string _name,uint _age); // 创建一个事件
function emitEvent() public{ // 不能加 pure or view
emit LogEvent("Muxue",17); // 触发事件
}
}
事件的主题
- 将事件索引化
- 一个没主题的事件,无法搜索到
- 一个事件,最多有4个主题
- 事件签名
- 参数签名
经过Keccak-256
算法加密
简单来说:主题就是为了讲事件索引化,可查询到这个事件
pragma solidity ^0.6.0;
contract TestEvent{
event LogEvent(string indexed _name,uint indexed _age); // 创建一个事件
function emitEvent() public{ // 不能加 pure or view
emit LogEvent("Muxue",17); // 触发事件
}
}
异常
- 程序编译或运行中发生的错误 即异常
- 发生运行时异常,会将之前修改的状态全部还原(0.6.0版本可以选择)
- solidity异常
- 0.4.10之前,throw 条件不满足,中断运行,恢复修改的状态,耗光gas
- 0.4.10之后,throw废弃,
require()
,assert()
,revert()
代替原来的throw - 0.6.0版本,增加了
try catch
- 功能介绍
① 条件检查
-require()
:还原状态 返回gas
-assert()
:还原状态 耗光gas
② 引发异常
-throw
:已经废弃
-revert()
:与thorw的区别,允许返回错误原因,可以退回gas
③ 捕获/处理异常
-try..catch
:只适合于外部调用,
pragma solidity ^0.6.0;
contract TestException{
uint public data = 100;
function testThrow(uint _i) public pure{
//if(_u<10) throw;
}
function testRequire(uint _i) public{
data = 200; // 假如下面引发异常了,会把修改的状态还原回去
require(_i>10,"_i < 10");
}
function testAssert(uint _i) public{
data = 200; // 假如下面引发异常了,会把修改的状态还原回去
assert(_i>10);
}
function testRevert(uint _i) public returns(uint){
data = 200; // 假如下面引发异常了,会把修改的状态还原回去
if(_i<10){
revert("_i < 10");
}
return 12;
}
event successEvent();
event failEvent();
function testTry(uint _i) external returns(uint){
try this.testRevert( _i ) returns(uint _value){ // 某个函数可能会出现异常, 必须外部调用所以加this. 返回值需要定义一个变量
// 如果没有异常走这个代码块
emit successEvent();
return _value;
}catch{
// 如果有异常走这个代码块
emit failEvent();
}
}
}