Ethereum-SE01

Ethereum基础

Ethereum概念

先跟着以太坊官方开发文档看一遍,差不多基本概念了解。

Solidity语法和JS基本一致。

Account

账户形式有两种,一种是合约账户,一种的普通账户。两者不同的点就是普通账户手里有私钥,合约账户没有私钥,所以无法随意转移token。

这就导致如果你的Token转进了合约账户且没有预留相关的方法转移这个token,则该Token数量将永远锁死。

GasPrice/GasLimit

GasPrice表示价格,用wei来表示,表示一个gas值多少钱。

GasLimit表示gas数量,表示我愿意最大用多少个gas来付费。超过了表示交易会滚,但是ETH = GasPrice * GasLimit * 1^-18的钱依旧给矿工了。

一次以太坊交易所花的ETH = GasPrice * GasNumber * 1^-18。GasNumber表示在一次交易或者合约调用时具体花费的gas数量。

下面是一个简单的转账交易,简单的转账交易一般gasLimit都是21000。而且gasNumber基本上是100%

当一次合约调用所设置的GasLimit大于实际花费的gas数量,则该交易失败,意味着你付费了但是没有得到任何的结果。这时候你需要提高的你的GasLimit来让合约执行。

GasPrice还决定了被执行的优先顺序,所有的交易会被放在sharepool中,然后按照GasPrice进行排序,高GasPrice会优先被执行,低GasPrice会被后执行。如果想要交易被快速执行,那么就提高GasPrice。

GasLimit存在的意义之一,保证了你写的合约代码没有死循环,因为每一个操作都会消耗一定的gas数,当你给的GasLimit超出了实际的消耗,则该交易会被回滚。也保护了以太坊不会遭受死循环攻击。

以太坊黄皮书中有合约每个操作具体花费的gas数量。

Unit Converter:https://etherscan.io/unitconverter?wei=1

GasTracker:https://etherscan.io/gastracker

GasNow:https://www.gasnow.org/

Web3合约调用

前端调用合约使用web3,可以理解以太坊区块链就是一个巨大的服务端,我们通过web3来访问服务端。

Infura

Infura可以理解为一个巨大的Nginx,通过他来访问以太坊(当然你也可以自己用geth同步所有区块,然后连接本地的RPC)。

MateMask钱包

钱包(开源):Chrome的Extension,基本上所有的DeFi应用(基于以太坊)的都会首选的钱包插件。

Truffle开发工具链

开发工具链(开源)。从开发、部署、测试都很完善,后面直接用这个工具部署到以太坊正式网络。

truffle:https://www.trufflesuite.com/

ganache:https://www.trufflesuite.com/ganache

部分玩家喜欢使用remix进行开发和部署,但个人觉得体验上来说并不是特别好(个人使用VSCode开发)。

  1. 启动本地开发链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 每次启动的时候助记词都不一样
# port: 7545
# network_id: 5777
# chain_id: 1337
# 账户默认打100000ETH
ganache-cli -p 7545 -i 5777 --chainId 1337 -e 100000

# 固定私钥,方便每次启动ganache的时候私钥都是相同的,避免总是配置MateMask
# ETH数量是1e18次方
ganache-cli -p 7545 -i 5777 --chainId 1337 --account="私钥,100000000000000000000000000" --account="私钥,10000000000000000000000000"

# 合约太大的时候用这个
--allowUnlimitedContractSize

# gas limit
-l 12345678

# gas price
-g 12345678

# 助记词
-m 助记词
  1. 添加truffle-config.js配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const HDWalletProvider = require('@truffle/hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();

const mnemonic = '助记词'
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*",
chain_id: 1337,
gasPrice: 5000000000,
gas: 5000000,
websockets: true
},

kovan: {
provider: () => new HDWalletProvider(mnemonic, `https://kovan.infura.io/v3/key`),
network_id: 42,
chain_id: 42,
gasPrice: 5000000000,
gas: 12500000,
confirmations: 2, // # of confs to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
},
},

compilers: {
solc: {
version: "0.8.6",
settings: {
optimizer: {
enabled: true,
runs: 200
},
/// https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options
// evmVersion: "byzantium"
}
}
}
};
  1. 编译/部署/测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 编译
truffle compile

# 部署
truffle migrate
# 部署到指定网络
truffle migrate --network development/ropsten/kovan/mainnet
# 重新部署
truffle migrate --reset
# 指定部署几个脚本
truffle migrate --reset -f 1 --to 3
# 指定部署某个脚本
truffle migrate -f 4 --to 4

# 编译全部合约、并部署几个脚本,然后测试
truffle test --network development --compile-all -f 1 --to 8

Write Contract

Decimals

10 ** 18会出现溢出情况,应该改为uint256(10) ** 18

View/Pure

在solidity中,不改变链上状态的方法都要标记为view或者pure,否则调用该方法的时候会产生一笔交易,即使这个方法什么也没做。

emit这种也会修改链上状态,所以不能在view中使用。

pure是严格不访问、不修改。view是严格不修改。

msg.sender

A => ContractA: msg.sender == A

ContractA => ContractB: 在ContractB中拿到的msg.sender == ContractA

A => ContractA => ContractB: 在ContractA中拿到的msg.sender == A,在ContractB中拿到的msg.sender == ContractA。

Float

solidity暂时不支持浮点型计算,所有的计算都是整型。

所以在计算的时候一般都是先乘然后再除,否则会出现精度丢失。

开源合约库:https://openzeppelin.com/

public/external

public修饰的方法内部和外部都可以直接通过函数名字调用。

external修饰的方法,只能是外部调用,如果内部想要调用需要通过this.functionName()来调用。

在修饰view方法的时候没有区别,如果在内部通过this.functionName()来调用,则是转为外部调用(貌似会消耗更多gas?)

Truffle Migration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// 使用artifacts来创建一个引用
const ABC = artifacts.require('ERC20');
const WBTC = artifacts.require('ERC20');

module.exports = async (deployer, network, accounts) => {
/// deployer 主要是用来调用部署脚本,来部署合约用。
/// network 标识当前网络名称。truffle migrate --network development/ropsten/kovan/mainnet
/// accounts 是一个数组,当本地连接ganache的时候,是ganache提供的账户数组。

/// 部署一个无参数ABC合约
await deployer.deploy(ABC);
/// 部署一个有参数ABC合约
await deployer.deploy(ABC, "ERC20Name", "ERC20Symbol");
/// 等待部署完成并拿到实例
abc = await ABC.deployed();
/// 当我们需要引用一个已经部署的实例
wbtc = await WBTC.at('0x2260fac5e5542a773aa44fbcfedf7c193bc2c599');
/// 调用合约方法,totalSupply是一个view方法,不消耗gas。返回的是uint类型,在js中使用bn实例表现。
let totalSupply = await wbtc.totalSupply();
/// 这里我们要把bn转换成string类型显示
console.log(totalSupply.toString());
/// 查询余额
let balanceOf = await wbtc.balanceOf("0xabcdefghijklmnopqistuvwxyz");
/// 转WBTC给`0xabcdefghijklmnopqistuvwxyz`,转出账户默认是accounts[0],由于WBTC的精度是8,所以1e8表示1个WBTC
let transferSuccess = await wbtc.transfer("0xabcdefghijklmnopqistuvwxyz", web3.utils.toBN(1e8));
/// 当然你也可以指定任意转出账户。
let transferSuccess = await wbtc.transfer("0xabcdefghijklmnopqistuvwxyz", web3.utils.toBN(1e8), { from: accounts[1] });
}
1
2
3
> /// 调用合约后面加上{ from: accounts[1] }表示使用指定的账户进行操作。而不默认使用accounts[0]
> let transferSuccess = await wbtc.transfer("0xabcdefghijklmnopqistuvwxyz", web3.utils.toBN(1e8), { from: accounts[1] });
>

bn.js

truffle使用了这个库作为uint类型的返回值或者参数传递。

bn.js: https://github.com/indutny/bn.js/

1
2
3
4
exports.BN = (number) => {
return web3.utils.toBN(number);
};
exports.MAXBN = web3.utils.toBN("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");

Truffle Test

1
2
3
4
5
6
7
8
9
10
11
12
13
/// 测试基本结构
contract("# Name", accounts => {
before(async () => {
/// 配置测试环境
await wbtc.approve(uni_router.address, MAXBN);
};
it(`Is right.`, async () => {
/// 具体测试
await wbtc.transfer(accounts[1], BN(1e8));
/// 测试判断
assert(true, 'Else fail message info.');
}
}

Truffle uses the Mocha testing framework and Chai for assertions to provide you with a solid framework from which to write your JavaScript tests.

详细使用:https://www.trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript