本文对UniswapV1的exchange合约进行分析,此版本的编程语言为Vyper,与solidity,python比较相似。
该合约主要分成流动性增减、价格查询、交易兑换、基本ERC20代币功能四部分
**本解析建立在阅读过UniswapV1白皮书的基础之上[UniswapV1_WhitePaper](https://hackmd.io/@HaydenAdams/HJ9jLsfTz)**
1、流动性增减
1.1 流动性添加
# @dev min_liquidity does nothing when total UNI supply is 0. 用户期望的LP代币数量。如果最终产生的LP代币数量过少,则交易会回滚避免损失。(LP:流动性代币)
# @param min_liquidity Minimum number of UNI sender will mint if total UNI supply is greater than 0.
# @param max_tokens Maximum number of tokens deposited. Deposits max amount if total UNI supply is 0. 用户愿意提供的最大代币量。如果计算得出的代币数量大于这个参数,代表用户不愿意提供更多代币,交易也会回滚。
def addLiquidity(min_liquidity: uint256, max_tokens: uint256, deadline: timestamp) -> uint256:
assert deadline > block.timestamp and (max_tokens > 0 and msg.value > 0)
total_liquidity: uint256 = self.totalSupply
if total_liquidity > 0: # 流动性池已经建立
assert min_liquidity > 0
# 此处要获得池子中eth的存储量——因为原生代币(eth)已经转进来了,为了获得原来的eth数量,因此需要减去已经转进来的eth数
#注:原生代币与ERC20代币的转账方式有些许不同,不理解者可自行查找相关资料,或者阅读我上篇跨链相关文档
eth_reserve: uint256(wei) = self.balance - msg.value
token_reserve: uint256 = self.token.balanceOf(self) #获取池子中token的数量
# 用户应该提供的对应的token数量为:(公式其实就是等比例获取,可以看作:)token_amount/token_reserve=msg.value/eth_reserve,经过转化成为 ↓
# eth兑换对应的token数目,因为函数向下取整,因此为了交易所不亏损,+1
token_amount: uint256 = msg.value * token_reserve / eth_reserve + 1
liquidity_minted: uint256 = msg.value * total_liquidity / eth_reserve # 对应比例应该铸造的UNI代币
assert max_tokens >= token_amount and liquidity_minted >= min_liquidity # 要求被提供的代币<用户想提供的最大代币数;同时要求获得的UNI数量应该超过min_liquidity,即用户期望的最小数量
self.balances[msg.sender] += liquidity_minted # 用户添加了流动性,给其对应账户添加余额
self.totalSupply = total_liquidity + liquidity_minted # 总流动性增加UNI代币
assert self.token.transferFrom(msg.sender, self, token_amount) # transferFrom(from, to, value) 同时存ERC20跟ETH。此函数属于ERC20规范,可以自行查阅。
log.AddLiquidity(msg.sender, msg.value, token_amount)
log.Transfer(ZERO_ADDRESS, msg.sender, liquidity_minted)
return liquidity_minted
else:
assert (self.factory != ZERO_ADDRESS and self.token != ZERO_ADDRESS) and msg.value >= 1000000000 # 验证池子已经被建立且进行了基本的初始化Exchange(exchange).setup(token)
assert self.factory.getExchange(self.token) == self # 断言本代币的对应的交易所是本合约
token_amount: uint256 = max_tokens # 如果流动性为0,则存入用于预设的最大token数
initial_liquidity: uint256 = as_unitless_number(self.balance) # 等比例记录,流动性池,以及发放的代币都是等比例计算的,因此此处的initial_liquidity可以以eth为参照,也可以以token为参照,也可以eth+token总数作为参照
self.totalSupply = initial_liquidity
self.balances[msg.sender] = initial_liquidity
assert self.token.transferFrom(msg.sender, self, token_amount) # transferFrom(from, to, value) 同时存ERC20跟ETH
log.AddLiquidity(msg.sender, msg.value, token_amount)
log.Transfer(ZERO_ADDRESS, msg.sender, initial_liquidity)
return initial_liquidity
1.2 流动性移除
# @dev Burn UNI tokens to withdraw ETH and Tokens at current ratio. 销毁UNI代币提取ETH或者TOKEN
# @param amount Amount of UNI burned. amount:销毁的UNI数量
# @param min_eth Minimum ETH withdrawn. min_eth:预期最小LP获得数数
def removeLiquidity(amount: uint256, min_eth: uint256(wei), min_tokens: uint256, deadline: timestamp) -> (uint256(wei), uint256):
assert (amount > 0 and deadline > block.timestamp) and (min_eth > 0 and min_tokens > 0)
total_liquidity: uint256 = self.totalSupply # 获取该交易池的总流动性
assert total_liquidity > 0 # 总流动性>0
token_reserve: uint256 = self.token.balanceOf(self) # token余额
eth_amount: uint256(wei) = amount * self.balance / total_liquidity # UNI数量兑换等比例的eth
token_amount: uint256 = amount * token_reserve / total_liquidity # UNI数换等比例的token数
assert eth_amount >= min_eth and token_amount >= min_tokens # 兑换的数量应该大于用户最小提取期望值
self.balances[msg.sender] -= amount # 减少用户的UNI余额
self.totalSupply = total_liquidity - amount # 减小总流动性池的UNI余额
send(msg.sender, eth_amount) # 给用户转eth,因此 self.balance 也会减少
assert self.token.transfer(msg.sender, token_amount) # 池子给用户转ERC20
log.RemoveLiquidity(msg.sender, eth_amount, token_amount)
log.Transfer(msg.sender, ZERO_ADDRESS, amount)
return eth_amount, token_amount
2、价格查询
价格查询主要的两个函数:getInputPrice、getOutputPrice。功能都是查询价格,我们根据Input,Output对这两个函数进行字面意义上的解读。
①getInputPrice(input_amount, input_reserve, output_reserve) -> uint256:
input_amount:我准备往池子里添加多少流动性(即我要卖出多少token/eth,我向池子出售=我向池子里添加,所以input)
因此这个函数的返回值是:我出售input_amount数量的token/eth,我能获得多少eth/token。
②getOutputPrice(output_amount,input_reserve。output_reserve) -> uint256:
output_amount:我想要从池子中获得多少token/eth(池子向我出售,池子token/eth流出,所以output),我需要为此花费多少。类比成买水果就是,我想要买一斤苹果,我需要花费多少钱,等价过来就是,我想要购买1eth,我需要花费多少token。
因此这个函数的返回值是:我购买output_amount数量的token/eth,我应该花费多少eth/token。
2.1 getInputPrice函数
Uniswap使用的是恒定做市商,即xy=k,(x+α)(y-▲)=k。
这里x=input_reserve,α=input_amount,y=output_reserve
此函数计算方式为:(input_reserve+input_amount)(output_reserve-▲)=k -> 求▲
返回值即是▲
# 通用化公式,token的话可以直接使用,但是eth的话需要转换成wei为单位进行计算
def getInputPrice(input_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
assert input_reserve > 0 and output_reserve > 0 # 确定两种代币都得>0
input_amount_with_fee: uint256 = input_amount * 997 # 取.3%作为手续费
numerator: uint256 = input_amount_with_fee * output_reserve
denominator: uint256 = (input_reserve * 1000) + input_amount_with_fee
return numerator / denominator # 用户卖出input_amount数量应该换回多少数量的代币
2.2 getOutputPrice函数
Uniswap使用的是恒定做市商,即xy=k,(x+α)(y-▲)=k。
这里x=input_reserve,y=output_reserve,▲=output_amount
此函数计算方式为:(input_reserve+α)(output_reserve-output_amount)=k -> 求α
返回值即是α
# output_amount:我准备购买多少,通过这个函数计算出我为此需要花费多少
def getOutputPrice(output_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
assert input_reserve > 0 and output_reserve > 0
numerator: uint256 = input_reserve * output_amount * 1000
denominator: uint256 = (output_reserve - output_amount) * 997 # 经过计算 1000/997=0.3009027% 收取的手续费
return numerator / denominator + 1 # 用户买入output_amount数量后汇率将变成什么样子,用户需要将这笔钱补上,即应该花费多少数量的代币购买output_amount
3 交易兑换
交易兑换有三类:①eth->token,②token->eth,③token->token
3.1 eth->token兑换
3.1.1 ethToTokenInput
def ethToTokenInput(eth_sold: uint256(wei), min_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
assert deadline >= block.timestamp and (eth_sold > 0 and min_tokens > 0) # 简单的判断
token_reserve: uint256 = self.token.balanceOf(self) # 获取池子中token数量
# 此处self.balance - eth_sold)是因为eth是原生代币,已经转入本合约了.
# 此时eth已经转入了,因此第一个参数是售卖eth_sold数量,返回值是用户应该获得的token数量
tokens_bought: uint256 = self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance - eth_sold), token_reserve)
assert tokens_bought >= min_tokens # 判断理论上能获得的金额是否比用户预期的金额高,否则拒绝继续执行
assert self.token.transfer(recipient, tokens_bought) # 给用户转token,完成兑换
log.TokenPurchase(buyer, eth_sold, tokens_bought)
return tokens_bought
3.1.2 ethToTokenOutput
def ethToTokenOutput(tokens_bought: uint256, max_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth > 0)
token_reserve: uint256 = self.token.balanceOf(self)
eth_sold: uint256 = self.getOutputPrice(tokens_bought, as_unitless_number(self.balance - max_eth), token_reserve) #计算出购买tokens_bought数量的token需要花费的eth
eth_refund: uint256(wei) = max_eth - as_wei_value(eth_sold, 'wei') # 交易入口会将msg.value作为max_eth传入
if eth_refund > 0: # 如果传入本合约的eth过多则退回部分
send(buyer, eth_refund)
assert self.token.transfer(recipient, tokens_bought)
log.TokenPurchase(buyer, as_wei_value(eth_sold, 'wei'), tokens_bought)
return as_wei_value(eth_sold, 'wei')
3.1.3 兑换函数
3.1.1、3.1.2其实都是private函数,为内部调用函数,真正可供外部调用函数如下:
def ethToTokenSwapInput(min_tokens: uint256, deadline: timestamp) -> uint256:
return self.ethToTokenInput(msg.value, min_tokens, deadline, msg.sender, msg.sender)
def ethToTokenTransferInput(min_tokens: uint256, deadline: timestamp, recipient: address) -> uint256:
assert recipient != self and recipient != ZERO_ADDRESS # 接收者不能为本合约,也不能指向0地址
return self.ethToTokenInput(msg.value, min_tokens, deadline, msg.sender, recipient)
def ethToTokenSwapOutput(tokens_bought: uint256, deadline: timestamp) -> uint256(wei):
return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, msg.sender)
def ethToTokenTransferOutput(tokens_bought: uint256, deadline: timestamp, recipient: address) -> uint256(wei):
assert recipient != self and recipient != ZERO_ADDRESS
return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, recipient)
代币兑换主要分成Swap,Transfer两种,其中不同之处在于Swap的代币接收者是自己,而Transfer可以指定其他人作为函数接收者。
3.2 token->eth兑换
3.2.1 tokenToEthInput
# 给出想售卖的token,推算能换取的eth数量
def tokenToEthInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
assert deadline >= block.timestamp and (tokens_sold > 0 and min_eth > 0)
token_reserve: uint256 = self.token.balanceOf(self)
eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance)) # 通过函数获得应该兑换的eth数量
wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei') # as_wei_value函数是将eth转换成“wei”单位
assert wei_bought >= min_eth
send(recipient, wei_bought)
assert self.token.transferFrom(buyer, self, tokens_sold)
log.EthPurchase(buyer, tokens_sold, wei_bought)
return wei_bought
3.2.2 tokenToEthOutput
# 确定想要兑换的eth数,推算出需要的token数目
def tokenToEthOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
assert deadline >= block.timestamp and eth_bought > 0 # 常规检查
token_reserve: uint256 = self.token.balanceOf(self) #获取token数目
tokens_sold: uint256 = self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance)) # 计算获得的eth数量
# tokens sold is always > 0
assert max_tokens >= tokens_sold # 要求用户可接受的最高token花费需要比计算出来的token高,否则交易回滚
send(recipient, eth_bought)
assert self.token.transferFrom(buyer, self, tokens_sold) # 在ERC20合约中从该用户的余额中划token到本合约中,若执行失败回滚
log.EthPurchase(buyer, tokens_sold, eth_bought)
return tokens_sold
3.2.3 兑换函数
def tokenToEthSwapInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp) -> uint256(wei):
return self.tokenToEthInput(tokens_sold, min_eth, deadline, msg.sender, msg.sender)
def tokenToEthTransferInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, recipient: address) -> uint256(wei):
assert recipient != self and recipient != ZERO_ADDRESS
return self.tokenToEthInput(tokens_sold, min_eth, deadline, msg.sender, recipient)
def tokenToEthSwapOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp) -> uint256:
return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, msg.sender)
def tokenToEthTransferOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, recipient: address) -> uint256:
assert recipient != self and recipient != ZERO_ADDRESS
return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, recipient)
3.3 token->token兑换
3.3.1 tokenToTokenInput
思路: token1->eth->token2
def tokenToTokenInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
assert (deadline >= block.timestamp and tokens_sold > 0) and (min_tokens_bought > 0 and min_eth_bought > 0)
assert exchange_addr != self and exchange_addr != ZERO_ADDRESS # 判断目标token合约地址是否合法
token_reserve: uint256 = self.token.balanceOf(self) # 代币池
eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance)) # token1可以兑换的eth数量
wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')
assert wei_bought >= min_eth_bought # 判断可兑换的eth数量大于用户要求的min_eth_bought
assert self.token.transferFrom(buyer, self, tokens_sold) # 将用户的token1转移到本合约
tokens_bought: uint256 = Exchange(exchange_addr).ethToTokenTransferInput(min_tokens_bought, deadline, recipient, value=wei_bought) # value指定了此次转入的eth数量,函数通过eth数量计算对应的token2数量,并实现token2的转账(转到用户地址上)——虽然都是UNIswap合约,但也需要真金白银的转才能确保安全性与平衡性
log.EthPurchase(buyer, tokens_sold, wei_bought)
return tokens_bought # 返回获得多少token2
3.3.2 tokenToTokenOutput
思路: token2->eth->token1
def tokenToTokenOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth_sold > 0)
assert exchange_addr != self and exchange_addr != ZERO_ADDRESS# 判断目标token合约地址是否合法
eth_bought: uint256(wei) = Exchange(exchange_addr).getEthToTokenOutputPrice(tokens_bought)# 思路:计算在目标token2的兑换池子中,获得预期token2数需要花费的eth数量
token_reserve: uint256 = self.token.balanceOf(self)
tokens_sold: uint256 = self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance)) # 通过eth_bought计算在本池子中预期花费多少token1进行购买
# tokens sold is always > 0
assert max_tokens_sold >= tokens_sold and max_eth_sold >= eth_bought # 计算兑换成本/回报是否符合用户预期
assert self.token.transferFrom(buyer, self, tokens_sold) # 将token从用户转到本合约中
eth_sold: uint256(wei) = Exchange(exchange_addr).ethToTokenTransferOutput(tokens_bought, deadline, recipient, value=eth_bought) # 在本合约中将token1兑换成eth后,再将eth转到目标合约中,在目标合约中进行token2兑换并转到用户地址上
log.EthPurchase(buyer, tokens_sold, eth_bought)
return tokens_sold # 返回应该花费多少token1
3.3.3 兑换合约
def tokenToTokenSwapInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, token_addr: address) -> uint256:
exchange_addr: address = self.factory.getExchange(token_addr)
return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, msg.sender, exchange_addr)
def tokenToTokenTransferInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, recipient: address, token_addr: address) -> uint256:
exchange_addr: address = self.factory.getExchange(token_addr)
return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, recipient, exchange_addr)
def tokenToTokenSwapOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, token_addr: address) -> uint256:
exchange_addr: address = self.factory.getExchange(token_addr)
return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, msg.sender, exchange_addr)
def tokenToTokenTransferOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, recipient: address, token_addr: address) -> uint256:
exchange_addr: address = self.factory.getExchange(token_addr)
return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, recipient, exchange_addr)
4 基本功能
此部分是基于上述代码的简单扩展,查询功能,了解即可
def getEthToTokenInputPrice(eth_sold: uint256(wei)) -> uint256:
assert eth_sold > 0
token_reserve: uint256 = self.token.balanceOf(self)
return self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance), token_reserve)
def getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei):
assert tokens_bought > 0
token_reserve: uint256 = self.token.balanceOf(self)
eth_sold: uint256 = self.getOutputPrice(tokens_bought, as_unitless_number(self.balance), token_reserve)
return as_wei_value(eth_sold, 'wei')
def getTokenToEthInputPrice(tokens_sold: uint256) -> uint256(wei):
assert tokens_sold > 0
token_reserve: uint256 = self.token.balanceOf(self)
eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
return as_wei_value(eth_bought, 'wei')
def getTokenToEthOutputPrice(eth_bought: uint256(wei)) -> uint256:
assert eth_bought > 0
token_reserve: uint256 = self.token.balanceOf(self)
return self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))