二、学习基础智能合约

作者: SegmentFault博客  更新时间:2020-07-06 23:29:13  原文链接


第一讲 介绍

1、 智能合约是什么?

智能合约并非现实中常见的合同,而是存在区块链上,可以被触发执行的一段程序代码,这些代码实现了某种预定的规则,是存在于区块链执行环境中的“自治代理”。智能合约需要被触发,代码才会执行,不被触发他的状态将会始终保持一个状态,并且部署后的智能合约将不可被修改。智能合约语言的语法和js脚本语言很像,因此有过js开发经验的小伙伴们学习起来会很快。

2、 编程工具的介绍。

我们都知道“预先善其事、必先利其器”的道理,现实中织布是需要织布机才能完成织布,同样的我们的智能合约学习也是要有编程工具的使用的。我们本套课程都将以 remix 为编程工具进行讲解课程。remix 就是我们手里的织布机,能织出什么布就看我们这些使用 remix 的程序员了。

地址为( http://remix.ethereum.org/ )可以直接在浏览器中开发,很方便,只要有一个 google chrome 谷歌浏览器就可以开发了。

remix 有几个主要的常用面板,分别是文件面板、编译器、运行器、以及占用最大一部分的文本编辑器组成。

文件面板:我们可以在这个面板进行创建文件、选择文件等管理文件的操作。

编译器:我们可以将sol文件进行编译,编译不通过将不能执行合约,并且可以得到code.json以及abi.json。我们可以将他们在支持sol语言的公链上运行。

运行器:可以将sol智能合约部署在eth链上,并且能对合约的方法进行调用等操作。

文本编辑器:我们的代码就是写在这个位置的,后面基本大部分时间你将面对的是这个页面。

3、 我的第一个智能合约程序

下面的智能合约是我们第一个合约程序,因此我们命名为 “FirstContract.sol” 文件名

pragma solidity ^0.6.0;

// first contract
contract FirstContract {
    // first method
    function firstMethod() public pure returns (string memory) {
        return 'I am first method !!!';
    }
}

上面代码很多小伙伴应该不是很懂什么意思,不懂没关系,我来给大家一行一行讲解。

pragma solidity ^0.6.0;

这一行是说明使用solidity 0.6.0版本写的,可以运行在0.6.0到0.7.0之间的版本上。

contract FirstContract {

这一句是定义一个叫 FirstContract 名称的合约。

function firstMethod() public pure returns (string memory){

这一行是定义一个方法叫做 firstMethod, 该方法有返回值,类型是 string 类型的。

return 'I am first method !!!';

这一行是这个方法将会返回 “I am first method !!!”。

看起来可能还是会有小伙伴们有不明白的地方,但是我们先只教这么多,关于什么是string,string 就是字符串的意思,字符串你就可以当作是任意的abcde这些字母等还有标点符号写在了单引号或者双引号中。这就是字符串最通俗易懂的解释了,小伙伴们,大家可以动手试试自定义一些字符串让他返回。

第二讲 智能合约结构

在solidity中,合约有点类似面向对象语言中的类,每个合约中包含状态变量、函数、函数变量、函数修饰器、事件、结构、和枚举类的声明,合约也可以继承其他的合约。大家可能对类和类中的结构的概念没有什么了解,我简单给大家举个例子。一个类可以比作是汽车,汽车里面的油就是变量,然后油门、刹车等就是函数,我们踩油门相当于调用类中的函数,汽车动起来,油减少,相当于变量值改变了。

我们来根据上面的描述写一个汽车的合约。先使用remix 创建一个CarContract1.sol文件,然后设定一个CarContract1名字的合约。汽车有了,还要有一个油箱,设定一个变量_gasoline,作为油箱。然后我们再给汽车加一个油门,写一个startUp函数作为油门。现在有了油箱但是不知道有多少油,再加gatGasoline函数作为一个仪表盘。咱们只有油箱没有油汽车也跑不了,在加一个加油的接口,给汽车加油,使用addGasoline函数进行加油。下面就是我们完整的小汽车的代码。

CarContract1.sol

pragma solidity ^0.6.0;

contract CarContract1 {
    uint256 _gasoline;
    
    function startUp() public {
        require(_gasoline >= 1, "gasoline is not haved");
        _gasoline = _gasoline - 1;
    }
    
    function getGasoline() public view returns(uint256 gasoline) {
        return _gasoline;
    }
    
    function addGasoline(uint256 gasoline) public {
        _gasoline = _gasoline + gasoline;
    }
    
}

1、 状态变量

小汽车合约中的_gasoline就是我们定义的状态变量,类型是 uint256 类型。 该变量是存储在链上的,也就是说他的数据是被保存起来的,每次改动都会记录下来。因此我们在进行调用 addGasoline 函数时,会给这个小汽车加油成功,_gasoline 的值会变化,同样的我们调用 startUp 函数时,_gasoline 的值也会变化。

2、 函数

在CarContract1小汽车中,startUp()、getGasoline()、addGasoline(uint256 gasoline) 都是函数。这些函数有的是没有参数的,又叫无参函数,比如:startUp()、getGasoline()。有的是有参数的,就叫有参函数,比如:addGasoline(uint256 gasoline)。这些函数,有的有返回值,有的没有返回值,根据具体场景来定,一般call操作都是有返回值的,call操作不会改变合约状态。只有send操作,才会进行改变合约的状态。

3、 函数变量

我们都知道加不同的型号汽油会有不一样的效果,我们来给汽车换下不同的型号汽油,在汽车上我们放置一个桶名字是_bucket,用来装另一个型号的汽油。如果我们自己的两个容器里面有一个是空的,我们可以直接进行转换汽油。但是我们自己的两个容器中都有油的时候,两个容器很明显不能进行交换汽油,这个时候我们需要向隔壁的老李借一个桶 __tempBucket,这样三个容器就能进行转换油箱里面的汽油和桶里面的汽油进行对换了,换完以后把桶还回去。

我们进行在进行造一个新的小汽车名字是CarContract2,增加一个桶,设定变量为_bucket,作为桶。还需要记录当前汽车的油的型号。设定变量 _gasolineType 为当前油类型,默认是 1类型。设定一个函数 changeGasolineType,进行交换汽油类型,在设定一个函数进行查看当前汽车的类型 getGasolineType 。至此我们小汽车升级成功。

CarContract2.sol

pragma solidity ^0.6.0;

contract CarContract2 {
    uint256 _gasoline;
    uint256 _bucket;
    int _gasolineType = 1;
    
    function startUp() public {
        require(_gasoline >= 1, "gasoline is not haved");
        _gasoline = _gasoline - 1;
    }
    
    function getGasoline() public view returns(uint256 gasoline) {
        return _gasoline;
    }
    
    function addGasoline(uint256 gasoline) public {
        _gasoline += gasoline;
    }
    
    function changeGasolineType() public {
        require(_gasoline != 0 || _bucket != 0, "can not change");
        
        if (_gasoline == 0) {
            _gasoline = _bucket;
            _bucket = 0;
        } else if (_bucket == 0) {
            _bucket = _gasoline;
            _gasoline = 0;
        } else {
            uint256 __tempBucket = _gasoline;
            _gasoline = _bucket;
            _bucket = __tempBucket;
        }
        
        _gasolineType = -1 * _gasolineType;
    }
    
    function getGasolineType() public view returns(int gasolineType) {
        return _gasolineType;
    }
    
}

上面的小汽车2代正式出炉,我来给大家讲下做了那些升级,首先我们的changeGasolineType内部定义了 __tempBucket 变量,该变量就是函数变量,是临时创建的并且不会被记录在链上的变量,也就是我们用完就还给隔壁老李了,还回去的时候桶是空的。

4、 函数修饰器

我们的小汽车还是很简单,我们在给他加一点东西,规定小汽车要想启动必须关闭车门。

下面我们再一次修改我们的小汽车,加一个_doorStatus状态变量作为我们的车门状态。再加连个函数getDoorStatus()、changeDoorStatus(),用来控制开门/关门并且查看门的状态。并且加入一个whenDoorClose()作为我们的判断器。

pragma solidity ^0.6.0;

contract CarContract3 {
    uint256 _gasoline;
    uint256 _bucket;
    int _gasolineType = 1;
    
    bool _doorStatus;
    
    modifier whenDoorClose() {
        require(!_doorStatus, "door is not close");
        _;
        
    }
    
    function startUp() public whenDoorClose {
        require(_gasoline >= 1, "gasoline is not haved");
        _gasoline = _gasoline - 1;
    }
    
    function getGasoline() public view returns(uint256 gasoline) {
        return _gasoline;
    }
    
    function addGasoline(uint256 gasoline) public {
        _gasoline += gasoline;
    }
    
    function changeGasoline() public {
        require(_gasoline != 0 || _bucket != 0, "can not change");
        
        if (_gasoline == 0) {
            _gasoline = _bucket;
            _bucket = 0;
        } else if (_bucket == 0) {
            _bucket = _gasoline;
            _gasoline = 0;
        } else {
            uint256 __tempBucket = _gasoline;
            _gasoline = _bucket;
            _bucket = __tempBucket;
        }
        
        _gasolineType = -1 * _gasolineType;
    }
    
    function getGasolineType() public view returns(int gasolineType) {
        return _gasolineType;
    }
    
    
    function getDoorStatus() public view returns(bool doorStatus) {
        return _doorStatus;
    }
    
    function changeDoorStatus() public {
        _doorStatus = ! _doorStatus;
    }
}

上面我们的3代小汽车已经完成了,whenDoorClose() 就是我们定义的函数修饰器 使用modifier 来定义的。

5、 事件

每次都到没有油了才去加油,我们加一个功能,当行驶时油量低于5的时候我们要进行预警。

我们加入一个 gasolineAlarm 事件,该事件有一个参数,当前的油量。这样我们在启动的函数中加入这个事件的调用,判断本次使用后的油量是否小于等于5,是的话进行调用该事件

pragma solidity ^0.6.0;

contract CarContract4 {
    uint256 _gasoline;
    uint256 _bucket;
    int _gasolineType = 1;
    
    bool _doorStatus;
    
    modifier whenDoorClose() {
        require(!_doorStatus, "door is not close");
        _;
        
    }
    
    event gasolineAlarm(uint256 gasoline);
    
    function startUp() public whenDoorClose {
        require(_gasoline >= 1, "gasoline is not haved");
        _gasoline = _gasoline - 1;
        if (_gasoline <= 5) {
            emit gasolineAlarm(_gasoline);
        }
    }
    
    function getGasoline() public view returns(uint256 gasoline) {
        return _gasoline;
    }
    
    function addGasoline(uint256 gasoline) public {
        _gasoline += gasoline;
    }
    
    function changeGasoline() public {
        require(_gasoline != 0 || _bucket != 0, "can not change");
        
        if (_gasoline == 0) {
            _gasoline = _bucket;
            _bucket = 0;
        } else if (_bucket == 0) {
            _bucket = _gasoline;
            _gasoline = 0;
        } else {
            uint256 __tempBucket = _gasoline;
            _gasoline = _bucket;
            _bucket = __tempBucket;
        }
        
        _gasolineType = -1 * _gasolineType;
    }
    
    function getGasolineType() public view returns(int gasolineType) {
        return _gasolineType;
    }
    
    function getDoorStatus() public view returns(bool doorStatus) {
        return _doorStatus;
    }
    
    function changeDoorStatus() public {
        _doorStatus = ! _doorStatus;
    }
}

我们已经更新到第四代小汽车了,四代小汽车的gasolineAlarm 就是我们定义的事件,事件是会在虚拟机上记录一条日志的,我么可以通过查询日志的方式得到事件内容。

6、 结构

我们的汽车感觉成熟了,这个时候我们要给我们的汽车打上一些特性,比如颜色,比如车轮数,比如车门数等等。

我们在小汽车里面加入CarInfo结构体,里面可以定义color颜色,wheelNum 车轮数等等,然后我们加入设置和获取的函数:setCarInfo()、getCarInfo(), 这样我们的小汽车就有了一些参数了。

pragma solidity ^0.6.0;

contract CarContract5 {
    uint256 _gasoline;
    uint256 _bucket;
    int _gasolineType = 1;
    bool _doorStatus;
    
    struct CarInfo {
        string color;
        uint8 wheelNum;
    }
    
    CarInfo _carInfo;
    
    modifier whenDoorClose() {
        require(!_doorStatus, "door is not close");
        _;
        
    }
    
    event gasolineAlarm(uint256 gasoline);
    
    function startUp() public whenDoorClose {
        require(_gasoline >= 1, "gasoline is not haved");
        _gasoline = _gasoline - 1;
        if (_gasoline <= 5) {
            emit gasolineAlarm(_gasoline);
        }
    }
    
    function getGasoline() public view returns(uint256 gasoline) {
        return _gasoline;
    }
    
    function addGasoline(uint256 gasoline) public {
        _gasoline += gasoline;
    }
    
    function changeGasoline() public {
        require(_gasoline != 0 || _bucket != 0, "can not change");
        
        if (_gasoline == 0) {
            _gasoline = _bucket;
            _bucket = 0;
        } else if (_bucket == 0) {
            _bucket = _gasoline;
            _gasoline = 0;
        } else {
            uint256 __tempBucket = _gasoline;
            _gasoline = _bucket;
            _bucket = __tempBucket;
        }
        
        _gasolineType = -1 * _gasolineType;
    }
    
    function getGasolineType() public view returns(int gasolineType) {
        return _gasolineType;
    }
    
    
    function getDoorStatus() public view returns(bool doorStatus) {
        return _doorStatus;
    }
    
    function changeDoorStatus() public {
        _doorStatus = ! _doorStatus;
    }
    
    function setCarInfo(string memory color, uint8 wheelNum) public {
        _carInfo.color = color;
        _carInfo.wheelNum = wheelNum;
        
        //_carInfo = CarInfo(color, wheelNum);

    }
    
    function getCarInfo() public view returns(string memory color, int wheelNum) {
        color = _carInfo.color;
        wheelNum = _carInfo.wheelNum;
    }
}

我们的5代小汽车加入了CarInfo就是结构体,结构体中不能进行设置初值,我们能把一类的属性等进行分类的放在结构体中,可以充当我们的数据模型。

7、 枚举类

我们的小汽车想要开门,需要打开车锁,车锁是一种状态,开/关。

我们加入枚举类DoorSwitch,定义两个状态open,close 。在定义whenDoorSwitch函数修饰器,进行判断。

pragma solidity ^0.6.0;

contract CarContract6 {
    uint256 _gasoline;
    uint256 _bucket;
    int _gasolineType = 1;
    bool _doorStatus;
    
    enum DoorSwitch{ open, close }
    
    DoorSwitch _doorSwitch;
    
    struct CarInfo {
        string color;
        uint8 wheelNum;
    }
    
    CarInfo _carInfo;
    
    modifier whenDoorClose() {
        require(!_doorStatus, "door is not close");
        _;
        
    }
    
    modifier whenDoorSwitch() {
        if (!_doorStatus) {
            require(_doorSwitch == DoorSwitch.open, "door switch is close");
        }
        _;
    }
    
    event gasolineAlarm(uint256 gasoline);
    
    function startUp() public whenDoorClose {
        require(_gasoline >= 1, "gasoline is not haved");
        _gasoline = _gasoline - 1;
        if (_gasoline <= 5) {
            emit gasolineAlarm(_gasoline);
        }
    }
    
    function getGasoline() public view returns(uint256 gasoline) {
        return _gasoline;
    }
    
    function addGasoline(uint256 gasoline) public {
        _gasoline += gasoline;
    }
    
    function changeGasoline() public {
        require(_gasoline != 0 || _bucket != 0, "can not change");
        
        if (_gasoline == 0) {
            _gasoline = _bucket;
            _bucket = 0;
        } else if (_bucket == 0) {
            _bucket = _gasoline;
            _gasoline = 0;
        } else {
            uint256 __tempBucket = _gasoline;
            _gasoline = _bucket;
            _bucket = __tempBucket;
        }
        
        _gasolineType = -1 * _gasolineType;
    }
    
    function getGasolineType() public view returns(int gasolineType) {
        return _gasolineType;
    }
    
    
    function getDoorStatus() public view returns(bool doorStatus) {
        return _doorStatus;
    }
    
    function changeDoorStatus() public {
        _doorStatus = ! _doorStatus;
    }
    
    function setCarInfo(string memory color, uint8 wheelNum) public {
        _carInfo.color = color;
        _carInfo.wheelNum = wheelNum;
        
        //_carInfo = CarInfo(color, wheelNum);
    }
    
    function getCarInfo() public view returns(string memory color, int wheelNum) {
        color = _carInfo.color;
        wheelNum = _carInfo.wheelNum;
    }
    
    function setDoorSwitch(DoorSwitch doorSwitch) public {
        _doorSwitch = doorSwitch; 
    }
}

我们已经更新到6代小汽车了,在6代小汽车中我们加入了DoorSwitch车门的开关,使用的就是枚举定义的,在实际项目中枚举定义的话,一般使用在状态和类型的定义上,方便进行管理。

到此我们的小汽车已经完成了,经历了6代的更新,相信大家对于本节课程有空了一定的了解了。智能合约包含的状态变量、函数、函数变量、函数修饰器、事件、结构、枚举类都已经在制作和升级小汽车中使用了。

第三讲 数据类型

在solidity中有专门的数据类型,什么是数据类型呢,我们可以认为数字是类型,是否是类型,地址是类型等。在solidity中存在以下常用的类型,uint/int、bool、 address、 mapping、bytes、string、fixed/ufixed 常用的类型。

pragma solidity ^0.6.0;


contract Test {
    uint v1 = 1;
    int v2 = -2;
    bool v3 = true;
    address v4 = 0x8a5fa31F2bf83812ECd8E5Ef1878dD12bBaDb40C;
    mapping(uint => uint) v5;
    bytes v6 = "0x123"; 
    string v7 = "asd";
    fixed v8 = 1.3;
    ufixed v9 = 1.2;
}

1、 uint/int类型

uint/int 类型都是整型,也就是都是整数。1、2、3、4类似的数没有小数点。区别是uint是没有符号的。看代码中的 v1 就是无符号整数,v2就是有符号整数。默认是0

2、bool

bool 中文是布尔类型,表示是/否的类型。v3就是bool类型,只有两个值 true/false,默认是false

3、address

address是地址类型,存储地址的,账户地址,合约地址等,都可以。

4、mapping

映射类型示例中的代码不是很全,我来解释下,实际上他是一个key-value模型,也就是一个key对应一个value。比如我们说小明,我们就是到小明这个人。就是这个道理。

5、bytes

定长数组,就是说他的长度是固定的,不能改变,设定是多少就是多少。

6、string

字符串类型,可以放字符串,长度是255位二级制数。

7、fixed/ufixed

浮点类型,就是带有小数点的,1.2、1.4、1.9这类数值。也是分为有无符号项。

第四讲 控制语句

在solidity中可以使用以下控制结构,有 if,else,while,for,break,continue,return,? : 我们来以此介绍这些结构。

1、if else语句

大家肯定好奇什么是if else语句。他就是我们说的如果 否者。也就是说如果我怎么样否者我怎么样。注意else只能与if一同使用,大家一起来看下面代码:

pragma solidity ^0.6.0;


contract Test {
    uint256 temp;
    
    function test1(bool doorSwitch) public {
        if (doorSwitch) {
            temp = 1;
        }
    }
    
    function test2(bool doorSwitch) public {
        if (doorSwitch) {
            temp = 1;
        }
        temp = 2;
    }
    
    function test3(bool doorSwitch) public {
        if (doorSwitch) {
            temp = 1;
        } else {
            temp = 2;
        }
    }
    
    function getTemp() public view returns(uint256){
        return temp;
    }
}

上面代码中我们定义了三个测试方法,以及一个获取temp值的方法。第一个测试方法表示如果我们传进去的doorSwitch是true,门是开的,那么temp就等于1,否者doorSwitch等于false的话temp值不变。第二个测试方法表示如果我们传进去的doorSwitch是true,门是开的,那么temp就先等于1,然后等于2,否者doorSwitch等于false的话temp直接等于2。第三个测试方法表示如果我们传进去的doorSwitch是true,门是开的,那么temp就等于1,否者doorSwitch等于false的话temp等于2。

2、while语句

while 语句是循环语句,表示满足条件就一直循环,一般我么会和break来使用,当达到某种情况进行跳出循环,可以让循环体自动结束循环。

pragma solidity ^0.6.0;


contract Test {
    function test1() public {
        int a = 1;
        while(true) {
            if (a == 5) {
                break;
            }
            a++;
        }
    }
    
    function test2() public {
        int a = 1;
        while(a != 5) {
            
            a++;
        }
    }
}

上面两个函数内部都有一个循环,实际上两个循环的效果是一样的,有一些细微的差别,需要根据具体业务场景分析了,不建议使用while 一不小心死循环就不好玩了。

3、for循环

for循环概念直接上代码大家好理解些。

pragma solidity ^0.6.0;


contract Test {
    function test1() public {
        for(uint8 i = 0; i < 10; i ++) {
            ...
        }
    }
}

上面就是一个常用的for循环使用方式,循环10次 “...” 的内容。

4、continue 语句

continue语句是跳过本次循环进入下一次循环,来看代码:

pragma solidity ^0.6.0;


contract Test {
    function test1() public {
        for(uint8 i = 0; i < 10; i ++) {
            if (i == 3) {
                continue;
            }
            
            ...
        }
    }
}

上面的代码是如果i = 3后跳过这次循环。

5、return 语句

return 是返回的命令当遇到return时就是表示结束了。看代码:

pragma solidity ^0.6.0;


contract Test {
    function test1() public {
        for(uint8 i = 0; i < 10; i ++) {
            if (i == 3) {
                return;
            }
            
            ...
        }
    }
}

上面的代码可以看出,运行到i = 3时,程序直接结束。

6、 结束

到此我们的控制语句基本学习结束。这些概念每个语言都差不多,所以很重要。多练小伙伴!

结束

学习完上面的课程大家对于智能合约基本已经学习好了,但是还是缺乏使用,只是知道有啥东西,什么时候用不知道,这个时候需要大家进行锻炼了,多写一些小东西练习。