79solidity语言学习——2020年07月22日12:49:06

Posted oneapple

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了79solidity语言学习——2020年07月22日12:49:06相关的知识,希望对你有一定的参考价值。

79、solidity

2020年07月21日16:26:53

一、Solidity概述

特点:

  1. 比较简单,没有多线程的概念
  2. 类似javascript
  3. 不成熟,有一写bug

1. 文档

本文参考文档

官方中文文档

官方英文文档

2. 合约包含的基本元素

//指定编译器版本,版本标识符
pragma solidity ^0.4.17;


//关键字 contract 跟java的class一样  智能合约是Inbox      
contract Inbox{
    //string 是数据类型,message是成员变量,在整个智能合约生命周期都可以访问
    //public 是访问修饰符,是storage类型的变量,成员变量和是全局变量
    string public message

    //函数以function开头,构造函数
    function Inbox (string initMessage) public {
        //本地变量
        var tmp = initMessage;
        message = tmp;
    }

    //view是修饰符,表示该函数仅读取成员变量,不做修改
    function getMessage() public view returns(string) {
        return message;
    }
}

技术图片

2. 合约的生命周期

//析构函数
function destroy() {
    //销毁当前合约,并把它所有资金发送到给定的地址
    selfdestruct(msg.sender);
}

调用之后,合约仍然存在于区块链之上,但是函数无法被调用,调用会抛出异常。//TODO

二、数据类型分类

  1. 值类型(值传递)
  2. 引用类型(指针传递), 没有*号操作符,而是使用两个关键字来表示
    1. memory(值类型)
    2. storage(引用类型)

1. 值类型

值类型是指变量在传递过程中是将数值完整的拷贝一份,再赋值给新的变量,这种方式需要开辟新的内存空间,效率较低,两个变量完全独立,修改一个不会影响另外一个。

值类型包含

  • 布尔(Booleans)
  • 整型(Integer)
  • 地址(Address) 《- - 没见过 0xd5957914c31E1d785cCC58237d065Dd25C61c4D0
  • 定长字节数组(fixed byte arrays) // var b1 [10]byte , bytes10
  • 有理数和整型(Rational and Integer Literals,String literals)
  • 枚举类型(Enums)
  • 函数(Function Types)

2. 引用类型

举例: string storage str1

solidity没有指针,对于复杂的结构进行高效传递方式(相当于指针)是使用关键字storage进行修饰。

复杂类型,占用空间较大的。在拷贝时占用空间较大。所以考虑通过引用传递。常见的引用类型有:

  • 字符串(string)
  • 不定长字节数组(bytes)
  • 数组(Array)
  • 结构体(Structs)

三、值类型介绍

0. 状态变量vs局部变量

定义在合约之内,但是在函数之外的变量,我们叫做状态变量,这些变量是会上传到区块链上保存的。

下面这个合约中的message就是状态变量

solidity语言没有main函数,只要合约部署到区块链上,就会永不停歇的执行。

pragma solidity ^0.4.24;

contract Inbox{
    //状态变量
    string public message; 

    function Inbox()payable {

    }

    function setMessage(string newMessage) public{
        //局部变量
        string memory tmp= "hello";
        message = newMessage;
    }

    function getMessage() public constant returns(string){
        return message;
    }
}

1. 布尔

go语言: var flag bool , flag := true

bool flag1 ;
bool flag2 = false;

2. 整型

  • int(有符号整型,有正有负)
  • uint(无符号整型,无负数)
  • 以8位为区间,支持int8,int16,int24 至 int256,uint同理。 int默认为int256,uint默认为uint256

创建:01.integer.sol

如果是int和uint进行相加如何处理?

pragma solidity ^0.4.24;

contract test1 {

    int8 public i8 = 333; //成员变量就是状态变量
    int i256 = 256;

    function add() constant returns(int) {
        return i8 + i256;
    }

   function isEqual(int a, int b) public pure returns(bool) {
       return a == b;
   }
}

3. 函数类型

函数类型也就是我们所说的函数,本身也是一个特殊的变量,它可以当做变量赋值当做函数参数传递当做返回值

- 函数声明

函数名,函数签名(返回值,参数类型,修饰符)

技术图片

!- 几个非常非常非常重要的关键字

修饰符 说明
public 公有,任何人(拥有以太坊账户的)都可以调用 牢记
private 私有, 只有智能合约内部可以调用 牢记
external 仅合约外部可以调用,合约内部需使用this调用 先忽略
internal 仅合约内部和继承的合约可以调用 先忽略
view/constant 函数会读取但是不会修改任何contract的状态变量 牢记
pure(纯净的) 函数不使用任何智能合约的状态变量 牢记
payable 调用函数需要付钱,钱付给了智能合约的账户 牢记
returns 返回值函数声明中使用 牢记

- 访问可见性

- public、private

修饰为public的状态变量会默认生成一个同名的public函数

状态变量默认是internal的(先理解为private即可)

创建:02.publiPrivate可见性.sol

pragma solidity ^0.4.24;


contract  Test {
    //状态变量
    //类型不匹配时需要显示转换类型
    //返回值需要使用returns描述


    //public/private 可以修饰状态变量
    //状态变量默认是私有的
    uint256 public ui256 = 100;

    int8 private i10 = -10;


    //private 修饰的函数为私有的,只有合约内部可以调用
    function add() private view returns(uint256) {
        return ui256 + uint256(i10);
    }


    function isEqueal() public view returns(bool) {
        return ui256 == uint256(i10);
    }

    //Public修饰的函数为共有的,合约内外都可以调用
    function Add() public view returns(uint256){
        return add();
    }
}

- 学员问题

  1. contract 误写为 constant

  2. 忘记添加分号

  3. 修改代码后要重新create(重新部署合约),新版本(deploy)

  4. compile一直是红色的,提示:Compiler not found

    技术图片

- view,constant,pure讲解

  1. 如果一个函数里面,访问了状态变量,但是没有修改,我们使用view或者constant修饰。
  2. 如果访问了状态变量,而且修改了,那么就不能constant和view,否则会报错,不修饰即可。
  3. 如果没有使用过状态变量,我们要修饰为pure。
  4. 如果你修饰为constant,但是你在函数中修改了,效果是:不会报错,正常执行,但是值不会改变

创建:03.constantViewPure.sol

pragma solidity ^0.4.24;


contract test1 {

    int8 public i8 = 100; //成员变量就是状态变量
    int i256 = 256;

    //表示不会修改函数内的状态变量
    //为了明确语义,一般要加上constant(view两者完全相同)
    function add() private constant returns(int) {
        return i8 + i256;
    }

    //public 表示所有的人都可以看到的,而且可以调用
    //private表示所有人都可以看到,但是无法调用
    function mins() constant returns(uint256) {
        return  uint256(i256 - i8);
    }

   function isEqual(int a, int b) public pure returns(bool) {
       return a == b;
   }

    function setValue(int8 num) {
        i8 = num;
    }

    function setValue1(int8 num) constant {
        i8 = num;
    }
}

- payable

  1. 任何函数,只要修饰为payable,那么就可以在调用这个方法的时候,对value字段赋值,然后将价值value的钱转给合约。
  2. 若这个函数没有指定payable,但是对value赋值了,那么本次调用会报错。

创建:04.payable向合约转账.sol

pragma solidity ^0.4.24;


contract test1 {
    uint128 public num;

    //如果构造函数中未指定payable关键字,那么创建合约时不允许转账
    //如果指定了payable,则可以转账
    constructor() public  {

    }

    //任何函数,只要指定了payable关键字,这个合约就可以接受转账,调用时,也可以转0
    function giveMoney() public payable {

    }
}

- 构造函数

仅在部署合约时调用一次,完成对合约的初始化。可以在创建合约时转钱到合约

相当于go里面的init函数

  1. 合约同名函数(已废弃)
  2. constructor关键字修饰(推荐)

学员问题:

技术图片 技术图片

注意,所有在合约内的东西对外部的观察者来说都是可见,将某些东西标记为private仅仅阻止了其它合约来进行访问和修改,但并不能阻止其它人看到相关的信息。

- 匿名函数

  • 用于转账

一个合约可以有且只有一个匿名函数,此函数不能有参数,也不能有任何返回值,当我们企图去执行一个合约上没有的函数时,那么合约就会执行这个匿名函数。

当合约在只收到以太币的时候,也会调用这个匿名函数,而且一般情况下会消耗很少的gas,所以当你接收到以太币后,想要执行一些操作的话,你尽可以把你想要的操作写到这个匿名函数里,因为这样做成本非常便宜。

//如果想向合约转账,在合约中添加如下函数即可
function() payable {
    //函数体什么都不填
}
  • 用于处理不存在的函数
  • 合约之间调用,非js调用
contract Test {
  function() { x = 1; }
  uint x;
}

contract Caller {
  function callTest(address testAddress) {
    Test(testAddress).call(‘0xabcdefgh‘); // hash does not exist
    // results in Test(testAddress).x becoming == 1.
  }
}

4. 地址(Address)

- 概述

以太坊地址的长度,大小20个字节,20 * 8 = 160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,通过合约的地址串,调用合约内的函数。

- 运算符

描述 符号
比较运算符 <=,<,==,!=,>=,>

- 操作

属性/方法 含义 备注
balance 获取余额 属性,其余的都是方法
send 转账 不建议使用
transfer 转账 建议使用
call 合约内部调用合约
delegatecall 调底层代码,别用
callcode 调底层代码,别用

技术图片

- 余额(balance)

返回指定地址的余额

创建:05.address地址.sol

pragma solidity ^0.4.24;


contract  Test {


    address public addr1 = 0x0014723a09acff6d2a60dcdf7aa4aff308fddc160c;

    //地址address类型本质上是一个160位的数字

    //可以进行加减,需要强制转换
    function add() public view returns(uint160) {
        return uint160(addr1) + 10;
    }


    //1. 匿名函数:没有函数名,没有参数,没有返回值的函数,就是匿名函数
    //2. 当调用一个不存在的方法时,合约会默认的去调用匿名函数
    //3. 匿名函数一般用来给合约转账,因为费用低
    function () public  payable {

    }


    function getBalance() public view returns(uint256) {
        return addr1.balance;
    }


    function getContractBalance() public view returns(uint256) {
        //this代表当前合约本身
        //balance方法,获取当前合约的余额
        return address(this).balance;
    }

}

- 合约地址(this)

如果只是想返回当前合约账户的余额,可以使用this指针,this表示合约自身的地址

pragma solidity ^0.4.0;

contract addressTest{
    function getBalance() constant public returns  (uint){
        //return addr.balance;
        return this.balance; // <<----此处使用this代替
    }
}

- 转账(send,transfer)

send和transfer函数提供了由合约向其他地址转账的功能。

对比项 send transfer 备注
参数 转账金额 转账金额 wei单位
返回值 true/false 无(出错抛异常) transfer更安全

创建:06.transfer和send.sol

pragma solidity ^0.4.24;


contract  Test {


    address public addr0 = 0x00ca35b7d915458ef540ade6068dfe2f44e8fa733c;
    address public addr1 = 0x0014723a09acff6d2a60dcdf7aa4aff308fddc160c;


    //1. 匿名函数:没有函数名,没有参数,没有返回值的函数,就是匿名函数
    //2. 当调用一个不存在的方法时,合约会默认的去调用匿名函数
    //3. 匿名函数一般用来给合约转账,因为费用低
    function () public  payable {

    }

    function getBalance() public view returns(uint256) {
        return addr1.balance;
    }

    function getContractBalance() public view returns(uint256) {
        return address(this).balance;
    }

    //由合约向addr1 转账10以太币
    function transfer() public {
        //1. 转账的时候单位是wei
        //2. 1 ether = 10 ^18 wei (10的18次方)
        //3. 向谁转钱,就用谁调用tranfer函数
        //4. 花费的是合约的钱
        //5. 如果金额不足,transfer函数会抛出异常
        addr1.transfer(10 * 10 **18);
    }

    //send转账与tranfer使用方式一致,但是如果转账金额不足,不会抛出异常,而是会返回false
    function sendTest() public {
        addr1.send(10 * 10 **18);
    }
}

- call方法

5. 枚举类型(Enums)

  • 枚举类型是在Solidity中的一种用户自定义类型。
  • 枚举可以显示的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运行时检查数值范围,如果不匹配,将会引起异常。
  • 枚举类型应至少有一名成员,枚举元素默认为uint8,当元素数量足够多时,会自动变为uint16,第一个元素默认为0,使用超出范围的数值时会报错。

创建:07.enum枚举.sol

pragma solidity ^0.4.0;

contract test {

    enum WeekDays {
        Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
    }

    WeekDays currentDay;
    WeekDWys defaultday = WeekDays.Sunday;

    function setDay(WeekDays _day) public {
        currentDay = _day;
    }

    function getDay() public view returns(uint256) {
        return uint256(currentDay);
    }

    function getDefaultDay() public view returns(uint256) {
        return uint256(defaultday);   
    }
}

6. 字节数组

[]byte go -> bytes(后面讲)

- 定长的字节数组

solidity内置了一些数组的数据类型:(和go语言做一下对比, var b8 [8]byte),完全只读

  • bytes1, ... ,bytes32,允许值以步长1递增。
  • byte默认表示bytes1,byte是类型,bytes是类型,bytes1是内置数组
  • bytes1只能存储1个字节,即8位的内容,bytes2最多只能存储2个字节,即16位的内容。以此类推...
  • 长度可以读取 length(返回bytes5类型的长度,而不是赋值的长度)
  • 长度不可以修改
  • 可以通过下标访问
  • 内容不可修改

支持运算:

描述 符号
比较运算 <=,<,==,!=,>=,>
位运算符 &, ,^(异或),~非
下标访问 [0,n),n表示长度

内置成员:length,返回数组长度

存储方式:16进制ascii码

创建:08.内置定长数组byte1.sol

pragma solidity ^0.4.2;

//bytes1
contract fixedArray {

    /*
    1. 长度可以读取 length
    2. 长度不可以修改
    3. 可以通过下标访问
    4. 内容不可修改   
    */
    //bytes1 ... bytes32

    //bytes1 b1 = "xy";
    bytes2 b2 = "xy";

    bytes3 public b3 = "xy";

    uint public len = b3.length;

    //b3.length = 10;

    bytes8 b8 = "12345678";

    //b8_0返回0x31,即十进制的数字1的ascii值(3*16+1=49)
    bytes1 public b8_0 = b8[0];

    //b = "HELLO";ERROR,定义之后不可修改
    //b8[1] = "0";
    //b8= "4567";
}

- 动态大小的字节数组(见后面章节)

bytes: 动态长度的字节数组(非值类型)

string: 动态长度的UTF-8编码的字符类型(非值类型)

一个好的使用原则是: bytes用来存储任意长度的字节数据,string用来存储任意长度的UTF-8编码 的字符串数据。 如果长度可以确定,尽量使用定长的如byte1到byte32中的一个,因为这样更省空间。

四、 引用类型介绍

1. 不定长字节数组(bytes)

bytes -> []byte

  • 动态字节数组
  • 引用类型(表明可以使用storage来修饰,进行引用传递,指针的效果)
  • 支持下标索引
  • 支持lengthpush方法(push会帮助分配空间的)
  • 可以修改
  • 以十六进制格式赋值: ‘h‘ -> 0x68 -> 104
  • 格外注意:对于bytes,如果不使用下标访问,那么可以不用先申请空间, 直接赋值即可,或者直接push

注意的坑:

旧版本的remix可以直接在remix中使用"helloworld"形式给bytes赋值,新版本不允许,必须使用0x格式

例如,如果函数类型为:byte b1, 那么赋值时需要输入的格式为: "h"(旧版本), 0x68(新版本)

创建:09.动态字节数组.sol

pragma solidity ^0.4.24;


contract  Test {

    bytes public name;


    function getLen() public view returns(uint256) {
        return name.length;
    }

    //1. 可以不分空间,直接进行字符串赋值,会自动分配空间
    function setValue(bytes input) public {
        name = input;
    }

    //2. 如果未分配过空间,使用下标访问会访问越界报错
    function getByIndex(uint256 i) public view returns(byte) {
        return name[i];
    }

    //3. 可以设置长度,自动分配对应空间,并且初始化为0
    function setLen(uint256 len) public {
        name.length = len;
    }


    //4.可以通过下标进行数据修改
    function setValue2(uint256 i) public {
        name[i] = "h";
    } 

    //5. 支持push操作,在bytes最后面追加元素
    function pushData() public {
        name.push("h");
    }

}

2. 字符串(string)

  • 动态尺寸的UTF-8编码字符串,是特殊的可变字节数组
  • 引用类型
  • 不支持下标索引
  • 不支持length、push方法
  • 可以修改(需通过bytes转换)

创建:10.string字符串.sol

pragma solidity ^0.4.24;


contract  Test {

    string public name = "lily";   


    function setName() public {
        bytes(name)[0] = "L";   
    }

    function getLength() public view returns(uint256) {
        return bytes(name).length;
    }

    function setLength(uint256 i) public {
        bytes(name).length = i;

        bytes(name)[i - 1] = "H";
    } 
}

设置name 长度10,name结果

{ "0": "string: Lilyu0000u0000u0000u0000u0000u0000" }

3. 数据位置(Data location)

复杂类型,不同于之前值类型,占的空间更大,超过256字节,因为拷贝它们占用更多的空间,如数组(arrays)数据结构(struct),他们在Solidity中有一个额外的属性,即数据的存储位置:memorystorage

- 内存(memory)

  • 数据不是永久存在的,存放在内存中,越过作用域后无法访问,等待被回收。
  • 被memory修饰的变量是直接拷贝,即与上述的值类型传递方式相同。

- 存储 (storage)

  • 数据永久保存在。
  • 被storage修饰的变量是引用传递,相当于只传地址,新旧两个变量指向同一片内存空间,效率较高,两个变量有关联,修改一个,另外一个同样被修改。
  • 只有引用类型的变量才可以显示的声明为storage

- 状态变量

状态变量总是stroage类型的,无法更改

- 局部变量

默认是storage类型(仅限数据结构或数组,string),但是可以声明为memory类型。

技术图片

创建:11.storageVsMemory.sol

pragma solidity ^0.4.24;


contract  Test {
    string public name = "lily";
    uint256 public num = 10;



    function call1() public {
        setName(name);    
    }


    //对于引用类型数据,作为函数参数时,默认是memory类型(值传递)
    //function setName(string input) private {
    function setName(string memory input) private {
        num = 20;
        bytes(input)[0] = "L";
    }

    function call2() public {
        setName2(name);
    }

    //2. 如果想引用传递,那么需要明确指定为stroage类型
    function setName2(string storage input) private {
        num = 30;
        bytes(input)[0] = "L";
    }

    //如果局部变量是string,数组,结构体类型数据,默认情况下是storage类型
    function localTest() public {
        //string tmp = name;
        string storage tmp = name;
        num = 40;
        bytes(tmp)[0] = "L";
    }

    function localTest1() public {

        //也可以明确设置为memory类型
        string memory tmp = name;
        num = 50;
        bytes(tmp)[0] = "L";
    }
}

4. 转换(bytes1/bytes/string)

技术图片

综合示例:

创建:12.bytesStringbyte1相互转化.sol

pragma solidity ^0.4.24;


contract  Test {

    bytes10 public b10 = 0x68656c6c6f776f726c64; //helloworld

    bytes public bs10 = new bytes(b10.length);

    //将固定长度数组的值赋值给不定长度数组
    function fixedByteToBytes() public {
        //bs10 = b10;
        for (uint256 i = 0; i < b10.length; i++) {
            bs10[i] = b10[i];
        }
    }


    //将bytes转成string
    string public str1;

    function bytesToString() public {
        fixedByteToBytes();
        str1 = string(bs10);
    }



    //将string转成bytes
    bytes public bs20;

    function stringToBytes() public {
        bytesToString();
        bs20 = bytes(str1);
    }
}

5. 数组

- 内置数组

已介绍

  • string(不定长)
  • bytes(不定长)
  • bytes1...bytes32(定长)

- 自定义数组

定长数组

go numbers [10] uint

  • 类型T,长度K的数组定义为T[K],例如:uint [5] numbers, byte [10] names;
  • 内容可变
  • 长度不可变,不支持push
  • 支持length方法

创建:13.自定义定长数组.sol

pragma solidity ^0.4.24;


contract  Test {

    //Type[Len] name

    uint256[10] public numbers = [1,2,3,4,5,6,7,8,9, 10];

    uint256 public sum;

    // - 类型T,长度K的数组定义为T[K],例如:uint [5] numbers,  byte [10] names;
    // - 内容可变
    // - 长度不可变,不支持push
    // - 支持length方法

    function total() public returns(uint256) {
        for (uint256 i = 0; i < numbers.length; i++) {
            sum += numbers[i];
        }

       return sum; 
    }

    function setLen() public {
        //numbers.length = 10;
    }

    function changeValue(uint256 i , uint256 value) public {
        numbers[i] = value;
    }

    //++++++++++++++++++++++++++++++++++

    bytes10 public helloworldFixed = 0x68656c6c6f776f726c64;

    byte[10] public helloworldDynamic = [byte(0x68), 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64];

    bytes public b10;

    function setToBytes() public  returns (string){
        for (uint256 i=0; i< helloworldDynamic.length; i++) {
            byte b1 = helloworldDynamic[i];
            b10.push(b1);
        }

        return string(b10);
    }
}

不定长数组

  • 定义格式为T [ ],例如:string[ ] names, byte[ ] citys。
  • 内容可以修改
  • 可以改变长度(仅限storage类型) 支持lengthpush方法
  • memory类型的不定长数组不支持修改长度
  • 即使没有手动分配空间,直接改变长度,那么也会自动分配空间

创建:14.自定义不定长数组.sol

pragma solidity ^0.4.24;


contract  Test {

    //第一种创建方式,直接赋值
    uint8[] numbers = [1,2,3,4,5,6,7,8,9,10];

    function pushData(uint8 num) public {
        numbers.push(num);
    }

    function getNumbers() public view returns(uint8[]) {
        return numbers;
    }


    //第二种:使用new关键字进行创建,赋值给storage变量数组
    uint8[] numbers2;

    function setNumbers2() public {
        numbers2 = new uint8[](7);
        numbers2.length = 20;
        numbers2.push(10);
    }

    function getNumbers2() public view returns(uint8[]) {
        return numbers2;
    }

    function setNumbers3() public {
        //使用new创建的memory类型数组,无法改变长度
        uint8[] memory numbers3 = new uint8[](7);
        //uint8[] memory numbers3;

        //numbers3.length = 100; //无法修改
        //numbers3.push(x0);
    }
}

二维数组

//TODO

15.二维数组.sol

6. 结构体

创建:16.struct结构体.sol

pragma solidity ^0.4.5;

contract Test {
    //定义结构之后无分号,与枚举一致
    struct Student {
        string name;
        uint age;
        uint score;
        string sex;
    }

    Student[] public students;


    //两种赋值方式
    Student public stu1 = Student("lily", 18, 90, "girl");
    Student public stu2 = Student({name:"Jim", age:20, score:80, sex:"boy"});

    function assign() public {
        students.push(stu1);
        students.push(stu2);

        stu1.name = "Lily";
    }
}

7. 字典/映射/hash表(mapping)

  • 键key的类型允许除映射外的所有类型,如数组,合约,枚举,结构体,值的类型无限制。
  • 无法判断一个mapping中是否包含某个key,因为它认为每一个都存在,不存在的返回0或false。
  • 映射可以被视作为一个哈希表,在映射表中,不存储键的数据,仅仅存储它的keccak256哈希值,用来查找值时使用。
  • 映射类型,仅能用来定义状态变量,或者是在内部函数中作为storage类型的引用。
  • 不支持length

创建:17.mapping映射.sol

pragma solidity ^0.4.20;


contract test {
    //id -> name
    mapping(uint => string) id_names;

    constructor()  public{
        id_names[0x01] = "lily";
        id_names[0x02] = "Jim";
        id_names[0x02] = "Lily";
    }

    function getNameById(uint id)  public returns (string){
        //加上storage如何赋值?
        string memory name = id_names[id];
        return name;
    }

    function setNameById(uint id)  public returns (string){
        // mapping(uint => string) memory id_name = id_names;
        // var ids = id_names;
        id_names[id] = "Hello";
    } 


    // function getMapLength() public returns (uint){
    //     return id_names.length;
    // }

}

五、高级语法

1. 自动推导var(忘了她吧)

为了方便,并不总是需要明确指定一个变量的类型,编译器会通过第一个向这个对象赋予的值的类型来进行推断

uint24 x = 0x123;
var y = x;

需要特别注意的是,由于类型推断是根据第一个变量进行的赋值。所以下面的代码将是一个无限循环,因为一个uint8的i的将小于2000。

for (var i = 0; i < 2000; i++)
{
    //uint8 -> 255
    //无限循环
}
pragma solidity ^0.4.24;

contract Test{
    function a() view returns (uint, uint){
        uint count = 0;
        var i = 0;
        for (; i < 257; i++) {
            count++;
            if(count >= 260){
                break;
            }
        }
        return (count, i);
    }
}

结果:

0: uint256: 260
1: uint256: 3

分析:

i, count
255, 256
0, 257
1, 258
2, 259
3, 260


00
01
10

 11111111111111111111111
1000000000000000
1000000000000001

2. 全局函数/变量

- 最重要的两个全局变量

  • msg.sender

    每一次和以太坊交互时都会产生一笔交易,这笔交易的执行人就是msg.sender。简而言之:谁调用的,msg.sender就是谁,每笔交易的msg.sender都可以不同。举例:

    1. 部署合约的时候,msg.sender就是部署的账户。
    2. 调用setMessage时,msg.sender就是调用账户。
    3. 调用getMessage时,msg.sender就是调用账户。

demo:

pragma solidity ^0.4.24;


contract  Test {

    address public owner;
    uint256 a;
    address public caller;

    constructor() public {
        //在部署合约的时候,设置一个全局唯一的合约所有者,后面可以使用权限控制
        owner = msg.sender;
    }

    //1. msg.sender是一个可以改变的值,并不一定是合约的创造者
    //2. 任何人调用了合约的方法,那么这笔交易中的from就是当前msg.sender
    function setValue(uint256 input) public {
        a = input;
        caller = msg.sender;
    }
}
  • msg.value

    我们在介绍payable关键字的时候说,如果函数修饰为payable,那么这个函数可以接收转账,这笔钱通过remix的value输入框传递进来。

    在转账操作中,这笔钱是通过我们调用一个函数从而产生一笔交易而转入合约的,换句话说,是这笔交易附带了一笔钱。在合约中,每次转入的value是可以通过msg.value来获取到的。注意,

    1. 单位是wei
    2. 有msg.value,就必须有payable关键字

demo:

pragma solidity ^0.4.24;


contract  Test {

    //uint256 public money;

    mapping(address=> uint256) public personToMoney;

    //函数里面使用了msg.value,那么函数要修饰为payable
    function paly() public payable {

        // 如果转账不是100wei,那么参与失败
        // 否则成功,并且添加到维护的mapping中
        if (msg.value != 100) {
            throw;
        }

        personToMoney[msg.sender] = msg.value;

    }

    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }
}

注意,测试时,传入的地址要加上英文双引号!!!

- 区块和交易的属性

函数 含义
blockhash(uint blockNumber) 哈希值(byte32)
block.coinbase (address) 当前块矿工的地址。
block.difficulty (uint)当前块的难度
block.gaslimit (uint)当前块的gaslimit
block.number (uint)当前区块的块号
block.timestamp (uint)当前块的时间戳
msg.data (bytes)完整的调用数据(calldata)
gasleft() (uint)当前还剩的gas
msg.sender (address)当前调用发起人的地址
msg.sig (bytes4)调用数据的前四个字节(函数标识符)
msg.value (uint)这个消息所附带的货币量,单位为wei
now (uint)当前块的时间戳 等同于block.timestamp
tx.gasprice (uint) 交易的gas价格
tx.origin (address)交易的发送者(完整的调用链)

示例:(使用图形化ganache测试)

pragma solidity ^0.4.24;


contract Test {

    bytes32 public blockhash1;
    address public coinbase;
    uint public difficulty;
    uint public gaslimit;
    uint public blockNum;
    uint public timestamp;
    bytes public calldata;
    uint public gas;
    address public sender;
    bytes4 public sig;
    uint public msgValue;
    uint public now1;
    uint public gasPrice;
    address public txOrigin;

    function tt () public payable {
        //给定区块号的哈希值,只支持最近256个区块,且不包含当前区块
        blockhash1 = blockhash(block.number - 1);
        coinbase = block.coinbase ;//当前块矿工的地址。
        difficulty = block.difficulty;//当前块的难度。
        gaslimit = block.gaslimit;// (uint)当前块的gaslimit。
        blockNum = block.number;// (uint)当前区块的块号。
        timestamp = block.timestamp;// (uint)当前块的时间戳。
        calldata = msg.data;// (bytes)完整的调用数据(calldata)。
        gas = gasleft();// (uint)当前还剩的gas。
        sender = msg.sender; // (address)当前调用发起人的地址。
        sig = msg.sig;// (bytes4)调用数据的前四个字节(函数标识符)。
        msgValue = msg.value;// (uint)这个消息所附带的货币量,单位为wei。
        now1 = now;// (uint)当前块的时间戳,等同于block.timestamp
        gasPrice = tx.gasprice;// (uint) 交易的gas价格。
        txOrigin = tx.origin;// (address)交易的发送者(完整的调用链)  
    }
}

3. 错误处理

在创建合约时设置owner(合约的所有人)

pragma solidity ^0.4.24;


contract Test {

    address public owner;

    constructor() public {
        owner = msg.sender;
    }
}

传统方法:采用 throw 和 if ... throw 模式(已过时),例如合约中有一些功能,只能被授权为拥有者的地址才能调用

if(msg.sender != owner) { 
    throw; 
}

等价于如下任意一种形式:

if(msg.sender != owner) { 
    revert(); 
} 

//assert和require是推荐的方式,里面的参数要求值为true,即期望的结果
assert(msg.sender == owner); 
require(msg.sender == owner);

示例:

//描述编译器版本
pragma solidity ^0.4.24;


contract Inbox{ 

    //定义变量:类型 + 变量名
    string public message;   // var name string
    address public manager; //合约的部署者(拥有者)
    address public caller;  //合约函数的调用者


    function Inbox() payable {
        manager = msg.sender;
    }

    function setMessage(string newMessage) public {
        caller = msg.sender;

        // if (manager != msg.sender) {
        //     throw; //如果函数调用者不是管理员,直接抛异常
        // }


        // 断言:
        // 1. 一条语句,既包含了条件,又可以抛异常(推荐)
        // 2. 条件是期望的结果,与普通的条件判断相反
        //   (条件为true,继续执行,条件为false,抛出异常)

        // require(manager == msg.sender);
        assert(manager == msg.sender);

        message = newMessage;

    }


    //如果有返回值,一定要加上returns关键字,使用()包裹起来    
    function getMessage() public constant returns(string){
        return message;
    }
}

4. 修饰器(modifier)

修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。下面我们来看一段示例代码:

//描述编译器版本
pragma solidity ^0.4.24;


contract Inbox{ 

    //定义变量:类型 + 变量名
    string public message;   // var name string
    address public manager; //合约的部署者(拥有者)
    address public caller;  //合约函数的调用者


    function Inbox() payable {
        manager = msg.sender;
    }

    //一个函数可以使用多个修饰器
    function setMessage(string newMessage) public onlyManager onlyManager2(msg.sender){
        caller = msg.sender;
        message = newMessage;

    }

    //如果有返回值,一定要加上returns关键字,使用()包裹起来    
    function getMessage() public constant returns(string){
        return message;
    }

    modifier onlyManager {
        require(manager == msg.sender);
        _; //下划线代表修饰器所修饰的代码
    }

    //修饰器可以带有参数
    modifier onlyManager2(address _caller) {
        require(manager == _caller);
        _; //下划线代表修饰器所修饰的代码
    }
}

5. 两个常用单位

- 货币单位

  • 一个字面量的数字,可以使用后缀weifinneyszaboether来在不同面额中转换。
  • 不含任何后缀的默认单位是wei。如1 ether == 1000 finney的结果是true
pragma solidity ^0.4.24;

contract EthUnit{
    uint  a = 1 ether;
    uint  b = 10 ** 18 wei;
    uint  c = 1000 finney;
    uint  d = 1000000 szabo;

    function f1() constant public returns (bool){
        return a == b;
    }

    function f2() constant public returns (bool){
        return a == c;
    }

    function f3() constant public returns (bool){
        return a == d;
    }

    function f4() constant public returns (bool){
        return 1 ether == 100 wei;
    }
}

- 时间单位

  • seconds,minutes,hours,days,weeks,years均可做为后缀,默认是seconds为单位。
  • 1 = 1 seconds
  • 1 minutes = 60 seconds
  • 1 hours = 60 minutes
  • 1 days = 24 hours
  • 1 weeks = 7 days
  • 1 years = 365 days
pragma solidity ^0.4.0;

contract TimeUnit{

    function f1() pure public returns (bool) {
        return 1 == 1 seconds;
    }

    function f2() pure public returns (bool) {
        return 1 minutes == 60 seconds;
    }

    function f3() pure public returns (bool) {
        return 1 hours == 60 minutes;
    }

    function f4() pure public returns (bool) {
        return 1 days == 24 hours;
    }

    function f5() pure public returns (bool) {
        return 1 weeks == 7 days;
    }

    function f6() pure public returns (bool) {
        return 1 years == 365 days;
    }
}

6. 事件(Event)

相当于打印log,但是需要在调用端才能看到,web3调用时演示

pragma solidity ^0.4.0;

contract ClientReceipt {
    //定义,注意,需要加分号,相当于一句语句,与struct和enum不同。
    //类似于定义函数原型
    event Deposit(
        address indexed _from,
        uint indexed _id,
        uint _value
    );

    function deposit(uint _id) {
        //使用
        Deposit(msg.sender, _id, msg.value);

        //TODO
    }
}

技术图片

7. 访问函数(Getter Functions)

编译器为自动为所有的public的状态变量创建访问函数。下面的合约例子中,编译器会生成一个名叫data的无参,返回值是uint的类型的值data。状态变量的初始化可以在定义时完成。

pragma solidity ^0.4.24;


contract  Test {

    // 加了public 的转态变量,solidity会自动的生成一个同名个访问函数。
    // 在合约内部使用这个状态变量的时候,直接当初变量使用即可, 不能直接当成方法使用
    // 如果在合约外面向访问这个public变量(data),就需要使用xx.data()形式
    uint256 public data = 200;


    function getData() public view returns(uint256) {
        return data;
    }

    //This代表合约本身,如果在合约内部使用this自己的方法的话,相当于外部调用
    function getData1() public view returns(uint256) {
        //return this.data;   //不能使用.data形式
        return this.data();
    }
}

contract Test1 {

    function getValue() public view returns(uint256) {
        Test t1 = new Test();
        return t1.data();
    }
}

8. 合约(重要)

- 创建合约和外部调用

  1. new关键字,返回值是一个address,需要显示转化类型后才能使用
  2. C c1形式,此时c1是空的,需要赋值地址才能使用,否则报错

demo:

pragma solidity ^0.4.24;


contract  C1 {

    uint256 public value ;

    constructor(uint256 input) public {
        value = input;
    }

    function getValue() public view returns(uint256) {
        return value;
    }
}

contract C2 {
    C1 public c1;  //0x0000000000000
    C1 public c11;  //0x0000000000000
    C1 public c13;

    function getValue1() public returns(uint256) {

        //创建一个合约,返回地址
        address addr1 = new C1(10);  //balance , transfer方法
        //return addr1.getValue();

        //需要显示的转换为特定类型,才可以正常使用
        c1 = C1(addr1);

        return c1.getValue();
    }


    function getValue2() public returns(uint256) {

        //定义合约的时候,同时完成类型转换
        c11 = new C1(20);
        return c11.getValue();
    }


    function getValue3(address addr) public returns(uint256) {
        //传进来的地址必须是同类型的,如果是不是C1类型的,转换时报错
        c13 = C1(addr);
        return c13.getValue();
    }
}

- 继承

  1. is关键字, 可以同时继承多个父合约。
  2. 当父合约存在同名函数时,默认为最远继承原则
  3. 可以指定某个父合约,调用它的方法
pragma solidity ^0.4.24;


contract baseA {

    function getData() public pure returns(uint256) {
        return 1;
    }
} 


contract baseB {

    function getData() public pure returns(uint256) {
        return 2;
    }
} 


contract sonA is baseA, baseB {

    function getSonData() public pure returns(uint256){
        return 3;
    }

    function getData3() public pure returns(uint256) {
        return baseA.getData();
    }
}

- 合约间如何转钱

这个是官方示例,介绍如何使用一个合约向另一个合约转账。

pragma solidity ^0.4.24;


contract TestA {

    string public message;

    function invest(string _input) payable public {
        message = _input;    
    }

    function getBalanceA() public view returns(uint256) {
        return address(this).balance;
    }
} 



contract TestB {

    TestA public a1;

    constructor() public {
        a1 = new TestA();
    }

    function() public payable {

    }

    function pay() public {
        //TestB调用TestA的invest方法时,如何转账给TestA?
        //把TestB的钱转给TestA, 并不是调用pay函数人的钱
        a1.invest.value(5 ether).gas(21000)("hangtou!");
    }

    function getBalanceB() public view returns(uint256) {
        return address(this).balance;
    }

}

9. internal和external

访问函数有外部(external)可见性。如果通过内部(internal)的方式访问,比如直接访问,你可以直接把它当一个变量进行使用,但如果使用外部(external)的方式来访问,如通过this.,那么它必须通过函数的方式来调用。

pragma solidity ^0.4.24;


//private ,           intenal ,            external,            public
//合约本身可以调用,  合约及子类可以调用, 只能在合约外部调用, 可以被任意的合约调用

contract C1{
    uint public c = 10;

    function accessPrivate() private returns(uint) {
        return c;
    }

    function accessInternal() internal returns (uint){
        return c;
    }

    function accessExternal() external returns(uint){
        return c;
    }

    function call1() public returns(uint) {
        // accessExternal(); //无法在内部调用external修饰的函数
        accessInternal();
    }

    function call2() public {
        this.accessExternal(); //this调用函数,相当于外部调用

        // this.c;   // ok
        // uint a = this.c; // error
        uint b = this.c();   // ok
        // c();
    }

    function call3() public returns(uint) {

    }
}

contract C2{

    function callExternal() public returns(uint){
        C1 c1 = new C1();
        // external修饰的只能在外部调用
        return c1.accessExternal();

        //internal修饰的只能在内部调用
        // return c1.accessInternal();
    }
}

contract C3 is C1 {

    function test() public returns(uint) {
        // C1 c1 = new C1();
        // c1.accessPrivate();

        // this.accessInternal(); //error
        // c1.accessInternal(); // error
        return accessInternal();

    }

}

10. 元组(tuple)

return(a, b, c)

solidity无法返回自定义的数据结构,所以若想返回一个自定义结构的数据,需要在函数中一次返回多个值,即元组。元组是一个数据集合,类似于字典但是无法修改数据,使用圆括号包括多种数据类型。

  1. 可以包含多个数据
  2. 类型可以不同
  3. 不可以修改
  4. 使用圆括号包裹
pragma solidity ^0.4.5;

contract Test {

    struct Student {
        string name;
        uint age;
        uint score;
        string sex;
    }

    //两种赋值方式
    Student public stu1 = Student("lily", 18, 90, "girl");
    Student public stu2 = Student({name:"Jim", age:20, score:80, sex:"boy"});

    Student[] public Students;

    function assign() public {
        Students.push(stu1);
        Students.push(stu2);

        stu1.name = "Lily";
    }

    //1. 返回一个Student结构
    function getLily() public view returns(string, uint, uint, string) {
        require(Students.length != 0);
        Student memory lily = Students[0];

        //使用圆括号包裹的多个类型不一致的数据集合:元组
        return (lily.name, lily.age, lily.score, lily.sex);
    }
}

11. 内置数学函数

keccak256(...) returns (bytes32)

哈希函数,代替sha3(废弃)

pragma solidity ^0.4.24;


contract Test {

    function test() public pure returns(bytes32){
        bytes memory v1 = abi.encodePacked("abc", "b", uint256(1), "hello");
        return keccak256(v1);
    }
}

12. 其他

for、break、continue

- new

创建对象,合约等

- delete

  • delete操作符可以用于任何变量(map除外),将其设置成默认值
  • 如果对动态数组使用delete,则删除所有元素,其长度变为0: uint[ ] array0 ; arry0 = new uint
  • 如果对静态数组使用delete,则重置所有索引的值: uint[10] array1 = [1,2,3,4,5,6];
  • 如果对map类型使用delete,什么都不会发生
  • 但如果对map类型中的一个键使用delete,则会删除与该键相关的值
pragma solidity ^0.4.24;


contract  Test {

    //01. string 
    string public str1 = "hello";

    function deleteStr() public {
        delete str1;
    }

    function setStr(string input) public {
        str1 = input;
    }

    //02. array 对于固定长度的数组,会删除每个元素的值,但是数组长度不变
    uint256[10] public arry1 = [1,2,3,4,5];

    function deleteFiexedArry() public {
        delete arry1;
    }

    //03. array new

    uint256[] arry2 ;
    function setArray2() public {
        arry2 = new uint256[](10);
        for (uint256 i = 0; i< arry2.length; i++) {
            arry2[i] = i;
        }
    }

    function getArray2() public view returns(uint256[]) {
        return arry2;
    }

    function deleteArray2() public {
        delete arry2;
    }

    //04. mapping

    mapping(uint256 => string) public m1;

    function setMap() public {
        m1[0] = "hello";
        m1[1] = "world";
    }

    //Mapping不允许直接使用delete,但是可以对mapping的元素进行指定删除
    // function deleteM1() public {
    //     delete m1;
    // }

    function deleteMapping(uint256 i) public {
        delete m1[i];
    }
}

13. 编码规范

  1. public放到最前面
  2. 函数参数加下划线

14.合约销毁

  1. selfdestruct(msg.sender); 可以销毁合约,并将合约内的金额转给指定的地址。
  2. 合约销毁后,不再工作,无法继续调用其方法。
  3. 合约销毁,并不是将合约删除,只是不工作。
  4. 合约销毁权限一定要控制好。
pragma solidity ^0.4.24;


contract Test {

    string public name;

    address manager;

    constructor(string _input) public payable {
        name = _input;
        manager = msg.sender;
    }

    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }


    function kill() public {

        require(manager == msg.sender) ;

        selfdestruct(msg.sender);
    }

}

六、智能合约案例

1. 查看代币合约

技术图片 技术图片

技术图片

2. 代币源码

- ERC20标准

合约中实现这些标准接口函数

contract ERC20 {
    function totalSupply() constant returns (uint totalSupply);
    function balanceOf(address _owner) constant returns (uint balance);
    function transfer(address _to, uint _value) returns (bool success);
    function transferFrom(address _from, address _to, uint _value) returns (bool success);
    function approve(address _spender, uint _value) returns (bool success);
    function allowance(address _owner, address _spender) constant returns (uint remaining);
    event Transfer(address indexed _from, address indexed _to, uint _value);
    event Approveal(address indexed _owner, address indexed _spender, uint _value);

    string public constant name = "Token Name";
    string public constant symbol = "SYM";
    uint8 public constant decimals = 18;  // 大部分都是18
}

- 参考链接

  1. 以太坊:什么是ERC20标准?

    https://www.jianshu.com/p/a5158fbfaeb9

  2. 以太坊ERC20 Token标准完整说明

    https://blog.csdn.net/diandianxiyu_geek/article/details/78082551?utm_source=gold_browser_extension

- 查看真实的代币情况

//演示

- 分析BNB代码

pragma solidity ^0.4.8;

/**
 * Math operations with safety checks
 */
contract SafeMath {
  //internal > private 
    //internal < public
    //修饰的函数只能在合约的内部或者子合约中使用
    //乘法
  function safeMul(uint256 a, uint256 b) internal returns (uint256) {
    uint256 c = a * b;
    //assert断言函数,需要保证函数参数返回值是true,否则抛异常
    assert(a == 0 || c / a == b);
    return c;
  }
//除法
  function safeDiv(uint256 a, uint256 b) internal returns (uint256) {
    assert(b > 0);
    uint256 c = a / b;
    //   a = 11
    //   b = 10
    //   c = 1

      //b*c = 10
      //a %b = 1
      //11
    assert(a == b * c + a % b);
    return c;
  }

    //减法
  function safeSub(uint256 a, uint256 b) internal returns (uint256) {
    assert(b <= a);
    assert(b >=0);
    return a - b;
  }

  function safeAdd(uint256 a, uint256 b) internal returns (uint256) {
    uint256 c = a + b;
    assert(c>=a && c>=b);
    return c;
  }
}

contract QiongB is SafeMath{

    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;
    address public owner;

    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;


    mapping (address => uint256) public freezeOf;


    /* This generates a public event on the blockchain that will notify clients */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /* This notifies clients about the amount burnt */
    event Burn(address indexed from, uint256 value);

    /* This notifies clients about the amount frozen */
    event Freeze(address indexed from, uint256 value);

    /* This notifies clients about the amount unfrozen */
    event Unfreeze(address indexed from, uint256 value);

    /* Initializes contract with initial supply tokens to the creator of the contract */

    //1000000, "QiongB", 18, "QB"
    function QiongB(
        uint256 initialSupply, //发行数量
        string tokenName, //token的名字 BinanceToken
        uint8 decimalUnits, //最小分割,小数点后面的尾数 1ether = 10** 18wei
        string tokenSymbol //QB
        ) {
        decimals = decimalUnits;                           // Amount of decimals for display purposes
        balanceOf[msg.sender] = initialSupply * 10 ** 18;              // Give the creator all initial tokens
        totalSupply = initialSupply * 10 ** 18;                        // Update total supply
        name = tokenName;                                   // Set the name for display purposes
        symbol = tokenSymbol;                               // Set the symbol for display purposes

        owner = msg.sender;
    }

    /* Send coins */
    //某个人花费自己的币
    function transfer(address _to, uint256 _value) {
        if (_to == 0x0) throw;                               // Prevent transfer to 0x0 address. Use burn() instead
        if (_value <= 0) throw; 
        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows

        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                     // Subtract from the sender

        balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                            // Add the same to the recipient
        Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place
    }

    /* Allow another contract to spend some tokens in your behalf */
    //找一个人A帮你花费token,这部分钱并不打A的账户,只是对A进行花费的授权
    //A: 1万
    function approve(address _spender, uint256 _value)
        returns (bool success) {
        if (_value <= 0) throw; 
        //allowance[管理员][A] = 1万
        allowance[msg.sender][_spender] = _value;
        return true;
    }

    //mapping (管理员 => mapping (A =>1万)) public allowance;
    //mapping (管理员 => mapping (B =>2万)) public allowance;

    /* A contract attempts to get the coins */
    function transferFrom(address _from /*管理员*/, address _to, uint256 _value) returns (bool success) {
        if (_to == 0x0) throw;                                // Prevent transfer to 0x0 address. Use burn() instead
        if (_value <= 0) throw; 
        if (balanceOf[_from] < _value) throw;                 // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) throw;  // Check for overflows
        if (_value > allowance[_from][msg.sender]) throw;     // Check allowance
           // mapping (address => mapping (address => uint256)) public allowance;
        balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value);                           // Subtract from the sender
        balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                             // Add the same to the recipient
        //allowance[管理员][A] = 1万-五千 = 五千
        allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value);
        Transfer(_from, _to, _value);
        return true;
    }

    function burn(uint256 _value) returns (bool success) {
        if (balanceOf[msg.sender] < _value) throw;            // Check if the sender has enough
        if (_value <= 0) throw; 
        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
        totalSupply = SafeMath.safeSub(totalSupply,_value);                                // Updates totalSupply
        Burn(msg.sender, _value);
        return true;
    }

    function freeze(uint256 _value) returns (bool success) {
        if (balanceOf[msg.sender] < _value) throw;            // Check if the sender has enough
        if (_value <= 0) throw; 
        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
        freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value);                                // Updates totalSupply
        Freeze(msg.sender, _value);
        return true;
    }

    function unfreeze(uint256 _value) returns (bool success) {
        if (freezeOf[msg.sender] < _value) throw;            // Check if the sender has enough
        if (_value <= 0) throw; 
        freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value);                      // Subtract from the sender
        balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value);
        Unfreeze(msg.sender, _value);
        return true;
    }

    // transfer balance to owner
    function withdrawEther(uint256 amount) {
        if(msg.sender != owner)throw;
        owner.transfer(amount);
    }

    // can accept ether
    function() payable {
    }
}

七、验证(verify)合约

Ropste Duke AD,已经Verify

https://ropsten.etherscan.io/address/0x8136d63e3c1e3e8e93560a965c635f2704ce7c22#code

1. 为什么要验证合约

部署了一个合约之后,如果想让大家参与进来,那么必须接受大家的审计(审计一定要部署完就做,这样可以保证完全匹配),以确保你的合约的功能确实如你所说,全世界的人都看到了合约的所有功能,那么就可以放心使用了

2. 验证方式

//操作 ,优化填否

3. 操作验证后的合约

技术图片
  1. 浏览器上的数据是如何解析的?

注意:

  1. 在remix部署到ropsten,将合约的地址添加到metamask的AddToken中,只输入地址即可,其他两项(name,decimals会自动加载出来)
  2. metamask中只能查看token数量,但是无法进行token交易
  3. 可以使用Myetherwallet页面网站来进行转账
  4. 可以使用MyEtherWallet客户端进行转账
  5. 可以在验证合约之后,在浏览器上直接完成转账。

4. 以太坊上的数据为什么能被看到?

所有的数据都是通过交易产生的,无论是上传数据,亦或是调用方法,那么传入的数据都是通过inputdata传递进来的,而这个字段是公开的,所有所有的数据都是透明的。

END

2020年07月22日12:48:08

以上是关于79solidity语言学习——2020年07月22日12:49:06的主要内容,如果未能解决你的问题,请参考以下文章

2020年2月编程语言排行

54go mod使用——2020年07月11日15:46:14

全国PPP综合信息平台项目管理库2020年2月报

关注全国PPP综合信息平台项目管理库2020年2月报

000917电广传媒看涨90%目标¥21.20分析日17年10月

2020年2月9日——教育与学习1