Skip to content

Commit 886c4ff

Browse files
Y omeletteY omelette
Y omelette
authored and
Y omelette
committed
added code comments on uniswapV2 Factory, ERC20 and Pair contracts
1 parent ee547b1 commit 886c4ff

File tree

4 files changed

+362
-48
lines changed

4 files changed

+362
-48
lines changed

contracts/UniswapV2ERC20.sol

+77-6
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,44 @@ import './libraries/SafeMath.sol';
66
contract UniswapV2ERC20 is IUniswapV2ERC20 {
77
using SafeMath for uint;
88

9+
// 这三行代码定义了ERC20代币的三个对外状态变量(代币元数据):名称,符号和精度。
10+
// 注意,由于该合约为交易对合约的父合约,而交易对合约是可以创建无数个的,所以这无数个交易对合约中的 ERC20 代币的名称、符号和精度都一样。
911
string public constant name = 'Uniswap V2';
1012
string public constant symbol = 'UNI-V2';
1113
uint8 public constant decimals = 18;
12-
uint public totalSupply;
13-
mapping(address => uint) public balanceOf;
14-
mapping(address => mapping(address => uint)) public allowance;
1514

15+
uint public totalSupply; // 记录代币发行总量的状态变量
16+
mapping(address => uint) public balanceOf; //用一个 map 记录每个地址的代币余额
17+
mapping(address => mapping(address => uint)) public allowance; // 用来记录每个地址的授权分布, allowance[addressA][addressB]=addressA授权addressB可使用的代币额度
18+
19+
// 用来在不同 Dapp 之间区分相同结构和内容的签名消息
1620
bytes32 public DOMAIN_SEPARATOR;
21+
// 这一行代码根据事先约定使用`permit`函数的部分定义计算哈希值,重建消息签名时使用。
1722
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
1823
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
19-
mapping(address => uint) public nonces;
24+
mapping(address => uint) public nonces;// 记录合约中每个地址使用链下签名消息交易的数量,用来防止重放攻击。
2025

26+
// ERC20 标准中的两个事件定义
2127
event Approval(address indexed owner, address indexed spender, uint value);
2228
event Transfer(address indexed from, address indexed to, uint value);
2329

30+
/**
31+
* @dev: 构造函数
32+
* @notice: 构造函数只做了一件事,计算`DOMAIN_SEPARATOR`的值
33+
*/
2434
constructor() public {
2535
uint chainId;
2636
assembly {
2737
chainId := chainid
2838
}
39+
/**
40+
* @dev: eip712Domain - 是一个名为`EIP712Domain`的 结构,它可以有以下一个或者多个字段:
41+
* @notice EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)
42+
* @param {string} name 可读的签名域的名称,例如 Dapp 的名称,在本例中为代币名称
43+
* @param {string} version 当前签名域的版本,本例中为 "1"
44+
* @param {uint256} chainId 当前链的 ID,注意因为 Solidity 不支持直接获取该值,所以使用了内嵌汇编来获取
45+
* @param {address} verifyingContract 验证合约的地址,在本例中就是本合约地址
46+
*/
2947
DOMAIN_SEPARATOR = keccak256(
3048
abi.encode(
3149
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
@@ -37,34 +55,70 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 {
3755
);
3856
}
3957

58+
/**
59+
* @dev: 进行代币增发
60+
* @notice: 它是`internal`函数(可在函数名前添加下划线,表示该函数为internal函数,下同)
61+
* @param {address} to
62+
* @param {uint} value
63+
*/
4064
function _mint(address to, uint value) internal {
4165
totalSupply = totalSupply.add(value);
4266
balanceOf[to] = balanceOf[to].add(value);
4367
emit Transfer(address(0), to, value);
4468
}
4569

70+
/**
71+
* @dev: 进行代币燃烧
72+
* @param {address} from
73+
* @param {uint} value
74+
*/
4675
function _burn(address from, uint value) internal {
4776
balanceOf[from] = balanceOf[from].sub(value);
4877
totalSupply = totalSupply.sub(value);
4978
emit Transfer(from, address(0), value);
5079
}
5180

81+
/**
82+
* @dev: 进行授权操作, owner地址授权给spender地址value数量的token使用权
83+
* @param {address} owner
84+
* @param {address} spender
85+
* @param {uint} value
86+
*/
5287
function _approve(address owner, address spender, uint value) private {
5388
allowance[owner][spender] = value;
5489
emit Approval(owner, spender, value);
5590
}
5691

92+
93+
/**
94+
* @dev: 转移代币操作, 将value数量的token从from地址转移给to地址
95+
* @param {address} from
96+
* @param {address} to
97+
* @param {uint} value
98+
*/
5799
function _transfer(address from, address to, uint value) private {
58100
balanceOf[from] = balanceOf[from].sub(value);
59101
balanceOf[to] = balanceOf[to].add(value);
60102
emit Transfer(from, to, value);
61103
}
62104

105+
/**
106+
* @dev: 授权代币操作的外部调用接口,msg.sender授权给spender地址value数量的token使用权
107+
* @param {address} from
108+
* @param {address} to
109+
* @param {uint} value
110+
*/
63111
function approve(address spender, uint value) external returns (bool) {
64112
_approve(msg.sender, spender, value);
65113
return true;
66114
}
67115

116+
/**
117+
* @dev: 用户转移代币操作的外部调用接口。,将value数量的token从msg.sender地址转移给to地址
118+
* @param {address} from
119+
* @param {address} to
120+
* @param {uint} value
121+
*/
68122
function transfer(address to, uint value) external returns (bool) {
69123
_transfer(msg.sender, to, value);
70124
return true;
@@ -78,16 +132,33 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 {
78132
return true;
79133
}
80134

135+
/**
136+
* @dev: 使用线下签名消息进行approve(授权)操作
137+
* @notice UniswapV2 的核心合约虽然功能完整,但对用户不友好,用户需要借助它的周边合约(v2-periphery)才能和核心合约交互
138+
* 比如用户减少流动性,此时用户需要将自己的流动性代币(一种 ERC20 代币)燃烧掉。由于用户调用的是周边合约,周边合约未经授权
139+
* 是无法进行燃烧操作的。此时,如果按照常规操作,用户需要首先调用交易对合约对周边合约进行授权,再调用周边合约进行燃烧,这个
140+
* 过程实质上是调用两个不同合约的两个交易(无法合并到一个交易中),它分成了两步,用户需要交易两次才能完成。
141+
* 使用线下消息签名后,可以减少其中一个交易,将所有操作放在一个交易里执行,确保了交易的原子性。
142+
* @param {address} owner, approve操作变量
143+
* @param {address} spender, approve操作变量
144+
* @param {address} value, approve操作变量
145+
* @param {address} deadline, 授权approve操作的截止时间
146+
* @param {address} v, 用户签名后,椭圆曲线相关数据,用于获取签名的用户地址
147+
* @param {address} 同v
148+
* @param {address} 同s
149+
*/
81150
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
82-
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
151+
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED'); //超过deadline则表示授权已失效
83152
bytes32 digest = keccak256(
84153
abi.encodePacked(
85154
'\x19\x01',
86155
DOMAIN_SEPARATOR,
87156
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
157+
// 每调用一次`permit`,相应地址的 nonce 就会加 1,
158+
// 这样再使用原来的签名消息就无法再通过验证了(重建的签名消息不正确了),用于防止重放攻击。
88159
)
89160
);
90-
address recoveredAddress = ecrecover(digest, v, r, s);
161+
address recoveredAddress = ecrecover(digest, v, r, s); //获取消息签名者的地址
91162
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
92163
_approve(owner, spender, value);
93164
}

contracts/UniswapV2Factory.sol

+118-7
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,160 @@
11
pragma solidity =0.5.16;
2-
32
import './interfaces/IUniswapV2Factory.sol';
43
import './UniswapV2Pair.sol';
54

65
contract UniswapV2Factory is IUniswapV2Factory {
7-
address public feeTo;
8-
address public feeToSetter;
6+
address public feeTo; // 收取手续费的地址
7+
address public feeToSetter; // 设置feeTo的权限者地址
98

10-
mapping(address => mapping(address => address)) public getPair;
11-
address[] public allPairs;
9+
// tokenA和tokenB的交易对地址存储(tokenA[tokenB] = pair)
10+
mapping(address => mapping(address => address)) public getPair; // 获取交易对的pair地址
11+
address[] public allPairs; // 用来储存所有的pair
1212

13+
// 定义交易对创建事件,返回参数tokenA地址,tokenB地址,pair地址,allPairs长度(第几个交易对)
1314
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
14-
15+
16+
/**
17+
* @dev: 构造函数
18+
* @param {address} _feeToSetter: 手续费控制的权限者地址
19+
*/
1520
constructor(address _feeToSetter) public {
1621
feeToSetter = _feeToSetter;
1722
}
1823

24+
/**
25+
* @return 返回交易对的数量
26+
*/
1927
function allPairsLength() external view returns (uint) {
2028
return allPairs.length;
2129
}
2230

31+
/**
32+
* @dev: 创建tokenA和tokenB的交易对并获得pair地址
33+
* @param {address} tokenA
34+
* @param {address} tokenB
35+
* @return {address} 返回对应的pair地址
36+
*/
2337
function createPair(address tokenA, address tokenB) external returns (address pair) {
24-
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
38+
//判断tokenA不等于tokenB
39+
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
40+
// 将tokenA和tokenB进行比大小,如果tokenA小于tokenB,则交换给token0和token1
41+
// 因为地址的底层是uint160,所以有大小排序
2542
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
43+
// 判断token0不能为0地址
44+
// 此时判断token0就等于同时判断了token0和token1,因为tokenA和tokenB已经进行过大小判断,token0地址是绝对小于任何地址
2645
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
46+
// 判断token0是否和token1有产生过交易对,如果没配对那么mapping就是0地址,如果非0地址代表已经配对过了
2747
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
48+
// 表达式type(x)可用于检索参数x的类型信息(x仅能是合约或整型常量)
49+
// type(x).creationCode 获得包含x的合约的bytecode,是bytes类型(不能在合约本身或继承的合约中使用,因为会引起循环引用)
2850
bytes memory bytecode = type(UniswapV2Pair).creationCode;
51+
// 将排序好的token对进行打包后通过keccak256得到hash值
52+
// 因为两个地址是为确定值,所以salt是可以通过链下计算出来
2953
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
54+
// 内联汇编
3055
assembly {
56+
/**
57+
* @dev: create2方法 - 在已知交易对及salt的情况下创建一个新的交易对,返回新的交易对地址(针对此算法可以提前知道交易对的地址)
58+
* @notice 转注释2
59+
* @notice create2(V, P, N, S) - V: 发送V数量wei以太,P: 起始内存地址,N: bytecode长度,S: salt
60+
* @param {uint} 指创建合约后向合约发送x数量wei的以太币
61+
* @param {bytes} add(bytecode, 32) opcode的add方法,将bytecode偏移后32位字节处,因为前32位字节存的是bytecode长度
62+
* @param {bytes} mload(bytecode) opcode的方法,获得bytecode长度
63+
* @param {bytes} salt 盐值
64+
* @return {address} 返回新的交易对地址
65+
*/
3166
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
3267
}
68+
// 将新的交易对地址初始化到pair合约中(因为create2函数创建合约时无法提供构造函数参数)
3369
IUniswapV2Pair(pair).initialize(token0, token1);
70+
// 将token0和token1的交易对地址设置到mapping中(0和1的双向交易对)
3471
getPair[token0][token1] = pair;
3572
getPair[token1][token0] = pair; // populate mapping in the reverse direction
73+
// 将新的交易对地址添加到allPairs数组中
3674
allPairs.push(pair);
75+
// 触发交易对创建事件
3776
emit PairCreated(token0, token1, pair, allPairs.length);
3877
}
3978

79+
/**
80+
* @dev: 设置团队手续费开关
81+
* @notice 在uniswapV2中,用户交易代币时,会被收取交易额的千分之三手续费分配给所有流动性提供者.
82+
* @param {address} 不为零地址,则代表开启手续费开关(手续费中的1/6分给此地址),为零地址则代表关闭手续费开关
83+
*/
4084
function setFeeTo(address _feeTo) external {
4185
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
4286
feeTo = _feeTo;
4387
}
4488

89+
/**
90+
* @dev: 转让设置feeTo的权限者
91+
* @notice 转注释1
92+
*/
4593
function setFeeToSetter(address _feeToSetter) external {
4694
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
4795
feeToSetter = _feeToSetter;
4896
}
97+
98+
/**
99+
@dev: 注释1
100+
在转让权限时可能会出现一个情况,当管理员进行转让时可能输入错误的地址,正常情况下这种情况发生后将不可更改,并且将丧失永久管理权.
101+
有一种方法可以解决这类问题,就是使用一个中间地址值过渡一下,而被新设置owner的对象需要再调用一次方法才能完成权限的转移.
102+
如果原管理员发现转移地址错误了,可在目标错误地址未进行确认时及时更改过来
103+
address public owner;
104+
address public newOwner;
105+
// 进行转移权限到newOwner,如果发现错误可再一次设置
106+
function transferOwnership(address _newOwner) public onlyOwner {
107+
newOwner = _newOwner;
108+
}
109+
// 被转移权限的需调用此方法来确认接受此权限,此时将完成权限转移的设置
110+
function acceptOwnership() public {
111+
require(msg.sender == newOwner,"invalid operation");
112+
emit OwnershipTransferred(owner, newOwner);
113+
owner = newOwner;
114+
newOwner = address(0);
115+
}
116+
*/
117+
118+
/**
119+
@dev: 注释2
120+
create2的知识扩展:
121+
因为以太坊evm中账号的内存管理是每个账号(包含合约)都有一个内存区域,该区域是线性的并且在字节等级上寻址,但是读取限定为256位(32字节)大小,写的时候可以为8位(1字节)或256位(32字节)大小.
122+
solidity中内联汇编访问本地变量时,如果本地变量为值类型,则直接使用该值;如果本地变量是引用类型(memory或calldata),则会使用memory或calldata中的内存地址,而不是值本身.
123+
solidity中动态大小的字节数组,是引用类型,类似string也是引用类型.
124+
所以在create2函数调用时使用了type(x).creationCode 来获得了x合约的bytecode,类型为bytes为引用类型.根据上述的内存读取限制和内联汇编访问本地引用类型的规则,它在内联汇编中的实际值为该字节数组的内存地址.
125+
因为bytecode开始的32字节存储的是creationCode的长度,从第二个32字节开始才是存的实际creationCode内容,所以create2函数中的第二个参数需要为实际creationCode内容,才进行了add(bytecode, 32)的方式将值偏移到后32字节后.
126+
127+
create2的solidity方法:
128+
因为内联汇编的可读性较为差些,所以在solidity的0.6.1以上新增了加盐创建合约的create2方法,该方法直接通过new在合约类型后面加上salt选项来进行自定义加盐的合约创建,等效于内联汇编中的create2函数.
129+
示例代码:
130+
// SPDX-License-Identifier: GPL-3.0
131+
pragma solidity ^0.7.0;
132+
133+
contract D {
134+
uint public x;
135+
constructor(uint a) {
136+
x = a;
137+
}
138+
}
139+
140+
contract C {
141+
function createDSalted(bytes32 salt, uint arg) public {
142+
/// 这个复杂的表达式只是告诉我们,如何预先计算地址。
143+
/// 这里仅仅用来说明。
144+
/// 实际上,你仅仅需要 ``new D{salt: salt}(arg)``.
145+
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
146+
bytes1(0xff),
147+
address(this),
148+
salt,
149+
keccak256(abi.encodePacked(
150+
type(D).creationCode,
151+
arg
152+
))
153+
)))));
154+
155+
D d = new D{salt: salt}(arg);
156+
require(address(d) == predictedAddress);
157+
}
158+
}
159+
*/
49160
}

0 commit comments

Comments
 (0)