Solidity

起因是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. 区分大小写

数据类型

基本类型

  1. 布尔(bool)
  2. 整形(int / uint)
  3. 地址(address)
  4. 定长字节数组
  5. 定长浮点(fixed / ufixed)
  6. 枚举类型(enum)
  7. 函数类型(function)

引用类型

  1. 数组(array) {不定长字节数组, 字符串}
  2. 结构体(struct)
  3. 映射(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也没报错)
Solidity

对于存储型的数组(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等关键字的

账户和地址

分为外部账户合约账户, 外部账户地址, 外部合约地址

也就是外部账户和外部合约都各有地址

外部账户:我们钱包的一个用户 给你一个地址 就是一个外部账户
Solidity
合约账户:我们部署一个合约 都会给我们一个地址
Solidity

地址
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{
        
    }
}

transfersend的区别:

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;
    }
    
}

Solidity

错误处理

最常用的应该就是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:只有外部可以调用,只有函数可以用
    Solidity
    Solidity

可以明显的看出 被privateinternal修饰的函数 外部不可以调用

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以上的版本 不支持重写了
Solidity

修改器 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一步步的调着看

Solidity

合约

构造函数

  • 合约创建时自动调用,且只调用一次。每个合约只能有一个构造函数
  • 构造函数一般是为状态变量初始化
  • constructor 关键字
  • 分为有参和无参
pragma solidity ^0.6.0;

contract TestNewContract{
    uint age;
    constructor(uint _age) public{
        age = _age;
    }    
}

封装性

  1. 函数合约

  2. 修饰符(访问修饰符)

    • public, private , internal, external

      • 状态变量:publicprivate, internal
      • 函数:publicprivate, 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传给第一个参数
    }
}

合约销毁

合约生命周期

  1. 合约创建(new、sdk)
  2. 合约操作、使用(调用函数实现功能)
  3. 合约销毁(区块链上 关于合约的存储和代码都会被删除)
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);   // 销毁合约
    }
    
}

映射

引用数据类型

关键字及特性

  1. mapping
  2. 存储一对数据,以key-value形式的
  3. key的数据类型是有要求的:动态数组,枚举,structmapping都不可以;value基本上都可以
  4. 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;
        
    }
}

结构体

引用数据类型

关键字及特性

  1. 关键字struct
  2. 大部分类型都可以,但是不可以包含自己本身
  3. 结构体作为函数的返回值类型使用时
    • 结构体中有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;
    }
    
}

枚举

  1. 作用:增强代码的可读性
  2. 基本数据类型
  3. 关键字:enum
  4. 书写时不能有;
  5. 不能有""
  6. 不能有中文
  7. 结果可以转为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);
    }
}

事件与日志

  1. 合约中不能直接访问日志中的内容,可以通过sdk的方式进行交互 获取
  2. 日志通过事件来实现
  3. 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);  // 触发事件
    }
}

Solidity

事件的主题

  1. 将事件索引化
  2. 一个没主题的事件,无法搜索到
  3. 一个事件,最多有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);  // 触发事件
    }
}

Solidity

Solidity

异常

  1. 程序编译或运行中发生的错误 即异常
  2. 发生运行时异常,会将之前修改的状态全部还原(0.6.0版本可以选择)
  3. solidity异常
    • 0.4.10之前,throw 条件不满足,中断运行,恢复修改的状态,耗光gas
    • 0.4.10之后,throw废弃,require() ,assert(), revert()代替原来的throw
    • 0.6.0版本,增加了try catch
  4. 功能介绍
    条件检查
    - 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();
        }
    }
}
上一篇:Solidity和智能合约


下一篇:数字货币智能合约的概念与演变