hello shaokun

code is law


  • 首页

  • 归档

常用linux/vim命令(不定时更新)

发表于 2021-01-31

目标

  • 常用操作的记录,不定时更新,方便查找

linux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 查看末尾的的100行 tail 100 
- 查看少量文本 cat
- 查看大文件 less -mN text.txt (-mN显示进度与行号, 搜索 ? / ,从后往前查看 shif + G 移动到最后 space 向下翻页 b向上翻页),极力推荐
- 递归现实当前目录下的所有文件 ls -R
- 不断输出查看末尾的的100行 tail 100 -f
- 查看磁盘信息 df -h
- 查看当前目录的信息 du -h -d 1
- 移动到命令行首 ctrl + a
- 移动到命令行尾 ctrl + e
- 删除当前位置到行尾的数据 ctrl + k
- 删除当前位置到行首的数据 ctrl + u
- 清屏 ctrl + l
- 改名 mv test.txt test1.txt
- 复制 cp text.txt text1.txt (只能复制文件,可配合压缩来处理)
- 压缩gizp tar -zcvf test1.txt.tar.gz text.txt
- 解压gizp tar -zxvf test1.txt.tar.gz
- 删除 rm -rf test.txt

vim

1
2
3
4
5
6
7
8
- 跳转到页面顶部  gg
- 跳转到页面底部 G
- 向上半屏查看 ctrl + b
- 向下半屏查看 ctrl + f
- 显示行号 set nu
- 删除当前行 dd
- 拷贝1,4行到5行开始 1,4 copy 5
- 移动1,4行到第5行开始 1,4 move 5
关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

你可能不知道的操作(1)

发表于 2020-12-18

node使用es6的引入模式

  • node 版本大于14,将文件的扩展名改为.mjs(小于14执行的时候加上 –experimental-modules)
  • 如果有packjson,配置 type字段为module,具体玩法,这样就可以在web和node中采用同一种方式引入模块啦
  • 当然更好的方式使用ts来书写咯!!!

    1
    2
    3
    {
    "type":"module"
    }
  • 使用这种方式后,__filename没有了,可以使用如下方式实现呢

1
2
3
4
5
6
7
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
console.log("__fileName:", __filename);
console.log("__dirName:", __dirname);

脱离webpack在html中直接使用react

  • 如果你喜欢用react,页面内容较少,可以采用此种方式呢,仅供玩玩
  • 注意react的script标签的type为 text/babel,具体玩法看这里
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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
</head>

<body>
<div id="root"></div>

<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

<script type="text/babel">

function Hello({ name }) {
const savedCallback = React.useRef(null);
const [count, updateCount] = React.useState(0)

React.useEffect(() => {
savedCallback.current = function () {
updateCount(c => c + 1)
}
}, [])

React.useEffect(() => {
const interval = setInterval(() => savedCallback.current(), 1000);
return () => clearInterval(interval);
}, []);

return <div>
hello {name} + {count}
</div>
}


ReactDOM.render(<Hello name="World" />, document.getElementById("root"));

</script>
</body>

</html>
关于我

软件开发攻城狮一枚,熟悉dapp,web,android,node,go等软件的开发。

antd实现theme的切换(2)

发表于 2020-11-29

目标

  • 如果只有一套可以按照官方的介绍的方式,配置在项目中即可达到目的
  • 本篇文章实现的是根据ui提供的less文件使用webpack导出自定义的css

实现流程

  • 目录结构
1
2
3
4
5
6
custom-less-theme
package-lock.json src
package.json webpack.config.js

./src:
main.js
  • 安装依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"scripts": {
"build": "webpack "
},
"dependencies": {
"antd": "^4.8.6",
"css-loader": "^5.0.1",
"css-minimizer-webpack-plugin": "^1.1.5",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"mini-css-extract-plugin": "^1.3.1",
"style-loader": "^2.0.0",
"webpack": "^5.8.0",
"webpack-cli": "^4.2.0"
}
}
  • 导出less文件即main.js的内容
1
import "antd/dist/antd.less"
  • webpack.config.js的配置,固定写法,其中用到了两个插件,一个为将less转化成的css提取出来,一个为压缩提取出来的css.
  • 这里可以将要修改的变量采用一个json来配置,方便管理
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 path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
mode: "production",
entry: "./src/main.js", //入口js
output: {
filename: "main.js", // 输出的js,我们不需要,这里是个空文件
path: path.resolve(__dirname, "dist"),
},
plugins: [
new MiniCssExtractPlugin({
filename: "myant.min.css", // 输出的css文件名
}),
],
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
},
module: {
rules: [
{
test: /\.less$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: "css-loader",
},
{
loader: "less-loader",
options: {
lessOptions: {
javascriptEnabled: true,
modifyVars: {
"primary-color": "#1DA57A", // 要修改的less变量
},
},
},
},
],
},
],
},
};
  • 执行打包, npm run build
结果演示
  • 实现的效果为从antd->antd dark -> andt的primary颜色的三个主题来回变化,其中绿色的primary即是本次实现的自定义主题的效果啦

graphql query demo

总结

熟悉各个工具的使用,细心跟着文档做就可以啦

关于我

软件开发攻城狮一枚,熟悉dapp,web,android,node,go等软件的开发。

antd实现theme的切换

发表于 2020-11-28

目标

  • 在react的开发中,如果使用在antd作为其基础的ui库,根据官方文档,没有提供类似material-ui的themeProvider来非常方便的实现动态的切换当前页面的主题的方式(如果有有方便的方式,请告诉我一下…),那么接下来我们将采用非常规的方式来实现这个目的
  • 本篇文章中并不是在介绍如何自定义主题呢,请注意一下

实现流程

  1. 首先按照介绍安装antd,
  2. 进入antd/dist/中,将antd.dark.min.css / antd.min.css文件拷贝到public目录中(一般我们仅需要这两个主题),注意是css
  3. 修改public目录下的index.html,并将其加入到样式表中
1
2
<link type="text/css" rel="StyleSheet" href="./antd.dark.min.css" />
<link type="text/css" rel="StyleSheet" href="./antd.min.css"/>
  1. 实现切换主题(由于这个css文件基本不会改变,故根据名称加载即可,当然其他方法也可以),其原理就是动态覆盖css变量而已咯
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
import {Button} from 'antd';
import React, {useState} from 'react';

function switchTheme(theme: number) {
let cssArr = ["antd.min.css", "antd.dark.min.css"];
let headHTML = document.getElementsByTagName('head')[0];
let link = document.createElement("link");
link.type = "text/css";
link.rel = "Stylesheet";
link.href = cssArr[theme % 2];
headHTML.append(link);
}

function App() {
const [theme, updateSet] = useState(0);
React.useEffect(() => {
switchTheme(theme);
}, [theme]);
return (
<div className="App">
<Button type={"primary"} onClick={() => {
updateSet(x => x + 1);
}}> switch theme </Button>
</div>
);
}
结果演示

graphql query demo

总结

  • 对没有提供如react-material-ui中themeProvider来实现theme的切换的ui框架,采用此种方式实现虽然操作上不怎么优雅,但是也能够达到目的,在没有更好的方式来实现的时候,可以一试
  • 目前验证antd,React Suite均能够实现通过此种方式,理论上提供css样式修改的ui框架均可以采用此方式是来实现实现主题的切换
  • 建议将所有的主题提前在header中加载,这样在切换主题时直接从缓存中加载,避免切换的过程中出现闪烁的情况
关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

koa + graphql的使用(2)

发表于 2020-11-15

目标

koa + graphql的使用在这篇文章中,我们只实现了基本上的如何使用graphql,那么我们来实现业务上的graphql

实现

  • 目录结构,大体上分为三个目录结构,schema用于定于所有的类型,resolver定义相应类型的实现,models定义一些service的具体实现,如查询数据库,app.ts为项目入口
1
2
3
4
5
6
7
8
9
10
11
./src:
app.ts models resolvers schema

./src/models:
index.ts

./src/resolvers:
index.ts message.ts user.ts

./src/schema:
index.ts message.ts user.ts
  • 项目入口app.ts,可以看到和上诉定义的目录结构一致,引入根数据,注意其中的context,这里用于挂在挂载全局变量,实现一些中间件的挂载,如auth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Koa from "koa";
import { ApolloServer } from "apollo-server-koa";
import resolvers from "./resolvers";
import typeDefs from "./schema";
import models from "./models";
const app = new Koa();
const server = new ApolloServer({
typeDefs,
resolvers,
context: {
models,
me: models.users[1],
},
});
server.applyMiddleware({ app });

app.listen({ port: 5000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);
  • 定义user的schema,注意其中的query加上了extend字段(message.ts内容一致)
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

import {gql} from "apollo-server-koa";

const User = gql`
extend type Query {
users: [User!]
user(id: ID!): User
me: String!
}

type User {
id: ID!
username: String!
messages: [Message!]
}
`;
export default User;
```

- 定义schema的导出文件,定义空的schema,以便具体的实体可以继承,固定写法

```javascritp
import { gql } from "apollo-server-koa";
import message from "./message";
import user from "./user";
const linkSchema = gql`
type Query {
_: Boolean
}

type Mutation {
_: Boolean
}

type Subscription {
_: Boolean
}
`;

export default [linkSchema, user, message];
  • user的resolver具体实现,根据user的schema实现,这里注意resolver接收四个参数,根据官网的说法,我们一般用上前三个就够了,
  • 分别是parent,即上一个查询的的结果
  • args,本次执行的前端页面传过来的参数
  • context,即app中的context,即返回的那个object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {IResolvers} from "apollo-server-koa";

const user: IResolvers = {
Query: {
users: (parent, args, {models}) => {
return Object.values(models.users);
},
user: (parent, {id}, {models}) => {
return models.users[id];
},
},
User: {
messages: (user, args, {models}) => {
return Object.values(models.messages).filter(
//@ts-ignore
(message) => message.userId === user.id
);
},
},
};

export default user;
  • resolver 导出文件,直接导出数组即可
1
2
3
4
import message from "./message";
import user from "./user";

export default [message, user];
  • models 模拟本次程序的数据,现实中用数据库代替即可
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
let users = {
1: {
id: "1",
username: "hello shoakun 1",
messageIds: [1],
},
2: {
id: "2",
username: "hello shaokun2",
messageIds: [2],
},
};

let messages = {
1: {
id: "1",
text: "Hello World",
userId: "1",
},
2: {
id: "2",
text: "By World",
userId: "2",
},
};

export default {
users,
messages,
};
结果演示

源码
graphql query demo

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

一步一步教你打造自己的defi-xswap(6)

发表于 2020-11-08

welcome

本篇文章主要配合前端实现xswap的最后一个功能swap

swap公式

  • 此公式由 UniswapV2Library提供
  • getAmountOut 即我付出一定量的token,可以得到另一种token的多少,比如我只有100个tokenA,这个就是查看我这个100tokenA可以换取到多少个tokenB
  • getAmountIn, 即我要得到一定量的token,我得付出多少另一种token,比如我想要100个tokenA,那么这个就是查看我要付出多少个tokenB
  • 每次交易,均有千分之三fromToken的损失,这部分费用是按照LP的比例分给提供LP的做市商
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}

// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}

demo验证

  • 本次演示前端代码
  • 目前有两步操作,一是转入tokenA到pair中,二是根据上述的算法,动态计算出tokenA转入可以得到的tokenB的数量,调用pair的swap方法将tokenB提到自己的钱包中
  • 前端需使用bignumber.js这个lib来计算大数,可以看到和合约的实现是一样的,只有这样才能通过验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import BN from 'bignumber.js'
function getAmountIn(amountOut: string, reserveIn: string, reserveOut) {
return new BN('1000')
.times(amountOut)
.times(reserveIn)
.div(new BN(reserveOut).minus(amountOut).times(997).plus(1))
.toFixed()
}

function getAmountOut(amountIn: string, reserveIn: string, reserveOut: string) {
let feeIn = new BN(amountIn).times(997)
return feeIn
.times(reserveOut)
.div(feeIn.plus(new BN(1000).times(reserveIn)))
.toFixed()
}

xswap

总结

  • xswap到这里已经结束了.
  • 本系列只重点着重于合约代码层面的实现,采用react全家桶实现的前端界面(文中未做过多介绍前端代码的实现).
  • 个人觉得uniswap的精髓在其整个的设计与其创新,理论上来讲,是真正实现了一个去中心化的交易所的功能
  • 目前前端页面只提供了tokenA,与tokenB的操作,且只适用于rinkeby网络,如果各位同学想要进一步发挥,可以完全参考uniswap的实现uniswap-peripher,uniswap前端页面自己实现一套属于自己的swap吧

关于我

区块链程序猿一枚

一步一步教你打造自己的defi-xswap(5)

发表于 2020-11-07

welcome

本篇文章主要介绍UniswapV2Pair中的以及一个名词,三个方法,四个变量,理解完后,实现xswap的add,remove的功能

一个名词

  • liquidity (LP)流动性,即pair的token的数量,代表着你所有这个pair价值的量,其价值为你的LP占总LP的百分比乘以pair的总价值

四个变量

1
2
3
4
address public token0;			//	交易对中的token0 的地址
address public token1; // 交易对中的token1 的地址
uint112 private reserve0; // 交易对地址中的 token0的数量,即token0的价值
uint112 private reserve1; // 交易对地址中的 token1的数量 ,即token1的价值

三个方法

  • mint 即生产LP,其规则根据你向这个pair中按照固定比例(第一笔设定比例)转入一定数量token0,token1.当然你可以不按照这个比例转入,但是你得到的LP是按照这个比例中的小的给你算的,所以不要做这样的事呢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function mint(address to) external lock returns (uint liquidity) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity);

_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}
  • burn 即销毁你转入的LP,即你先把LP转入pair合约中,然后根据你转入的LP数量按照占总量LP的比例返回pair中token0,token1到你指定的地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function burn(address to) external lock returns (uint amount0, uint amount1) {
    (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
    address _token0 = token0; // gas savings
    address _token1 = token1; // gas savings
    uint balance0 = IERC20(_token0).balanceOf(address(this));
    uint balance1 = IERC20(_token1).balanceOf(address(this));
    uint liquidity = balanceOf[address(this)];
    bool feeOn = _mintFee(_reserve0, _reserve1);
    uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
    amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
    amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
    require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
    _burn(address(this), liquidity);
    _safeTransfer(_token0, to, amount0);
    _safeTransfer(_token1, to, amount1);
    balance0 = IERC20(_token0).balanceOf(address(this));
    balance1 = IERC20(_token1).balanceOf(address(this));

    _update(balance0, balance1, _reserve0, _reserve1);
    if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
    emit Burn(msg.sender, amount0, amount1, to);
    }
  • swap 即使用token0交换token1或者相反交易.注意一下,swap是不会改变pair的LP的量.这个呢会改变当前两种token对应的价格.所以一个token的价格也即是通过这种方式来定义的呢!

  • 这里具体的操作呢?这里设计得非常巧妙!
  • function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock 函数签名中,amount0Out与amount1Out一定有一个值为0,所以首先得转入你需要交易的卖掉的token,如果是token0,那么调用此方法的第二个参数即amount1Out设置为大于0的值即可得到你想要的另一种token,即完成了一次交易,至于这个值填多少? 值为理论转出的99.7%
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
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}

_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}

demo验证

  • 本次演示前端代码
  • 相关合约地址: factory:0xfed46C22C2C39126F3312fBe739a0D9cD29AD1D1 tokenA: 0x425CBf6caF564FfFe1629ACADBD17Ee421B1f6aE tokenB: 0x7A759B6cb32Ba3098530909aD5178dc6125A2c26
  • 由于执行添加或者swap需要一对token,所以顺带创建了2个测试token,xtoken模板提供了faucet方法,每次调用将会空投100个token到操作账户的地址
  • 这里我初始定价为 2 tokenA = 1 tokenB,
  • 使用faucet方法分别得到一定数量的tokenA tokenB(这里只演示了获取TokenA)
  • add 转账tokenA 都pair合约中,转账tokenB到pair合约中,换取LP到自己的账户 (这里一共执行了三次合约)
  • remove 转账LP到pair合约中,再根据转进去的LP按照总的比例取出换回来的tokenA,tokenB
    xswap

总结

  • 注意一下,本系列所有的合约仅部署在了rinkeby测试链上,如果需要源码演示请切换网络才
  • 此合约部署只需要UniswapV2Factory.sol这一个合约即可,这里我flatten一下这个合约,各位同学可以直接使用faltten UniswapV2Factory.sol
  • 看完pari这个合约,合约代码方面的技术难题了,本篇文章只实现了add的功能,下一篇实现其最后一个功能swap

关于我

区块链程序猿一枚

一步一步教你打造自己的defi-xswap(4)

发表于 2020-11-02

welcome

本篇文章编写智能合约中的create2的使用,也就是uniswap中的UniswapV2Factory合约中的createPair方法.具体的信息可参考eip1014与the-magical-world-of-create2.

create2的使用方式

1
2
3
4
5
6
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
  • 此处主要注意看以上两行代码,按照字面意思我们需要UniswapV2Pair的合约, token0, token1组成的salt,最后面再调用UniswapV2Pair的初始化函数,即完成了整个create2的使用,
  • 对的,就是这么简单.那么这样单独抽出一个UniswapV2Factory的作用是什么呢?
  • 因为我们是批量创建相同的合约,只是每个合约的里面记录的数据不同而已,所以这里我们还得引入另一个合约函数来配合使用,他就是
    UniswapV2Library中的pairFor方法,此方法可以根据创建的参数返回正确的合约地址.其中factory参数就是上述使用create2的合约地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
    (address token0, address token1) = sortTokens(tokenA, tokenB);
    pair = address(uint(keccak256(abi.encodePacked(
    hex'ff', // 固定值
    factory, // 使用create2的合约地址
    keccak256(abi.encodePacked(token0, token1)), // 确定唯一的合约
    hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
    ))));
    }

学以致用

此处我们使用create2实现一个功能,记录Apple手机的制作流程,具体使用AppleFactory定义手机的信息,然后把指定具体的工厂来加工生产出Apple,并将数据保存在区块链上

  1. 一台手机包括唯一序列号,外观颜色,运行内存大小,以及存储空间,这里加了一个加工次数来记录整个流程,其中核心方法initialize为初始化此产品的基本信息,
    processAction记录加工流程, updatePlayer交接给下一个工厂完成继续制造
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

contract Apple {
struct Action {
uint step;
string from;
string to;
uint256 time;
address player;
}

uint256 public id; // 序列号
string public color; //颜色
uint256 public flashMemory; // 运行内存大小 4g
uint256 public diskMemory; // 储存空间大小 256g
address public player; // 可以更新此合约数据的用户
uint256 public actionCount; // 总共加工次数
Action[] public actions;
address public factory;

function getApple()
public
view
returns (uint _id, uint _memory, uint _disk, uint _count, address _player, string memory _color){
_id = id;
_memory = flashMemory;
_disk = diskMemory;
_count = actionCount;
_player = player;
_color = color;

}

constructor() {
factory = msg.sender;
}

event Trace(address player, string from, string to, uint256 time);
event PlayerUpdate(address _pre, address newPlayer, uint256 time);

function initialize(
address _player,
uint256 _id,
uint256 _memory,
uint256 _disk,
string memory _color)
external {
require(msg.sender == factory, "permission deny");
player = _player;
id = _id;
color = _color;
flashMemory = _memory;
diskMemory = _disk;
_processAction("apple", "init");
}

function getActions(uint _start, uint _end) public view returns (Action[] memory atcs){
uint end = _end + 1 >= actions.length ? actions.length : _end;
atcs = new Action[](end - _start);
for (uint i = _start; i < end; i++) {
atcs[i - _start] = actions[i];
}
}

modifier onlyPlayer {
require(player == msg.sender, "permission deny");
_;
}

function updatePlayer(address _player) onlyPlayer external {
address oldPlayer = player;
player = _player;
emit PlayerUpdate(oldPlayer, _player, block.timestamp);
}

function _processAction(string memory _from, string memory _to) internal {
actionCount += 1;
actions.push(Action({from : _from, to : _to, time : block.timestamp, step : actionCount,player:msg.sender}));
emit Trace(player, _from, _to, block.timestamp);
}

function processAction(string memory _from, string memory _to) onlyPlayer public {
_processAction(_from, _to);
}

}
  1. factory合约应由苹果公司根据实际的生产情况进行管理,并录入基本信息(此处由于测试,任何人都可以创建),此处的makeApple即使生产apple, getApple可以用来得到生产出来的apple的具体合约地址,
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

import "./Apple.sol";

interface IApple {
function initialize(address _player, uint256 _id, uint256 _mem, uint256 _disk, string memory _c) external;
}

contract AppleFactory {

address[] public apples;

mapping(address => bool) public checkApple;

bytes32 public hash;

function getAllApples(uint _start, uint _end) public view returns(address[] memory _apples){
_apples = new address[](_end - _start);
for(uint i = _start; i < _end ; i++){
_apples[i - _start] = apples[i];
}
}

function totalApple() external view returns (uint256) {
return apples.length;
}

event AppleCreated(address maker, uint256 _id, uint256 _memory, uint256 _disk, string _color, address _apple);

function makeApple(
uint256 _memory,
uint256 _disk,
string memory _color)
external returns (address apple) {
uint256 _id = apples.length + 1;
bytes memory bytecode = type(Apple).creationCode;
if (hash == bytes32(0)) {
hash = keccak256(bytecode);
}
bytes32 salt = keccak256(abi.encodePacked(_id, _memory, _color));
assembly {
apple := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
require(checkApple[apple] == false, 'this apple have been made');
IApple(apple).initialize(msg.sender, _id, _memory, _disk, _color);
apples.push(apple);
emit AppleCreated(msg.sender, _id, _memory, _disk, _color, apple);
}
/// 可以通过同样的参数计算出对应的合约地址
function getApple(int256 _id, uint256 _memory, uint256 _disk, string calldata _color)
external
view
returns (address apple){
apple = address(uint(keccak256(abi.encodePacked(
hex'ff',
address(this),
keccak256(abi.encodePacked(_id, _memory, _disk, _color)),
hash
))));
}
}

demo

demo演示

  • 前端源码,合约源码
  • 首先制造成产了一台iPhone,填入颜色,内存与储存空间
  • 进入加工环节,当执行process时,自动进入到下一个加工环节
  • …
  • 跟随者加工流程指导加工生产完成,这样可以记录每一台手机的生产流程,且具体的流程可信

总结

  1. 可以按照编程语言中的面向对象思想进行理解,create2传入的参数bytecode就是class的字节码,而create2相当于new,而我们又可以根据传入create2加上其factory的地址以及salt恢复找到以这salt为key的合约地址且唯一,而且合约的地址提前可知呢.Ó´
  2. 那么按照介绍使用create2,我们需要两个合约,一个template合约(类),一个factory合约(负责new)即可,是不是很简单的?
  3. 如果使用create2创建了一个合约,再使用同样的参数进行调用,是不能成功的,除非已创建的合约调用selfdestruct(address)释放掉才行
  4. 接下来就可以进入到在第一篇文章中所提到的三个合约中的UniswapV2Pair,也即使最后一个合约
  5. 其实到这里来说,合约知识中的难点已经讲解的差不多了,剩下的其实是关于uniswap中的创新思想的复习咯

关于我

区块链程序猿一枚

一步一步教你打造自己的defi-xswap(3)

发表于 2020-10-27

welcome

本篇文章主要看看UniswapV2Pair的父类合约UniswapV2ERC20,主要学习一下eip712

UniswapV2ERC20

此合约如果熟悉ERC20 token的同学一定知道,她其实是标准的erc20合约(其标准的erc20合约的内容此处就不再详细介绍了)
但是相对标准的erc20合约多了一个permit方法,那么这个方法的作用是什么什么呢?其实是eip721的一个实现

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
contract UniswapV2ERC20 is IUniswapV2ERC20 {
using SafeMath for uint;

string public constant name = 'Uniswap V2';
string public constant symbol = 'UNI-V2';
uint8 public constant decimals = 18;
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;

bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public nonces;

event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);

constructor() public {
uint chainId;
assembly {
chainId := chainid
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}

function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}

function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
}

function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}

function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}

function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}

function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}

function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
}

UniswapV2ERC20中的eip712

  1. eip712其实是也是用你的私钥对一段文字进行签名,但是签名的过程中更加语义化,目前metamask已经支持,接下来看看premit中的核心方法
1
2
3
4
5
6
7
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
  1. 第一个参数 \x19\x01 固定值,为什么选择这个?请参考上面的链接
  2. 第二个参数:DOMAIN_SEPARATOR 其签名一般如下,固定值,在合约的构造函数中初始化,标志着只是eip712的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)
constructor() public {
uint chainId;
assembly {
chainId := chainid
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
  1. 第三个参数的前段部分 PERMIT_TYPEHASH,与DOMAIN_SEPARATOR的含义类似,代表着这个函数的签名
  2. 第三个参数的后端部分统一理解为传进来signature对应的数据,此处用v,r,s代替

eip712的简单实现

  • 此处采用的结构为 keccak256(‘Test(address owner,uint256 amount,uint256 nonce)’),此处注意数据类型和字段名中间有一个空格,参数之间的逗号之间没有空格

  • eip712主要功能是能够离线签名,让任意的人可以代替你做同样的事情,相当于授权.此处仅做演示签名后的地址正确性

  • 此处仅测试验证结果成功即增加用户输入的amount

  • 合约源码

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

pragma solidity ^0.7.0;

contract Test {
bytes32 public DOMAIN_SEPARATOR;
address public rc;
mapping(address => uint) public nonces;
mapping(address => uint) public amounts;
bytes32 public constant TEST_TYPEHASH = keccak256('Test(address owner,uint256 amount,uint256 nonce)');
constructor() {
uint chainId;
assembly {
chainId := chainid() // 大于6.0以上是个方法,不是属性
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes("shaokun")), // 此处一般填写此合约有意义的值
keccak256(bytes('1')),
chainId,
address(this)
)
);
}

function testEIP712(address owner, uint256 amount, uint8 v, bytes32 r, bytes32 s) external {

bytes32 digest = keccak256(
abi.encodePacked('\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(TEST_TYPEHASH, owner, amount, nonces[owner]++))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, "account not fit");
rc = recoveredAddress;
amounts[owner] += amount;

}
}

流程演示

这一周花了点时间用react做了个UI,使用了react + react-material开发UI,web3-react实现与区块链的交互xswap前端

  1. 使用remix部署合约在rinkeby测试链上,合约地址0x9F8C390b7048395d4DeBc7636031aD992115C303 ethersacn
  2. 从链上读出当前签名所用的nonce
  3. 根据nonce与amount使用账号进行签名,并发送到链上
  4. 当交易执行成功,获取新的nonce和amount,验证结果
  5. 再次执行签名,将获取的签名数据(其中如果数量为25,发送将会提醒失败,改为签名的数据19则正常弹出metamask)通过remix发送,等待交易结果后返回页面查询数据已经更改

demo

js签名流程

目前web3不支持此类签名方式,metamask是支持的,请注意一下签名的请求方式

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
46
47
48
49
50
51
52
// 两个字段 types与domain
const eip712Obj = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
// 签名的结构,与合约保持一致
Test: [
{ name: 'owner', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
],
},

domain: (chainId: number) => ({
name: 'shaokun',
version: '1',
chainId: chainId,
verifyingContract: '0x9F8C390b7048395d4DeBc7636031aD992115C303',
}),
}

// 签名所要的数据
const data = JSON.stringify({
types: eip712Obj.types,
domain: eip712Obj.domain(web3.chainId!!), // 获取当前的id,并返回与合约一致的数据
primaryType: 'Test', // 最开始hash的类型,如果有多个类型,需指定按照合约的顺序的第一个
// test的数据具体内容
message: {
owner: web3.account,
amount: inputV,
nonce,
},
})


// 此处使用的web3-react,provider 为metamask
web3.library.provider.request(
{
method: 'eth_signTypedData_v4',
params: [web3.account, data], // 两个参数,第一个必须为账号地址,第二个
}).then((result: string) => {
const signature = result.substring(2)
const r = '0x' + signature.substring(0, 64)
const s = '0x' + signature.substring(64, 128)
const v = parseInt(signature.substring(128, 130), 16)
// 得到签名结果的r,s,v,发送到链上校验即可
console.log('---sign result -',{r,s,v})
})

总结

  • 关于语义化签名的就介绍到此了,那么uniswap中的permit的作用就是也就是如此,至于怎么使用它是怎么使用的呢,后续再看吧
  • UniswapV2ERC20 基础合约就没有什么难点了,其余的方法为标准的erc20 token的方法
  • 接下来讲解UniswapV2Factory,又是一个开发智能合约很重要的一个知识点呢

关于我

区块链程序猿一枚

一步一步教你打造自己的defi-xswap(2)

发表于 2020-10-25

welcome

本篇文章主要看看uniswap的合约的源码和结构,以及如何找到源码的入口(当然也可以直接阅读uniswap的doc来达到目的)

获取源码

从uniswap合约源码 github把源码colne到本地,进入contract目录

  • 可以看到合约的代码量不多,那么我们该如何开始呢?
  • 从目录的命名来看,我们貌似只需要关注暴露在外面的三个合约 UniswapV2ERC20.sol, UniswapV2Factory.sol, UniswapV2Pair.sol即可,其他的文件夹的内容为interface和libraries

这里如果不熟悉的同学可以试着run一下这个这个test,然后根据test去查看项目如何执行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-rw-r--r--  1 kun  staff  3435 10 25 11:14 UniswapV2ERC20.sol
-rw-r--r-- 1 kun staff 1867 10 25 11:14 UniswapV2Factory.sol
-rw-r--r-- 1 kun staff 9788 10 25 11:14 UniswapV2Pair.sol
drwxr-xr-x 7 kun staff 238 10 25 11:14 interfaces
drwxr-xr-x 5 kun staff 170 10 25 11:14 libraries
drwxr-xr-x 3 kun staff 102 10 25 11:14 test

./interfaces:
total 40
-rw-r--r-- 1 kun staff 823 10 25 11:14 IERC20.sol
-rw-r--r-- 1 kun staff 159 10 25 11:14 IUniswapV2Callee.sol
-rw-r--r-- 1 kun staff 1148 10 25 11:14 IUniswapV2ERC20.sol
-rw-r--r-- 1 kun staff 661 10 25 11:14 IUniswapV2Factory.sol
-rw-r--r-- 1 kun staff 2424 10 25 11:14 IUniswapV2Pair.sol

./libraries:
total 24
-rw-r--r-- 1 kun staff 602 10 25 11:14 Math.sol
-rw-r--r-- 1 kun staff 563 10 25 11:14 SafeMath.sol
-rw-r--r-- 1 kun staff 578 10 25 11:14 UQ112x112.sol

./test:
total 8
-rw-r--r-- 1 kun staff 187 10 25 11:14 ERC20.sol

试玩uniswap

  1. eth兑换uni,从etherscan中可以看到函数签名为swapExactETHForTokens
  2. 添加uni->eth的资金池,调用的函数签名为addLiquidityETH,(中间approve了两次此合约可以转移我钱包中的uni的操作,第二次是重复的)
  3. 那么我们可以从这两个函数开始阅读其合约代码

demo

router合约

  1. 在我们试玩找入口时,我们发现其调用的函数签名并没有在我们Colne的项目中有找到,那是怎么回事呢?按照文档,这是uniswap为我们的sdk,我们可以根据这基础的合约创建属于我们的swap(是的,我们正在做这件事),而他们自己也做了一个swap uniswap合约源码,这个才是他们基于sdk开发的一个应用呢
  2. 那我们根据eth兑换uni这笔交易,找到其在etherscan上验证过的的合约,发现其实际调用的是UniswapV2Router02这个函数名的合约,全局搜索找到其函数签名如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
  1. 先忽略UniswapV2Library方法,观察此合约,其核心方法集中在_swap(amounts, path, to)这个方法上,继续搜索可以发现很多方法都调用了此方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
  1. 发现其最终落在了**IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
        amount0Out, amount1Out, to, new bytes(0)
    );**这个方法上,这个方法就在我们的clone项目中了,那么其核心方法我们就只要关心**IUniswapV2Pair. swap()**  
    5. 为什么我们colne项目中没有这个router的合约? (本系列结束后回答)  
    

只有在etherscan上验证过的合约才能够找到其合约元am,这也是最真实的,如果做defi,一般会将其验证,增加其去中心化的公信力

总结

  1. 通过一系列的操作,我们总算找到了合约入口啦,接下来应该进入到正式的合约阅读步骤啦,是不是很激动?

  2. 其中有些细节我觉得通过文字来描述就有点啰嗦了,然后就跳过了,如果你感觉到其中哪些地方迷糊,可以给我发邮件

关于我

区块链程序猿一枚

12…4

shaokun

点滴积累,聚少成多! 心中有善,不骄不躁

38 日志
© 2021 shaokun
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4
访问人数 访问总量 次