目录
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 编码规范
起源于以太坊(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
智能合约的定义:
“智能合约”(smart contract)这个术语至少可以追溯到1995年,是由多产的跨领域法律学者尼克·萨博(Nick Szabo)提出来的。他在发表在自己的网站的几篇文章中提到了智能合约的理念。他的定义如下: “一个智能合约是一套以数字形式定义的承诺(promises),包括合约参与方可以在上面执行这些承诺的协议。” |
智能合约的本质:数字化合同。
智能合约的特点:代码代替人仲裁和执行合同,同时能够触发支付。
智能合约于普通合约对比图示:
普通合约图示如下:
Bob和Alice签署合同,由法院进行背书,公示和执行。
智能合约图示如下:
Bob和Alice共同认可的合约,以代码的形式上传到区块链上。双方缴纳保证金到合约中,当满足一定条件,由外部输入条件,合约根据逻辑触发条件,将保证金转账给一方。
以太坊是运⾏在⼀个计算机⽹络中的软件,它确保数据以及称为智能合约的⼩程序可以在没有中⼼协调者的情况下被所 有⽹络中的计算机复制和处理。以太坊的愿景是创建⼀个⽆法停⽌,抗屏蔽(审查)和⾃我维持的去中⼼化世界计算机。 它延伸了⽐特币的区块链概念:在全球范围的多个计算机上验证,存储和复制交易数据(因此术语叫“分布式账本”)。以太坊(Ethereum)在这个概念上更进⼀步,使之(交易数据)在全球范围的多个计算机上运⾏代码成为现实。 ⽐特币⽤来分布式储存数据的,以太坊⽤来分布式储存数据并且计算。这些⼩型的电脑运⾏程序叫做智能合约,合约由参与者在他们⾃⼰的机器上通过⼀种称为 “以太坊虚拟机(EVM)”的操作系统运⾏。 |
智能合约与以太坊的关系——智能合约是⼀个部署在以太坊区块链上的程序。
全世界的计算机通过网络互连,每个节点都运行一个以太坊客户端,即组成以太坊网络。
小结:
|
以太坊爱好者网站
https://ethfans.org/wikis/Home
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共识机制。(获取测试币⽐较困难)
remix在线编译器
访问链接,方便调试,但是依赖网络,需。
https://remix.ethereum.org/
旧版界面
左侧是文件夹,右侧 compile 编译合约, run 使用deploy部署合约,At address(输入合约地址,可以加载合约)
搭建本地网络
方便调试,相对稳定。
npm install remix-ide -g
启动
remix-ide
访问 http://localhost:8080,即可打开本地编译器。
编译合约
remix编辑器中⾃动集成了solidity的编译器,所以可以⾃动编译我们的合约代码
编译原理
使⽤remix,由⾼级语⾔变成机器语⾔
格式片段
6080604052610410806100136000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b87721461005c578063ce6d41de146100c5578063e21f37ce14610155575b600080fd5b34801561006857600080fd5b506100c3600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101e5565b005b3480156100……
json格式的描述⽂件
[
{
"constant": false,
"inputs": [
{
"name": "newMessage",
"type": "string"
}
……
]
图示
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
数据类型思维导图
https://naotu.baidu.com/file/8ea7d69f9df11dc89433d37d818214de
值类型
值类型是指变量在传递过程中将数值完整的copy一份,再赋值给新的变量。
这种方式需要重新开辟新的内存空间,两个变量完全独立,互不影响,修改一个不会影响另外一个。缺点是效率低。
值类型包含
引用类型
solidity没有指针类型,对于复杂的结构进行高效传递方式(相当于指针)是使用关键字storage进行修饰。
简单来说就是,如果在变量之前添加storage就是引用传递,不加就是值传递。但是只对复杂类型有效。
复杂类型,占用空间较大。所以考虑通过引用传递。
引用类型包含
合约包含的基本元素
//指定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 链接自定义网络。
常见错误:
7.3.1 布尔
bool b1;
bool b2 = false;
bool b3 = true;
7.3.2 整形
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();
}
}
常用关键字说明 view,constant,pure
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
//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函数。
匿名函数
一个合约可以有且只有一个匿名函数,此函数不能有参数,也不能有任何返回值,当我们企图去执行一个合约上没有的函数时,那么合约就会执行这个匿名函数。
当合约在只收到以太币的时候,也会调用这个匿名函数,而且一般情况下会消耗很少的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;
}
}
转账(send,transfer)
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)
//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递增。length
,返回数组长度支持运算:
描述 | 运算符 |
比较运算 | <=,<,==,!=,>=,> |
位运算符 | &,\,^(异或),~非 |
下标访问 | [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
引用类型包含:字符串,不定长数组,数组,结构体,映射 (mapping)
7.4.1 不定长数组
bytes 相当于golang []byte
下标索引
storage
来修饰,进行引用传递,指针的效果)length
、push
方法(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)
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)
存储 (storage)
状态变量
局部变量
storage Vs Memory
//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)
//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 数组
内置数组
自定义数组
相当于golang numbers [10] uint
//自定义定长数组
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);
}
}
不定长数组
length
、push
方法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中的字典
keccak256
哈希值,用来查找值时使用。
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;
// }
}
为了⽅便,并不总是需要明确指定⼀个变量的类型,编译器会通过第⼀个向这个对象赋予的值的类型来进⾏推断。
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
函数 | 含义 |
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)交易的发送者(完整的调⽤链) |
注意调试此处代码,需要开启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都可以不同。举例:
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来获取到的。
注意:
//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;
}
}
在创建合约时设置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);
有点像中间件
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);
_; //下划线代表修饰器所修饰的代码
}
}
wei
,finney
,szabo
或ether
来在不同面额中转换。wei
。如1 ether
== 1000 finney
的结果是true
。参考 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;
}
}
时间单位
//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;
}
}
相当于打印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;
}
}
编译器为自动为所有的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();
}
}
创建合约和外部调用
需要显示转化类型
后才能使用。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();
}
}
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();
}
}
创建两个合约,先创建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
访问函数有外部(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();
}
}
return(a, b, c)
solidity无法返回自定义的数据结构,所以若想返回一个自定义结构的数据,需要在函数中一次返回多个值,即元组。元组是一个数据集合,类似于字典但是无法修改数据,使用圆括号包括多种数据类型。
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);
}
}
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");
}
}
for、break、continue
8.11.1 new
创建对象,合约等
8.11.2 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];
}
}
//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);
}
}
登录以太坊代币浏览器
https://etherscan.io/tokens
看下BNB,它的合约比较早,简单易懂。
https://etherscan.io/token/0xB8c77482e45F1F44dE1745F52C74426C631bDD52
https://etherscan.io/address/0xB8c77482e45F1F44dE1745F52C74426C631bDD52#code
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
}
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