solidity 从入门到发币(eth)

Posted 费纸的涛哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 发币

9.3 本地部署合约测试

9.4 验证合约 --完成发币

10 编码规范


1 Solidity与智能合约

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

参考文档

Solidity文档区块链技术-智能合约Solidity编程语言

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

solidity英文文档:Expressions and Control Structures — Solidity 0.8.12 documentation

以太坊发展的⽂章:ETH以太坊的诞生发展史_区块链_金色财经

官⽅⽹址: 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在线编译器

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

Remix - Ethereum IDE

旧版界面

左侧是文件夹,右侧 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 数据类型分类

数据类型思维导图

  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;
    
    

在线转换工具 ASCII 在线转换器 -ASCII码-十六进制-二进制-十进制-字符串-ascii查询器 ab126软件园

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 mon

以上是关于solidity 从入门到发币(eth)的主要内容,如果未能解决你的问题,请参考以下文章

智能合约实战 solidity 语法学习 11 [ 以太坊发币 验证合约 体验下过程 ] 附代码

智能合约实战 solidity 语法学习 11 [ 以太坊发币 验证合约 体验下过程 ] 附代码

Solidity极简入门#22. Call

ETH以太坊怎样进行一键发币?

eth入门之网络

扎格伯克败走加密货币:2亿美元打包变卖技术,核心团队出走殆尽,发币计划仅2年就从入门到放弃...