uniswap v1原理
简介:
Uniswap 本质上是一个自动的交易所,能够自动地和用户交互并兑换ETH和ERC-20代币,兑换比例的确定(即价格)采用恒定乘积自动做市系统(constant-product automated market maker),也就是说,交易所资金池内的ETH和ERC-20代币数量的乘积总体是恒定的。
DeFi领域最具影响力的创新之一是自动化做市商(AMM), 同时AMM也是去中心化交易所(DEX)最为关键的技术之一。Uniswap爆发式增长,迅速成长为DeFi龙头,v1版本作为一个概念验证版本,代码也最简单,最适合入门,下面介绍v1
自动化做市商
流动性提供者
通过智能合约实现市商功能,流动性提供者将持有的资产存入智能合约,合约按照预先设定的规则与买卖双方完成交易,并将交易手续费支付给流动性提供者
任何人都可以在uniswap上创建流动池,一个流动池由一个代币对构成。
假如你有一个项目发行了R代币,你就可以创建ETH/R的TokenPair为你的代币提供流动性。价格就是根据你提供两种代币的数量比值确定的。
如果你提供10个ETH,1000个R,那么中间价就是1R=0.01ETH.
但是价格不是一成不变的,他是需要反映供需关系的。当R增加时,他的价格就会降低。
怎样计算的呢?
恒定乘积公式: x × y=k (x和y分别是两种代币的储备量,交易时保持k不变。)
如果换取2个ETH:
也就是说需要存入250个R。
再看下面三个公式:
中间价和执行价是不一样的,称作滑点。交易前后中间价变高了,这是反映了市场的供需关系。并且根据曲线看是不会出现流动性枯竭的,两种代币的储备是不会变成0的,随着储备量减少,价格会急剧上升。
除了流动性提供者(也就是上面那种情况),还有流动性添加者可以添加流动性
流动性添加者
流动性添加者,也称为做市商。以R-ETH的池子为例,做市商往池子里添加这两种币种,并获得LP Token(做市凭证)。当他们取消做市的时候,把LP Token换成DAI和ETH,并同时获得做市期间获得的交易手续费。
在做市商提供了流动性以后,用户就可以和这个池子进行交易了,可以选择把DAI换成ETH,也可以把ETH换成DAI。交易的过程中,需要给流动性提供者支付手续费,目前是收取支付币种的0.3%。
Uniswap V1存在的问题:
1.不能直接创建两种ERC20代币的流动性池,只能创建ETH和ERC20代币的流动性池.这样两种ERC20代币就不能直接兑换,需要通过ETH转换。
2.在实际的运行过程中,受制于以太坊吞吐量和速度的问题,Uniswap也遭遇过价格操纵的情况。
Uniswap V1源码解析
代码仓库:bengda233/uniswap-v1-contracts: 🐍Uniswap V1 smart contracts (github.com)
Uniswap V1是用Vyper语言进行开发的,Vyper是一种语法和Python非常接近的语言。
V1的体系分为两个部分:
Exchange: 用于进行ETH和ERC-20代币之间的兑换。
Factory:用于创建和记录所有的Exchange,也用于查询代币对应的Exchange。
Exchange
exchange里所有方法的合集:
setup(token_addr: address)
构造函数,但是目前部署不支持使用,因为exchange合约在factory里构造addLiquidity(min_liquidity: uint256, max_tokens: uint256, deadline: timestamp) -> uint256
向资金池中添加流动性。
流程:
1.用户调用该方法并发送一定量的ETH2.Uniswap通过比较发送的ETH量和池子中的ETH数量,计算用户需要发送的代币数量,并将该数量从用户转给自己(因为Uniswap Exchange要求用户按照当前资金池中的ETH和代币的比例添加流动性)
需要存入的token数量= 存入的ETH数量 * 当前池子总token数量 / 当前池子中ETH数量(未更新) + 1
- 向用户发放LP(Liquidity Pool)代币,其数量为AmountLPToken(证明用户确实提供了流动性及用户流动性所占的份额)
用户拿到的LP数量 = 存入的ETH数量 * 总LP数量 / 当前池子中ETH数量(未更新)
由于Uniswap去中心化的特性,添加流动性的交易发出时和确认时流动性池的兑换比例(或者说价格)可能不同。为了避免这个问题给用户造成的损失,addLiquidity
函数提供了三个参数进行控制:
min_liquidity:用户期望的LP代币数量。如果最终产生的LP代币数量过少,则交易会回滚避免损失。
max_tokens:用户想要提供的最大代币量。如果计算得出的代币数量大于这个参数,代表用户不愿意提供更多代币,交易也会回滚。
deadline:时限。如果交易确认的区块时间大于deadline,也会回滚。
removeLiquidity(amount: uint256, min_eth: uint256(wei), min_tokens: uint256, deadline: timestamp) -> (uint256(wei), uint256):
取回流动性
流程:
1.用户输入想要取出的LP数量2.uniswap计算返回的ETH数量和token数量 返回ETH数量= 想要取出的LP数量* 当前池里ETH数量 / 总LP数量
返回token数量 = 想要取出的LP数量* 当前池里token数量 / 总LP数量3.检查是否大于用户的期望值,是就返给用户,否就回滚
getInputPrice(input_amount: uint256, input_reserve: uint256, output_reserve: uint256)
精确指定换入(input)值,确定池子中输入单位和输出单位存量时,精确的输入数量能换出输出数量。
输入的0.3%作为交易费用,剩下的进入池子。以此来算出能够兑换的Output.
getOutputPrice(output_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
确定输入种类、输出种类以及输出金额。计算方式为:
uniswap提供的四个价格查询的函数:
getEthToTokenInputPrice(eth_sold: uint256(wei)) -> uint256:
输入要卖出的ETH数量,返回能得到的token数量getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei):
输入要买的token数量,返回需要给出的ETH数量getTokenToEthInputPrice(tokens_sold: uint256) -> uint256(wei):
输入要卖的token数量,返回得到的token数量getTokenToEthOutputPrice(eth_bought: uint256(wei)) -> uint256
输入得到的ETH数量,返回需要的token数量
Eth与代币间的互换:
来看内部实现的四个函数
ethToTokenInput
:通过精确的ETH输入量eth_sold 计算价格并交换代币。通过getInputPrice计算输出的代币数量。同样包含了min_tokens最小代币输出量和deadline的时间限制。
ethToTokenOutput
:通过精确的代币输出量tokens_bought 计算价格并交换代币。通过getOutputPrice计算输入的ETH数量,并可能在ETH需求量小于用户发送量时产生Refund。同样包含了min_tokens最小代币输出量和deadline的时间限制。
tokenToEthInput
:与上同理
tokenToEthOutput
:与上同理
用ETH以兑换代币的四个方法
- ethToTokenSwapInput
- ethToTokenTransferInput
- ethToTokenOutput
- ethToTokenSwapOutput
swap与transfer的区别是:swap的接收者为调用者自己,而transfer的调用者可以任意指定。
用token对换ETH的四个方法
- tokenToEthSwapInput
- tokenToEthTransferInput
- tokenToEthSwapOutput
- tokenToEthTransferOutput
代币之间互换
-
tokenToTokenInput
输入卖出的token数量,要买入另外的token的exchange合约地址和最小预期值 得到能买入的数量。
先算出能够买入的ETH数量,再用这个ETH去另外的token的exchange合约买token。 -
tokenToTokenOutput
输入想要买入的token数量,要买入另外的token的exchange合约地址和最小预期值 得到需要卖出的数量。 先算出需要的ETH数量,再根据ETH数量返回需要的token数量。
用token对换token的四个方法
- tokenToTokenSwapInput
- tokenToTokenTransferInput
- tokenToTokenSwapOutput
- tokenToTokenTransferOutput
- tokenToExchangeSwapInput
- tokenToExchangeTransferInput
- tokenToExchangeSwapOutput
- tokenToExchangeTransferOutput
上面四个和下面四个的区别在于上面的是输入需要对换token的地址,下面是输入对换token的exchange合约地址
Factory
Factory里所有方法的合集:
initializeFactory(template: address):
初始化Factory,只有创建时被调用。template是链上部署的合约,用于作为后续创建的Exchange的模板。
createExchange(token: address) -> address:
根据template模板和token地址创建一个新的Exchange,并返回合约地址。随后调用新创建的Exchange的setup函数设置代币地址,并将新创建的Exchange记录在合约内。
注意:在做验证的过程中,函数约束每一个代币只能对应一个Exchange,这是为了约束某个代币的所有流动性都划分在一个池子中,增加池子中对应的存储量,降低交易的滑点。
参考:
DeFi | Uniswap V1原理解析 - 知乎 (zhihu.com)
DeFi发展史:Uniswap V1 源码阅读和分析 | Hexo (iondex.github.io)