02-UniswapV1-源码篇

文章目录

UniswapV1源码地址 https://github.com/Uniswap/v1-contracts,核心合约只有2个:交易所合约和工厂合约。

一、 交易所合约(uniswap_exchange)

主要分为以下几个模块:

1. 状态变量层

name: public(bytes32) // 代币名称(LP代币名称,如Uniswap V1 ETH-XXX LP)
symbol: public(bytes32) // 代币符号(LP代币符号,如UNI-V1-LP)
decimals: public(uint256) // 代币小数位数(LP代币通常为18)
totalSupply: public(uint256) // LP代币总供应量(随流动性增减动态变化)
balances: uint256[address] // 地址对应的LP代币余额
allowances: (uint256[address])[address] // 授权方-被授权方的LP代币转账额度
token: address(ERC20) // Uniswap V1交易对对应的ERC20代币地址
factory: Factory // Uniswap V1工厂合约引用

2. 公开价格查询层(外层查询 → 底层定价)

(1)用ETH兑换代币

getEthToTokenInputPricegetInputPrice(底层定价) // 输入ETH数量,返回可以兑换多少代币。
getEthToTokenOutputPricegetOutputPrice(底层定价) // 输入指定要兑换的代币数量,返回需要多少ETH。

(2) 用代币兑换ETH

getTokenToEthInputPricegetInputPrice(底层定价) // 输入代币数量,返回可以兑换多少ETH
getTokenToEthOutputPricegetOutputPrice(底层定价)// 输入指定要兑换的ETH数量,返回需要多少代币?

函数名前缀 EthToToken / TokenToEth :明确交易方向
函数名后缀 Input / Output:明确 "输入 / 输出" 的计价视角。InputPrice:已知输入金额,计算能得到的输出金额,OutputPrice:已知想要的输出金额,计算需要输入的金额。

3. 流动性操作层(外层操作 → 无内部依赖,核心逻辑独立)

addLiquidity(添加流动性)
RemoveLiquidity(移除流动性)

4. ETH 换 Token 兑换层(外层调用 → 内部核心逻辑)

(1) 默认兑换

__default__(默认兑换)→ ethToTokenInput(内部核心)

对应合约的回退函数(fallback function),当用户直接向交易对合约转账ETH(未显式调用任何函数)时,会自动触发该逻辑,本质是调用ethToTokenInput完成ETH→代币兑换,兑换后的代币默认转给转账者(msg.sender),无需手动指定参数(使用默认滑点/截止时间)。

(2)按ETH输入量兑换(已知要花多少ETH,算能换多少代币)

  • ethToTokenSwapInputethToTokenInput(内部核心)
    显式调用的ETH兑换代币函数:用户传入要花费的ETH数量(通过msg.value传递),函数根据恒定乘积公式计算可兑换的代币数量,兑换后的代币直接转给调用者本人(msg.sender),需传入minTokens(最小接收代币数,防滑点)、deadline(交易截止时间,防MEV)参数做安全保护。
  • ethToTokenTransferInputethToTokenInput(内部核心)
    显式调用的ETH代兑代币函数:核心逻辑与ethToTokenSwapInput完全一致(按ETH输入量算代币输出),仅多一个address recipient参数------用户用自己的ETH兑换代币后,代币会直接转给指定的recipient地址(第三方),而非调用者本人,适用于代他人兑换、跨合约转账等场景。

(3)按代币输出量兑换(已知要换多少代币,算需花多少ETH)

  • ethToTokenSwapOutputethToTokenOutput(内部核心)
    显式调用的目标代币兑换函数:用户传入想要获取的代币数量(目标输出量),函数反向计算需要支付的ETH数量并自动扣减,兑换后的代币转给调用者本人(msg.sender),需传入maxEth(最大支付ETH数,防滑点)、deadline参数,确保用户不会因滑点多付ETH。
  • ethToTokenTransferOutputethToTokenOutput(内部核心)
    显式调用的目标代币代兑函数:核心逻辑与ethToTokenSwapOutput一致(按目标代币量算ETH输入),多一个address recipient参数------兑换后的代币直接转给指定的recipient 。

5. Token 换 ETH 兑换层(外层调用 → 内部核心逻辑)

上边是用ETH换代币,这个是用代币换ETH,逻辑和上边差不多,不解释了

  • tokenToEthSwapInputtokenToEthInput(内部核心)
  • tokenToEthTransferInputtokenToEthInput(内部核心)
  • tokenToEthSwapOutputtokenToEthOutput(内部核心)
  • tokenToEthTransferOutputtokenToEthOutput(内部核心)

6. 跨代币/跨交易所兑换层(外层扩展 → 基础兑换逻辑)

(1) 用代币A兑换代币B,按代币A输入量计算

  • tokenToTokenSwapInputtokenToTokenInputethToTokenTransferInputethToTokenInput

    先调用本交易所tokenToTokenSwapInputtokenToTokenInput,计算出代币A在本交易所中值多少ETH,再调用另外一个交易所ethToTokenTransferInputethToTokenInput ,将这些ETH换成代币B。兑换后的代币B转给调用者本人(msg.sender)

  • tokenToTokenTransferInputtokenToTokenInputethToTokenTransferInputethToTokenInput

    和上边逻辑差不多,多一个address recipient参数,兑换后的代币B直接转给指定的recipient。

(2)用代币A兑换代币B,按代币B输出量计算

tokenToTokenSwapOutputtokenToTokenOutputethToTokenTransferOutputethToTokenOutput
tokenToTokenTransferOutputtokenToTokenOutputethToTokenTransferOutputethToTokenOutput

(3)直接传交易所地址

上边的几个函数,都有参数token_addr: address,指的是目标代币地址。然后通过工厂合约查出目标代币对应的交易所地址。适合普通用户(只需知道代币地址)

下边的这几个函数逻辑和上边是一样的。只是把参数token_addr换成exchange_addr,exchange_addr就是目标代币的交易所地址,省了一步转换。适合进阶场景已知交易对地址,省 gas。
tokenToExchangeSwapInputtokenToTokenInputethToTokenTransferInputethToTokenInput
tokenToExchangeTransferInputtokenToTokenInputethToTokenTransferInputethToTokenInput
tokenToExchangeSwapOutputtokenToTokenOutputethToTokenTransferOutputethToTokenOutput
tokenToExchangeTransferOutputtokenToTokenOutputethToTokenTransferOutputethToTokenOutput

7. LP 代币 ERC20 接口层

就是ERC20合约的几个方法

  • balanceOf
  • transfer
  • transferFrom
  • approve
  • allowance

7.源码

源码找大模型注释了下,自己又补充了下,已经比较详细了。

vyper 复制代码
# Uniswap V1 交易所核心合约
# 功能:实现 ETH 与 ERC20 代币的兑换、流动性添加/移除、价格计算

# 工厂合约接口:用于获取代币对应的交易所地址
contract Factory():
    def getExchange(token_addr: address) -> address: constant

# 交易所合约核心接口
contract Exchange():
    # 查询「指定输出Token数量」所需的ETH输入量
    def getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei): constant
    # ETH兑换Token并转账给指定接收者(指定最小输出Token量)
    def ethToTokenTransferInput(min_tokens: uint256, deadline: timestamp, recipient: address) -> uint256: modifying
    # ETH兑换指定数量Token并转账给接收者(指定最大ETH输入量)
    def ethToTokenTransferOutput(tokens_bought: uint256, deadline: timestamp, recipient: address) -> uint256(wei): modifying

# 事件定义:记录核心操作,供前端/链下解析
TokenPurchase: event({buyer: indexed(address), eth_sold: indexed(uint256(wei)), tokens_bought: indexed(uint256)})  # ETH买Token
EthPurchase: event({buyer: indexed(address), tokens_sold: indexed(uint256), eth_bought: indexed(uint256(wei))})    # Token买ETH
AddLiquidity: event({provider: indexed(address), eth_amount: indexed(uint256(wei)), token_amount: indexed(uint256)}) # 添加流动性
RemoveLiquidity: event({provider: indexed(address), eth_amount: indexed(uint256(wei)), token_amount: indexed(uint256)}) # 移除流动性
Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256}) # LP代币转账
Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256}) # LP代币授权

# 合约状态变量(LP代币属性 + 核心关联地址)
name: public(bytes32)                             # LP代币名称:Uniswap V1
symbol: public(bytes32)                           # LP代币符号:UNI-V1
decimals: public(uint256)                         # LP代币小数位:18(与ETH一致)
totalSupply: public(uint256)                      # LP代币总供应量
balances: uint256[address]                        # 地址对应的LP代币余额
allowances: (uint256[address])[address]           # LP代币授权:owner -> spender 的授权额度
token: address(ERC20)                             # 当前交易所交易的ERC20代币地址
factory: Factory                                  # 创建当前交易所的工厂合约地址

# 合约初始化函数(由工厂合约调用,仅执行一次)
@public
def setup(token_addr: address):
    # 校验:工厂/代币地址未初始化 + 传入代币地址非空
    assert (self.factory == ZERO_ADDRESS and self.token == ZERO_ADDRESS) and token_addr != ZERO_ADDRESS
    self.factory = msg.sender  # 记录工厂合约地址(调用者)
    self.token = token_addr    # 绑定当前交易所的交易代币,比如DAI合约地址
    self.name = 0x556e697377617020563100000000000000000000000000000000000000000000  # 赋值"Uniswap V1"
    self.symbol = 0x554e492d56310000000000000000000000000000000000000000000000000000  # 赋值"UNI-V1"
    self.decimals = 18  # LP代币小数位固定为18

# 添加流动性(铸造LP代币)
# 参数说明:
# min_liquidity: 最小铸造LP数量(防止滑点)
# max_tokens: 最大愿意存入的代币数量
# deadline: 交易有效截止时间(防止长时间未确认导致亏损)
# 返回值:铸造的LP代币数量
@public
@payable  # 接收ETH
def addLiquidity(min_liquidity: uint256, max_tokens: uint256, deadline: timestamp) -> uint256:
    # 基础校验:截止时间未过期 + 代币/ETH输入量>0
    assert deadline > block.timestamp and (max_tokens > 0 and msg.value > 0)
    total_liquidity: uint256 = self.totalSupply  # 当前LP总供应量

    # 场景1:非首次添加流动性(已有LP代币流通)
    if total_liquidity > 0:
        assert min_liquidity > 0  # 非首次必须指定最小LP数量
        eth_reserve: uint256(wei) = self.balance - msg.value  # 计算添加前的ETH储备(当前余额 - 本次存入ETH)
        token_reserve: uint256 = self.token.balanceOf(self)   # 计算添加前的代币储备
        # 按ETH存入量计算需存入的代币数量(+1 防止精度丢失)
        token_amount: uint256 = msg.value * token_reserve / eth_reserve + 1
        # 计算铸造的LP数量:按ETH占比分配(核心规则:LP占比 = ETH占比)
        liquidity_minted: uint256 = msg.value * total_liquidity / eth_reserve
        
        # 校验:存入代币量不超过最大值 + 铸造LP量不低于最小值
        assert max_tokens >= token_amount and liquidity_minted >= min_liquidity
        # 更新LP余额:给用户增加LP代币
        self.balances[msg.sender] += liquidity_minted
        # 更新LP总供应量
        self.totalSupply = total_liquidity + liquidity_minted
        # 从用户钱包划转代币到交易所(需用户提前授权)
        assert self.token.transferFrom(msg.sender, self, token_amount)
        
        # 记录事件
        log.AddLiquidity(msg.sender, msg.value, token_amount)
        log.Transfer(ZERO_ADDRESS, msg.sender, liquidity_minted)  # 模拟从0地址铸造LP
        return liquidity_minted

    # 场景2:首次添加流动性(初始化池子)
    else:
        # 校验:工厂/代币地址已初始化 + 存入ETH≥1e9 wei(防止小额攻击)
        assert (self.factory != ZERO_ADDRESS and self.token != ZERO_ADDRESS) and msg.value >= 1000000000
        # 校验:当前交易所是工厂合约为该代币创建的官方交易所
        assert self.factory.getExchange(self.token) == self
        token_amount: uint256 = max_tokens  # 首次添加直接存入max_tokens数量的代币
        initial_liquidity: uint256 = as_unitless_number(self.balance)  # 初始LP数量 = 交易所ETH余额(即本次存入的ETH)
        # 初始化LP总供应量和用户余额
        self.totalSupply = initial_liquidity
        self.balances[msg.sender] = initial_liquidity
        # 从用户钱包划转代币到交易所
        assert self.token.transferFrom(msg.sender, self, token_amount)
        
        # 记录事件
        log.AddLiquidity(msg.sender, msg.value, token_amount)
        log.Transfer(ZERO_ADDRESS, msg.sender, initial_liquidity)
        return initial_liquidity

# 移除流动性(销毁LP代币,提取ETH+代币)
# 参数说明:
# amount: 销毁的LP代币数量
# min_eth: 最小提取ETH数量(防止滑点)
# min_tokens: 最小提取代币数量
# deadline: 交易有效截止时间
# 返回值:提取的ETH数量、代币数量
@public
def removeLiquidity(amount: uint256, min_eth: uint256(wei), min_tokens: uint256, deadline: timestamp) -> (uint256(wei), uint256):
    # 基础校验:LP销毁量>0 + 截止时间未过期 + 最小提取量>0
    assert (amount > 0 and deadline > block.timestamp) and (min_eth > 0 and min_tokens > 0)
    total_liquidity: uint256 = self.totalSupply  # 当前LP总供应量
    assert total_liquidity > 0  # 必须有LP代币流通才能移除
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    
    # 按LP占比计算可提取的ETH和代币数量
    eth_amount: uint256(wei) = amount * self.balance / total_liquidity
    token_amount: uint256 = amount * token_reserve / total_liquidity
    
    # 校验:提取量不低于最小值(防止滑点亏损)
    assert eth_amount >= min_eth and token_amount >= min_tokens
    # 更新LP余额:扣减用户的LP代币(销毁)
    self.balances[msg.sender] -= amount
    # 更新LP总供应量
    self.totalSupply = total_liquidity - amount
    # 向用户转账ETH
    send(msg.sender, eth_amount)
    # 向用户转账代币
    assert self.token.transfer(msg.sender, token_amount)
    
    # 记录事件
    log.RemoveLiquidity(msg.sender, eth_amount, token_amount)
    log.Transfer(msg.sender, ZERO_ADDRESS, amount)  # 模拟销毁LP(转至0地址)
    return eth_amount, token_amount

# 核心价格计算函数(输入金额→输出金额,含0.3%手续费)
# 参数说明:
# input_amount: 卖出的资产数量(ETH/代币)
# input_reserve: 池子中输入资产的储备量
# output_reserve: 池子中输出资产的储备量
# 返回值:可买入的输出资产数量
@private
@constant
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  # 扣除0.3%手续费(仅99.7%参与兑换)
    numerator: uint256 = input_amount_with_fee * output_reserve  # 分子:手续费后输入量 × 输出储备
    denominator: uint256 = (input_reserve * 1000) + input_amount_with_fee  # 分母:(输入储备×1000) + 手续费后输入量
    return numerator / denominator  # 恒定乘积公式计算输出量

# 核心价格计算函数(输出金额→输入金额,含0.3%手续费)
# 参数说明:
# output_amount: 想要买入的资产数量(ETH/代币)
# input_reserve: 池子中输入资产的储备量
# output_reserve: 池子中输出资产的储备量
# 返回值:需要卖出的输入资产数量
@private
@constant
def getOutputPrice(output_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
    assert input_reserve > 0 and output_reserve > 0  # 储备量必须>0
    numerator: uint256 = input_reserve * output_amount * 1000  # 分子:输入储备 × 输出量 × 1000
    denominator: uint256 = (output_reserve - output_amount) * 997  # 分母:(输出储备-输出量) × 997
    return numerator / denominator + 1  # +1 防止精度丢失

# ETH兑换Token核心逻辑(内部函数)
# 参数说明:
# eth_sold: 卖出的ETH数量
# min_tokens: 最小接收Token数量
# deadline: 截止时间
# buyer: 交易发起者
# recipient: Token接收者
# 返回值:实际兑换的Token数量
@private
def ethToTokenInput(eth_sold: uint256(wei), min_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
    # 基础校验:截止时间未过期 + ETH/最小Token量>0
    assert deadline >= block.timestamp and (eth_sold > 0 and min_tokens > 0)
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    # 计算可兑换的Token数量(调用getInputPrice)
    tokens_bought: uint256 = self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance - eth_sold), token_reserve)
    assert tokens_bought >= min_tokens  # 校验:兑换量不低于最小值
    # 向接收者转账Token
    assert self.token.transfer(recipient, tokens_bought)
    # 记录事件
    log.TokenPurchase(buyer, eth_sold, tokens_bought)
    return tokens_bought

# 默认回退函数:直接用ETH兑换Token(极简版,无最小输出/截止时间)
@public
@payable
def __default__():
    self.ethToTokenInput(msg.value, 1, block.timestamp, msg.sender, msg.sender)

# ETH兑换Token(指定最小输出量)
# 参数:min_tokens-最小Token量,deadline-截止时间
# 返回:实际兑换的Token数量
@public
@payable
def ethToTokenSwapInput(min_tokens: uint256, deadline: timestamp) -> uint256:
    return self.ethToTokenInput(msg.value, min_tokens, deadline, msg.sender, msg.sender)

# ETH兑换Token并转账给指定接收者(指定最小输出量)
# 参数:recipient-Token接收者
# 返回:实际兑换的Token数量
@public
@payable
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)

# ETH兑换指定数量Token核心逻辑(内部函数)
# 参数说明:
# tokens_bought: 想要买入的Token数量
# max_eth: 最大愿意花费的ETH数量
# deadline: 截止时间
# buyer: 交易发起者
# recipient: Token接收者
# 返回值:实际花费的ETH数量
@private
def ethToTokenOutput(tokens_bought: uint256, max_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
    # 基础校验:截止时间未过期 + Token/最大ETH量>0
    assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth > 0)
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    # 计算需要花费的ETH数量(调用getOutputPrice)
    eth_sold: uint256 = self.getOutputPrice(tokens_bought, as_unitless_number(self.balance - max_eth), token_reserve)
    # 计算需退还的ETH(用户存入的ETH - 实际花费的ETH)
    eth_refund: uint256(wei) = max_eth - as_wei_value(eth_sold, 'wei')
    # 如有退款,转回给用户
    if eth_refund > 0:
        send(buyer, eth_refund)
    # 向接收者转账Token
    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')

# ETH兑换指定数量Token(指定最大ETH花费)
# 参数:tokens_bought-目标Token数量,deadline-截止时间
# 返回:实际花费的ETH数量
@public
@payable
def ethToTokenSwapOutput(tokens_bought: uint256, deadline: timestamp) -> uint256(wei):
    return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, msg.sender)

# ETH兑换指定数量Token并转账给接收者(指定最大ETH花费)
# 参数:recipient-Token接收者
# 返回:实际花费的ETH数量
@public
@payable
def ethToTokenTransferOutput(tokens_bought: uint256, deadline: timestamp, recipient: address) -> uint256(wei):
    assert recipient != self and recipient != ZERO_ADDRESS  # 接收者不能是交易所/0地址
    return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, recipient)

# Token兑换ETH核心逻辑(内部函数)
# 参数说明:
# tokens_sold: 卖出的Token数量
# min_eth: 最小接收ETH数量
# deadline: 截止时间
# buyer: 交易发起者
# recipient: ETH接收者
# 返回值:实际兑换的ETH数量
@private
def tokenToEthInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
    # 基础校验:截止时间未过期 + Token/最小ETH量>0
    assert deadline >= block.timestamp and (tokens_sold > 0 and min_eth > 0)
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    # 计算可兑换的ETH数量(调用getInputPrice)
    eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
    wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')  # 转换为wei单位
    assert wei_bought >= min_eth  # 校验:兑换量不低于最小值
    # 向接收者转账ETH
    send(recipient, wei_bought)
    # 从用户钱包划转Token到交易所(需提前授权)
    assert self.token.transferFrom(buyer, self, tokens_sold)
    # 记录事件
    log.EthPurchase(buyer, tokens_sold, wei_bought)
    return wei_bought

# Token兑换ETH(指定最小输出ETH量)
# 参数:tokens_sold-卖出Token数量,min_eth-最小ETH量,deadline-截止时间
# 返回:实际兑换的ETH数量
@public
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)

# Token兑换ETH并转账给指定接收者(指定最小输出ETH量)
# 参数:recipient-ETH接收者
# 返回:实际兑换的ETH数量
@public
def tokenToEthTransferInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, recipient: address) -> uint256(wei):
    assert recipient != self and recipient != ZERO_ADDRESS  # 接收者不能是交易所/0地址
    return self.tokenToEthInput(tokens_sold, min_eth, deadline, msg.sender, recipient)

# Token兑换指定数量ETH核心逻辑(内部函数)
# 参数说明:
# eth_bought: 想要买入的ETH数量
# max_tokens: 最大愿意卖出的Token数量
# deadline: 截止时间
# buyer: 交易发起者
# recipient: ETH接收者
# 返回值:实际卖出的Token数量
@private
def tokenToEthOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
    # 基础校验:截止时间未过期 + ETH量>0
    assert deadline >= block.timestamp and eth_bought > 0
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    # 计算需要卖出的Token数量(调用getOutputPrice)
    tokens_sold: uint256 = self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))
    assert max_tokens >= tokens_sold  # 校验:卖出量不超过最大值
    # 向接收者转账ETH
    send(recipient, eth_bought)
    # 从用户钱包划转Token到交易所(需提前授权)
    assert self.token.transferFrom(buyer, self, tokens_sold)
    # 记录事件
    log.EthPurchase(buyer, tokens_sold, eth_bought)
    return tokens_sold

# Token兑换指定数量ETH(指定最大Token卖出量)
# 参数:eth_bought-目标ETH数量,max_tokens-最大Token量,deadline-截止时间
# 返回:实际卖出的Token数量
@public
def tokenToEthSwapOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp) -> uint256:
    return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, msg.sender)

# Token兑换指定数量ETH并转账给接收者(指定最大Token卖出量)
# 参数:recipient-ETH接收者
# 返回:实际卖出的Token数量
@public
def tokenToEthTransferOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, recipient: address) -> uint256:
    assert recipient != self and recipient != ZERO_ADDRESS  # 接收者不能是交易所/0地址
    return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, recipient)

# Token跨代币兑换核心逻辑(内部函数:Token→ETH→目标Token)
# 参数说明:
# tokens_sold: 卖出的当前代币数量
# min_tokens_bought: 最小接收目标Token数量
# min_eth_bought: 中间兑换ETH的最小数量
# deadline: 截止时间
# buyer: 交易发起者
# recipient: 目标Token接收者
# exchange_addr: 目标Token的交易所地址
# 返回值:实际兑换的目标Token数量
@private
def tokenToTokenInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
    # 基础校验:截止时间未过期 + 输入量>0 + 最小输出量>0
    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  # 目标交易所不能是当前交易所/0地址
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    # 第一步:当前Token兑换ETH
    eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
    wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')
    assert wei_bought >= min_eth_bought  # 校验:ETH兑换量不低于最小值
    # 从用户钱包划转当前Token到交易所
    assert self.token.transferFrom(buyer, self, tokens_sold)
    # 第二步:ETH兑换目标Token(调用目标交易所的ethToTokenTransferInput)
    tokens_bought: uint256 = Exchange(exchange_addr).ethToTokenTransferInput(min_tokens_bought, deadline, recipient, value=wei_bought)
    # 记录事件
    log.EthPurchase(buyer, tokens_sold, wei_bought)
    return tokens_bought

# Token跨代币兑换(指定最小输出目标Token量)
# 参数:token_addr-目标Token地址
# 返回:实际兑换的目标Token数量
@public
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)  # 获取目标Token的交易所地址
    return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, msg.sender, exchange_addr)

# Token跨代币兑换并转账给接收者(指定最小输出目标Token量)
# 参数:recipient-目标Token接收者,token_addr-目标Token地址
# 返回:实际兑换的目标Token数量
@public
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)

# Token跨代币兑换核心逻辑(指定输出目标Token量,内部函数)
# 参数说明:
# tokens_bought: 想要买入的目标Token数量
# max_tokens_sold: 最大卖出当前Token数量
# max_eth_sold: 中间兑换ETH的最大数量
# deadline: 截止时间
# buyer: 交易发起者
# recipient: 目标Token接收者
# exchange_addr: 目标Token的交易所地址
# 返回值:实际卖出的当前Token数量
@private
def tokenToTokenOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
    # 基础校验:截止时间未过期 + 目标Token/ETH量>0
    assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth_sold > 0)
    assert exchange_addr != self and exchange_addr != ZERO_ADDRESS  # 目标交易所不能是当前交易所/0地址
    # 第一步:查询兑换目标Token所需的ETH数量
    eth_bought: uint256(wei) = Exchange(exchange_addr).getEthToTokenOutputPrice(tokens_bought)
    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))
    # 校验:卖出量不超过最大值
    assert max_tokens_sold >= tokens_sold and max_eth_sold >= eth_bought
    # 从用户钱包划转当前Token到交易所
    assert self.token.transferFrom(buyer, self, tokens_sold)
    # 第三步:ETH兑换目标Token(调用目标交易所的ethToTokenTransferOutput)
    eth_sold: uint256(wei) = Exchange(exchange_addr).ethToTokenTransferOutput(tokens_bought, deadline, recipient, value=eth_bought)
    # 记录事件
    log.EthPurchase(buyer, tokens_sold, eth_bought)
    return tokens_sold

# Token跨代币兑换(指定输出目标Token量,最大输入当前Token量)
# 参数:token_addr-目标Token地址
# 返回:实际卖出的当前Token数量
@public
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)

# Token跨代币兑换并转账给接收者(指定输出目标Token量)
# 参数:recipient-目标Token接收者,token_addr-目标Token地址
# 返回:实际卖出的当前Token数量
@public
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)

# Token跨交易所兑换(指定最小输出目标Token量)
# 参数:exchange_addr-目标Token的交易所地址(非工厂创建)
# 返回:实际兑换的目标Token数量
@public
def tokenToExchangeSwapInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, exchange_addr: address) -> uint256:
    return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, msg.sender, exchange_addr)

# Token跨交易所兑换并转账给接收者(指定最小输出目标Token量)
# 参数:recipient-目标Token接收者,exchange_addr-目标交易所地址
# 返回:实际兑换的目标Token数量
@public
def tokenToExchangeTransferInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, recipient: address, exchange_addr: address) -> uint256:
    assert recipient != self  # 接收者不能是当前交易所
    return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, recipient, exchange_addr)

# Token跨交易所兑换(指定输出目标Token量)
# 参数:exchange_addr-目标交易所地址
# 返回:实际卖出的当前Token数量
@public
def tokenToExchangeSwapOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, exchange_addr: address) -> uint256:
    return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, msg.sender, exchange_addr)

# Token跨交易所兑换并转账给接收者(指定输出目标Token量)
# 参数:recipient-目标Token接收者,exchange_addr-目标交易所地址
# 返回:实际卖出的当前Token数量
@public
def tokenToExchangeTransferOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, recipient: address, exchange_addr: address) -> uint256:
    assert recipient != self  # 接收者不能是当前交易所
    return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, recipient, exchange_addr)

# 公开价格查询:ETH兑换Token(指定ETH输入量,查可买Token量)
@public
@constant
def getEthToTokenInputPrice(eth_sold: uint256(wei)) -> uint256:
    assert eth_sold > 0  # ETH输入量>0
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    return self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance), token_reserve)

# 公开价格查询:ETH兑换Token(指定Token输出量,查需花费ETH量)
@public
@constant
def getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei):
    assert tokens_bought > 0  # Token输出量>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')

# 公开价格查询:Token兑换ETH(指定Token输入量,查可买ETH量)
@public
@constant
def getTokenToEthInputPrice(tokens_sold: uint256) -> uint256(wei):
    assert tokens_sold > 0  # Token输入量>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')

# 公开价格查询:Token兑换ETH(指定ETH输出量,查需卖出Token量)
@public
@constant
def getTokenToEthOutputPrice(eth_bought: uint256(wei)) -> uint256:
    assert eth_bought > 0  # ETH输出量>0
    token_reserve: uint256 = self.token.balanceOf(self)  # 当前代币储备
    return self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))

# 查询当前交易所绑定的代币地址
@public
@constant
def tokenAddress() -> address:
    return self.token

# 查询创建当前交易所的工厂合约地址
@public
@constant
def factoryAddress() -> address(Factory):
    return self.factory

# ====================== ERC20 标准接口(LP代币) ======================
# 查询地址的LP代币余额
@public
@constant
def balanceOf(_owner : address) -> uint256:
    return self.balances[_owner]

# 转账LP代币
@public
def transfer(_to : address, _value : uint256) -> bool:
    self.balances[msg.sender] -= _value  # 扣减发送者余额
    self.balances[_to] += _value         # 增加接收者余额
    log.Transfer(msg.sender, _to, _value)  # 记录转账事件
    return True

# 授权转账LP代币(需提前approve)
@public
def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
    self.balances[_from] -= _value              # 扣减授权者余额
    self.balances[_to] += _value                # 增加接收者余额
    self.allowances[_from][msg.sender] -= _value  # 扣减授权额度
    log.Transfer(_from, _to, _value)            # 记录转账事件
    return True

# 授权LP代币额度给spender
@public
def approve(_spender : address, _value : uint256) -> bool:
    self.allowances[msg.sender][_spender] = _value  # 设置授权额度
    log.Approval(msg.sender, _spender, _value)      # 记录授权事件
    return True

# 查询授权额度(owner给spender的LP代币授权量)
@public
@constant
def allowance(_owner : address, _spender : address) -> uint256:
    return self.allowances[_owner][_spender]

二、工厂合约

1. 介绍

Uniswap V1 引入工厂合约(Factory) 的核心目的是:标准化、自动化管理所有代币的交易所合约,解决「代币-交易所」的映射问题,降低部署成本与用户使用门槛

简单说:工厂合约是 Uniswap 生态的「交易所注册表 + 标准化部署器」,是 AMM 规模化落地的关键设计。

2.核心作用

(1)解决「代币-交易所」的唯一映射问题

  • 无工厂合约的痛点
    每发行一个 ERC20 代币,开发者需手动部署一个交易所合约,且用户无法快速找到代币对应的官方交易所地址,易被钓鱼合约欺骗;
  • 工厂合约的解决方案
    工厂合约维护一个 token_addr → exchange_addr 的全局映射表,通过 getExchange(token_addr) 函数可唯一、可信地查询代币对应的官方交易所地址,确保用户交易的是正版合约。

(2) 标准化部署交易所合约,避免重复开发

  • 无工厂合约的痛点
    不同开发者部署的交易所合约可能存在代码差异(比如手续费比例、定价逻辑),导致生态碎片化,用户需适配不同合约接口;
  • 工厂合约的解决方案
    工厂合约是所有交易所合约的「母合约」,所有代币的交易所合约都通过工厂合约统一创建 ,确保:
    ① 所有交易所合约使用完全相同的核心逻辑(定价、流动性、兑换规则);
    ② 无需重复编写/审计交易所代码,仅需审计工厂+交易所模板,降低安全风险。

(3) 降低部署成本与使用门槛

  • 部署端
    代币发行方只需调用工厂合约的「创建交易所」函数,即可一键部署专属交易所,无需手动编写/部署交易所代码;
  • 用户端
    用户无需记住每个代币的交易所地址,只需通过工厂合约的 getExchange 函数,输入代币地址就能找到对应的交易所,简化交互流程。

3. 使用流程

(1)Uniswap 团队:搭建基础框架(仅做1次)

① 部署交易所模板合约 :先写好一套标准化的 Exchange 合约代码(包含兑换、流动性核心逻辑),部署到以太坊主网得到「交易所模板地址」;

② 部署工厂合约 :部署 Factory 合约到以太坊主网,得到官方工厂地址 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95

③ 初始化工厂:调用工厂的 initializeFactory 函数,传入「交易所模板地址」,让工厂记住这个模板------后续所有新交易所都复用这套模板代码(无需重复部署代码,低成本、高统一)。

(2)代币发行方:上线代币交易所(核心操作)

① 准备:项目方已部署好自己的 ERC20 代币合约(如 USDT 地址 0xdAC17F958D2ee523a2206206994597C13D831ec7),拿到「代币地址」;

② 调用工厂创建交易所:向工厂地址 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95 发起交易,调用 createExchange 函数,传入「代币地址」;

③ 工厂自动执行(核心):

  • 校验:该代币未创建过交易所、模板地址有效;
  • 克隆模板:基于工厂绑定的交易所模板,克隆生成新的交易所合约(复用模板代码,仅创建新合约地址,不重复部署代码);
  • 绑定代币:自动调用新交易所的 setup 函数,把「代币地址」赋值给交易所的 token 变量,完成绑定(如 USDT 交易所地址 0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14 绑定 USDT 代币地址);
  • 记录映射:工厂更新「代币地址→新交易所地址」的映射关系,方便后续查询。
    ④ 结果:得到该代币专属的交易所地址(如 USDT 对应 0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14),且永久绑定。

(3)用户:使用交易所(日常操作)

① 查询交易所:调用工厂的 getExchange 函数,传入「代币地址」(如 USDT 地址),工厂返回该代币的官方交易所地址;

② 交互:用查到的交易所地址,进行 ETH/代币兑换、添加/移除流动性等操作(可在 Etherscan 验证交易所地址的 tokenAddress 字段,确认绑定的代币地址无误)。

4. 核心亮点(复用模板的价值)

(1)代码复用

所有代币的交易所都用同一套模板代码,保证逻辑完全一致(如 0.3% 手续费、恒定乘积定价规则),无需重复开发/审计;

(2)低成本部署

克隆模板仅生成新地址,比重新部署整套 Exchange 合约省gas(以太坊部署合约的gas成本与代码长度正相关);

(3)可信性

用户只需通过官方工厂地址 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95 查地址,就能确保是正版交易所(模板统一,无恶意篡改)。代币与交易所的映射关系一旦生成永久生效,一个代币仅对应一个官方交易所地址。

5. 源码

vyper 复制代码
# Uniswap V1 工厂合约(Factory)
# 核心功能:标准化部署交易所合约、维护「代币-交易所」的双向映射关系、提供全局查询能力

# 交易所合约接口:仅声明setup初始化函数(用于工厂部署后初始化交易所)
contract Exchange():
    def setup(token_addr: address): modifying  # 初始化交易所,绑定对应的代币地址

# 事件定义:记录新交易所创建,供链下/前端追踪「代币-交易所」映射关系
NewExchange: event({token: indexed(address), exchange: indexed(address)})

# 工厂合约状态变量
exchangeTemplate: public(address)          # 交易所合约模板地址(所有新交易所都基于该模板克隆)
tokenCount: public(uint256)                # 已创建交易所的代币总数(用于id_to_token的索引)
token_to_exchange: address[address]        # 代币地址 → 交易所地址 映射(核心:快速查代币对应的交易所)
exchange_to_token: address[address]        # 交易所地址 → 代币地址 映射(反向查询:查交易所绑定的代币)
id_to_token: address[uint256]              # 序号 → 代币地址 映射(按创建顺序索引代币,方便遍历)

# 工厂合约初始化函数(仅执行一次)
# 参数:template - 交易所合约模板地址(所有新交易所基于该模板克隆)
@public
def initializeFactory(template: address):
    # 校验:工厂合约未初始化(防止重复调用)
    assert self.exchangeTemplate == ZERO_ADDRESS
    # 校验:模板地址非空(必须传入合法的交易所模板)
    assert template != ZERO_ADDRESS
    # 保存交易所模板地址,后续创建交易所时克隆该模板
    self.exchangeTemplate = template

# 创建代币对应的专属交易所合约
# 参数:token - 要绑定的ERC20代币地址
# 返回值:新创建的交易所合约地址
@public
def createExchange(token: address) -> address:
    # 校验1:代币地址非空(不能为0地址)
    assert token != ZERO_ADDRESS
    # 校验2:工厂合约已初始化(已设置交易所模板)
    assert self.exchangeTemplate != ZERO_ADDRESS
    # 校验3:该代币尚未创建过交易所(一个代币仅对应一个官方交易所)
    assert self.token_to_exchange[token] == ZERO_ADDRESS

    # 核心步骤1:基于交易所模板克隆新的交易所合约(低成本部署,无需重复部署代码)
    exchange: address = create_with_code_of(self.exchangeTemplate)
    # 核心步骤2:初始化新交易所,绑定当前代币地址(调用交易所的setup函数)
    Exchange(exchange).setup(token)

    # 核心步骤3:更新映射关系,维护「代币-交易所」的双向绑定
    self.token_to_exchange[token] = exchange  # 代币→交易所
    self.exchange_to_token[exchange] = token  # 交易所→代币

    # 核心步骤4:更新代币计数和序号映射(方便按创建顺序查询代币)
    token_id: uint256 = self.tokenCount + 1   # 新代币序号(从1开始)
    self.tokenCount = token_id                # 更新总代币数
    self.id_to_token[token_id] = token        # 序号→代币地址

    # 记录事件:广播新交易所创建,供前端/链下解析
    log.NewExchange(token, exchange)
    # 返回新创建的交易所地址
    return exchange

# 查询指定代币对应的官方交易所地址
# 参数:token - 代币地址
# 返回值:该代币的交易所地址(0地址表示未创建)
@public
@constant  # 纯读取,不修改合约状态
def getExchange(token: address) -> address:
    return self.token_to_exchange[token]

# 查询指定交易所绑定的代币地址
# 参数:exchange - 交易所地址
# 返回值:该交易所绑定的代币地址(0地址表示无效交易所)
@public
@constant
def getToken(exchange: address) -> address:
    return self.exchange_to_token[exchange]

# 按创建序号查询代币地址(用于遍历所有已上线的代币)
# 参数:token_id - 代币创建序号(从1开始)
# 返回值:对应序号的代币地址
@public
@constant
def getTokenWithId(token_id: uint256) -> address:
    return self.id_to_token[token_id]
相关推荐
devmoon2 小时前
快速了解兼容 Ethereum 的 JSON-RPC 接口
开发语言·网络·rpc·json·区块链·智能合约·polkadot
devmoon2 小时前
用Remix IDE在Polkadot Hub部署一个最基础的Solidity 合约(新手友好)
web3·区块链·智能合约·编译·remix·polkadot
暴躁小师兄数据学院3 小时前
【WEB3.0零基础转行笔记】Golang编程篇-第4讲:Go语言中的流程控制
开发语言·后端·golang·web3·区块链
devmoon3 小时前
使用 Remix IDE 在 Polkadot Hub 测试网部署 ERC-20 代币(新手完整实战教程)
web3·区块链·智能合约·solidity·remix·polkadot·erc-20
China_Yanhy3 小时前
入职 Web3 运维日记 · 第 7 日:消失的 5 万 U —— 归档节点与 Nginx 的智能分流
运维·区块链
ETFOption4 小时前
ETF期权实战手册:从策略构建到动态管理的完整流程
区块链
模型时代1 天前
Infosecurity Europe欧洲信息安全展将推出网络安全初创企业专区
安全·web安全·区块链
devmoon1 天前
智能合约实战 - 水龙头哪里领和创建第一个智能合约地址
web3·区块链·测试用例·智能合约·solidity
Mr.朱鹏1 天前
预测-下一个互联网风口?【PolyMarket调研】
web3·区块链·互联网·预测·加密货币·polymartet·风口