zoukankan      html  css  js  c++  java
  • uniswap v2 core源码解析系列

    前言:uniswap让我再一次感知到知易行难,2019年的时候就知道这个项目,组内有人分享,但并没引起我足够的重视,导致起个大早赶个晚集,所以人的精力还是要专注,长时间专注于一个领域才开花结果。

    UniswapV2合约分为核心合约和周边合约,均使用Solidity语言编写。其核心合约实现了UniswapV2的完整功能(创建交易对,流动性供给,交易代币,价格预言机等),但对用户操作不友好;而周边合约是用来让用户更方便的和核心合约交互。

    UniswapV2核心合约主要由factory合约(UniswapV2Factory.sol)、交易对模板合约(UniswapV2Pair.sol)及辅助工具库与接口定义等三部分组成。

    解析版本二:

    https://gitee.com/facelay/uniswap-v2-core-master

     UniswapV2介绍

    https://blog.csdn.net/weixin_39430411/article/details/108665694

    UniswapV2核心合约学习(1)— UniswapV2Factory.sol

    https://blog.csdn.net/weixin_39430411/article/details/108842197

    mapping(address => mapping(address => address)) public getPair;这个状态变量是一个map(其key为地址类型,其value也是一个map),它用来记录所有的交易对地址。注意,它的名称为getPair并且为public的,这样的目的也是让默认构建的同名函数来实现相应的接口。注意这行代码中出现了三个address,前两个分别为交易对中两种ERC20代币合约的地址,最后一个是交易对合约本身的地址。

    createPair函数
    该函数顾名思义,是用来创建交易对。之所以将该函数放在最后讲,是因为该函数相对复杂,并且还有一些知识点拓展。下面开始具体分析该函数,函数代码在前面源码部分已经列出了。注意:下文中所说的第几行均不包含空行(跳过空行)。

    该函数接受任意两个代币地址为参数,用来创建一个新的交易对合约并返回新合约的地址。注意,它的可见性为external并且没有任何限定,意味着合约外部的任何账号(或者合约)都可以调用该函数来创建一个新的ERC20/ERC20交易对(前提是该ERC20/ERC20交易对并未创建)。

    UniswapV2核心合约学习(2)——UniswapV2ERC20.sol

    https://blog.csdn.net/weixin_39430411/article/details/108965441

    bytes32 public DOMAIN_SEPARATOR;
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
    • bytes32 public DOMAIN_SEPARATOR;用来在不同Dapp之间区分相同结构和内容的签名消息,该值也有助于用户辨识哪些为信任的Dapp,具体可见eip-712提案。

    • bytes32 public constant PERMIT_TYPEHASH这一行代码根据事先约定使用permit函数的部分定义计算哈希值,重建消息签名时使用。

    constructor构造器。该构造器只做了一件事,计算DOMAIN_SEPARATOR的值。根据EIP-712的介绍,该值通过domainSeparator = hashStruct(eip712Domain)计算。

    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(
    'x19x01',
    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);
    }

    permit使用线下签名消息进行授权操作。

    为什么会有使用线下签名然后再线上验证操作这种方式呢?

    首先线下签名不需要花费任何gas,然后任何其它账号或者智能合约可以验证这个签名后的消息,

    然后再进行相应的操作(这一步可能是需要花费gas的,签名本身是不花费gas的)。

    线下签名还有一个好处是减少以太坊上交易的数量,UniswapV2中使用线下签名消息主要是为了消除代币授权转移时对授权交易的需求。

    1、链下签名消息
    链下签名消息相关知识可以参考Solidity官方文档中的Solidity by Example下的Micropayment Channel示例。根据应用场景的不同,签名的消息包含不同的内容,但一般都要包含一个防重放攻击的元素。通常使用和以太坊交易本身相同的技巧,即使用一个nonce记录账号进行交易的数量,智能合约检查该nonce以确保签名消息不被多次使用。本例中签名消息的内容包括:[PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline]。从代码nonces[owner]++中 可以看到,每调用一次permit,相应地址的nonce就会加1,这样再使用原来的签名消息就无法再通过验证了(重建的签名消息不正确了),也就防止了重放攻击。

    在以太坊中,在ECDSA签名原有的r和s的基础上加了一个v,使用它们可以验证签名消息的账号。Solidity中有一个内置的函数ecrecover来获取消息的签名地址,它使用签名消息和r,s,v作为参数。

    使用链下签名消息的常用流程是在首先链上根据输入参数重建整个签名消息,然后将重建的签名消息和输入的签名消息进行处理及比较对照,来进行相关判定和验证输入信息未受到篡改。

    链下签名计算实质上是模拟的是Solidity中的keccak256及abi.encodePacked函数,因此本合约中消息签名的计算方式为bytes32 digest = keccak256(这行及接下来的代码。计算后得到一个hash值digest,利用这个值和函数参数中的,r,s,v,使用ecrecover函数就可以得到消息签名者的地址。将这个对址和owner相对比,就可以验证该消息是否由owner签名的(显而易见每个账号只能对本地址进行授权操作)。注意:签名内容包含了spender和value,如果签名内容的任意值做了更改,使用原来的r,s,v是无法通过验证的。

    查看了一下UniswapV2的前端,它使用了web3-react中的eth_signTypedData_v4方法来计算签名消息中的r,s,v的,最终传递给了permit函数作为参数。这里V1版本前端直接使用的是Javascript + React,V2版本前端使用的是TypeScript + React。

    2、EIP-712
    该提案是用来增强链下签名消息在链上的可用性的。具体内容参见github上的EIP地址:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md,它同时提供了一个测试示例Example.sol,本合约中DOMAIN_SEPARATOR的计算方法和示例中是一致的。因为原生的签名消息对用户不太友好,用户无法从中获取更多信息,使用EIP-712第一可以让用户了解消息签名的大致描述,第二可以让用户辨识哪些是信任的Dapp,哪些是高风险的Dapp,从而不随便签名消息让自己遭受损失(比如一个恶意Dapp进行伪装等)。

    3、为什么存在permit函数
    现在我们来弄明白为什么存丰permit函数。UniswapV2的核心合约虽然功能完整,但对用户不友好,用户需要借助它的周边合约才能和核心合约交互。但是在涉及到流动性供给时,比如用户减少流动性,此时用户需要将自己的流动性代币(一种ERC20代币)燃烧掉。由于用户调用的是周边合约,周边合约未经授权是无法进行燃烧操作的( 上面提到过)。此时,如果按照常规操作,用户需要首先调用交易对合约对周边合约进行授权,再调用周边合约进行燃烧,这个过程实质上是调用两个不同合约的两个交易(无法合并到一个交易中),它分成了两步,用户需要交易两次才能完成。

    使用线下消息签名后,可以减少其中一个交易,将所有操作放在一个交易里执行,确保了交易的原子性。在周边合约里,减小流动性来提取资产时,周边合约在一个函数内先调用交易对的permit函数进行授权,接着再进行转移流动性代币到交易对合约,提取代币等操作。所有操作都在周边合约的同一个函数中进行,达成了交易的原子性和对用户的友好性。

    因此permit函数存在并且执行了授权操作的原因:

    第三方合约在进行ERC20代币转移时(代币交易),用户首先需要调用代币合约进行授权(授权交易),然后才能调用第三方合约进行转移。这样整个过程将构成分阶段的两个交易,用户必须交易两次,失去了交易的原子性。使用线下消息签名线上验证的方式可以消除对授权交易的需求,permit就是进行线上验证并同时执行授权的函数。

    当然如果用户会操作的话,也可以手动授权,不使用permit函数相关的周边合约接口进行交易。

    UniswapV2核心合约学习(3)——UniswapV2Pair.sol

    https://blog.csdn.net/weixin_39430411/article/details/108965855

  • 相关阅读:
    Kinect 开发 —— 硬件设备解剖
    Kinect 开发 —— 引言
    (转)OpenCV 基本知识框架
    OpenCV —— 摄像机模型与标定
    OpenCV —— 跟踪与运动
    OpenCV —— 图像局部与分割(二)
    OpenCV —— 图像局部与部分分割(一)
    OpenCV —— 轮廓
    OpenCV —— 直方图与匹配
    OpenCV —— 图像变换
  • 原文地址:https://www.cnblogs.com/zccst/p/14822893.html
Copyright © 2011-2022 走看看