剖析Defi之Uinswap_0


Uniswap是什么,不需要讲了吧。YYDS(永远嘀神)

介绍几个概念:

  恒定乘积算法:可以简单看作X * Y = K,这里K(乘积)保持不变,所以叫恒定乘积算法,该函数是一个反曲线。

  自动流动性协议,一般叫自动做市。在上面的公式里,X/Y的比值一般叫价格。当X发生变化时,根据乘积不变,Y也要发生变化,从而X/Y也就是价格发生了变化, 所以叫自动做市。这里可以根据X和Y的比例同时提供对应的X和Y,这种操作叫增加(提供)流动性。增加流动性后的K值虽然相比以前发生了变化,但是在随后的交易里是不变的。相应的,也有减少流动性的操作。

Uniswap的交易对是任意ERC20交易对,增加流动性(提供相应的两种ERC20币)会获得流动性代币,你也可以随时使用流动性代币赎回你的ERC20代币。增加流动性会减小交易时的价格滑点。

Uniswap采用恒定乘积自动做市,因此大额交易和小额交易相比,其执行时的比率(价格)会指数级变得更差,当然就是这样设计的。

由于套利行为的存在,Uniswap价格始终趋向于结算价。

交易时,Uniswap收取0.3%的手续费,支付给所有流动性提供者。这实质上会使恒定乘积的K变大。

uniswap核心我认为是 X*Y=K。

学习X-Y-K模型,附上链接区块链 - 深入理解Uniswap协议

整个 UniswapV2 产品拆分出了多个小型的开源项目,主要包括:

  • uniswap-interface
  • uniswap-v2-sdk
  • uniswap-sdk-core
  • uniswap-info
  • uniswap-v2-subgraph
  • uniswap-v2-core
  • uniswap-v2-periphery
  • uniswap-lib

我们主要关注uniswap_v2_core,它是uniswap的核心逻辑;


uniswap_v2_core 有3个主要文件分别是:

  • UniswapV2Factory.sol:工厂合约
  • UniswapV2Pair.sol:交易兑合约
  • UniswapV2ERC20.sol:LPToken 合约

工厂合约是用来部署交易兑合约,调用工厂合约的createPair()创建信的交易对,

交易对合约 管理流动性资金池,UNI-WETH,USDT-WETH。

LP合约:资金池里注入流动性的一种凭证,也称为流动性代币。

来源崔棉大师

UniswapFactory合约代码

 1 pragma solidity =0.5.16;
 2 
 3 import './interfaces/IUniswapV2Factory.sol';
 4 import './UniswapV2Pair.sol';
 5 
 6 contract UniswapV2Factory is IUniswapV2Factory {
 7     address public feeTo;
 8     address public feeToSetter;
 9 
10     mapping(address => mapping(address => address)) public getPair;
11     address[] public allPairs;
12 
13     event PairCreated(address indexed token0, address indexed token1, address pair, uint);
14 
15     constructor(address _feeToSetter) public {
16         feeToSetter = _feeToSetter;
17     }
18 
19     function allPairsLength() external view returns (uint) {
20         return allPairs.length;
21     }
22 
23     function createPair(address tokenA, address tokenB) external returns (address pair) {
24         require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
25         (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
26         require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
27         require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
28         bytes memory bytecode = type(UniswapV2Pair).creationCode;
29         bytes32 salt = keccak256(abi.encodePacked(token0, token1));
30         assembly {
31             pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
32         }
33         IUniswapV2Pair(pair).initialize(token0, token1);
34         getPair[token0][token1] = pair;
35         getPair[token1][token0] = pair; // populate mapping in the reverse direction
36         allPairs.push(pair);
37         emit PairCreated(token0, token1, pair, allPairs.length);
38     }
39 
40     function setFeeTo(address _feeTo) external {
41         require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
42         feeTo = _feeTo;
43     }
44 
45     function setFeeToSetter(address _feeToSetter) external {
46         require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
47         feeToSetter = _feeToSetter;
48     }
49 }

1. 第一行:规定了Solidity编译器的版本,还可以用pragma solidity >=0.5.16,^0.5.16;

2.  第三至第四行则是导入工厂合约所需的接口合约

3. 第六行开始步入正题,继承导入的合约

4 .feeTo 定义了地址类型的状态变量,作用是是否开启手续费,在V2中,当用户交易代币时,会收取0.3%的手续费分给提供流动性的人,当feeTo为零地址(默认值)关闭手续费,反之则收取手续费。

5. feeToSetter记录设置feeto的设置者

6.mapping 映射和py的字典相似,三个address,前两个是交易对相关的币地址,最后一个是交易对本身地址

7 allPairs 记录所有交易对地址,mapping无法遍历。

8. 事件 PairCreated,创建交易对时触发事件,参数有indexed,可以过滤筛选

9. 构造函数,参数_feeToSetter 赋值给feeToSetter,前面提到这是记录feeTo的设置者地址。

10 .函数allPairsLength 返回所有交易对长度  修饰符external,外部调用

11,creatPair是factory的最重要函数,作用是创建交易对。接受2个address参数(任意代币地址),external 意味着合约外都可以直接调用,返回值是 交易对地址

 1  function createPair(address tokenA, address tokenB) external returns (address pair) {
 2          require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
 3        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
 4        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
 5       require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
 6         bytes memory bytecode = type(UniswapV2Pair).creationCode;
 7        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
 8        assembly {
 9           pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
10        }
11        IUniswapV2Pair(pair).initialize(token0, token1);
12         getPair[token0][token1] = pair;
13         getPair[token1][token0] = pair; // populate mapping in the reverse direction
14          allPairs.push(pair);
15          emit PairCreated(token0, token1, pair, allPairs.length);
16      }

  第一行验证两种代币地址不能相同。

  第二行:由于address是uint160可以比较大小,这里是把代币地址排序(从小到大)

  第三行 验证 token0不是零地址,由于token1比token0大,所以只验证了一次。

   第四行 验证交易对未创建。


   创建新合约 还可以使用

UniswapV2Pair newPair = new UniswapV2Pair();

   使用 create2 最大的好处其实在于:可以在部署智能合约前预先计算出合约的部署地址

       第六行: 获取模板合约UinswapV2Pair合约代码的的创建字节码(creationCode) 返回结果是bytes 的数组,

  第七行:abi.encodePacked(对给定的参数进行打包编码,注参数为变量),keccak256 计算哈希。这里的意思是用两个代币地址作为参数 生成一个固定值salt。


  第八行: assembly 内联汇编的标志,
create2 有四个参数v:发送eth的数量单位wei;p:代码的起始内存地址,
n:代码长度(mload获取长度)s:salt前面计算的哈希
  第十一行:调用新创建的交易对合约的初始方法,参数是排序后的两个代币地址。
  第十二-十三行:token:0,1/1,0是同一个地址,用户提交的代地址数是未排序,所以添加两次。
  第十四行:allpair数组添加新生成的交易对地址。
  第十五行:触发交易对创建事件



12:设置新的feeto。切换接受手续费地址
1 function setFeeTo(address _feeTo) external {
2          require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
3        feeTo = _feeTo;
4      }

  通过require 验证函数调用者必须是 feeTo的设置者。

13:转让feeToSetter

1   function setFeeToSetter(address _feeToSetter) external {
2          require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
3         feeToSetter = _feeToSetter;
4      }

通过require 验证函数调用者必须是 feeTo的设置者。


以上就是uniswap的工厂合约。通过解读的Zero_Nothing文章