石家庄老站长

点击联系客服
客服QQ: 客服微信:
 找回密码
 立即注册
查看: 70|回复: 0

学习uniswap工厂工厂合同

[复制链接]

1

主题

1

帖子

-7

积分

限制会员

积分
-7
发表于 2021-9-9 10:41:47 | 显示全部楼层 |阅读模式
目录

1、核心流程2、合同结构3、流动性生成3、交易4、代码注释5、代码行解释5.1简单代码部分5.2交易对代码生成:createPair函数

1、核心流程

Uniswap核心合同包括三个合同、工厂合同、配对合同、ERC20合同核心合同部署时,只需在工厂合同工厂合同部署时设置创建者即可,添加交易对时,需要提供两个token的地址。工厂协议随后通过create2的方法,按2进制大小对两个令牌地址进行排序,从而缓解为该交易分发新配对协议的分发。(*译者:译者:译者:译者:译者:译者:译者:译者:译者:译者)将此散列值分发到create2的hash,以便能够使用两个token地址进行create2计算的用户可以将两个token存储在配对协议中然后,在配对协议中为用户创建的erc20token配对协议可以是流动性token,流动性token可以执行ERC20操作,将流动性token传递给其他用户或去除流动性,配对协议可以消除流动性。另外,两个token同时返还的数量将根据流动性数量和两个token的储备进行重新计算,如果有手续费收益,用户可以通过一个token交换另一个token,配对合同将扣除数千分之三的手续费。以uniswap核心合同为基础,有一个路由合同,使核心合同路由合同能够更好地运行。增加流动性,去除流动性,交换,配对合同可以完成所有交易工作,但路由合同可以整合所有工作,与前端一起完成更好的交易。路由协议的代码数量很多,部署时超过gas限制,因此路由协议分为两个版本,功能补充2、合约结构





3、创建流动性





3、交易





4、代码注释

pragma  solidity=0 . 5 . 16;

“Import”./interfaces/iuniswapv2fa
ctory.sol';
import './UniswapV2Pair.sol';
//uniswap工厂合约,实现了IUniswapV2Factory接口
contract UniswapV2Factory is IUniswapV2Factory {
        // 这个状态变量主要是用来切换开发团队手续费开关。在UniswapV2中,用户在交易代币时,会被收取交易额的千分之三的手续费分配给所有流动性供给者。如果feeTo不为零地址,则代表开关打开,此时会在手续费中分1/6给开发团队。feeTo设置为零地址(默认值),则开关关闭,不从流动性供给者中分走1/6手续费,也就是总的千分之五。它的访问权限设置为public后编译器会默认构建一个同名public函数,正好用来实现IUniswapV2Factory.sol中定义的相关接口。
    address public feeTo;
    // 这个状态变量是用来记录谁是feeTo设置者。其读取权限设置为public的主要目的同上。
    address public feeToSetter;
    // 这个状态变量是用来记录所有的交易对地址。实现了IUniswapV2Factory的getPair方法。前两个分别为交易对中两种ERC20代币合约的地址,最后一个是交易对合约本身的地址。
    mapping(address => mapping(address => address)) public getPair;
    // 记录所有交易对地址的数组。实现了IUniswapV2Factory的allPairs方法,因为上面map虽然存储了所有交易对,但是无法遍历
    address[] public allPairs;
    // 配对合约的Bytecode的hash
    bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(UniswapV2Pair).creationCode));
    // 交易对被创建时触发的事件。注意参数中的indexed表明该参数可以被监听端(轻客户端)过滤。
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);
    /**
     * @dev 构造函数。参数提供了一个初始feeToSetter地址作为feeTo的设置者地址,不过此时feeTo仍然为默认值零地址,开发团队手续费未打开。
     * @param _feeToSetter 收税开关权限控制
     */
    constructor(address _feeToSetter) public {
        feeToSetter = _feeToSetter;
    }
    /**
     * @dev 返回所有交易对地址数组的长度
     */
    function allPairsLength() external view returns (uint) {
        return allPairs.length;
    }
    /**
     * 该函数接受任意两个代币地址为参数,用来创建一个新的交易对合约并返回新合约的地址。注意,它的可见性为external并且没有任何限定,意味着合约外部的任何账号(或者合约)都可以调用该函数来创建一个新的ERC20/ERC20交易对(前提是该ERC20/ERC20交易对并未创建)。
     * @param tokenA TokenA
     * @param tokenB TokenB
     * @return pair 配对地址
     * @dev 创建配对
     */
    function createPair(address tokenA, address tokenB) external returns (address pair) {
        // 来验证两种代币的合约地址不能相同,也就交易对必须是两种不同的ERC20代币
        require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
        // 将tokenA和tokenB进行大小排序,确保tokenA小于tokenB
        (address token0, address token1) = tokenA  tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        // 确认token0不等于0地址,用来验证两个地址不能为零地址。为什么只验证了token0呢,因为token1比它大,它不为零地址,token1肯定也就不为零地址
        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
        // 验证交易对未被创建
        require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
        // 给bytecode变量赋值"UniswapV2Pair"合约的创建字节码
        bytes memory bytecode = type(UniswapV2Pair).creationCode;
        // 将token0和token1打包后创建哈希
        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        // 内联汇编
        // solium-disable-next-line
        assembly {
            //通过create2方法布署合约,并且加盐,返回地址到pair变量
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        // 调用pair地址的合约中的"initialize"方法,传入变量token0,token1
        IUniswapV2Pair(pair).initialize(token0, token1);
        // 配对映射中设置token0=>token1=pair
        getPair[token0][token1] = pair;
        // 配对映射中设置token1=>token0=pair
        getPair[token1][token0] = pair; // populate mapping in the reverse direction
        // 配对数组中推入pair地址
        allPairs.push(pair);
        // 触发配对成功事件
        emit PairCreated(token0, token1, pair, allPairs.length);
    }
    /**
     * @dev 用来设置新的feeTo以切换开发团队手续费开关(可以为开发团队接收手续费的地址,也可以为零地址)。注意,该函数首先使用require函数验证了调用者必须为feeTo的设置者feeToSetter,如果不是则会重置整个交易。
     * @param _feeTo 收税地址
     */
    function setFeeTo(address _feeTo) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeTo = _feeTo;
    }
    /**
     * @dev 该函数用来转让feeToSetter。它首先判定调用者必须是原feeToSetter,否则重置整个交易。
     * @param _feeToSetter 收税权限控制
     */
    function setFeeToSetter(address _feeToSetter) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeToSetter = _feeToSetter;
    }
}

5、代码逐行解读
5.1 简单代码部分
[ol]
  • 代码的第一行,设定使用的Solidity编译器的版本。这里估计是为了更严谨,使用了精确的编译器版本0.5.16,而不是我们常用的>= 0.5.16或者^0.5.16[/ol]
    pragma solidity =0.5.16;
  • 代码中的两个import语句分别导入了factory所必须实现的接口合约及交易对模板合约[/ol]
    import './interfaces/IUniswapV2Factory.sol';
    import './UniswapV2Pair.sol';
  • contract UniswapV2Factory is IUniswapV2Factory 定义了UniswapV2Factory合约是一个IUniswapV2Factory,它必须实现其所有接口。[/ol]
    contract UniswapV2Factory is IUniswapV2Factory
  • feeTo这个状态变量主要是用来切换开发团队手续费开关。在UniswapV2中,用户在交易代币时,会被收取交易额的千分之三的手续费分配给所有流动性供给者。如果feeTo不为零地址,则代表开关打开,此时会在手续费中分1/6给开发团队。feeTo设置为零地址(默认值),则开关关闭,不从流动性供给者中分走1/6手续费。它的访问权限设置为public后编译器会默认构建一个同名public函数,正好用来实现IUniswapV2Factory.sol中定义的相关接口。[/ol]
    address public feeTo;
  • feeToSetter这个状态变量是用来记录谁是feeTo设置者。其读取权限设置为public的主要目的同上。[/ol]
    address public feeToSetter;
  • mapping(address => mapping(address => address)) public getPair;这个状态变量是一个map(其key为地址类型,其value也是一个map),它用来记录所有的交易对地址。注意,它的名称为getPair并且为public的,这样的目的也是让默认构建的同名函数来实现相应的接口。注意这行代码中出现了三个address,前两个分别为交易对中两种ERC20代币合约的地址,最后一个是交易对合约本身的地址。[/ol]
    mapping(address => mapping(address => address)) public getPair;
  • allPairs,记录所有交易对地址的数组。虽然交易对址前面已经使用map记录了,但map无法遍历。如果想遍历和索引,必须使用数组。注意它的名称和权限,同样是为了实现接口。[/ol]
    address[] public allPairs;
  • 配对合约的Bytecode的hash[/ol]
    bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(UniswapV2Pair).creationCode));
  • event PairCreated(address indexed token0, address indexed token1, address pair, uint);交易对被创建时触发的事件,注意参数中的indexed表明该参数可以被监听端(轻客户端)过滤。[/ol]
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);
  • 构造器,很简单。参数提供了一个初始feeToSetter地址作为feeTo的设置者地址,不过此时feeTo仍然为默认值零地址,开发团队手续费未打开。[/ol]
    constructor(address _feeToSetter) public {
        feeToSetter = _feeToSetter;
    }
  • 这个函数非常简单,返回所有交易对地址数组的长度,这样在合约外部可以方便使用类似for这样的形式遍历该数组。[/ol]
    function allPairsLength() external view returns (uint) {
        return allPairs.length;
    }
  • 用来设置新的feeTo以切换开发团队手续费开关(可以为开发团队接收手续费的地址,也可以为零地址)。注意,该函数首先使用require函数验证了调用者必须为feeTo的设置者feeToSetter,如果不是则会重置整个交易。[/ol]
    function setFeeTo(address _feeTo) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeTo = _feeTo;
    }
  • 该函数用来转让feeToSetter。它首先判定调用者必须是原feeToSetter,否则重置整个交易。[/ol]
    function setFeeToSetter(address _feeToSetter) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeToSetter = _feeToSetter;
    }

    但这里有可能存在这么一种情况:当原feeToSetter不小心输错了新的设置者地址_feeToSetter时,设置会立即生效,此时feeToSetter为一个错误的或者陌生的无控制权的地址,无法再通过该函数设置回来。虽然UniswapV2团队不会存在这种疏忽,但是我们自己在使用时,还是有可能发生的。有一种方法可以解决这个问题,就是使用一个中间地址值过渡一下,而新的feeToSetter必须再调用一个接受方法才能真正成为设置者。如果在接受之前发现设置错误,原设置者可以重新设置。具体代码实现可以参考下面的Owned合约的owner转让实现:

    pragma solidity ^0.4.24;
    contract Owned {
        address public owner;
        address public newOwner;
        event OwnershipTransferred(address indexed _from, address indexed _to);
        constructor() public {
            owner = msg.sender;
        }
        modifier onlyOwner {
            require(msg.sender == owner,"invalid operation");
            _;
        }
        function transferOwnership(address _newOwner) public onlyOwner {
            newOwner = _newOwner;
        }
        function acceptOwnership() public {
            require(msg.sender == newOwner,"invalid operation");
            emit OwnershipTransferred(owner, newOwner);
            owner = newOwner;
            newOwner = address(0);
        }
    }

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

    [ol]
  • 用来验证两种代币的合约地址不能相同,也就交易对必须是两种不同的ERC20代币。[/ol]
    require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
  • 用来对两种代币的合约地址从小到大排序,因为地址类型底层其实是uint160,所以也是有大小可以排序的。[/ol]
    (address token0, address token1) = tokenA  tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
  • 用来验证两个地址不能为零地址。为什么只验证了token0呢,因为token1比它大,它不为零地址,token1肯定也就不为零地址[/ol]
    require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
  • 验证交易对并未创建(不能重复创建相同的交易对)[/ol]
    require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS');
  • 用来获取交易对模板合约UniswapV2Pair的创建字节码creationCode。注意,它返回的结果是包含了创建字节码的字节数组,类型为bytes。类似的,还有运行时的字节码runtimeCode。creationCode主要用来在内嵌汇编中自定义合约创建流程,特别是应用于create2操作码中,这里create2是相对于create操作码来讲的。注意该值无法在合约本身或者继承合约中获取,因为这样会导致自循环引用[/ol]
    bytes memory bytecode = type(UniswapV2Pair).creationCode;
  • 第6行用来计算一个salt。注意,它使用了两个代币地址作为计算源,这就意味着,对于任意交易对,该salt是固定值并且可以线下计算出来[/ol]
    bytes32 salt = keccak256(abi.encodePacked(token0, token1));
  • assembly代表这是一段内嵌汇编代码,Solidity中内嵌汇编语言为Yul语言。在Yul中,使用同名的内置函数来代替直接使用操作码,这样更易读。后面的左括号代表内嵌汇编作用域开始。[/ol]
    assembly {
  • 在Yul代码中使用了create2函数(该函数名表明使用了create2操作码)来创建新合约。[/ol]
    pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
  • 调用新创建的交易对合约的一个初始化方法,将排序后的代币地址传递过去。为什么要这样做呢,因为使用create2函数创建合约时无法提供构造器参数。[/ol]
    IUniswapV2Pair(pair).initialize(token0, token1);
  • 用来将交易对地址记录到map中去。因为:1、A/B交易对同时也是B/A交易对;2、但在查询交易对时,用户提供的两个代币地址并没有排序,所以需要记录两次[/ol]
    getPair[token0][token1] = pair;
    getPair[token1][token0] = pair;
  • 交易对地址记录到数组中去,便于合约外部索引和遍历。[/ol]
    allPairs.push(pair);
  • 触发交易对创建事件[/ol]
    emit PairCreated(token0, token1, pair, allPairs.length);
  • 回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|无图版|手机版|小黑屋|石家庄@IT精英团

    GMT+8, 2022-8-13 21:21 , Processed in 0.152471 second(s), 25 queries .

    Powered by Discuz! X3.4

    © 2001-2021 Comsenz Inc.

    快速回复 返回顶部 返回列表