手把手教你从0到1构建UniswapV2:part1

  • Louis
  • 更新于 2024-07-27 22:49
  • 阅读 367

Uniswap是一个运行在以太坊区块链上的去中心化交易所。它完全是自动化的、非托管的、去中心化的。它经历了多次的迭代开发。目前线上稳定运行的是第三个版本。之前关于UniswapV1的系列文章中,我展示了如何从头开始构建它并解释了它的核心机制。

简单介绍

Uniswap是一个运行在以太坊区块链上的去中心化交易所。它完全是自动化的、非托管的、去中心化的。它经历了多次的迭代开发。目前线上稳定运行的是第三个版本。

之前关于 Uniswap V1 的系列文章中,我展示了如何从头开始构建它并解释了它的核心机制。从这篇开始,我们会花一些时间专门讨论 Uniswap V2 的相关内容:同样,我们将从头开始构建它的复刻版本,并将学习去中心化交易所的核心原理。与之前的系列不同,这里不会过多详细介绍 Uniswap 的常数乘积公式和相关核心数学——如果您想了解,请阅读 V1 系列。

工具的介绍

在这个系列的文章中,我们将使用一种全新的工具:Foundry,我们将使用它进行合约的开发和测试,Foundry 是由 Georgios Konstantopoulos 用 Rust 编写的现代以太坊工具包。它比 Hardhat 快得多,而且它还有一个优势就是可以使用solidity编写测试代码,这会让一些不会写JavaScript的朋友少一些心智负担。

我们还将使用Solmate来实现ERC20, 而不是OpenZeppelin,因为后者有些臃肿和固执己见。使用OpenZeppelin的ERC20不允许将代币转移到零地址,这个限制并不是我们想要的。反过来Solmate是一个Gas优化合约的集合,并且没有那么多限制。

还值得注意的是,自 2020 年 Uniswap V2 推出以来,很多事情都发生了变化。例如,自 Solidity 0.8 发布以来, SafeMath 库已过时,该版本引入了本机溢出检查。所以,可以这么说,我们正在构建 Uniswap 的现代版本。

Uniswap V2 架构

Uniswap V2 的核心架构思想是 “池化” :流动性提供者可以将其流动性质押在合约中;质押的流动性允许其他任何人以去中心化的方式进行交易。与 Uniswap V1 类似,交易者支付少量费用,该费用会累积在合约中,然后由所有流动性提供者共享。

Uniswap V2的核心合约是UniswapV2Pair,“pool”和“pair”是可互换的术语,它们所代表的意思是相同的,都是代表UniswapV2Pair合约。该合约的主要的功能是接收用户的Token代币,并使用累积的代币储备来执行交换。这也是为什么我们叫它“池子”的原因。每个UniswapV2Pair合约只能包含一对代币,并且只能允许这两个代币之间进行交换,这就是它叫做“对”的原因。

Uniswap V2 的合约代码库分为两个存储仓库:

核心的仓库存储这些合约:

1、UniswapV2ERC20 – 用于生成 LP 代币的 ERC20合约。它还实现了 EIP-2612标准用于支持链下签名授权。

2、UniswapV2Factory - 和V1版本类似,这是一个工厂合约,用于创建配对合约并充当他们的注册表。注册表使用 create2 生成地址,我们会详细讲解它是如何工作的。

3、UniswapV2Pair - 符合核心逻辑的主合约。值得注意的是,工厂只允许创建独特的没有被创建过的交易对,这样做可以避免稀释流动性。

v2-periphery 这个仓库中包含多个合约,这些合约使得使用Uniswap更加容易,其中包含了UniswapV2Router,它是Uniswap UI 以及在Uniswap之上工作的其他web和去中心化应用程序的主要入口点。该合约的接口和Uniswap V1中的Exchange合约中的接口非常接近。

v2-periphery 这个仓库中的另一个重要合约是UniswapV2Library,它是实现重要计算的辅助函数集合,我们会继承这两个合约。

我们将从核心合约开始我们的实现,首先关注最重要的机制。我们会看到这些合约很通用,颗粒度很细,这种设计让整个架构更加细化。

让我们正式开始!

集中汇聚流动性

没有流动性是不可能进行交易的。因此,我们需要实现的第一个功能是创建一个流动性池子,看看它是如何工作的。

流动性池子是一个合约,这个合约可以存储代币,并允许使用这些代币进行交换,因此“汇集流动性”意味着将Token代币发送到智能合约并将其存储一段时间。

你应该已经知道,每个合约都拥有自己的存储空间,就像ERC20 tokens一样,每个合约都拥有一个mapping,这个mapping是从用户地址到其余额的映射。我们的“池子”合约中会存放ERC20的余额,这显然还不足够。

主要原因是,仅仅依赖ERC20的余额很可能会被价格操纵,想象一下:有人将大量的代币发送到流动性池子中,这会暂时增加池子中的代币供应量,增加供应量之后,池子中的代币价格会下跌,因为流动性是根据恒定乘积做市商算法工作的。这个时候恶意用户可以用比较低的价格购买目标代币,进行有利可图的交换,并最终兑现,恶意用户可以从池子中移走代币完成获利。为了避免这种情况,我们需要跟踪池子中的储备,并且我们还需要控制它们的更新时间。

我们将使用reserve0和reserve1变量来跟踪池子中的储备:

contract ZuniswapV2Pair is ERC20, Math {
  ...

  uint256 private reserve0;
  uint256 private reserve1;

  ...
}

为了简洁起见,我省略了很多代码,可以去github仓库获取完整代码。

如果你看过我的UniswapV1系列你可能还记得,我们实现了一个addLiquidity函数,这个函数的作用是计算新流动性并发行LP代币。其实,Uniswap V2 在 UniswapV2Router 合约中实现了相同的功能。并且在配对合约中,这个功能是基础功能:流动性管理被简单的视为LP代币管理。当我们为池子添加流动性时,合约会铸造LP代币;当你移除流动性时,LP代币会被销毁。我们在上面的架构层面已经解释过了,核心的合约是仅执行核心操作的低级别的合约,UniswapV2Router负责高级别的业务逻辑和用户交互。

让我们看下这个基础函数:

function mint() public {
   uint256 balance0 = IERC20(token0).balanceOf(address(this));
   uint256 balance1 = IERC20(token1).balanceOf(address(this));
   uint256 amount0 = balance0 - reserve0;
   uint256 amount1 = balance1 - reserve1;

   uint256 liquidity;

   if (totalSupply == 0) {
      liquidity = ???
      _mint(address(0), MINIMUM_LIQUIDITY);
   } else {
      liquidity = ???
   }

   if (liquidity <= 0) revert InsufficientLiquidityMinted();

   _mint(msg.sender, liquidity);

   _update(balance0, balance1);

   emit Mint(msg.sender, amount0, amount1);
}

让我们来稍微梳理下这个函数:

1、获取当前合约的代币余额:

uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));

这两行代码获取合约当前持有的 token0token1的余额。

2、计算新增的代币量:

uint256 amount0 = balance0 - reserve0;
uint256 amount1 = balance1 - reserve1;

这里计算了自上次更新以来新增的 token0token1的数量。reserve0reserve1 是先前记录的代币储备量。

3、初始化流动性变量:

uint256 liquidity;

4、计算新增的流动性:

if (totalSupply == 0) {
  liquidity = ???
  _mint(address(0), MINIMUM_LIQUIDITY);
} else {
  liquidity = ???
}

这里我故意将liquidity的计算方法打了问号,我们后面会专门的去分析这里的流动性计算规则。

5、检查流动性是否足够:

if (liquidity <= 0) revert InsufficientLiquidityMinted();

如果计算出的流动性小于或等于零,则抛出异常,表示新增的流动性不足。

6、铸造LP代币:

_mint(msg.sender, liquidity);

向调用者铸造相应数量的 LP 代币。

7、更新储备和余额:

_update(balance0, balance1);

8、触发...

剩余50%的内容订阅专栏后可查看

  • 原创
  • 学分: 0
  • 分类: Uniswap
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
该文章收录于 手把手带你实现Uniswap
1 订阅 6 篇文章

0 条评论

请先 登录 后评论
Louis
Louis
0x2b75...1A7D
区块链开发工程师,技术交流或者有工作机会可加VX: magicalLouis