Uniswap V1 合约源码之保姆级解析

本文对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))


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值