solidity 从入门到发币(eth)

目录

 

1 Solidity与智能合约

2 智能合约概述

3 以太坊简介

4 以太坊交互工具

5 开发环境搭建

6 常见概念

7 Solidity基础语法

7.1 数据类型分类

7.2 remix的使用--第一个智能合约

7.3 值类型

7.4 引用类型

8 Solidity高级语法

8.1 ⾃动推导var(忘了她吧)

8.2 全局函数/变量

最重要的两个全局变量(msg.sender  和 msg.value)

8.3 错误处理

8.4 修饰器(modifier)

8.5 两个常用单位

8.5.1 货币单位

8.6 事件(Event)

8.7 访问函数(Getter Functions)

8.8 合约

8.8.1 合约的创建

8.8.2 合约的继承

8.8.3 合约间如何转钱

8.8.4 internal和external

8.9 元组(tuple)

8.10 内置数学函数

8.11 其他 

8.12 合约销毁

9.0 发币

9.1 代币源码

9.2 发币

本地部署合约测试

验证合约

10 编码规范


 

1 Solidity与智能合约

起源于以太坊(Ethereum),设计的目的是能在以太坊虚拟机(EVM)上运行。Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。所以先从智能合约开始。

参考文档

Solidity文档https://www.tryblockchain.org/

solidity官方文档:  https://solidity-cn.readthedocs.io/zh/develop/

solidity英文文档:https://docs.soliditylang.org/en/latest/control-structures.html#external-function-calls

以太坊发展的⽂章:https://www.jinse.com/blockchain/471570.html

官⽅⽹址: https://www.ethereum.org

交易浏览器: https://etherscan.io

以太坊⻩⽪书: https://github.com/ethereum/yellowpaper

 

2 智能合约概述

智能合约的定义:

“智能合约”(smart contract)这个术语至少可以追溯到1995年,是由多产的跨领域法律学者尼克·萨博(Nick Szabo)提出来的。他在发表在自己的网站的几篇文章中提到了智能合约的理念。他的定义如下:

“一个智能合约是一套以数字形式定义的承诺(promises),包括合约参与方可以在上面执行这些承诺的协议。”

智能合约的本质:数字化合同。

智能合约的特点:代码代替人仲裁和执行合同,同时能够触发支付。

智能合约于普通合约对比图示:

普通合约图示如下:

Bob和Alice签署合同,由法院进行背书,公示和执行。

 

 

智能合约图示如下:

Bob和Alice共同认可的合约,以代码的形式上传到区块链上。双方缴纳保证金到合约中,当满足一定条件,由外部输入条件,合约根据逻辑触发条件,将保证金转账给一方。

 

3 以太坊简介

以太坊是运⾏在⼀个计算机⽹络中的软件,它确保数据以及称为智能合约的⼩程序可以在没有中⼼协调者的情况下被所 有⽹络中的计算机复制和处理。以太坊的愿景是创建⼀个⽆法停⽌,抗屏蔽(审查)和⾃我维持的去中⼼化世界计算机。

它延伸了⽐特币的区块链概念:在全球范围的多个计算机上验证,存储和复制交易数据(因此术语叫“分布式账本”)。以太坊(Ethereum)在这个概念上更进⼀步,使之(交易数据)在全球范围的多个计算机上运⾏代码成为现实。

⽐特币⽤来分布式储存数据的,以太坊⽤来分布式储存数据并且计算。这些⼩型的电脑运⾏程序叫做智能合约,合约由参与者在他们⾃⼰的机器上通过⼀种称为 “以太坊虚拟机(EVM)”的操作系统运⾏。

智能合约与以太坊的关系——智能合约是⼀个部署在以太坊区块链上的程序。

全世界的计算机通过网络互连,每个节点都运行一个以太坊客户端,即组成以太坊网络。

 

 

小结:

  1. 以太坊是⼀个区块链的⽹络,由很多节点组成
  2. 以太坊可以转账,可以做数据存储(通过交易是承载)
  3. 以太坊可以执⾏程序,程序叫做智能合约,所有节点都运⾏这个程序
  4. 以太坊⽹络有很多个,主⽹只有⼀个,还有很多测试⽹络,我们也可以⾃⼰搭建私链
  5. ⼀个node节点其实就是⼀个运⾏以太坊客户端的计算机
  6. 以太坊是公有链,每个⼈都可以⾃由的加⼊退出以太坊⽹络
  7. 每⼀个以太坊节点都可以同步全部的账本/区块链信息(blockchain)

 

4 以太坊交互工具

以太坊爱好者网站

https://ethfans.org/wikis/Home

  • 开发者:web3.js 以太坊项目开发的js库
  • 一般用户(消费者):

metamask (浏览器插件,firefox, chrome,适合开发测试,也适合小白用户)

Ethereum Wallet

https://github.com/ethereum/mist/releases

mist浏览器 (很多bug,早期版本)

以太坊网络

(国服,私服,美服,韩服),互不相通,但是功能⼀致,每个⼈都可以同时注册

1.主网络

花费真实的以太币

2.测试网络

使用geth,或者Ganache工具搭建本地测试网络。

Morden(已停服)

Ropsten

以太坊官⽅提供的测试⽹络,是为了解决Morden难度炸弹问题⽽重新启动的⼀条区块链,⽬前仍在运⾏, 共识机制为PoW。

获取Ropsten测试币

https://faucet.ropsten.be

输入公钥地址即可。

 

 

Kovan

Kovan⽬前仍在运⾏,但仅有Parity钱包客户端可以使⽤这个测试⽹络。

为了解决测试⽹络中PoW共识机制的问题,以太坊钱包Parity的开发团队发起了⼀个新的测试⽹络Kovan。Kovan使⽤ 了权威证明(Proof-of-Authority)的共识机制,简称PoA。

PoW是⽤⼯作量来获得⽣成区块的权利,必须完成⼀定次数的计算后,发现⼀个满⾜条件的谜题答案,才能够⽣成有效 的区块。

PoA是由若⼲个权威节点来⽣成区块,其他节点⽆权⽣成,这样也就不再需要挖矿。由于测试⽹络上的以太币⽆价值, 权威节点仅仅是⽤来防⽌区块被随意⽣成,造成测试⽹络拥堵,完全是义务劳动,不存在作恶的动机,因此这种机制在 测试⽹络上是可⾏的。

Kovan与主⽹络使⽤不同的共识机制,影响的仅仅是谁有权来⽣成区块,以及验证区块是否有效的⽅式,权威节点可以 根据开发⼈员的申请⽣成以太币,并不影响开发者测试智能合约和其他功能。

Rinkeby

Rinkeby也是以太坊官⽅提供的测试⽹络,使⽤PoA共识机制。(获取测试币⽐较困难)

 

 

5 开发环境搭建

remix在线编译器

访问链接,方便调试,但是依赖网络,需翻墙。

https://remix.ethereum.org/

 

 

旧版界面

左侧是文件夹,右侧 compile 编译合约, run 使用deploy部署合约,At address(输入合约地址,可以加载合约)

 

搭建本地网络

方便调试,相对稳定。

npm install remix-ide -g
启动
remix-ide

 

访问 http://localhost:8080,即可打开本地编译器。

 

编译合约

remix编辑器中⾃动集成了solidity的编译器,所以可以⾃动编译我们的合约代码

编译原理

使⽤remix,由⾼级语⾔变成机器语⾔

  • solidity ---> bytecode(机器语⾔,区块链系统读取)

格式片段

6080604052610410806100136000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b87721461005c578063ce6d41de146100c5578063e21f37ce14610155575b600080fd5b34801561006857600080fd5b506100c3600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101e5565b005b3480156100……
  • solidity ---> ABI (application binary interface)(⽅便程序员调⽤)

json格式的描述⽂件

[
	{
  	"constant": false,
    "inputs": [
    		{
      	"name": "newMessage",
        "type": "string"
        }
……
]

 

图示

 

6 常见概念

gas(汽油)(油耗) 由于以太坊是公链,所有⼈都可以⾃由的参与,为了防⽌垃圾数据充斥⽹络,所以以太坊上规定每⼀个操作都是要 有成本的,这个成本由 gas 来体现,你要转账,部署智能合约,调⽤智能合约⽅法,都要消耗⼀定数量的gas。
gasprice(汽油价格)(油价)

虽然操作消耗gas,但是最终真正花的还是eth,所以就有⼀个转换率的问题,gasprice就是起到⼀个汇率的作⽤, 它代表的是⼀个gas值多少eth,gas*gasprice就是最终的⼿续费,也就是从你账户扣除的eth。

这种设计就可以保证⽤户在以太坊上的操作的⼿续费不会随着eth的价格⽽发⽣剧烈的变动(例如:如果eth涨,那 么可以调低gasprice来降低⼿续费),

gaslimit(汽油上限) (油箱) 以太坊规定,每笔交易的gas最少21000,矿⼯可以调整这个值,所以最终的花费的gas是不确定的,所以以太坊就 设置了gaslimit,这个代表的是最多给旷⼯这么多gas(==防⽌⾃⼰写的合约⾥⾯有死循环==),如果最终使⽤的 gas少于这个gaslimit,剩余的还会返给你的,但是如果你的gaslimit不⾜以⽀付这次交易,那就是不会退回的,并且交易也就失败了,转账的额度也是回不来了,所以你转账设置的limit⼀定要⼤于21000。

每个操作中gas的成本

摘⾃以太坊⻩⽪书

https://github.com/wanshan1024/ethereum_yellowpaper/blob/master/ethereum_yellow_paper_cn.pdf

 

 

 

7 Solidity基础语法

 

7.1 数据类型分类

数据类型思维导图

https://naotu.baidu.com/file/8ea7d69f9df11dc89433d37d818214de

  1. 值类型(值传递)
  2. 引用类型(指针传递)

值类型

值类型是指变量在传递过程中将数值完整的copy一份,再赋值给新的变量。

这种方式需要重新开辟新的内存空间,两个变量完全独立,互不影响,修改一个不会影响另外一个。缺点是效率低。

值类型包含

  • 布尔(bool)
  • 整形(int)
  • 地址(address)
  • 定长数组(byte1……byte32)
  • 有理数(小数)
  • 枚举(enums)
  • 函数(function)

引用类型

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

简单来说就是,如果在变量之前添加storage就是引用传递,不加就是值传递。但是只对复杂类型有效。

复杂类型,占用空间较大。所以考虑通过引用传递。

引用类型包含

  • 字符串
  • 不定长数组
  • 数组
  • 结构体
  • mapping

 

7.2 remix的使用--第一个智能合约

合约包含的基本元素

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

//关键字 contract 跟java的class一样  智能合约名称是helloworld
contract helloworld {
    //状态变量
    //string 是数据类型,message是成员变量,在整个智能合约生命周期都可以访问
    //public 是访问修饰符,是storage类型的变量,成员变量和是全局变量
    string public message;
    //address 是地址类型,
    address public manager;
    
    //构造函数,这里在合约部署时将合约所有者传入
    constructor () public {
        manager = msg.sender; 
    }
   //函数以function开头
    function setMessage (string _message) public {
        //局部变量
        string memory tmp;
        tmp = _message;
        message = tmp;
    }
    //view是修饰符,表示该函数仅读取成员变量,不做修改
    function getMessage() public view returns (string) {
        return message;
    }
}

选择auto compile 自动编译

 

 

 

智能合约的部署与调用,默认使用VM即可。

JavaScript VM web内置的虚拟机,调试方便。我们使用这个。

Injected Web3 链接metamask

Web3 Provider 链接自定义网络。

 

常见错误:

  1. contract 误写为 constant
  2. 句末忘记添加分号
  3. 修改代码后要重新create(旧版),新版本(deploy)重新部署合约。
  4. 在remix中,⼿动调⽤setMessage⽅法的时候,没有加双引号(英⽂的引号,否则报错)
  5. 调⽤setMessage之后,没有检查是否设置成功,直接调⽤getMessage⽅法
  6. compile⼀直是红⾊的,提示:Compiler not found,需要在Compile中选择版本

 

7.3 值类型

7.3.1 布尔

bool b1;
bool b2 = false;
bool b3 = true;

7.3.2 整形

  • int(有符号整型,有正有负)
  • uint(无符号整型,无负数)
  • 以8位为区间,支持int8,int16,int24 至 int256,uint同理。 ==int默认为int256,uint默认为uint256
pragma solidity ^0.4.25;
//int
contract test2 {

    
    int public i256 = 256;
    int8 public i8 = 1;
    
    function add() constant returns(int) {
        return i8 + i256; //257
    }

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

 

7.3.3 函数类型

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

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

 

函数的几个关键字

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

 

pragma solidity ^0.4.25;

//function
contract Test3 {
    //状态变量
    //类型不匹配时需要显示转换类型
    //返回值需要使用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();
    }
}

 

 

 

 

常用关键字说明 viewconstantpure

1. 如果⼀个函数⾥⾯,访问了状态变量,但是没有修改,我们使⽤view或者constant修饰。

2. 如果访问了状态变量,⽽且修改了,那么就不能constant和view,否则会报错,不修饰即可。

3. 如果没有使⽤过状态变量,我们要修饰为pure。

4. 如果你修饰为constant,但是你在函数中修改了,效果是:不会报错,正常执⾏,但是值不会改变。

 

//常用关键字说明 view,constant,pure
contract test4 {

    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;
   }
		//可以修改i8
    function setValue(int8 num) {
        i8 = num;
    }
    
    //修饰为constant,在函数中修改了,效果是:不会报错,正常执⾏,但是值不会改变
  	function setValue1(int8 num) constant {
        i8 = num;
    }
}

 

关键字payable

  1. 任何函数,只要修饰为payable,那么就可以在调用这个方法的时候,对value字段赋值,然后将价值value的钱转给合约
  2. 若这个函数没有指定payable,但是对value赋值了,那么本次调用会报错。
//payable
contract  test5 {
    
    string public str;
    //修饰为payable的函数才可以接收转账
    function test1(string src) public payable {
        str = src;
    }
    //不指定payable无法接收,调用,如果传入value,会报错
    function test2(string src) public {
        str = src;
    }
  
    function getbalance() public view returns(uint256) {
        //this代表当前合约本身
        //balance方法,获取当前合约的余额
        return this.balance;
    }
}

 

构造函数和匿名函数

构造函数:仅在部署合约时调用一次,完成对合约的初始化。可以在创建合约时转钱到合约。相当于go里面的init函数。

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

匿名函数

  • 用于转账

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

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

contract test6 {
    //构造函数,合约同名函数(已废弃)
    // test6() {
        
    // }
    //构造函数,constructor关键字修饰
    constructor () {
        //初始化内容
    }
    
    //如果想向合约转账,在合约中添加如下函数即可
    function() payable {
    //函数体什么都不填
    }
   
   //balance方法,获取当前合约的余额 
    function getbalance() public view returns(uint256) {
        //this代表当前合约本身余额
        return this.balance;
    }
}

fallback 也被称为回滚函数,调用payable,花费最少的gas,省钱。

 

7.3.4 地址(Address)

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

运算符

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

地址操作

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

注意:call(),delegatecall(),callcode() 都是底层的消息传递调用,最好不用,除非万不得已再用。因为他们破坏了Solidity的类型安全。

contract  test7 {

    address public addr1 = 0xca35b7d915458ef540ade6068dfe2f44e8fa733c;
    //地址address类型本质上是一个160位的数字
    //可以进行加减,需要强制转换
    function add() public view returns(uint160) {
        return uint160(addr1) + 10;
    }

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

    }
		//获取addr1的余额
    function getBalance() public view returns(uint256) {
        return addr1.balance;
    }

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

调试结果如下:

 

合约地址(this)

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

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

    }

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

 

转账(sendtransfer

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

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

 

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

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

 

 

 

 

7.3.5 枚举类型(enums)

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

    enum WeekDays {
      	//0 -- 6
        Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
    }

    WeekDays currentDay;
    WeekDays defaultday = WeekDays.Sunday;
		//设置,如果超过6,比如7会抛异常
    function setDay(WeekDays _day) public {
        currentDay = _day;
    }
		
    function getDay() public view returns(uint256) {
        return uint256(currentDay);
    }
		//调试结:6
    function getDefaultDay() public view returns(uint256) {
        return uint256(defaultday);   
    }
}

 

7.3.6 定长字节数组

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

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

支持运算:

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

 

//定长的字节数组
contract  test11 {

    bytes1 b1 ="h";
    
    bytes20 b10 = "helloworld";
    //bytes10 public b10 = 0x68656c6c6f776f726c64; //length == 20
    function getLen() public view returns(uint256) {
        return b10.length;
    }
    
    function setValue() public pure{
        //1. 固定长度数组可以通过下标访问
        //2. 只能读取,不能写
        // b1[0] = v;
    }
    
    //3. 存储的时候是ascii值存储
    function getValue(uint256 i) public view returns(byte) {
        return b10[i];
    }
    //length == 20,acsii的长度
    function getLenth() public view returns(uint256) {
        return b10.length;
    }
    
}

 

在线转换工具 http://www.ab126.com/goju/1711.html

 

7.4 引用类型

引用类型包含:字符串,不定长数组,数组,结构体,映射 (mapping)

7.4.1 不定长数组

bytes 相当于golang []byte

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

 

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

    //1. 可以不分空间,直接进行字符串赋值,会自动分配空间
    function setValue(bytes input) public {
        name = input;
    }
    
    //2. 如果未分配过空间,使用下标访问会访问越界报错
	  //0x68656c6c6f776f726c64 == "hello"
    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');
    }
    
}

 

7.4.2 字符串(string)

  • 动态尺⼨的UTF-8编码字符串,是特殊的可变字节数组
  • 引⽤类型
  • 不⽀持下标索引
  • 不⽀持lengthpush⽅法
  • 可以修改(需通过bytes转换)

 

contract  test13 {

    string public name = "lily";   

    function setName() public {
        bytes(name)[0] = "L";
        //name[0] = "H"; //ERROR,不⽀持下标索引
    }

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

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

 

7.4.3 引用类型的内存分配(memory 和 storage)

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

内存(memory

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

存储 (storage)

  • 数据永久保存在。
  • 被storage修饰的变量是引⽤传递,相当于只传地址,新旧两个变量指向同⼀⽚内存空间,效率较⾼,两个变量有关联,修改⼀个,另外⼀个同样被修改。
  • 只有引⽤类型的变量才可以显示的声明为 storage (注意:值类型使用storage无效)
  • 所有修饰为storage都是上链的。

状态变量

  • 声明在合约开头,相当于golang和其他语言中的全局变量。
  • 状态变量总是stroage类型的,⽆法更改。

局部变量

  • 局部变量,默认是storage类型(仅限数据结构或数组,string),但是可以声明为memory类型。
  • 状态变量,默认是storage变量,所有修饰为storage都是上链的。

 

storage Vs Memory

  1. 调用call1,调用 setName, name不会被修改,num会被修改。
  2. 调用call2,调用setName2, name会被修改,num会被修改。
  3. 调用localTest,name会被修改,num会被修改。
  4. 调用localTest1,name不会被修改,num会被修改。

 

//memory vs storage
contract  test14 {
    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; //默认情况下是storage类型
        num = 40;
        bytes(tmp)[0] = "A";
    }
    
    function localTest1() public { 
        //也可以明确设置为memory类型
        string memory tmp = name;
        num = 50;
        bytes(tmp)[0] = "B";
    }
}

 

7.4.4 转换(byte1/bytes/string)

  1. 转换过程:先将固定数组逐个复制,转成bytes,然后转成string
  2. string可以直接转成bytes

 

 

  1. 调用bytesToString,调用fixedByteToBytes,将bytes转换成string
  2. 调用stringToBytes,调用bytesToString,将string转成bytes
//bytes1, bytes,string 转换
contract  test15 {
    //定长数组
    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; //string
    
    function bytesToString() public {
        fixedByteToBytes();
        str1 = string(bs10);
    }
    
    //将string转成bytes
    bytes public bs20;
    
    function stringToBytes() public {
        bytesToString();
        bs20 = bytes(str1);
    }
}

 

7.4.5 数组

内置数组

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

自定义数组

相当于golang  numbers [10] uint

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

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

    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;
    //bytes10 to bytes
    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类型的不定长数组不支持修改长度
  • 即使没有手动分配空间,直接改变长度,那么也会自动分配空间
contract  test17 {

    //第一种创建方式,直接赋值
    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[](0);
        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

 

7.4.6 结构体

 

contract test18 {
    //定义结构之后无分号,与枚举一致
    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.4.7 字典/映射/hash表(mapping

相当于golang map,Python中的字典

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

 

contract test19 {
    //id -> name
    mapping(uint => string) public id_names;
    
    //构造函数:
    //1. 对象在创建的时候,自动执行的函数,完成对象的初始化工作
    //2. 构造函数仅执行一次
    // function Test() public {
        
    // }

    constructor()  public{
        id_names[1] = "lily";
        id_names[2] = "Jim";
        id_names[3] = "Lily";
        id_names[3] = "Tom"; //覆盖 
    }
    
    function getNameById(uint id)  public view returns (string){
        //加上storage如何赋值?
        string memory name = id_names[id];
        return name;
    }
    //将输入的key值修改为Hello
    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;
    // }
    
}

 

8 Solidity高级语法

8.1 ⾃动推导var(忘了她吧)

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

uint24 x = 0x123;
var y = x;

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

for (var i = 0; i < 2000; i++)
{
    //uint8 -> 255 永远 <2000
    //无限循环
}

 

//var 
contract test20{
    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

 

8.2 全局函数/变量

 

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

最重要的两个全局变量(msg.sender  和 msg.value

 

注意调试此处代码,需要开启ganache,查看区块信息。

contract test21 {
    
    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 test() public payable {
        
        blockNum = block.number;// (uint)当前区块的块号。
        //给定区块号的哈希值,只支持最近256个区块,且不包含当前区块
        blockhash1 = blockhash(block.number - 1);
        coinbase = block.coinbase ;//当前块矿工的地址。
        difficulty = block.difficulty;//当前块的难度。
        gaslimit = block.gaslimit;// (uint)当前块的gaslimit。

        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)交易的发送者(完整的调用链)  
    }
}

 

 

8.2.3 msg.sender (重要)

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

  • 部署合约的时候,msg.sender就是部署的账户。
  • 调用setMessage时,msg.sender就是调用账户。
  • 调用getMessage时,msg.sender就是调用账户。
contract  test22 {

    address public owner;
    uint256 a;
    address public caller;

    constructor() public {
        //在部署合约的时候,设置一个全局唯一的合约所有者,后面可以使用权限控制
        owner = msg.sender;
    }
		//切换账号调试,查看caller
    //1. msg.sender是一个可以改变的值,并不一定是合约的创造者
    //2. 任何人调用了合约的方法,那么这笔交易中的from就是当前msg.sender
    function setValue(uint256 input) public {
        a = input;
        caller = msg.sender;
    }
}

 

8.2.4 msg.value (重要)

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

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

注意:

  1. 单位是wei。
  2. 有msg.value,就必须函数有payable关键字。
//msg.value
contract  test23 {

    //uint256 public money;

    mapping(address=> uint256) public whoToMoney;

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

        // 如果转账不是100wei,那么参与失败
        // 否则成功,并且添加到维护的mapping中
        if (msg.value != 100) {
            throw;
        }
				//map调用者的address,对应值是msg.value,记录到map中。
        whoToMoney[msg.sender] = msg.value;

    }

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

 

8.3 错误处理

 

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

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

contract test24 { 

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


    constructor() payable public{
        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;
    }
}

错误的几种等价

if(msg.sender != owner) { 
    throw; 
}
//等价于如下任意一种形式:
if(msg.sender != owner) { 
    revert(); //和throw没啥区别
} 

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

 

8.4 修饰器(modifier

有点像中间件

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

 

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


    constructor() payable public{
        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);
        _; //下划线代表修饰器所修饰的代码
    }

}

 

8.5 两个常用单位

8.5.1 货币单位

  • 一个字面量的数字,可以使用后缀weifinneyszaboether来在不同面额中转换。
  • 不含任何后缀的默认单位是wei。如1 ether == 1000 finney的结果是true
  • 不同的场景下习惯使用不同的单位,通常交易行都是以Ether为单位,购买一杯咖啡之类的小额交易使用Finney,计算Gas价格时一般使用GWei,在以太坊代码开发中使用最基本的单位Wei。

参考 https://ethgasstation.info/blog/gwei/

https://www.investopedia.com/terms/g/gwei-ethereum.asp

https://my.oschina.net/u/3734107/blog/1839814

Kwei (babbage) 1e3 wei 1,000  (K 是 kilo)

Mwei (lovelace) 1e6 wei 1,000,000 (M million)

Gwei (shannon) 1e9 wei 1,000,000,000 (G giga 千兆/十亿)

microether (szabo) 1e12 wei 1,000,000,000,000 

milliether (finney) 1e15 wei 1,000,000,000,000,000 

ether 1e18 wei 1,000,000,000,000,000,000

 

babbage    亨利·巴贝奇(Henry Babbage,1791-1871年)是一位英国数学家和机械工程师,被认为是计算机之父。  

lovelace  杰出的数学家,诗人拜伦勋爵的女儿艾达·洛夫莱斯(Ada Lovelace,1815-1852年)  

shannon  克劳德·香农(Claude Shannon,1916-2001年)是美国数学家和电气工程师,被称为“信息论之父”。  

szabo  1998年,尼克·萨博(Nick Szabo)设计了比特黄金(bit gold),一种去中心化的数字货币,据说也影响了中本聪(Satoshi Nakamoto)的比特币设计。萨博提出并创造了“智能合约”这个术语。尽管他一再否认自己是中本聪,但他仍然是另一个被怀疑是中本聪的人。 

finney  哈尔·芬尼(Hal Finney,1956-2014年)是加密活动家,PGP Corporation的开发人员,可重复使用的工作证明的创建者以及早期的比特币贡献者。Finney甚至是中本聪本人发送的比特币交易的第一个接收者。

Ether(buterin) -对于以太坊的创建者Vitalik Buterin。

contract test26{
    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() pure 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
//time
contract test27{

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

 

8.6 事件(Event)

相当于打印log,在remix看log字段,在web3.js 代码调用时可以看到。

contract  test28 {

    //uint256 public money;
    
    mapping(address=> uint256) public personToMoney;
    
    // 1. 定义一个事件,使用圆括号,后面加上分号
    // 2. 需要使用emit关键字
    // 3. 在web3调用时可以监听到事件
    event playEvent(address, uint256, uint256);
    
    
    
    function paly() public payable {

        require(msg.value == 100);
        personToMoney[msg.sender] = msg.value;
        
        emit playEvent(msg.sender, msg.value, block.timestamp);
        
    }
    
    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }

}

 

8.7 访问函数(Getter Functions)

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

contract  test29 {
    
    // 加了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();
    }
}
//is 是继承
contract test30 is test29{
    //外部调用
    function getValue() public view returns(uint256) {
        test29 t1 = new test29();
        return t1.data();
    }
}

 

8.8 合约

8.8.1 合约的创建

创建合约和外部调用

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

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 view returns(uint256) {
        //传进来的地址必须是同类型的,如果是不是C1类型的,转换时报错
        c13 = C1(addr);
        return c13.getValue();
    }
}

 

8.8.2 合约的继承

  • is关键字, 可以同时继承多个父合约。
  • 当父合约存在同名函数时,默认为最远继承原则(离is最远)。
  • 可以指定某个父合约,调用它的方法。
contract Base1{
  function data() public pure returns(uint){
    return 1;
  }
}

contract Base2{
  function data() public pure returns(uint){
    return 2;
  }
}


// 1. 使用is关键字进行继承,
// 2. 多个继承间使用逗号分隔,
// 3. 如果两个父合约含有相同方法,那么默认是最远继承原则

contract son1 is Base1, Base2{
    //return 2
}

//return 1
contract son2 is Base2, Base1{
    
}

//4. 可以指定父合约,调用特定的方法
contract son3 is Base1, Base2{
    function mydata() public pure returns(uint){
        return Base1.data();
    }
}
contract son4 is Base2, Base1{
    function mydata() public pure returns(uint){
        return Base2.data();
    }
}

 

 

8.8.3 合约间如何转钱

 

创建两个合约,先创建InfoFeed,然后创建Consumer,然后调用callFeed,向InfoFeed转账。

//合约间如何转钱
contract InfoFeed {
    
    function info() public payable returns (uint ret) {
        return 42; 
    }
    
    function getBlance() public view returns(uint256) {
        return address(this).balance;
    }
}

contract Consumer {
    
    InfoFeed public feed; //0x0000000000000
    
    function setFeed(address addr) public { 
        feed = InfoFeed(addr);  //0xfeabcdf......
    }
    
    function callFeed() public { 
        //合约间转账语法,
        feed.info.value(10).gas(800)(); 
    }
    
    function () payable public {
        
    }
    
    function getBlance() public view returns(uint256) {
        return address(this).balance;
    }
}

1.部署合约InfoFeed,调用info,将合约中转10eth

2.部署Consumer,调用fallback,向合约转入10eth

3.调用callFeed

 

8.8.4 internal和external

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

pragma solidity ^0.4.25;


//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();

    }

}

 

8.9 元组(tuple)

return(a, b, c)

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

  1. 可以包含多个数据
  2. 类型可以不同
  3. 不可以修改
  4. 使用圆括号包裹
contract tuple {
    
    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);
    }
}

 

8.10 内置数学函数

ripemd160

keccak256

addmod

ecrecover

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

//keccak256

contract keccak256test {
    
    function test() public pure returns(bytes32){
        bytes memory v1 = abi.encodePacked("hello", "b", uint256(1), "hello");
        return keccak256(v1);
    }
    
    
    
    function test1() public pure returns(bytes32) {
        //bytes32 hash = sha3("hello", 1, "world", 2);
        //bytes32 hash = keccak256("hello", "b",  uint256(1), "hello");
        
        //return hash;
        return keccak256("hello", "world");
    }
}

 

8.11 其他 

for、break、continue

8.11.1 new

创建对象,合约等

8.11.2 delete

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

 

//other
contract  other {
    
    //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];
    }
}

 

8.12 合约销毁

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

    string public name;

    address manager;

    constructor(string _input) public payable {
        name = _input;
        manager = msg.sender;
    }
    //合约销毁后,调用无效
    function setName() public {
        bytes(name)[0] = "L";   
    }
    
    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }


    function kill() public {

        require(manager == msg.sender) ;

        selfdestruct(msg.sender);
    }
}

 

9.0 发币

登录以太坊代币浏览器

https://etherscan.io/tokens

 

看下BNB,它的合约比较早,简单易懂。

https://etherscan.io/token/0xB8c77482e45F1F44dE1745F52C74426C631bDD52

 

 

https://etherscan.io/address/0xB8c77482e45F1F44dE1745F52C74426C631bDD52#code

 

 

 

9.1 代币源码

ERC20标准

以太坊:什么是ERC20标准?

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

以太坊ERC20 Token标准完整说明

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

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

contract ERC20 {
		//token 发行总量
    function totalSupply() constant returns (uint totalSupply);
    //查看某个账号的余额
    function balanceOf(address _owner) constant returns (uint balance);
    //某个人花费自己的币
    function transfer(address _to, uint _value) returns (bool success);
    //授权转账函数,与approve搭配使用,approve批准之后,调用transferFrom函数来转移token。
    function transferFrom(address _from, address _to, uint _value) returns (bool success);
    //授权,如找一个人A帮你花费token,这部分钱并不打A的账户,只是对A进行花费的授权人的钱。
    function approve(address _spender, uint _value) returns (bool success);
    //查看授权额度
    function allowance(address _owner, address _spender) constant returns (uint remaining);
    //事件,当成功转移token时,一定要触发Transfer事件
    event Transfer(address indexed _from, address indexed _to, uint _value);
    //事件,当调用approval函数成功时,一定要触发Approval事件
    event Approveal(address indexed _owner, address indexed _spender, uint _value);

    string public constant name = "Token Name"; //token 名称
    string public constant symbol = "BNB";	//token 符号
    uint8 public constant decimals = 18;  // 大部分都是18
}

 

9.2 发币

1000000, "ShieldCoin", "SC"

发行币名,ShieldCoin,符号,SC,发行量 100万

单位:wei, 最小分割,小数点后面的尾数 1ether = 10** 18wei

 

pragma solidity ^0.4.25;

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

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


contract ShieldCoin 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;
    
    
    //key:授权人                key:被授权人  value: 配额
    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, "ShieldCoin", "SC"
     constructor(
        uint256 _initialSupply, //发行数量 
        string _tokenName, //token的名字 SCoin
        //uint8 _decimalUnits, //最小分割,小数点后面的尾数 1ether = 10** 18wei
        string _tokenSymbol //SC
        ) public {
            
        decimals = 18;//_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) public {
        require (_to == 0x0);                               // Prevent transfer to 0x0 address. Use burn() instead
		require (_value <= 0); 
        require (balanceOf[msg.sender] < _value);           // Check if the sender has enough
        require (balanceOf[_to] + _value < balanceOf[_to]); // 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
        emit 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) public
        returns (bool success) {
		require (_value <= 0); 
        //allowance[管理员][A] = 1万
        allowance[msg.sender][_spender] = _value;
        return true;
    }
       

    /* A contract attempts to get the coins */
    function transferFrom(address _from /*管理员*/, address _to, uint256 _value) public returns (bool success) {
        require (_to == 0x0);                                // Prevent transfer to 0x0 address. Use burn() instead
		require (_value <= 0); 
        require (balanceOf[_from] < _value);                 // Check if the sender has enough
        
        require (balanceOf[_to] + _value < balanceOf[_to]);  // Check for overflows
        
        require (_value > allowance[_from][msg.sender]);     // 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);
        emit Transfer(_from, _to, _value);
        return true;
    }

    function burn(uint256 _value) public returns (bool success) {
        require (balanceOf[msg.sender] < _value);            // Check if the sender has enough
		require (_value <= 0); 
        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
        totalSupply = SafeMath.safeSub(totalSupply,_value);                                // Updates totalSupply
        emit Burn(msg.sender, _value);
        return true;
    }
	
	function freeze(uint256 _value) public returns (bool success) {
        require (balanceOf[msg.sender] < _value);            // Check if the sender has enough
		require (_value <= 0); 
        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
        freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value);                                // Updates totalSupply
        emit Freeze(msg.sender, _value);
        return true;
    }
	
	function unfreeze(uint256 _value) public returns (bool success) {
        require (freezeOf[msg.sender] < _value);            // Check if the sender has enough
		require (_value <= 0); 
        freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value);                      // Subtract from the sender
		balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value);
        emit Unfreeze(msg.sender, _value);
        return true;
    }
	
	// transfer balance to owner
	function withdrawEther(uint256 amount) public {
		require (msg.sender != owner);
		owner.transfer(amount);
	}
	
	// can accept ether
	function() public payable {
    }
}

 

本地部署合约测试

使用ganche,启动本地环境

使用账户部署合约

0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

1.测试(币名,货币标识符,总发行量)

查看 name,symbol,totalSupply

 

 

 

发币--部署eth ropsten测试网络

1.将metemask 连接到ropsten

 

2.remix链接到Injected Web3 ,然后,输入参数,点击Deploy

大概需要等10分钟,会部署成功。点击链接查看

https://ropsten.etherscan.io/tx/0x6a29f9bd6ff7ab3d8d18cdc108026cb3e6fab80437ef860864f5d7191f5fc027

 

验证合约

1. 为什么要验证合约

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

2. 验证方式

 

 

输入合约名称ShieldCoin ,

选择编译器版本对应代码版本 v0.4.24+commit.e67f0147

优化 no

将代码原封不动粘贴过来,上传之后,点击合约地址

 

 

查看合约,发币完成。

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

 

10 编码规范

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章