首页 > 其他分享 >Uniswap V2 — 从代码解释 DeFi 协议

Uniswap V2 — 从代码解释 DeFi 协议

时间:2023-04-22 17:57:53浏览次数:57  
标签:tokenA 代币 amountB V2 uint address Uniswap DeFi 合约

Uniswap V2 — 从代码解释 DeFi 协议

为了理解我们在分析代码时将要经历的不同组件,首先了解哪些是主要概念以及它们的作用是很重要的。所以,和我一起裸露吧,因为这是值得的。

我在 5 个段落中总结了您需要了解的主要重要概念,您将在本文结束时理解这些概念。

Uniswap 是一种去中心化交易协议。该协议是一套持久的、不可升级的智能合约,它们共同创建了一个自动化的做市商。

Uniswap 生态系统贡献流动性的流动性提供者、交换代币的交易员和与智能合约交互以开发代币新交互的开发人员组成。

每个 Uniswap智能合约或对管理一个由两个 ERC-20 代币储备组成的流动资金池。

每个流动性池重新平衡以保持 50/50 比例的加密货币资产,这反过来又决定了资产的价格。

流动性提供者可以是任何能够向 Uniswap 交易合约提供等值的 ETH 和 ERC-20 代币的人。作为回报,他们从交易合约中获得流动性提供者代币(LP 代币代表流动性提供者拥有的池的份额),可用于随时提取其在流动性池中的比例。

他们存储库中的主要智能合约是:

  • UniswapV2ERC20— 用于 LP 令牌的扩展 ERC20 实现。它还实施了 EIP-2612 以支持链下传输批准。
  • UniswapV2Factory— 与 V1 类似,这是一个工厂合约,它创建配对合约并充当它们的注册表。注册表使用 create2 来生成对地址——我们将详细了解它是如何工作的。
  • UniswapV2Pair— 负责核心逻辑的主合约。值得注意的是,工厂只允许创建独特的货币对,以免稀释流动性。
  • UniswapV2Router— Uniswap UI 和其他在 Uniswap 之上工作的网络和去中心化应用程序的主要入口点。
  • UniswapV2Library — 一组实现重要计算的辅助函数。

在这篇文章中,我们将提及所有这些,但我们将主要关注浏览UniswapV2RouterUniswapV2Factory编码,尽管UniswapV2Pair并且UniswapV2Library会涉及很多。

UniswapV2Router02.sol

该合约使创建货币对、添加和删除流动性、计算所有可能的掉期变化的价格以及执行实际掉期变得更加容易。路由器适用于通过工厂合约部署的所有对

您需要在合约中创建一个实例才能调用 addLiquidity、removeLiquidity 和 swapExactTokensForTokens 函数

address private constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

IUniswapV2Router02 public uniswapV2Router;
uniswapV2Router = IUniswapV2Router02(ROUTER);

现在让我们看看流动性管理:

函数 addLiquidity():

function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
  • tokenAtokenB:是我们需要获取或创建我们想要增加流动性的货币对的代币。
  • amountADesiredamountBDesired是我们要存入流动资金池的金额。
  • amountAMinamountBMin是我们要存入的最小金额。
  • to address 是接收 LP 代币的地址。
  • 截止日期,最常见的是block.timestamp

在内部 _addLiquidity() 中,它将检查这两个令牌中的一对是否已经存在,如果不存在,它将创建一个新令牌

if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
    IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}

然后它需要获取现有的代币数量或也称为reserveAand reserveB,我们可以通过 UniswapV2Pair 合约访问它

IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves()

现在,外部函数 addLiquidity, 返回(uint amountA, uint amountB, uint liquidity),那么它是如何计算的呢?

通过UniswapV2Library拿到上面提到的reserves之后,还有一系列的检查

如果该对不存在,并且新创建 amountAamountB返回一个新的,则将amountADesired作为amountBDesired参数传递(见上文)。

否则,它会做这个操作

amountBOptimal = amountADesired.mul(reserveB) / reserveA;

如果amountB小于或等于amountBDesired那么它将返回:

(uint amountA, uint amountB) = (amountADesired, amountBOptimal)

否则,它将返回

(uint amountA, uint amountB) = (amountAOptimal, amountBDesired)

其中amountAOptimal的计算方式与amountBOptimal

然后,要计算liquidity返回值将经过以下过程:

首先,它将使用现有/新创建的对的地址部署 UniswapV2Pair 合约。

它是如何做到的?它计算一对的 CREATE2 地址而无需进行任何外部调用:(阅读有关 CREATE2 Opcode 的更多信息

pair = address(uint(keccak256(abi.encodePacked(address(uint(keccak256(abi.encodePacked(
    hex'ff',
    factory,
    keccak256(abi.encodePacked(token0, token1)),
    hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));

然后,它获取新部署合约的地址,我们需要用它来从这对代币中铸造代币。

当您向货币对添加流动性时,合约会生成 LP 代币;当你移除流动性时,LP 代币就会被销毁。

pairFor因此,首先我们使用UniswapV2Library获取地址:

address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);UniswapV2Library.pairFor(factory, tokenA, tokenB);

因此,稍后可以铸造 ERC20 代币并计算返回的流动性:

liquidity = IUniswapV2Pair(pair).mint(to);

如果您想知道为什么它最终成为 ERC20,在 mint 函数中它是这样存储的https://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/UniswapV2Pair.sol#L112)

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

****函数removeLiquidity():

function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB);

从池中移除流动性意味着燃烧 LP 代币以换取一定数量的基础代币。

IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity);

然后,外部函数返回两个值(uint amountA, uint amountB),这些值是使用传递给函数的参数计算的。

随提供的流动性返回的代币数量计算如下

amount0 = liquidity.mul(balance0) / _totalSupply; 
amount1 = liquidity.mul(balance1) / _totalSupply;

然后它将这些数量的代币转移到指定的地址

_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);

您的 LP 代币份额越大,销毁后获得的储备份额就越大。

上面的这些计算发生在 burn 函数内部

IUniswapV2Pair(对).burn(对)

https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/UniswapV2Router02.sol#L114)

IUniswapV2Pair(pair).burn(to)

****函数swapExactTokensForTokens()

function swapExactTokensForTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
) external returns (uint[] memory amounts);

Uniswap 的核心功能是交换代币,所以让我们弄清楚代码中发生了什么,以便更好地理解它

您很可能听说过流动资金池中使用的神奇公式

X * Y = K

所以,这将首先发生在 swap 函数内部getAmountOut()

里面用到的关键函数有:

TransferHelper.safeTransferFrom().safeTransferFrom()

代币金额发送到配对代币的地方

在 UniswapV2Pair 合约的较低级别交换功能中,它将是

_safeTransfer(_token, to, amountOut);

这将实际转移回预期地址。

我知道信息量很大,但您将有足够的时间阅读所有内容,直到完全理解为止。所以……

UniswapV2Factory.sol

工厂合约是所有已部署对合约的注册表。这个合约是必要的,因为我们不希望有成对的相同代币,这样流动性就不会分成多个相同的对。

该合约还简化了配对合约的部署:无需通过任何外部调用手动部署配对合约,只需调用工厂合约中的方法即可。

好吧,让我们倒回去,因为在上面的这些行中已经说了非常重要的事情。我们把它们拆分开来分别分析:

该合约是所有已部署对合约的注册表

只部署了一个工厂合约,该合约用作 Uniswap 交易对的官方注册处。

现在,我们在代码中的什么地方看到了它以及发生了什么:

address[] public allPairs;

它有 的数组allPairs,如上所述,存储在这个合约中。这些对被添加到一个方法中,该方法createPair()通过将新初始化的对推送到数组来调用。

allPairs.push(pair);push(pair);

这个合约是必要的,因为我们不想拥有成对的相同代币

mapping(address => mapping(address => address)) public getPair;

它具有该对的地址与构成该对的两个令牌的映射。这用于检查一对是否已经存在。

require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS');

该合约还简化了配对合约的部署

这是一个更深层次的话题,但我将尝试总结一下这里发生的事情的重要性。

在以太坊中,合约可以部署合约。可以调用已部署合约的函数,该函数将部署另一个合约。

您不需要从您的计算机上编译和部署合约,您可以通过现有合约来执行此操作。

那么,Uniswap 是如何部署智能合约的呢?

通过使用操作码CREATE2

bytes memory bytecode = type(UniswapV2Pair).creationCode;type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
    pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}

在第一行,我们得到创建字节码UniswapV2Pair

下一行创建了salt一个字节序列,用于确定性地生成新合约的地址。

最后一行是我们调用以使用+create2确定性地创建新地址的地方。部署。bytecode``salt``UniswapV2Pair

并得到对地址,我们可以看到这是createPair()函数的返回值

function createPair(
  address tokenA, address tokenA, 
  address tokenB
) external returns (address pair)

当提供的标记不是现有的对_addLiquidity()时,它在内部函数中使用。

所以,这就是关于 Uniswap 代码的全部内容。

现在,为了看到我们测试的所有内容,我可以推荐您查看 Smart Contract Programmer 在他的defi-by-example 内容中实现的代码,他已经在视频中进行了解释

在这里你可以看到我们可以增加流动性的方式

function addLiquidity(
  address _tokenA,
  address _tokenB,
  uint _amountA,
  uint _amountB
) external {
  IERC20(_tokenA).transferFrom(msg.sender, address(this), _amountA);
  IERC20(_tokenB).transferFrom(msg.sender, address(this), _amountB);

  IERC20(_tokenA).approve(ROUTER, _amountA);
  IERC20(_tokenB).approve(ROUTER, _amountB);

  (uint amountA, uint amountB, uint liquidity) =
    IUniswapV2Router(ROUTER).addLiquidity(
      _tokenA,
      _tokenB,
      _amountA,
      _amountB,
      1,
      1,
      address(this),
      block.timestamp
    );

  emit Log("amountA", amountA);
  emit Log("amountB", amountB);
  emit Log("liquidity", liquidity);
}

以及我们必须如何考虑消除流动性

function removeLiquidity(address _tokenA, address _tokenB) external {
  address pair = IUniswapV2Factory(FACTORY).getPair(_tokenA, _tokenB);

  uint liquidity = IERC20(pair).balanceOf(address(this));
  IERC20(pair).approve(ROUTER, liquidity);

  (uint amountA, uint amountB) =
    IUniswapV2Router(ROUTER).removeLiquidity(
      _tokenA,
      _tokenB,
      liquidity,
      1,
      1,
      address(this),
      block.timestamp
    );

  emit Log("amountA", amountA);
  emit Log("amountB", amountB);
}

通过Github 获取更多区块链学习资料!

https://github.com/Manuel-yang/BlockChainSelfLearning

标签:tokenA,代币,amountB,V2,uint,address,Uniswap,DeFi,合约
From: https://www.cnblogs.com/YanAemons/p/17343584.html

相关文章

  • AP8854DC-DC降压型电源管理IC 12-80V 7V2.5A 电动车暖手套 方案
    AP8854一款宽电压范围降压型DC-D电源管理芯片,内部集成使能开关控制、基准电源、误差放大器、过热保护、限流保护、短路保护等功能,非常适合宽电压输入降压使用。AP8854带使能控制,可以大大节省外围器件,更加适合电池场合使用,具有很高的方案性价比。可内置可外置应用原理图如......
  • codeforces #864 div2 B
    GCDPartition这道题首先要解决一个问题,要把区间分成几块,可以证明分成两块是更优首先我们假设把区间分成了m(>=2)块b1,b2,b3,...,bm,则答案是gcd(b1,b2,b3,...,bm),则b1,b2是gcd(b1,b2,b3,...,bm)的倍数,那么b1+b2也是gcd(b1,b2,b3,...,bm)的倍数,所以gcd(b1,b2,......
  • vue3中使用defineExpose报TS-2339
    title:"vue3+ts使用defineExpose报TS:2339"date:2022-12-2919:32:44tags:["Vue","踩坑记录"]categories:["前端篇"]开头先把错误贴上src/hooks/usePageSearch.ts:9:27TS2339:Property'getPageData'doesnotexistontype&#......
  • 解决http下navigator.clipboard为undefined的问题
    clipboard只有在安全域名下才可以访问(https、localhost),而http域名下只能得到undefined。例如现在想要实现点击"分享"按钮,将当前页面的url复制到剪贴板:constclipboard=navigator.clipboardif(clipboard){clipboard.writeText(window.location.href)}在本......
  • import cv2
     pipinstallopencv_python-ihttp://pypi.douban.com/simple--trusted-hostpypi.douban.com ......
  • 'props' is not defined.
    这个错误表明在组件中,变量props没有被定义。通常情况下,我们需要在组件选项中指定props,并传递它们给setup()函数:'props'isnotdefined.exportdefaultdefineComponent({name:"message-out",props:{image:String,time:Number,text:String,......
  • SelfDefinedDataset显示没有属性get_datasets
    get_datasets是一个PyTorchLightning框架中的方法,用于返回数据加载器中包含的训练、验证和测试数据集。如果你的自定义数据集类没有该方法,则会出现AttributeError:'YourDataset'objecthasnoattribute'get_datasets'错误。要解决这个问题,你需要在自定义数据集类中实现g......
  • 关于项目组件打包时遇到 `Uncaught TypeError: Cannot read property 'toLowerCase' o
    在全局注册组件时采用遍历组件池的方法时 每个组件都应该具备name属性 否则会出现UncaughtTypeError:Cannotreadproperty'toLowerCase'ofundefined  ......
  • 边缘计算与V2X算力网络
    一、引言如今汽车行业的热点研究方向便是自动驾驶,根据我国智能网联汽车标准体系对于汽车驾驶自动化分级,目前大多数的智能网联汽车都处于L2阶段,即可以完成制动、加速或转向等驾驶功能,实际运用即为车道偏离修正或自适应巡航,但仍属于辅助驾驶的范畴,只有当进入L3级别,驾驶员无需手脚待......
  • Solon v2.2.12 发布,Java 应用开发框架
    Solon是一个高效的Java应用开发框架:更快、更小、更简单。它不是Spring、没有使用Servlet、JavaEE接口,是一个有自己接口标准的开放生态:150多个生态插件,可以满足各种场景开发大量的国产框架适配,可以为应用软件国产化提供更好支持,助力信创建设相对于SpringBoot和Sprin......