文章目录
- [一、 交易所合约(uniswap_exchange)](#一、 交易所合约(uniswap_exchange))
-
- [1. 状态变量层](#1. 状态变量层)
- [2. 公开价格查询层(外层查询 → 底层定价)](#2. 公开价格查询层(外层查询 → 底层定价))
-
- (1)用ETH兑换代币
- [(2) 用代币兑换ETH](#(2) 用代币兑换ETH)
- [3. 流动性操作层(外层操作 → 无内部依赖,核心逻辑独立)](#3. 流动性操作层(外层操作 → 无内部依赖,核心逻辑独立))
- [4. ETH 换 Token 兑换层(外层调用 → 内部核心逻辑)](#4. ETH 换 Token 兑换层(外层调用 → 内部核心逻辑))
-
- [(1) 默认兑换](#(1) 默认兑换)
- (2)按ETH输入量兑换(已知要花多少ETH,算能换多少代币)
- (3)按代币输出量兑换(已知要换多少代币,算需花多少ETH)
- [5. Token 换 ETH 兑换层(外层调用 → 内部核心逻辑)](#5. Token 换 ETH 兑换层(外层调用 → 内部核心逻辑))
- [6. 跨代币/跨交易所兑换层(外层扩展 → 基础兑换逻辑)](#6. 跨代币/跨交易所兑换层(外层扩展 → 基础兑换逻辑))
-
- [(1) 用代币A兑换代币B,按代币A输入量计算](#(1) 用代币A兑换代币B,按代币A输入量计算)
- (2)用代币A兑换代币B,按代币B输出量计算
- (3)直接传交易所地址
- [7. LP 代币 ERC20 接口层](#7. LP 代币 ERC20 接口层)
- 7.源码
- 二、工厂合约
-
- [1. 介绍](#1. 介绍)
- 2.核心作用
-
- (1)解决「代币-交易所」的唯一映射问题
- [(2) 标准化部署交易所合约,避免重复开发](#(2) 标准化部署交易所合约,避免重复开发)
- [(3) 降低部署成本与使用门槛](#(3) 降低部署成本与使用门槛)
- [3. 使用流程](#3. 使用流程)
-
- [(1)Uniswap 团队:搭建基础框架(仅做1次)](#(1)Uniswap 团队:搭建基础框架(仅做1次))
- (2)代币发行方:上线代币交易所(核心操作)
- (3)用户:使用交易所(日常操作)
- [4. 核心亮点(复用模板的价值)](#4. 核心亮点(复用模板的价值))
- [5. 源码](#5. 源码)
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兑换代币
getEthToTokenInputPrice → getInputPrice(底层定价) // 输入ETH数量,返回可以兑换多少代币。
getEthToTokenOutputPrice → getOutputPrice(底层定价) // 输入指定要兑换的代币数量,返回需要多少ETH。
(2) 用代币兑换ETH
getTokenToEthInputPrice → getInputPrice(底层定价) // 输入代币数量,返回可以兑换多少ETH
getTokenToEthOutputPrice → getOutputPrice(底层定价)// 输入指定要兑换的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,算能换多少代币)
ethToTokenSwapInput→ethToTokenInput(内部核心)
显式调用的ETH兑换代币函数:用户传入要花费的ETH数量(通过msg.value传递),函数根据恒定乘积公式计算可兑换的代币数量,兑换后的代币直接转给调用者本人(msg.sender),需传入minTokens(最小接收代币数,防滑点)、deadline(交易截止时间,防MEV)参数做安全保护。ethToTokenTransferInput→ethToTokenInput(内部核心)
显式调用的ETH代兑代币函数:核心逻辑与ethToTokenSwapInput完全一致(按ETH输入量算代币输出),仅多一个address recipient参数------用户用自己的ETH兑换代币后,代币会直接转给指定的recipient地址(第三方),而非调用者本人,适用于代他人兑换、跨合约转账等场景。
(3)按代币输出量兑换(已知要换多少代币,算需花多少ETH)
ethToTokenSwapOutput→ethToTokenOutput(内部核心)
显式调用的目标代币兑换函数:用户传入想要获取的代币数量(目标输出量),函数反向计算需要支付的ETH数量并自动扣减,兑换后的代币转给调用者本人(msg.sender),需传入maxEth(最大支付ETH数,防滑点)、deadline参数,确保用户不会因滑点多付ETH。ethToTokenTransferOutput→ethToTokenOutput(内部核心)
显式调用的目标代币代兑函数:核心逻辑与ethToTokenSwapOutput一致(按目标代币量算ETH输入),多一个address recipient参数------兑换后的代币直接转给指定的recipient 。
5. Token 换 ETH 兑换层(外层调用 → 内部核心逻辑)
上边是用ETH换代币,这个是用代币换ETH,逻辑和上边差不多,不解释了
tokenToEthSwapInput→tokenToEthInput(内部核心)tokenToEthTransferInput→tokenToEthInput(内部核心)tokenToEthSwapOutput→tokenToEthOutput(内部核心)tokenToEthTransferOutput→tokenToEthOutput(内部核心)
6. 跨代币/跨交易所兑换层(外层扩展 → 基础兑换逻辑)
(1) 用代币A兑换代币B,按代币A输入量计算
-
tokenToTokenSwapInput→tokenToTokenInput→ethToTokenTransferInput→ethToTokenInput先调用本交易所
tokenToTokenSwapInput→tokenToTokenInput,计算出代币A在本交易所中值多少ETH,再调用另外一个交易所ethToTokenTransferInput→ethToTokenInput,将这些ETH换成代币B。兑换后的代币B转给调用者本人(msg.sender) -
tokenToTokenTransferInput→tokenToTokenInput→ethToTokenTransferInput→ethToTokenInput和上边逻辑差不多,多一个
address recipient参数,兑换后的代币B直接转给指定的recipient。
(2)用代币A兑换代币B,按代币B输出量计算
tokenToTokenSwapOutput → tokenToTokenOutput → ethToTokenTransferOutput → ethToTokenOutput
tokenToTokenTransferOutput → tokenToTokenOutput → ethToTokenTransferOutput → ethToTokenOutput
(3)直接传交易所地址
上边的几个函数,都有参数token_addr: address,指的是目标代币地址。然后通过工厂合约查出目标代币对应的交易所地址。适合普通用户(只需知道代币地址)
下边的这几个函数逻辑和上边是一样的。只是把参数token_addr换成exchange_addr,exchange_addr就是目标代币的交易所地址,省了一步转换。适合进阶场景已知交易对地址,省 gas。
tokenToExchangeSwapInput → tokenToTokenInput → ethToTokenTransferInput → ethToTokenInput
tokenToExchangeTransferInput → tokenToTokenInput → ethToTokenTransferInput → ethToTokenInput
tokenToExchangeSwapOutput → tokenToTokenOutput → ethToTokenTransferOutput → ethToTokenOutput
tokenToExchangeTransferOutput → tokenToTokenOutput → ethToTokenTransferOutput → ethToTokenOutput
7. LP 代币 ERC20 接口层
就是ERC20合约的几个方法
balanceOftransfertransferFromapproveallowance
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]