学习核心合约UniswapV2Pair,在父合约UniswapV2ERC20的基础上增加资产交易及流动性提供等功能。

交易对合约本身是erc20合约,代币表示流动性,代币在提供流动性的地址里,当提供流动性,就会增发代币给提供者,反正提取流动性,就烧掉提取者的代币


  1 pragma solidity =0.5.16;
2
3 import './interfaces/IUniswapV2Pair.sol';
4 import './UniswapV2ERC20.sol';
5 import './libraries/Math.sol';
6 import './libraries/UQ112x112.sol';
7 import './interfaces/IERC20.sol';
8 import './interfaces/IUniswapV2Factory.sol';
9 import './interfaces/IUniswapV2Callee.sol';
10
11 contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
12 using SafeMath for uint;
13 using UQ112x112 for uint224;
14
15 uint public constant MINIMUM_LIQUIDITY = 10**3;
16 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
17
18 address public factory;
19 address public token0;
20 address public token1;
21
22 uint112 private reserve0; // uses single storage slot, accessible via getReserves
23 uint112 private reserve1; // uses single storage slot, accessible via getReserves
24 uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
25
26 uint public price0CumulativeLast;
27 uint public price1CumulativeLast;
28 uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
29
30 uint private unlocked = 1;
31 modifier lock() {
32 require(unlocked == 1, 'UniswapV2: LOCKED');
33 unlocked = 0;
34 _;
35 unlocked = 1;
36 }
37
38 function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
39 _reserve0 = reserve0;
40 _reserve1 = reserve1;
41 _blockTimestampLast = blockTimestampLast;
42 }
43
44 function _safeTransfer(address token, address to, uint value) private {
45 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
46 require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
47 }
48
49 event Mint(address indexed sender, uint amount0, uint amount1);
50 event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
51 event Swap(
52 address indexed sender,
53 uint amount0In,
54 uint amount1In,
55 uint amount0Out,
56 uint amount1Out,
57 address indexed to
58 );
59 event Sync(uint112 reserve0, uint112 reserve1);
60
61 constructor() public {
62 factory = msg.sender;
63 }
64
65 // called once by the factory at time of deployment
66 function initialize(address _token0, address _token1) external {
67 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
68 token0 = _token0;
69 token1 = _token1;
70 }
71
72 // update reserves and, on the first call per block, price accumulators
73 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
74 require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
75 uint32 blockTimestamp = uint32(block.timestamp % 2**32);
76 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
77 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
78 // * never overflows, and + overflow is desired
79 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
80 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
81 }
82 reserve0 = uint112(balance0);
83 reserve1 = uint112(balance1);
84 blockTimestampLast = blockTimestamp;
85 emit Sync(reserve0, reserve1);
86 }
87
88 // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
89 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
90 address feeTo = IUniswapV2Factory(factory).feeTo();
91 feeOn = feeTo != address(0);
92 uint _kLast = kLast; // gas savings
93 if (feeOn) {
94 if (_kLast != 0) {
95 uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
96 uint rootKLast = Math.sqrt(_kLast);
97 if (rootK > rootKLast) {
98 uint numerator = totalSupply.mul(rootK.sub(rootKLast));
99 uint denominator = rootK.mul(5).add(rootKLast);
100 uint liquidity = numerator / denominator;
101 if (liquidity > 0) _mint(feeTo, liquidity);
102 }
103 }
104 } else if (_kLast != 0) {
105 kLast = 0;
106 }
107 }
108
109 // this low-level function should be called from a contract which performs important safety checks
110 function mint(address to) external lock returns (uint liquidity) {
111 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
112 uint balance0 = IERC20(token0).balanceOf(address(this));
113 uint balance1 = IERC20(token1).balanceOf(address(this));
114 uint amount0 = balance0.sub(_reserve0);
115 uint amount1 = balance1.sub(_reserve1);
116
117 bool feeOn = _mintFee(_reserve0, _reserve1);
118 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
119 if (_totalSupply == 0) {
120 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
121 _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
122 } else {
123 liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
124 }
125 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
126 _mint(to, liquidity);
127
128 _update(balance0, balance1, _reserve0, _reserve1);
129 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
130 emit Mint(msg.sender, amount0, amount1);
131 }
132
133 // this low-level function should be called from a contract which performs important safety checks
134 function burn(address to) external lock returns (uint amount0, uint amount1) {
135 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
136 address _token0 = token0; // gas savings
137 address _token1 = token1; // gas savings
138 uint balance0 = IERC20(_token0).balanceOf(address(this));
139 uint balance1 = IERC20(_token1).balanceOf(address(this));
140 uint liquidity = balanceOf[address(this)];
141
142 bool feeOn = _mintFee(_reserve0, _reserve1);
143 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
144 amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
145 amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
146 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
147 _burn(address(this), liquidity);
148 _safeTransfer(_token0, to, amount0);
149 _safeTransfer(_token1, to, amount1);
150 balance0 = IERC20(_token0).balanceOf(address(this));
151 balance1 = IERC20(_token1).balanceOf(address(this));
152
153 _update(balance0, balance1, _reserve0, _reserve1);
154 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
155 emit Burn(msg.sender, amount0, amount1, to);
156 }
157
158 // this low-level function should be called from a contract which performs important safety checks
159 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
160 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
161 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
162 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
163
164 uint balance0;
165 uint balance1;
166 { // scope for _token{0,1}, avoids stack too deep errors
167 address _token0 = token0;
168 address _token1 = token1;
169 require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
170 if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
171 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
172 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
173 balance0 = IERC20(_token0).balanceOf(address(this));
174 balance1 = IERC20(_token1).balanceOf(address(this));
175 }
176 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
177 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
178 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
179 { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
180 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
181 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
182 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
183 }
184
185 _update(balance0, balance1, _reserve0, _reserve1);
186 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
187 }
188
189 // force balances to match reserves
190 function skim(address to) external lock {
191 address _token0 = token0; // gas savings
192 address _token1 = token1; // gas savings
193 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
194 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
195 }
196
197 // force reserves to match balances
198 function sync() external lock {
199 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
200 }
201 }


1 指定Solidity编译版本。

2    第三-第九行: 导入交易对合约需要实现的接口和交易对的父合约。

    导入Math库,导入UQ112x112这是自定义的数据格式库,在V2中使用两种代币的比值作为价格,为提高精度使用uint112来表示价格,UQ112x112 前面代表整数,后面代表小数。导入标准ERC20接口目的:获取交易对合约流通池的代币余额。

    导入IUniswapV2Factory合约,目的获取开发团队手续费地址。

    导入IUniswapV2Callee.sol.规定第三方合约要实现的接口格式。定义次接口可进行FlasSwap

3   定义UniswapV2Pair  实现了IUniswapV2Pair,并且继承UniswapV2ERC20.继承了父合约所有非私有接口与状态变量

4 12-13行 ;实现SafeMath、UQ112x112库函数。

5      定义最小流动性,在提供初始流动性时烧掉。

6   计算transfer的函数选择器。

7      18-20行:定义3个 address类型的公共状态变量,记录factory合约地址,2个代币的地址

8     21-23 reserve0,reserve1 记录最新的恒定乘积中的代币数量,blockTimestampLast  交易时的区块创建时间。

9  price0CumulativeLast  ,price1CumulativeLast ,j记录交易对中两种价格的累计值。

10   klast 记录恒定乘积中积的值,用于开发团队手续费计算

11      防止重入攻击,在函数修饰器中, _;表示执行被修饰的函数体,当函数被外部调用,unlocked 为0,函数执行完设置为1,在未执行完前,如果重入该函数,l

1  uint private unlocked = 1;
2 modifier lock() {
3 require(unlocked == 1, 'UniswapV2: LOCKED');
4 unlocked = 0;
5 _;
6 unlocked = 1;
7 }

12  getReserves函数  获取当前交易对的资产信息以及最后交易的区块时间

13  _safeTransfer 函数,使用call函数进行代币合约transfer的调用(函数选择器),检查返回值

14     定义四个event事件  mint swap burn sync  便于追踪。

15   constructor 构造器,工厂合约 factory 设为msg.sender。

16       initialize 初始化函数,由于使用create2创建合约,不能向构造函数传递参数。所以没有之间在构造函数传参。

17  _update 函数, 更新reserves,在每个block的第一次调用更新价格累积值。

 1 // update reserves and, on the first call per block, price accumulators
2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
3 require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
4 uint32 blockTimestamp = uint32(block.timestamp % 2**32);
5 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
6 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
7 // * never overflows, and + overflow is desired
8 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
9 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
10 }
11 reserve0 = uint112(balance0);
12 reserve1 = uint112(balance1);
13 blockTimestampLast = blockTimestamp;
14 emit Sync(reserve0, reserve1);
15 }
16

1) 四个参数  当前合约代币的两种余额,保存的恒定乘积中两种代币数值,将保存的数值更新为实时代币余额,并且同时进行价格累计的计算。

     2)第一行验证余额不能大于uint112类型的最大值。

     3)存储槽位是256位,两个代币数量各112位, 剩下32位 记录当前区块时间

     4)计算当前block 和上一次block的时间差值

    5) if 语句 当同区块的第二笔交易,timeElapsed为0,就不会计算累积值

    6)计算价格的累积值

    7) 更新reserve的值,更新bloc的 时间为当前时间,触发同步事件

18  _mintFree 函数 计算并发送开发团队手续费,

 1 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
2 address feeTo = IUniswapV2Factory(factory).feeTo();
3 feeOn = feeTo != address(0);
4 uint _kLast = kLast; // gas savings
5 if (feeOn) {
6 if (_kLast != 0) {
7 uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
8 uint rootKLast = Math.sqrt(_kLast);
9 if (rootK > rootKLast) {
10 uint numerator = totalSupply.mul(rootK.sub(rootKLast));
11 uint denominator = rootK.mul(5).add(rootKLast);
12 uint liquidity = numerator / denominator;
13 if (liquidity > 0) _mint(feeTo, liquidity);
14 }
15 }
16 } else if (_kLast != 0) {
17 kLast = 0;
18 }
19 }

    1) 函数参数:交易对中保存的恒定乘积中的代币数,返回类型 bool

    2)获取开发团队手续费地址,判断地址是否是零地址来判断开关是否打开

    3) 局部变量保存恒定乘积中积的值。(减少gas)

    4) if 判断 ,如果手续费开关打开,计算手续费值

    5)下面就是具体数学运算。

19  mint 函数:提供流动性时增发流动性代币给提供者

 1 function mint(address to) external lock returns (uint liquidity) {
2 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
3 uint balance0 = IERC20(token0).balanceOf(address(this));
4 uint balance1 = IERC20(token1).balanceOf(address(this));
5 uint amount0 = balance0.sub(_reserve0);
6 uint amount1 = balance1.sub(_reserve1);
7
8 bool feeOn = _mintFee(_reserve0, _reserve1);
9 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
10 if (_totalSupply == 0) {
11 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
12 _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
13 } else {
14 liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
15 }
16 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
17 _mint(to, liquidity);
18
19 _update(balance0, balance1, _reserve0, _reserve1);
20 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
21 emit Mint(msg.sender, amount0, amount1);
22 }
23

      1)函数参数 接受流动性代币的地址,返回值 增发的数量,函数修饰器lock 防重入

      2) 获取当前交易对的resverse

      3)第三-四行 获取两个代币的当前余额, 再减去reserve(池子里两个代币原有 的数量)  就得到  两个代币的投入量。

      4)计算开发团队手续费

      5)使用局部变量保存已经发行了流动性的数量。减少gas

      6) if(判断是否是初次提供)如果是初次, 计算恒定乘积公式中积的平方根,再减去最初始的流动性值(1000). 如果不是, 则每种代币按比例计算增发的流动性数量那流动性则是取两个值中较小的那个。

      7)require验证  增发的流动性大于0,

      8)_mint(to,liquidity) 增发流动性给接收者.

      9):调用_update ,更新当前保存的恒定乘积中两个代币的值.

      10)如果手续费打开,则更新乘积值.这个值只再计算手续费会用

      11)触发Mint 事件

20 burn函数:通过燃烧流动性LP,来提取对应的代币,减少教育对的流动性

 1  // this low-level function should be called from a contract which performs important safety checks
2 function burn(address to) external lock returns (uint amount0, uint amount1) {
3 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
4 address _token0 = token0; // gas savings
5 address _token1 = token1; // gas savings
6 uint balance0 = IERC20(_token0).balanceOf(address(this));
7 uint balance1 = IERC20(_token1).balanceOf(address(this));
8 uint liquidity = balanceOf[address(this)];
9
10 bool feeOn = _mintFee(_reserve0, _reserve1);
11 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
12 amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
13 amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
14 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
15 _burn(address(this), liquidity);
16 _safeTransfer(_token0, to, amount0);
17 _safeTransfer(_token1, to, amount1);
18 balance0 = IERC20(_token0).balanceOf(address(this));
19 balance1 = IERC20(_token1).balanceOf(address(this));
20
21 _update(balance0, balance1, _reserve0, _reserve1);
22 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
23 emit Burn(msg.sender, amount0, amount1, to);
24 }
25

        1) 参数 代币接收者的地址,返回值 提取的两种代币数量,lock 防重入。

        2) 先获取交易对的reserse,两个代币地址,保存在局部变量中,减少gas

        3)获取交易对合约地址拥有的两种代币数量.

        4)  获取当前合约地址的流动性代币余额,正常情况下,交易对合约里是不会有流动性代币的,因为所有流动性代币都是给到了流动性提供者的。而这里有值,其实是因为路由合约会先把用户的流动性代币划转到该配对合约里

        5)  计算手续

        6)  局部变量保存总流动性,节约gas

        7)按比例计算资产。提取数量=用户流动性/总流动性*代币总余额

8)require 提取数量大于0。

        9) _burn  将用户转让的流动性燃烧掉。

        10)_把相应的代币发送给接收者。

        11) 重新获取交易对合约地址所拥有的代币余额

        12)_uodate  更新当前保存的恒定乘积中两种代币的值

        13 更新Klast值

        14 触发燃烧事件

21  Swap函数:  交易对中 资产的交换

 1  function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
2 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
3 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
4 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
5 uint balance0;
6 uint balance1;
7 { // scope for _token{0,1}, avoids stack too deep errors
8 address _token0 = token0;
9 address _token1 = token1;
10 require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
11 if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
12 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
13 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
14 balance0 = IERC20(_token0).balanceOf(address(this));
15 balance1 = IERC20(_token1).balanceOf(address(this));
16 }
17 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
18 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
19 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
20 { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
21 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
22 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
23 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
24 }
25
26 _update(balance0, balance1, _reserve0, _reserve1);
27 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
28 }
29

        1)4个参数 转出token0的数量,token1的数量,通常者两个值有一个为0,to为接收者地址,data参数执行回调时传递的数据,通过路由合约兑换 该值为0。Lock 防重入攻击

        2)第2行 检验传入参数不能为0,第三行,获取revrese。

         3)第四行:兑换出的数量必须小于reverse

        4) 定义局部变量 保存当前交易对的两种代币余额

        5) 7-16行用{},特殊语法,避免堆栈过深

        6)8-10行:局部变量保存代币合约地址,并校验 接收者地址不i能为要兑换的代币地址

        7)if amount0/1Out 谁大于0就转出,

        8) if 如果data参数为空,则使用路由合约兑换(普通交易都是data为0)若不为空,to 地址转为 IUniswapV2Callee 并调用其 uniswapV2Call() 函数,这其实就是一个回调函数,to 地址需要实现该接口。并把data传过去

        9)获取交易对合约地址的余额(此时已经减去了要转出的代币数量)

        10)17-19行: 计算转入的代币数量 根据amountIn = balance - (reserve - amountOut),实际转入的一个0一个不为0。然后验证 其中一个转入值要大于0

        11)20-24 行:防止堆栈过深, 进行最终的恒定乘积验证。公式:blance-0.003*amountOut。新的恒定乘积的积大于旧的值

        12)更新恒定乘积的值reserve为balance

        13)触发swap事件。

22 skim 函数 强制交易对合约中两种代币实际余额和保存的恒定乘积资产数量保存一致。如果有多则发送给调用者。任何人都可以调用

1    // force balances to match reserves
2 function skim(address to) external lock {
3 address _token0 = token0; // gas savings
4 address _token1 = token1; // gas savings
5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
7 }

223 sync函数 和skim函数相反,强制保存的恒定乘积的资产数量为交易对合约中两种代币的实际余额。

剖析Defi之Uinswap_2的更多相关文章

  1. 剖析Defi之Uinswap_1

    学习UniswapERC20,它是交易对的父合约.UniswapV2ERC20 是流动性代币合约,也称为 LP Token.功能主要实习ERC20代币功能以及对线下签名授权. 1 pragma sol ...

  2. 剖析Defi之Uinswap_0

    Uniswap是什么,不需要讲了吧.YYDS(永远嘀神) 介绍几个概念: 恒定乘积算法:可以简单看作X * Y = K,这里K(乘积)保持不变,所以叫恒定乘积算法,该函数是一个反曲线. 自动流动性协议 ...

  3. 探索C#之6.0语法糖剖析

    阅读目录: 自动属性默认初始化 自动只读属性默认初始化 表达式为主体的函数 表达式为主体的属性(赋值) 静态类导入 Null条件运算符 字符串格式化 索引初始化 异常过滤器when catch和fin ...

  4. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  5. [C#] 剖析 AssemblyInfo.cs - 了解常用的特性 Attribute

    剖析 AssemblyInfo.cs - 了解常用的特性 Attribute [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5944391.html 序 ...

  6. Membership三步曲之进阶篇 - 深入剖析Provider Model

    Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...

  7. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...

  8. 探索c#之Async、Await剖析

    阅读目录: 基本介绍 基本原理剖析 内部实现剖析 重点注意的地方 总结 基本介绍 Async.Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下. ...

  9. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

随机推荐

  1. Spark(十六)【SparkStreaming基本使用】

    目录 一. SparkStreaming简介 1. 相关术语 2. SparkStreaming概念 3. SparkStreaming架构 4. 背压机制 二. Dstream入门 1. WordC ...

  2. 【STM32】使用SDIO进行SD卡读写,包含文件管理FatFs(八)-认识内存管理

    [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(一)-初步认识SD卡 [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(二)-了解SD总线,命令的相关介绍 [STM3 ...

  3. oc中调用c函数 实现将字符串转换成unsigned char

    帮助码友解决问题,从而复习了一下oc中调用c函数的方式 1,新建c 头文件  test.h 定义 c 函数 #ifndef test_h #define test_h void verificatio ...

  4. 3.2 go WaitGroup代码示例

    sync.WaitGroup提供了一种安全的多协程处理方法,内部使用race.atomic来处理,避免了资源竞争及锁的产生. 主要的方法有Add.Done.Wait,可以等待一组协程全部执行完毕后,主 ...

  5. 【Java基础】JAVA中优先队列详解

    总体介绍 优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素).这里牵涉到了大小关系,元素大小的评判可以通过元素本身的自然顺序( ...

  6. C++易错小结

    C++ 11 vector 遍历方法小结 方法零,对C念念不舍的童鞋们习惯的写法: void ShowVec(const vector<int>& valList) { int c ...

  7. ActiveMQ(三)——理解和掌握JMS(1)

    一.JMS基本概念 JMS是什么JMS Java Message Service,Java消息服务,是JavaEE中的一个技术. JMS规范JMS定义了Java中访问消息中间件的接囗,并没有给予实现, ...

  8. OAuth2.0实战:认证、资源服务异常自定义!

    大家好,我是不才陈某~ 这是<Spring Security 进阶>的第4篇文章,往期文章如下: 实战!Spring Boot Security+JWT前后端分离架构登录认证! 妹子始终没 ...

  9. Linux命令执行过程

    目录 一.命令分类 二.命令执行顺序 三.命令分类及查找基本命令 四.命令执行过程 一.命令分类 Linux命令分为两类,具体为内部命令和外部命令 内部命令: 指shell内部集成的命令,此类命令无需 ...

  10. AtCoder Beginner Contest 173 题解

    AtCoder Beginner Contest 173 题解 目录 AtCoder Beginner Contest 173 题解 A - Payment B - Judge Status Summ ...