2.1合约基本元素
2.2 合约的生命周期
调用destroy函数之后,合约仍然存在于区块链之上,但是函数无法被调用,调用后会抛出异常。
2.3 数据类型分类
1.值类型(值传递)
2. 引用类型(指针传递)
这两种类型的的特点相比大家也很熟悉,就不再赘述,在sol中只需要对变量进行修饰,其中值类型用memory,指针用storage。
2.4函数声明
几个非常非常重要的关键字
修饰符 | 说明 |
---|---|
public | 公有,任何人(拥有以太坊账户的)都可以调用 |
private | 私有, 只有智能合约内部可以调用 |
external | 仅合约外部可以调用,合约内部需使用this调用 |
internal | 仅合约内部和继承的合约可以调用 |
view/constant | 函数会读取但是不会修改任何contract的状态变量 |
pure(纯净的) | 函数不使⽤任何智能合约的状态变量 |
payable | 调用函数需要付钱,钱付给了智能合约的账户 |
returns | 返回值函数声明中使用 |
构造函数
仅在部署合约时调一次次,完成对合约的初始化。
constructor
2.5. 地址(Address)
作为以太坊的一种特殊的类型,大小为20个字节 ,20 * 8 = 160位 ,所以可以用一个 uint160 编码。地址是所有 合约的基础,所有的合约都会继承地址对象,通过合约的地址串,调⽤合约内的函数。
属性/方法 | 含义 |
---|---|
balance | 获取余额 |
transfer | 转账 |
call | 合约内部调用合约 |
2.6 Enums枚举类型
枚举类型是在Solidity中的一种用户字定义类型。 枚举可以显示的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运用时检查数值范 围,如果不匹配,将会引起异常。 枚举类型应至少有一名成员,枚举元素默认为uint8,当元素数量足够多时,会自动变为uint16, 第一个元素默认为0,使用超出范围的数值时会报错。
pragma solidity ^0.4.0;
contract test {
enum WeekDays
{ Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }
WeekDays currentDay;
WeekDays defaultday = WeekDays.Sunday;
function setDay (weekDays _day) public {
currentDay = _day;
}
function getDay() public view returns(uint256) {
return uint256(currentDay);
}
function getDefaultDay() public view returns(uint256) {
return uint256(defaultday);
}
2.7字典/映射/hash表(mapping)
1.键key的类型允许除映射外的所有类型,如数组,合约,枚举,结构体,值的类型,无限制。
2.无法判断一个mapping中是否包含某个key,因为它认为每一个都存在,不存在的返回0或false。
3.映射可以被视作为一个哈希表,在映射表中,不存储键的数据,仅仅存储它 的 keccak256 哈希值,用来查找值时使用。
4.映射类型,仅能用来定义状态变量,或者是在内部函数中作为storage类型的引用。
5.不支持length
6.key不支持string 类型
mapping(uint => string) id_names;
2.8重要的两个全局变量
1.msg.sender
每一次和以太坊交互时都会产生意笔交易,这笔交易的执行者就是msg.sender。
简而言之:谁调用的,msg.sender就是谁,每笔交易的msg.sender都可以不同。
2.msg.value
我们在介绍payable关键字的时候说,如果函数修饰为payable,那么这个函数可以接收转账,这 笔钱通过remix的value输入框传递进来。 在转账操作中,这笔钱是通过我们调用一个函数从而产生一笔交易而转入合约的,换句话说,是 这笔交易附带了一笔钱。在合约中,每次转一的value是可以通过msg.value来获取到的。
注意,
1.单位是wei
2.有msg.value,就必须有payable关键字
2.9 区块和交易的属性
函数 | 含义 |
---|---|
blockhash(uint blockNumber) | 哈希值(byte32) |
block.coinbase | (address) 当前块矿⼯的地址。 |
block.difficulty | (uint)当前块的难度 |
block.gaslimit | (uint)当前块的gaslimit |
block.number | (uint)当前区块的块号 |
block.timestamp | (uint)当前块的时间戳 |
msg.data | (bytes)完整的调⽤数据(calldata) |
gasleft() | (uint)当前还剩的gas |
msg.sender | (address)当前调⽤发起⼈的地址 |
msg.sig | (bytes4)调⽤数据的前四个字节(函数标识符) |
msg.value | (uint)这个消息所附带的货币量,单位为wei |
now (uint)当前块的时间戳 | 等同于block.timestamp |
tx.gasprice | (uint) 交易的gas价格 |
tx.origin | (address)交易的发送者(完整的调⽤链) |
2.10 修饰器(modifier)
修改器(Modifiers)可以庸来轻易的改变一个函数的行为。==比如用于在函数执行前检查某种前置条件 ==。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。下面我们来看一段示 例代码:
pragma solidity ^0.4.24;
contract Test {
uint256 public value ;
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner(address caller) {
//require(msg.sender == owner);
require(caller == owner);
//_;代表这个修饰器所修饰函数的代码 _;
}
//使用修饰器,将仅管理员可以执行的限定放到函数外面
function changeValue(uint256 _value) onlyOwner(msg.sender) public{
value = _value;
}
}
2.11 合约创建和外部调用
1.new关键字,返回值是一个address, 需要显示转化类型 后才能使用
2. contract1形式,此时contract1是空的,需要赋值地址才能使用·,否则报错。
//10表示contract1中constructor需要的参数
address addr1 = new Contract1(10);
c1 = C1(addr1);
return c1.getValue();
2.12 合约继承
is关键字
pragma solidity ^0.4.0;
contract Base1{
function data() pure returns(uint){
return 1;
}
}
contract Base2{
function data() pure returns(uint){
return 2;
}
}
contract son1 is Base1, Base2{
}
contract son2 is Base2, Base1{
}
2.13 内置数学函数
keccak256(…) returns (bytes32)
哈希函数,代替sha3(废弃)
pragma solidity ^0.4.24;
contract Test {
function test() public pure returns(bytes32){
bytes memory v1 = abi.encodePacked("abc", "b", uint256(1), "hello"); return keccak256(v1);
}
}
由于我们所部署的DAPP的架构是完全支持JavaScript中的ES6语法的,所以需要对ES6语法的基本规则进行一个简述。
3.1. 定义变量
1.使用 const 来定义一个常量,常量也就是不能被修改,不能被重新赋值的变量。
2.使用 let 来定义一个变量,⽽不要再使用 var 了,因为 var 有很多坑;可以认为 let 就是修 复了bug的 var 。比如,var允许重复声明变量而且不报错;var的作用域让人感觉疑惑。
3.2 解构赋值
数组的解构赋值
const arr = [1, 2, 3] //我们得到了一个数组
let [a, b, c] = arr //可以这样同时定义变量和赋值
console.log(a, b, c); // 1 2 3
对象的解构赋值(常用)
const obj = { name: '俊哥',address:'深圳', age: '100'} //我们得到了一个对象
let {name, age} = obj //可以这样定义变量并赋值
console.log(name, age); //俊哥 100
函数参数的解构赋值(常用)
const person = { name: '小明', age: 11}
function printPerson({name, age}) { // 函数参数可以解构一个对象
console.log(`姓名:${name} 年龄:${age}`);
}
printPerson(person) // 姓名:⼩明 年龄:11
3.3函数扩展
箭头函数。
将 function 换成 => 定义的函数,就是箭头函数 只适合用于普通函数,不要用在构造函数,不要用在成员函数,不要用着原型函数。
function add(x, y) {
return x + y
}
// 这个箭头函数等同于上面的add函数
(x, y) => x +y;
// 如果函数体有多个,则需要用大括号包裹
(x, y) => {
if(x >0){
return x + y
}else {
return x - y
}
}
3.4 Class继承
由于js一开始被设计为函数式语言,万物皆函数。所有对象都是从函数原型继承而来,通过 继承某个函数的原型来实现对象的继承。但是这种写法会让新学者产生疑惑,并且和传统的OOP 语言差别很大。ES6 封装了class语法来大大简化了对象的继承。
class Person {
constructor(name, age){
this.name = name
this.age = age
}
// 注意:没有function关键字
sayHello(){
console.log(`⼤家好,我叫${this.name}`);
}
}
class Man extends Person{
constructor(name, age){
super(name, age)
}
//重写父类的方法
sayHello(){
console.log('我重写了父类的方法!');
}
}
let p = new Person("小明", 33) //创建对象
p.sayHello() // 调用对象p的方法,打印
let m = new Man("王五", 33)
m.sayHello() // 我重写了父类的⽅方法!
3.5同步与异步
同步调用(阻塞)
var fs = require("fs");
var data = fs.readFileSync('input.txt');
console.log(data.toString());
console.log("程序执⾏结束!");
异步调用(非阻塞)
var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序执⾏结束!");
promise写法:
let fs = require('fs')
//解决办法:把每⼀个异步函数都封装成⼀个pomise
let readFilePromise = () => {
return new Promise((resolve, reject) => {
try {
fs.readFile('./1.txt', 'utf-8', function (err, data) {
console.log('读取⽂件: ', data)
resolve(data)
})
} catch (e) {
reject(e)
}
}) }
let writeFilePromise = (data) => {
return new Promise((resolve, reject) => {
fs.writeFile('./2.txt', data, 'utf-8', function (err) {
if (err) {
reject(err)
}
resolve('写⼊成功!')
})
})
}
let statPromise = () => {
return new Promise((resolve, reject) => {
fs.stat('./2.txt', function (err, stat) {
if (err) {
reject(err)
}
// console.log('⽂件状态:', stat)
resolve(stat)
})
})
}
//如果想使⽤async,await,promise,
//调⽤函数的外⾯修饰为async
//promise函数前⾯加上 await
let checkStat2 = async () => {
try {
let data = await readFilePromise()
let res = await writeFilePromise(data)
console.log('res :', res)
let stat = await statPromise()
console.log('stat:', stat)
} catch (e) {
console.log(e)
}
}
3.path模块和fs模块
名字 | 作用 |
---|---|
path.basename | 返回一个路径的最后一部分 |
path.dirname | 返回一个路径的目录名 |
path.extname | 返回一个路径的扩展名 |
path.join | 用于拼接给定的路径片段 |
path.normalize | 将一个路径正常化 |
path.resolve([from …], to) | 基于当前的执行目录,返回一个绝对路径,退一层演示 |
fs.stat/fs.statSync | 访问文件的元数据,比如文件大小,文件的修改时间 |
fs.readFile/fs.readFileSync | 异步/同步读取文件 |
fs.writeFile/fs.writeFileSync | 异步/同步写入文件 |
fs.readdir/fs.readdirSync | 读取文件夹内容 |
fs.unlink/fs.unlinkSync | 删除文件 |
fs.rmdir/fs.rmdirSync | 只能删除空文件夹 |
4.1智能合约编译原理
//导入·solc编译器
let solc = require('solc') //0.4.26
let fs = require('fs') //读取合约
let sourceCode = fs.readFileSync('./contracts/SimpleStorage.sol', 'utf-8')
let output = solc.compile(sourceCode, 1)
module.exports = output['contracts'][':SimpleStorage']
4.4 启动Ganache UI
4.5 部署合约
在depole.js中添加如下代码:
let {bytecode, interface} = require('./01-compile')
let Web3 = require('web3') //2. new 一个web3实例
let web3 = new Web3() //3. 设置网络
web3.setProvider('HTTP://192.168.28.30:7545')
const account = '0xd5957914c31E1d785cCC58237d065Dd25C61c4D0'
console.log(web3.currentProvider) //1. 拼接合约数据
let contract = new web3.eth.Contract(JSON.parse(interface)) //2. 拼接bytecode
contract.deploy({ data: bytecode, //合约的bytecode
arguments: ['HelloWorld'] //给构造函数传递参数,使用数组
}).send({
from: account,
gas: '3000000', //不要用默认值,一定要写大一些, 要使用单引号
gasPrice: '1',
}).then(instance => {
console.log('address :', instance.options.address) })
4.6 获取链上合约实例
创建interaction.js,这个是与合约交互的文件。
调用前需要将链上的合约实例找到,这样才能完成交互,需要用到 :
1.web3 : 指定网络
2. ABI:二进制接口
3. address:合约地址
let Web3 = require('web3')
//2. new 一个web3实例
let web3 = new Web3()
//3. 设置网络
web3.setProvider('HTTP://192.168.28.30:7545')
let abi = [{ "constant": true, "inputs": [], "name": "getValue", 9"outputs": [{"name": "", "type": "string"}], "payable": false, "stateMutability": "view", "type": "function" }, {"constant": false, "inputs": [{"name": "_str", "type": "string"}], "name": "setValue", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, {"inputs": [{"name": "_str", "type": "string"}], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }]
let address = '0x0FE5006b70A0D58AD3c4d4BC9DAC02C970510Cf6' //此处abi已经json对象,不需要进⾏parse动作
let contractInstance = new web3.eth.Contract(abi, address)
console.log('address :', contractInstance.options.address)
module.exports = contractInstance
4.7 调用合约
let test = async () => {
try {
let v1 = await instance.methods.getValue().call({
from: from
})
console.log('v1:', v1)
let res = await instance.methods.setValue('HelloHangTou').send({
from: from,
})
console.log('res:', res)
let v2 = await instance.methods.getValue().call({
from: from
})
console.log('v2:', v2)
} catch (e) {
console.log(e)
}
}
test()