tokenpocket交易所|uniswap交易所

作者: tokenpocket交易所
2024-03-07 21:41:44

uniswap交易所怎么使用?uniswap交易所下载教程 - 知乎

uniswap交易所怎么使用?uniswap交易所下载教程 - 知乎切换模式写文章登录/注册uniswap交易所怎么使用?uniswap交易所下载教程独角兽Uniswap交易所怎么交易?根据区块链数据显示,Uniswap交易所资产2.62亿美元,共有币种916个,同时拥有1024个交易对,24小时成交额7.61亿美元,由此数据可以看出,Uniswap交易所在市面上还是比较受欢迎的,而对于刚了解Uniswap交易所的投资者来说,想要在Uniswap交易所进行投资,第一步就是了解Uniswap交易所怎么交易?那么,Uniswap交易所到底怎么交易呢?下面小编给大家整理了Uniswap交易所交易教程,以供投资者参考。Uniswap交易所怎么交易?我们可以通过多链钱包 TokenPocket 来连接 Uniswap 进行交易。1、打开TP钱包,点击【发现】进入发现界面后,找到 Uniswap 并点击进入。Uniswap主要分为三个功能,分别是【兑换】【发送】【资金池】。2、【兑换功能】,在兑换界面中,输入表示的是拿来用于兑换的币种,而输出表示的是兑换后的币种,输入完成后点击兑换即可。3、【发送】功能,既可以作为一个普通的代币转账工具,又可以实现从兑换到转账的功能。选择好代币,输入数量,输入接受地址,点击【send】即可完成转账功能。这里需要提一下发送的另一个功能:添加兑换发送。点击发送界面的【添加兑换】添加一条兑换的选项,设置输入和输出的参数后点击【发送】授权后即可发送。4、【资金池】功能,在 Uniswap 中,除了可以用于代币兑换之外,还可以加入 Uniswap 的资金池,通过为 Uniswap 提供流动性而赚取收益。Uniswap交易所基本介绍:Uniswap是以太坊上的一种协议,用于交易ERC20代币,与大多数收取费用的交易所不同,Uniswap旨在充当公共平台,这是无需平台费用或中间商的区块链社区交易代币的工具,Uniswap使用数学方式以及ERC-20代币资金池和ETH资金池来完成相应的工作。目前Uniswap已发平台币UNI,上线了币安交易所。Uniswap是一个基于以太坊的交易所协议,允许任何人交换ERC20代币。普遍上讲,交易代币要求买卖双方创造委托订单,而Uniswap则是自动创建一个交易市场。 Uniswap旨在帮助解决去中心化交易所的流动性问题。去中心化交易所解决了中心化交易所的许多问题,包括被黑客入侵,管理不善和任意收费的风险,但是,去中心化交易所也有其自身的问题,主要是缺乏流动性,这意味着去中心化交易所缺乏大量资金,这使得交易比较慢。这就是Uniswap要解决的问题,它试图通过允许交易所交易代币而不依靠买卖双方创造这种流动性的方式来解决去中心化交易所的流动性问题。通过以上介绍,相信大家对于Uniswap交易所怎么交易这个问题已经有所了解,投资者在确定在交易所进行数字货币交易之前,一定要了解清楚该交易所的收费标准,也就是该交易所手续费的收取情况,只有了解清楚了,投资者才能确定自己最终能获利多少。发布于 2021-03-03 10:26Uniswap交易所区块链(Blockchain)​赞同 35​​6 条评论​分享​喜欢​收藏​申请

目前大火的去中心化交易所uniswap到底是什么? - 知乎

目前大火的去中心化交易所uniswap到底是什么? - 知乎首页知乎知学堂发现等你来答​切换模式登录/注册交易所期货交易所去中心化交易所(DEX)Uniswap目前大火的去中心化交易所uniswap到底是什么?关注者122被浏览138,086关注问题​写回答​邀请回答​好问题 7​添加评论​分享​44 个回答默认排序区块浮世绘关注区块链,关注浮世绘​ 关注作者:明观(个人知乎号:区块浮世绘)转载请说明出处。-----------------------------------------------割---------------------------------------------在2020年快要过半的时候,DeFi概念相关的币种直接引爆了行情。DEX (去中心化交易所)——作为DeFi项目最容易捕获价值的细分赛道之一,自然也是备受瞩目。细数一下,DEX的项目还真不少:Bancor、Cruve、Balancer、LRC…但是!如果你要问谁是DEX里最靓的仔,那必然是引领AMM潮流、“Shit Coin大赌场”、 DEX中崛起的独角兽、——Uniswap。2020超新星Uniswap今天的文章将从“引领AMM潮流”、“Shit Coin大赌场”、“DEX中崛起的独角兽”,这三个角度来展开介绍Uniswap,聊聊Uniswap崛起的全过程,还原一个牛市FOMO情绪下的区块链大“赌场”的真面貌。引领AMM潮流Uniswap的诞生充满了偶然与趣味性,据说Uniswap 的创始人Hayden Adams 的灵感来自于V神在reddit上发的一个帖子。灵感有了,Hayden Adams老兄便开始着手学习Solidity编程。是的,他一开始只是抱着学习Solidity编程试试看的想法,然而这个用来练手的项目很快就得到了认可,获得了一笔来自以太坊的资助奖金,这下启动资金的问题也解决了。2018年11月,Uniswap正式上线,其定位是一个基于以太坊的去中心化交易所(DEX)。DEX虽然在用户资产安全性方面有所提升,但是受限于其资金规模,大多数DEX的资产流动性并不好,交易速度也就成了一个硬伤。为了解决这个问题,Uniswap并未采用主流的订单薄交易系统,而是选择了自动化做市商机制。在传统的订单薄交易中,做市商或者交易者会按照不同的价位分别下单,根据价位来提供不同的流动性。而Uniswap的AMM机制则是将所有人的资产汇集到流动池中,并根据一种名为“恒定乘积做市商模型”的算法进行做市商。所谓的“恒定乘积”其实可以看成一个反比例函数“XY=Z”,无论X和Y怎么改变,Z始终是一个定值。放到Uniswap的交易中是指某次交易前后流动池里的两种代币数量的乘积是恒定的,即买前乘积=买后乘积。计算公式举个例子:我在Uniswap中新建一个ETH与X代币的流动池(免费创建,无需费用),创建时ETH和X的数量分别为10和100,这个时候X的价格为0.1ETH,两者数量的乘积为1000。创建完成之后,用户就可以在Uniswap上进行ETH与X的兑换了。假设有人用1个ETH来买X,这个时候1Eth进入流动池,ETH的数量变为11,那么要保持两者乘积不变,X的数量就要减少,这个减少的X的数量即为1个ETH能买到的X数量。根据恒定乘积可得,10*100=(10+1)*(100-X’),计算的X’=9.09,即1个ETH可以买到9.09个X,相对于原来1ETH=10X的价格来说,滑点(价格误差)为(10-9.09)/10*100%=9.09%。将1个ETH改成5个,同样可以算出5个ETH可以买到33.33个X,1个ETH只能够买到6.67个X,滑点为33.33%。从上述例子可以看出,“恒定乘积”算法其实原理一点都不复杂,并且有如下特点:1、根据交易情况反映价格。当有人用A代币兑换B代币(即买入B)时,B的价格就会上涨,反过来(卖出B)则B价格下跌,符合一般交易价格规律。2、流动性保持。无论流动池的资金规模如何,该算法均能提供流动性。3、不适合大额的交易兑换。我们发现在进行大额交易兑换的时候,价格变化很大,且不是线性的。当然,这个大额是相对于流动池的规模来判别的。所谓的“恒定乘积”是指在某次交易前后,乘积不变,也即是说流通池中的两种代币乘积并非是永远一成不变的。那什么时候乘积会改变?对价格有没有影响呢?在Uniswap的AMM机制中,用户也可以往流动池中添加资金来帮助降低交易的滑点。但是如果随意添加代币到流动池中,代币的数量比例会改变,价格会产生较大的波动。为了保持当前的两种代币价格兑换比例不会被改变,用户需要按当前比例数量的两种代币同时注入流动池中。这样一来,乘积会被扩大,但是两种代币的兑换价格并未改变。值得一提的是,Uniswap并没有发行自己的平台代币,为了激励用户往流动池中注入资金,资金池中的用户会按照资金比例平分该交易对的交易总额的0.3%的代币奖励。Uniswap在交易机制上的创新也在DEX领域掀起一股热潮,一大批AMM项目涌现出来,以Curve和Balancer为例,它们都有自己的定价函数,远比Uniswap的“恒定乘积”复杂,作为投资者和交易者,我们乐于看到这些创新和改变,也期待更多有趣有意义的项目。Shit Coin“大赌场”币圈中每一次新的热点之下,都会诞生出一个“大赌场”。所谓”大赌场“即各种项目鱼龙混杂,场子里充满了形形色色的投机者,大家都怀揣着”一夜暴富“的幻想。如今DeFi热潮袭来,Uniwsap的AMM机制助推了牛市的到来,但也成为了各种Shit Coin(没有价值的币)的天堂,上线一夜十倍百倍的币层出不穷。在这些过程中,甚至还产生了一些专业术语:钓鱼:所谓钓鱼,即愿者上钩。作恶者会在Uniswap上发一个毫无价值的币,用心点的做个网站,随便抄个白皮书,简陋点的就什么都没有。去微信群里发公告喊个单,等人来买,收到一些ETH之后,直接撤走币和ETH的流动池。”格局大“的作恶者收到几十个上百个ETH后就跑路,”格局小“的作恶者收到两三个ETH就会跑路。土狗:即上述钓鱼中的那些毫无价值的项目,其代币也就是我所说的Shit Coin。理性地分析一下赌场形成原因:1、作恶成本低。在上一章节中详细解释了Uniswap的“恒定乘积”AMM机制,其中有提到,为了提供更加多样的代币兑换和更好的流动性,用户是可以免费建立流动池的,这也就意味着无需上币费用即可上币,上币成本为0也就意味着作恶成本极大降低。2、AMM滑点。因为很多shit coin的池子流动很小,所以交易兑换时滑点相当大。只要短时间有一批人涌入,很容易买飞,就造成一种价格翻了十多倍的景象。3、FOMO心理。当然DeFi是热点,上线必涨成为共识,只要披着DeFi的外衣,蹭一蹭概念,上线就会遭到投机者哄抢。DEX中崛起的独角兽AMM自动做市商的创新,再加上新一轮DeFi热潮中各种十倍百倍币的推波助澜,Uniswap俨然已经成为DEX中的“独角兽”。而我个人最欣赏的,还是Unswap那令人愉悦的使用体验。在经历过V2的版本迭代之后,Uniswap舍弃众多繁琐的功能,将ERC20 代币之间的兑换功能做到了极致,提升了交易安全性并拥有远超其他DEX的代币兑换体验。毫不夸张地说,Uniswap是我用过的UI界面最好看,使用体验最好的DEX。目前在Token Pocket等主流去中心化钱包APP内,均可以直接登录Uniswap进行操作。在Uniswap内进行交易,你只需要在兑换界面,选择好兑换的币种和数量,页面下方会自动显示兑换的比例和交易滑点(价格误差),如果觉得合理,点击Swap 便可一键进行兑换交易,在交易上链后就能立即取得你应得的币。即使用户需要兑换的代币没有直接的交易对,Uniswap也会自动帮助用户寻找价格最优的途径,在交互界面上,用户还是只需发出一笔交易就能完成兑换,一键Swap就完事了。TokenPocket钱包对Uniswap支持友好,不仅访问速度快,而且还提供了汉化版。此外,很多用过Uniswap的朋友都会抱怨没有k线的问题,看到价格趋势。看不到K线不用慌,TP钱包汉化版本还提供K线和流动池规模查看功能,这点对用户来说真的是很贴心!代币的价格走势一目了然!通过价格走势用户也可以合理地判断出买入价格,心里也更加有底了!丰富的数据查看功能TP钱包最新版本增加了DeFi行情查看功能,各类代币行情一览无余,点击还能直接跳转Uniswap进行交易。根据从DeFiPulse得到数据来看,Uniswap在众多DEX项目中崛起的相当迅速。趁着今年的DeFi热潮,ETH质押量一飞冲天,在众多DEX中杀出一条血路并成功登顶。此外,在USDT与DAI的质押量上,Uniswap也均位列前三。ETH质押量一飞冲天Uniswap勇夺第一除质押量之外,CoinGecko上DEX交易量排行榜上,Uniswap仅次于Compound,屈居第二。多维化的数据显示,Uniswap这只独角兽正在崛起。交易量稳居第二综上所述,Uniswap是一个经过时间检验的、拥有良好体验的DEX项目,特别适合交易规模不大,需要进行代币快速兑换的交易者,也是一些规模较小,技术功底不错的项目团队上币的好去处。小伙伴们如果有对Uniswap有任何疑问,都可在公众号后台留言,我们会及时答复并手把手教会大家使用Uniswap~文章编辑排版: TokenPocket钱包DeFi系列知识科普专题是由TokenPocket发起的有深度、有态度的大型科普性内容专题,旨在科学系统地普及区块链及数字货币、数字钱包等相关知识。TokenPocket是全球领先的数字货币钱包,已为数百万用户提供可信赖的数字货币资产管理服务。专题文章由钱包社区优秀志愿者TP侠或官方撰写,文章版权归作者及TokenPocket所有,转载请注明出处。推荐阅读:你看我现在上车DeFi还有机会吗?快来康康 WalletConnect 都能干点啥~【DeFi科普】Uniswap(汉化版)操作指南编辑于 2021-05-20 09:58​赞同 85​​11 条评论​分享​收藏​喜欢收起​牛老师 Chris知名区块链副主编,曾任职于Coinbase,欢迎咨询问题​ 关注简而言之,有两个原因:(i) 定价权和 (ii) 盈利能力@DeFi_Made_Here 此前发表了一篇关于 Curve Finance 的帖子,对比了 Curve 与 Uniswap。这促使我写了这篇内容来提供很多人在对比 DEXs 时不会考虑的观点。首先,Uniswap v3 推出后,Uniswap 放弃了定价权。那是什么意思?对于在多个交易所之间交易的任何资产,只有一个交易所可以拥有定价权。一个例子就是:股票的 ADR 与主要交易的交易所中的股票。在加密行业中,一个代币可以在多个交易所、CEX 或 DEX 中上市。为什么说 Uniswap 在推出 v3 之后放弃了定价权?这就与 LP 如何在 v3 中提供流动性有关——LP 为提供最多流动性的地方选择了一个价格范围。这称为集中流动性。为什么是集中的?在 Uniswap v2 中,流动性沿着 xy=k 的不变曲线均匀分布,但由于大多数交易活动同时发生在一个范围内,所以 xy=k 曲线其他部分的流动性没有被利用,即资本效率低下。v3 设计就是为了解决这个问题。v3 比 v2 更具资本效率,但它需要 LPs 积极管理他们的头寸,因为交易对的价格范围不时变化(锚定资产除外)。这阻止了新项目在 v3 中为其原生代币建立新的流动资金池。为什么?由于初始流动性较浅,新代币的价格区间波动很大,v3 中有资金池的新项目需要经常调整价格区间。这带来了管理流动性的巨大成本,这是他们无法承受的。因此,大多数新代币都没有在 v3 上列出。由于 v3 上可用的新代币很少,Uniswap 就失去了定价权。怎么回事?要寻找蓝筹代币(例如 $ETH)的价格,人们会参考 Binance 平台上的价格。对于币安未上架的代币,由于在 v3 发布之前 v2 上上架了更多新代币,人们通常参考 v2 获取价格信息。由于管理流动性的巨大成本,v3 上的池子大多是流动性强且不太可能剧烈波动的蓝筹代币,Uniswap 作为价格信息主要来源的地位分崩离析。所以呢?没有定价权的 DEX 中的 LP 会因为被套利而蒙受巨大损失,不知情的订单流远小于有定价权的交易所。套利是对 LP 造成很大伤害的有毒流量的主要来源之一。欲了解更多信息:https://twitter.com/DeFi_Cheetah/status/1608677561919508480为什么 LP 在没有定价权的 DEX 中吃亏更多?ANS:更少的不知情订单流(人们主要在主要交易所进行交易)+ 更多的有毒流(套利者从价格信息的主要来源获取线索,并在其他 AMM 的价格发现过程中利用 LP)正如 @thiccythot_, @0x94305 @0xShitTrader 所指出的,v3 LP 由于巨大的有毒流量而持续亏损 - v3 交易量的约 43% 来自 MEV 机器人!何必呢?这就无法鼓励用户成为 v3 的 LP!这就影响了 v3 的盈利能力。没有定价权的交易所,很难在行业中占据领先地位,进而影响其盈利能力。相比之下,当检查稳定币是否已脱钩时,用户会参考 Curve Finance 而不是 CEX!通过比较,定价权的重要性不言而喻。Curve Finance 从 LP 收取 50% 的费用,Uniswap 将 100% 的费用给 LP;Uniswap 从所有交易中一无所获。没有利润的企业永远不是好企业,无论收入看起来有多大。Uniswap 意识到了这一点,并提议从 LP 中抽成。但事情并没有那么容易。Uniswap 这样做可能会遇到严重的麻烦。如前所述,如果没有定价权,LP 将更容易受到有毒流动的影响,因此提供流动性的动力就会减少。如果 Uniswap 现在抽成,这会进一步打击 LP。这会导致什么?Uni v3 上的大部分交易量都不是「粘性」的,因为超过 70% 的交易量是由算法驱动的。交易量只是跟随定价。因此 LP 的激励更少 -> TVL 和流动性更少 -> 滑点更高且执行价格更糟糕 -> 交易量更低 -> LP 费用更低且 LP 激励更低然后,陷入这个死亡螺旋。提高 LP 的交易费用以维持 TVL 和流动性如何?死亡螺旋不可避免:LP 的激励减少 -> 增加 LP 的交易费用 -> 更糟糕的执行价格 -> 更低的交易量 -> 更低的 LP 费用和 LP 激励这就是 Uniswap 一直不推动费用转换的原因。很多 web2 科技企业在过去几年都没有盈利,但实际上是在构建「护城河」,增强客户粘性。Uniswap 没有利润,但无法培养粘性用户行为,因为只有 <15% 的交易量来自其前端……为什么 Curve Finance 优于 Uniswap?你能想象如果 Uni v3 像 Curve 那样只将 50% 的费用给 LP,它的 TVL 和交易量会发生什么变化?通过 ve 模型 Curve 引导流动性,并赋予 $CRV 实用性。相比之下,$UNI 完全没有任何实用性,与 Uniswap 业务没有相关性。如果 Uni v3 可以从 LP 那里收取 50% 的费用并且仍然保持 TVL 和交易量,那么 Uniswap 胜过 Curve。但事实并非如此,因为它的大部分交易量都不是「粘性」或有机的。Uniswap 不能保证说——「随着时间的流逝,更多的用户习惯了我们的平台,导致更多的费用和更多的流动性。」Uniswap 上的交易量并不忠诚,除非它可以从其前端大幅增加交易量,否则交易量只会随着费用转换的推出而消失。此外,Uniswap TVL 是加过杠杆的:在 $34 亿美元 TVL 中,约 4.35 亿来自 $DAI/$USDC 对,MakerDAO 将其杠杆增加到高达 50 倍,因为它接受 Uni $DAI/$USDC LP 代币作为铸造 $DAI 的抵押品!然后可以将 $DAI 重新存入那里以获得 LP 代币来铸造更多 $DAI!因此,Curve Finance 优于 Uniswap,因为 (i) 它具有定价权,可以成为锚定资产价格信息的主要来源,并且 (ii) 它从 LP 交易费用中抽取 50%,但仍然可以在没有杠杆的情况下通过其卓越的 ve 代币经济学吸引巨大的 TVL!@DeFi_Made_Here 提出了一个很好的反驳论点:如果 Curve Finance TVL 如此依赖 $CRV 释放,一旦 $CRV 大幅下跌,TVL 将由于较低的 APR 而大幅下降。这是真的,但对于以太坊来说也是如此:如果 $ETH 暴跌,它更容易受到攻击并且更不安全。对我来说,web3 之所以如此特别,是因为我们每个人都能够以非托管方式发行数字资产,并通过充分利用代币发行来引导流动性或其他指标。到目前为止,Curve Finance 集中体现了 web3 项目如何做到这一点。最后,为什么 Uni v3 走错了一步?它增加了项目管理链上流动性的成本,从而放弃了其定价权。与其通过引入多条曲线来迎合不同的加密资产来提高 Uni v2 粘性曲线的资本效率,它只是创建了一个新模型,我认为它是订单簿的一个更糟糕的版本。现在,通过与聚合器(NFT 聚合器 或 DEX 聚合器 1 inch)竞争,它已从作为行业的基本效用转变为消费领域的竞争候选者之一。如果它能专注于使所有波动性加密资产的发行成为必然,那就像电和水一样——用户在交换代币时无法避免使用 Uniswap。这是 Uniswap 在我看来应该走的最佳路径,显然它选择了一条不同的路径。就是这样!我希望这能引起一些关于这些蓝筹 DeFI 项目下一步应该做什么的富有成果的讨论。欢迎分享给更多人,发表评论表达你的看法!更多币圈财富密码真干货,请关注公众号:加密前线《后续各种信息和福利通过此公众号发布》发布于 2023-01-10 21:08​赞同 1​​添加评论​分享​收藏​喜欢

小白课堂 | 一文读懂Uniswap,附Uniswap使用教程 - 知乎

小白课堂 | 一文读懂Uniswap,附Uniswap使用教程 - 知乎首发于数字货币切换模式写文章登录/注册小白课堂 | 一文读懂Uniswap,附Uniswap使用教程书匠火火玄学一、加密货币交易形式 当我们要进行加密货币交易时,使用最早也是目前使用最多的形式还是中心化交易所,在中心化交易所,我们首先需要注册,然后加密货币也需要存入到交易所,由交易所进行托管,如果要提现加密货币出来,也需要经过交易所审核同意。虽然中心化交易所有诸多优势,例如交易速度较快、用户不需要管理私钥,降低了用户的使用门槛,但是它的弊端也是显而易见的,用户的加密货币由交易所托管,交易所是有跑路风险的。也确实发生过多起交易所跑路的事件,几乎每年都有发生。那么,有没有更好的加密货币交易形式呢? 随着区块链技术的不断发展,加密货币交易形式也变得越来越多样化,我们不但可以使用中心化交易所进行交易,也可以使用去中心化交易所进行交易。在去中心化交易所进行交易时,不需要注册,只需要使用数字钱包连接去中心化交易所就可以进行加密货币的交易了,交易完成后,相应的加密货币会自动转入到用户的数字钱包中,用户的资产始终在自己的钱包中,并非像中心化交易所那样托管在交易所,所以,在去中心化交易所进行交易,安全性大大提高了。 目前,去中心化交易所主要有两种形式,一种是交易所撮合买方用户和卖方用户的订单,只不过操作过程发生在链上,典型代表有 EOS 去中心化交易所 Newdex,Newdex 依旧承担了中心化交易所的订单撮合模式。在 Newdex 交易,用户无需注册、资产无需托管,撮合发生在 EOS 链上,数据公开透明。 去中心化交易所的另一种形式是基于兑换池,而非基于买方和卖方订单所构成的订单簿,它类似于人机交易,采取的是柜台模式,就像我们去银行兑换不同国家的法币。典型的代表是 Uniswap。二、Uniswap 是什么Uniswap 是基于以太坊的代币交换协议,是基于兑换池,而不是订单簿的去中心化交易协议。而所谓的兑换池,指的则是一个资金池,用户在 Uniswap 中交易的价格则由这个资金池中的代币比例和算法来决定。Uniswap 是去中心化的,不仅跟传统的加密货币交易所不同,也跟普通的去中心化代币交易所不同。Uniswap 是一组部署到以太坊网络的合约,所有的交易都在链上进行。Uniswap 的交易对手并不是其他交易用户,而是跟代币池进行交易,且有自动做市的模型来计算交易价格,代币兑换价格与代币兑换池中代币的比例有关。三、Uniswap 的功能和优势 在 Uniswap 中,使用最多的功能就是代币之间的币币兑换交易了,由于 Uniswap 是基于以太坊的,所以在 Uniswap 中只能交易以太坊上的加密货币资产。目前 Uniswap 支持的币种大概在150种左右,以太坊上的主流资产基本上都可以在上面进行交易了。当然了,用户也可以自行添加交易对,也就是做市商,这样可以赚取一定的手续费。 使用 Uniswap 这种交易形式,比订单撮合模式速度更快,因为它是基于兑换池的人机交易,节省了撮合时间。而且 Uniswap 支持的币种间可以实现两两兑换交易,交易对明显比中心化交易所更丰富。 例如: LRC 和 KNC 之间的交易,在 Uniswap 是可以一步完成交易(虽然系统可能需要转换,但在用户看来就是一步交易)的。而在中心化交易所,LRC 和 KNC 没有直接的交易对,用户最少需要两次交易,即把 LRC 兑换成 ETH ,再用 ETH 和 KNC 交易。很显然,对于用户来说,Uniswap 更便捷。四、如何使用 Uniswap 进行币币兑换交易 我们可以通过多链钱包 TokenPocket 来连接 Uniswap 进行交易。在 TP 钱包的发现界面的 DeFi 区,找到 Uniswap 并点击进入。1、兑换我们可以选择 V2 版本。在兑换界面中,输入表示的是拿来用于兑换的币种,而输出表示的是兑换后的币种。例如:拿 LRC 兑换 KNC,输入部分要选择 LRC,而输出部分要选择 KNC。在选择好输入输出币种后,输入输填写数量,即用于兑换 KNC 的 LRC 数量,可以收到的 KNC 数量就会根据 LRC/KNC 当前的汇率自动计算出来并填充。输入完成后,点击"授权 LRC"按钮,然后点击右侧的"兑换"就可以完成 LRC 和 KNC 之间的兑换了。2、发送 在 Uniswap 的发送界面中,主要有两个功能。一个是转账功能。选择要转出的币种,输入转账数量以及接收地址,然后点击"send"就可以转账了,这和一般的钱包转账也没什么区别。另外一个功能是"兑换+转账",挺有意思的一个功能。 例如我们要把 LRC 兑换为 KNC,并转账到另外一个 ETH 地址中,就可以使用该功能,还是挺方便的。首先,在发送界面中点击"添加兑换"按钮。 然后选择兑换的两个币种和用于兑换 KNC 的 LRC 数量,并输入接收地址。最后,点击"授权LRC"按钮后,点击 "Send"按钮就可以完成代币的兑换以及转账了。3、资金池 在 Uniswap 中,除了可以用于代币兑换之外,还可以加入 Uniswap 的资金池,通过为 Uniswap 提供流动性而赚取收益。首先在"资金池"界面中点击"加入一个资金池"按钮,然后选择一个流动性池,例如选择 ETH/USDT,在选择了流动性池后,输入要存入资金池的币种数量。注意,交易对是成比例存入资金池的,例如上例中,如果存入 0.091216 ETH 到资金池,那么USDT就要存入21.751个。 输入完成后,点击下面的"授权 USDT",然后点击"供应"就可以加入资金池了。总之,通过 Uniswap 进行以太坊上数字资产之间的交易,还是非常方便的,不需要注册,也不需要托管在交易所,通过钱包连接到 Uniswap 就可以进行交易了,交易完成后,相应的数字资产也会自动转入到用户的 ETH 钱包中,而且在兑换时还可以选择转到其他地址中。如果要使用 Uniswap 进行交易,可以到多链钱包 TokenPocket 中体验和使用。源自公众号:库尔班区块链发布于 2020-12-15 18:58Uniswap去中心化交易所(DEX)​赞同 83​​31 条评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录数字货币在这里,读懂

Uniswap 交易所是什麼? 去中心化交易所(DEX) 介紹、使用方法及相關風險、如何提供流動性教學 - 幣學 | BShare.io

Uniswap 交易所是什麼? 去中心化交易所(DEX) 介紹、使用方法及相關風險、如何提供流動性教學 - 幣學 | BShare.io

跳至主要內容

鏈上數據

鏈上名詞

幣學知識

顧問諮詢

訂閱週報訂閱週報

訂閱週報訂閱週報

Main Menu

鏈上數據

鏈上名詞

幣學知識

顧問諮詢

首頁 » 部落格 » 幣學工具 » 交易所 » Uniswap 交易所是什麼? 去中心化交易所(DEX) 介紹、使用方法及相關風險、如何提供流動性教學

Uniswap 交易所是什麼? 去中心化交易所(DEX) 介紹、使用方法及相關風險、如何提供流動性教學

Bago 幣學

PrevPrevious在這波暴跌時勇敢抄底就會賺了嗎?現在與五月的暴跌有什麼差別?比特幣市場到底發生了什麼事情?鏈上數據資料分析週報 12/05~12/11

Next區塊鏈元宇宙 P2E 遊戲 Thetan Arena,5 分鐘教你如何遊玩、獲取代幣、購買英雄和提領代幣教學Next

本篇文章將著重於

如何使用 Uniswap如何進出礦池如何提供流動性、換取回報了解過程中所涉及的風險

Uniswap是加密貨幣行業最大的去中心化交易所(DEX)之一。 它在以太坊上運行,用戶使用的是自動做市商協議 (AMM), 而不是以往的現貨市場訂單來兌換任何 ERC-20 代幣。

這個協議是 2018 年底由工程師海登·亞當斯 (Hayden Adams) 發起。協議的靈感是來自以太坊聯合創始人 Vitalik Buterin 的一篇貼文。 Uniswap 目前已經來到平台的v3 設計,最新更新於 2021年5月發布。

Uniswap 通過鼓勵流動性提供者投入加密貨幣和建立流動性池來維持運作,買家使用這些流動礦池進行交易,而不是自己在現貨市場上尋找匹配的買家或賣家。

UNISWAP運作原理

接下來將介紹幾個去中心化交易所常見的名詞及功能:

流動性提供者:將加密貨幣存入AMM 提供人們交易的用戶,以換取利息和其他回報。流動性提供者會收到流動性提供者代幣 (liquidity provider tokens) 作為主要獎勵。

流動礦池:兩種加密貨幣所組成的礦池,例如 ETH-USDT,不需要找到匹配的買賣方,也能允許使用者買進賣出的礦池。

自動做市商 (AMM) :在沒有委託簿的情況下仍然能維持交易所功能的智能合約設計,主要依賴數學公式來為資產定價, 流動性提供者能為礦池提供資金,只要將兩種代幣存入礦池中的人都可以成為該協議的流動性提供者。

中心化加密貨幣交易所(例如:Binance 和Coinbase)與去中心化 AMM 交易所(例如: Uniswap) 的區別在於,沒有紀錄訂單的委託簿或中間人撮合買賣方的交易。此外,由於去中心化的性質,在Uniswap 上沒辦法進行ERC-20代幣的限價掛單,因此ERC-20 代幣也沒有掛單費用。

如何使用Uniswap?

要在 Uniswap上進行兌換,需要擁有 ETH或其他 ERC-20標準代幣,並通過Metamask 錢包交易這些代幣,如果不清楚如何辦理和使用Metamask,可以參考這篇文章,有詳細的介紹和使用說明。

Uniswap 上的交易通過四個簡單的步驟完成:

步驟 1. 連接你的錢包:使用像 Metamask這樣的熱錢包連接到 Uniswap (https://uniswap.org/)。

點選後會彈出視窗,要求用戶允許錢包連上Uniswap。

步驟 2. 選擇交易的代幣對:選擇想要出售的代幣以及要購買的代幣,上方選擇的是出售的代幣,下方則是想要購買的代幣,並且可以從代幣的下拉列表中進行搜索和選擇。

步驟 3. 檢查設置:有經驗的使用者可以點選右上角的設置圖標,可以設定像是滑點容忍度和交易截止時間等交易選項。

步驟 4. 允許使用錢包內的加密貨幣:交易時如果是第一次使用的幣別,交易前需要支付一筆十美金左右的費用,目的是允許Uniswap使用錢包內的加密貨幣,並且下次使用其他加密貨幣進行交易時,需要再次重複允許和付款的步驟。

步驟 5. 兌換:點擊兌換(Swap),會出現包含交易細節的彈出視窗,使用者可以在這裡查看訂單(包括 Uniswap 費用);點選確認兌換後,跳出第二個彈出窗口,目的是透過你的錢包完成這筆交易(其中包括以太坊礦工費 Gas Fee)。

確認後,AMM智能合約會在以太坊區塊鏈上完成剩餘的交易,自動將新代幣轉入到用戶的錢包中。過程可以使用交易ID(transction ID)或錢包地址在Etherscan.io區塊鏈瀏覽器上追蹤交易進度。

使用Uniswap的成本和風險

儘管在很簡單就能在Uniswap上交易ERC-20 代幣,但交易上還是存在一定的成本和風險。

智能合約風險:從本質上來說,智能合約並不是完全沒有漏洞,它存在一定的技術風險。由於智能合約是整個生態系統的基礎,也因此成為黑客和其他有心人士的攻擊重點。由於 Uniswap 允許任何人創建 ERC20 代幣並將其添加到平台,因此也有可能創建虛假智能合約。

滑點:根據市場狀況,各種交易都有可能出現滑點。雖然這可以使用滑點容忍度來限制,但它也限制了盈利能力,特別是在高波動的情況下。

獎勵失效風險:智能合約的結構通常會激勵協議參與者的良好行為。但有時後這些獎勵辦法無法滿足使用者的需求,例如項目方拿著錢跑路或代碼漏洞等,從而導致其他用途面臨經濟損失的風險。

礦工費(gas fee):使用以太坊區塊鏈一直以來都有個不容忽視的問題,也就是非常高昂的礦工費。這種狀況應該會隨著 Uniswap v3的推行而有所改善。

如何在 Uniswap 上提供流動性

將錢包連接到Uniswap界面後,用戶就可以通過流動池(pool)使用協議的流動性挖礦功能。

用戶可以點選「新倉位」向現有的流動池添加流動性,或是選擇「創建流動池」功能為特定代幣對創建新的流動池。

以下介紹如何在Uniswap增加流動性的幾個步驟

步驟 1. 進入「流動池」,然後點選「新倉位」。

步驟 2. 選擇您想要增加流動性的代幣對(例如ETH 和 USDT)。

步驟 3. 選擇代幣後,輸入您要為代幣對添加的代幣數量的二分之一。在其中一方輸入金額後,另一個代幣也會自動填入相應的金額,這是因為存入流動池的代幣價值必需相等。

步驟 4. 在繼續交易之前,用戶可以設定設定流動性挖礦的獎勵,在0.01-1%之間選擇,獎勵的比例越高,兌換率範圍越廣,就有可能因為幣價下跌承受較多的無常損失。

步驟 5. 通過錢包確認交易,並等待智能合約完成交易。

在為Uniswap協議提供流動性後,會按照在流動池中的份額比例獲得該代幣對所有交易的 0.01-1% 的獎勵。獎勵會在流動池中不斷累積,並可以通過提取流動性來同時領取,作為獎勵的流動性提供者代幣會自動發放到提供者的錢包內。

總結

Uniswap作為去中心化交易平台,為每個尋求被動收入的加密貨幣投資者創造了大量機會。 它的出現讓鏈上交易得更加重要,如果沒有AMM來完成鏈上每日海量的代幣交易,DeFi基本也很能成功,而 Uniswap的存在很好的解決了這個問題。

PrevPrevious在這波暴跌時勇敢抄底就會賺了嗎?現在與五月的暴跌有什麼差別?比特幣市場到底發生了什麼事情?鏈上數據資料分析週報 12/05~12/11

Next區塊鏈元宇宙 P2E 遊戲 Thetan Arena,5 分鐘教你如何遊玩、獲取代幣、購買英雄和提領代幣教學Next

美元終於顯出頹勢,這下要輪到比特幣了嗎?鏈上數據週報10/31-11/6

本週比特幣開盤價約為$20,600左右,高點突破$21K,來到了$21,400左右,隨後在周末從高點下跌,最後收在$20,900左右,單周上漲約1.5%左右。雖然最終收盤價在21K以下,但本週的上漲給市場帶來了不少信心,同一週S&P500是以超過3%的跌幅做收,並且DXY美元指數也距離高點更遠了一些,美元的上漲稍微露出了一些疲態,從這些資訊來看,似乎熊市的寒冬正在露出一絲暖陽?本週的鏈上數據週報將會以買賣行為、動能以及價格模型的角度分析目前BTC市場的情況,

看更多 »

資金出逃以太坊DeFi?dYdX的遷徙造成了什麼影響?穩定幣市場近況|鏈上數據週報06/20~06/26

Powell於6/22參議院聽證會的論述基本上與6月會議大致相同,或是與過去一年的基調也相同,經濟呈現穩健增長,致力於維持長期價格2%的穩定,不過近幾個月因為烏俄戰爭造成的原物料價格上漲,以及中國清零政策造成的供應鏈問題使得通膨持續的比想像中的久,供給面造成的價格上漲也讓聯準會更難發力。

從聯準會6月升息三碼開始,代表聯準會將更加強力道在降低高通膨上,市場擔憂的也從停滯性通膨轉往經濟衰退上:

紐約聯儲在6月發布的經濟預測模型,下修2022年GDP年增率至-0.62%,同時核心PCE物價年增率自2.8%上調至3.8%。根據模型預測軟著陸機率為10%,硬著陸機率則為80%(軟著為GDP年增率在未來10季都為正,硬著陸則為未來10季至少有一季年增率降至-1%以下)

看更多 »

2022年12月比特幣鏈上成本基礎為何?全網實現虧損將近一年,還是有人在偷偷收幣。鏈上數據週報 11/28-12/04

本週比特幣開盤價約為$16,400左右,而收盤價約為$17,120,單週上漲逾4%。可以到本週價格基本上是一路走高,雖上漲幅度並不算太大,但至少已經脫離了15K左右的價位,儘管如此,我們還是一直聽到各大機構陸續扛不住曝險倒閉的狀況。本週鏈上數據週報將會聚焦在整個比特幣區塊鏈網路的買賣行為和市場成本分析。

看更多 »

文章導讀

別錯過任何幣學機會

每週定期更新加密貨幣新知、最新工具以及你不可錯過的投資機會!

Leave this field empty if you're human:

[email protected]

Discord

Twitter

Instagram

Youtube

鏈上數據

鏈上名詞

幣學知識

顧問諮詢

Menu

鏈上數據

鏈上名詞

幣學知識

顧問諮詢

數據週報

Discord

Twitter

Instagram

Youtube

數據週報

[email protected]

© 2022 Bshare All Rights Reserved.

Scroll to Top

回到頂端

DeFi工具—UNISWAP使用教程 - 知乎

DeFi工具—UNISWAP使用教程 - 知乎首发于DeFi挖矿教程大全切换模式写文章登录/注册DeFi工具—UNISWAP使用教程派派什么是Uniswap?Uniswap是 2018 年 11 月发布在以太坊主网上的去中心化交易所协议,它的创建者是 Hayden Adams。当前Uniswap在DeFi Pluses上拍名第11位,较之前有下降。不同于其它挂单式去中心化交易所,Uniswap采用了全新的AMM自动做市商算法来执行交易的设计。相比于其他交易所,Uniswap 交易所的智能合约设计能够大幅减少 gas 的用量。如何理解Uniswap的自动做市机制?传统的交易所一般是提供一个挂单平台,通过程序(撮合引擎)撮合买卖双方达成交易。它是一个自由买卖的市场,具有买卖意愿的人们自行挂出“买单”和“卖单”,通过交易所“中介”实现双方订单的成交。传统交易所有以下特点:市场上必须要有用户进行挂单,要有一定量的订单(市场深度)。订单必须重叠才能成交,即买价高于或等于卖价。需要将资产存储在交易所。而Uniswap的模型却完全不同。简单来讲,Uniswap的撮合机制基于一个最基本的公式:K=X*Y。其中,X代表一种资产,Y代表与X等值的另一种资产, 而K则是它俩相乘后的一个函数。当K值不变的情况下,X与Y的值就成反比,即X增加,Y就会减小(反之亦然)。而因为资产Y的较少会导致相对市场的溢价,则会吸引用户来进行搬砖套利,从而抹平价差,使系统重归平衡。下面我们就以ETH与MakerDAO的算法稳定币DAI为例,来为大家解释下这个模型(不考虑手续费)。假设当前ETH价格为150USD,DAI的价格与美金挂钩,等于1USD。现在往系统里注入100ETH与15,000 DAI,则K=100*15,000=1,500,000。当用户将2个ETH兑换成DAI后,ETH总量减少为98,则此时DAI的总量=1,500,000/98=15,306。而因此增加的306个DAI就等于2个ETH的价格,即1ETH=153USD。此时,ETH就会相对市场有溢价,会吸引用户来进行搬砖套利。用户将自己的ETH卖给系统,补充资金池里的ETH,使系统再次回到最初的稳定状态。Uniswap有什么不足之处?在挂单机制下,如果价格达不到挂单者的设置价格,系统是不会成交的。但是在Uniswap的模式下,用户要注意当前资金池的流动性,以免滑点太多,造成资产损失。这里给大家进行了一个简单计算,从表中我们可以看出,随着系统内ETH数量的增加,用户购买单个DAI相对于ETH的成本就会增高。当数量为10ETH时,点差可以达到9.89%。而前段时间UMA上线Uniswap,价格从0.26一路走高到1.2美金的事情也印证了这一点。针对这一点,Balancer做了进一步的修改。但是总体来看,这类AMM机制的DEXes难免会因为流动性问题而出现滑点。不过,对于初创企业来说,此类的DEXes是它们为Token定价并进行销售的福音,Uniswap为他们省去了一大笔要支付给交易所跟做市商的钱。如何在比特派里使用Uniswap?第一步,打开比特派并找到Uniswap安卓版本可以在发现页下的推荐/热门DApp里找到Uniswap。iOS版请更新到bitpie PRO 版本,在发现页的搜索框里输入:https://uniswap.bitpie.com/* Uniswap因为使用的是新加坡域名,所以经常会遇到加载缓慢或者访问出错的情况,而比特派则对此做了优化,当前使用比特派访问Uniswap的体验十分顺畅。第二步,进入DApp主页面,选择要使用的功能图为改版后的Uniswap页面,个人感觉比之前更简洁了。第三步,选择兑换,输入兑换金额及币种,选择浮点后进行兑换这里以ETH兑换DAI为例,并选择接受增加最多0.5%的偏差。设定完毕后,点击“兑换”。待订单确认后,你的钱包地址里就会收到DAI了。从主页面我们可以看到,除了兑换还有“发送”与“资金池”功能。这两个功能都属于V2版本,因为使用时系统会自动切换到V2下。其中,发送与兑换功能大同小异,只是增加了一个收款地址,让你在兑换完成后可以将币发到自己/他人的指定地址,或者ENS域名。而资金池功能,是允许用户通过为系统注入ETH增加系统流动性而获取收益的。下图则展示了如何使用资金池功能为系统注入流动性。以上就是对Uniswap的简介及使用体验了。DeFi市场最近因为Compound的借贷挖矿又火了一把,引来的市场的广泛关注,这对DeFi市场来说是一件好事。同时,DeFi产品因其自身具有去中心化、抗审查、公开透明等特性也正逐渐受到市场的青睐。比特派也会持续关注DeFi生态,为大家带来更多的DeFi产品介绍及体验。本文仅为第三个工具在钱包端的操作教程,不构成任何投资建议。投资有风险,请您谨慎评估。安全、强大钱包就用比特派发布于 2020-09-02 17:08交易所智能合约​赞同 24​​3 条评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录DeFi挖矿教程大全持续更新各类 DeFi 工具使用,微博@比特

Uniswap Wallet

Uniswap Wallet

Uniswap in your pocket.

Safe, simple swapping & self-custody.

Uniswap in your pocket.

Safe, simple swapping & self-custody.

Uniswap in your pocket.

Safe, simple swapping & self-custody.

Learn more

Swap on multiple chains

Supports Ethereum, Polygon, Optimism, and Arbitrum.

One simple wallet

All your NFTs, tokens, and wallets in one place

Safe and secure by design

Built by the most trusted team in DeFi.

Swap on multiple chains

Supports Ethereum, Polygon, Optimism, and Arbitrum.

One simple wallet

All your NFTs, tokens, and wallets in one place

Safe and secure by design

Built by the most trusted team in DeFi.

Swap onmultiple chains

Supports Ethereum, Polygon, Optimism, and Arbitrum.

One simple wallet

All your NFTs, tokens, and wallets in one place

Safe and secure

Built by the most trusted team in DeFi.

Resources

Download

Support

Other Products

Swap Tokens

Explore NFTs

Learn more

Blog

Discord

Twitter

© Universal Navigation Inc.

Resources

Download

Support

Other Products

Swap Tokens

Explore NFTs

Learn more

Blog

Discord

Twitter

© Universal Navigation Inc.

Resources

Download

Support

Other Products

Swap Tokens

Explore NFTs

Learn more

Blog

Discord

Twitter

© Universal Navigation Inc.

Uniswap入门 - 廖雪峰的官方网站

Uniswap入门 - 廖雪峰的官方网站

廖雪峰的官方网站

Blog

Java教程

手写Spring

手写Tomcat

Makefile教程

Python教程

JavaScript教程

区块链教程

SQL教程

Git教程

文章

问答

More

Java教程

手写Spring

手写Tomcat

Makefile教程

Python教程

JavaScript教程

区块链教程

SQL教程

Git教程

文章

问答

Java教程

手写Spring

手写Tomcat

Makefile教程

Python教程

JavaScript教程

区块链教程

SQL教程

Git教程

文章

问答

 

Profile

Passkey

Sign Out

Sign In

English

简体中文

Uniswap入门

廖雪峰 / 文章 / ... / Reads: 30456 Edit

在现货交易中,买卖双方各自报价,然后撮合成交,这是最简单,最直接,也是微观层面可以直接实现的一种交易方式。因此,自世界上第一家证券交易所诞生以来,撮合交易一直是最主流的方式。

随着DeFi的兴起,在以太坊这样的去中心化链上怎么实现买卖双方的交易?最直接的想法是把买卖盘搬到链上。然而,严重的技术问题导致了这种方式既慢又不经济。

对以太坊这种可以运行智能合约的链来说,交易实际上就是执行合约的函数。一个合约部署在链上,实际上相当于一组函数代码存放在链上。任何用户都可以通过钱包软件来调用这些函数,实现与合约的交互。但是,有几个限制:

每次只能调用一个函数,不过这个问题不大,因为合约内部,一个函数又可以调用其他函数;

用户必须主动发起调用,合约自身无法主动调用任何函数,也不存在定时调用机制,这是区块链的确定性计算和可验证性决定的,因此,价格涨到某个位置自动卖出或者价格跌到某个位置自动买入是无法在链上实现的;

买卖订单写入链上非常昂贵,如果一个订单写入是$0.1,那么100万个订单就要$10万,并且,第二天100 万个新订单又需要$10万,这个成本是不可能被用户接受的;

链上极低的TPS(一般在每秒几十到几百),无法支持传统撮合交易高达每秒上百万的性能。

而智能合约有几个独特的技术特性:

智能合约没有私钥,即使是合约部署者,也没有合约的控制权。所谓的合约控制人,只能写到代码逻辑里;

合约可以持有资产,这意味着与合约交互时,既可以把资产转移到合约,也可以从合约转移资产出去。

一句话总结,就是链上的交易,总是交易员与合约交互,这与传统的撮合交易,总是人与人的交互不同。因此,简单照搬撮合模式的链上交易,无一例外都不成功。

直到2018年底,Uniswap上线,一个全新的Swap交易模式诞生了。

需要特别指出的是,此Swap不是指金融衍生品的掉期,它是Uniswap的现货交易模式。

Uniswap首先解决的问题是任何交易员都是在与链上的合约程序交易,由合约本身充当做市商,即自动化做市商 AMM:Automated Market Maker。对于两种资产组成的交易对,例如UNI/ETH,卖出ETH,买入UNI,或者卖出UNI,买入ETH,要与程序完成这笔交易,合约本身首先要持有这两种资产,比如100个ETH和2000个UNI,这样,交易员用1个ETH买入20个UNI后,合约持有资产可能变成101个ETH和1980个UNI。如果另一个交易员稍后卖出40个UNI,合约持有的资产又可能变成2020个UNI和99个ETH。

程序做市的资产从哪来呢?只能由流动性提供者LP(Liquidity Provider)先存入合约。为了鼓励LP将资产作为流动性存入合约,需要以手续费返还的形式作为激励。

和撮合产生市场价不同,和程序交易,需要用算法产生市场价。Uniswap引入了最简单的固定乘积公式x * y = k来由程序决定价格。

还是以UNI/ETH为例,如果初始状态下LP注入2000个UNI和100个ETH到流动性池子里,那么初始价格就是1ETH=20UNI,或者1UNI=0.05ETH,而乘积k = 2000 * 100 = 200000就是固定常数。

假设下一个交易员准备卖1个ETH,他将买入N个UNI,池子里会有100+1个ETH和2000-N个UNI,带入公式(100 + 1) * (2000 - N) = 100 * 2000,计算得知N=19.802,因此,不计手续费的情况下该交易员花费1ETH获得19.802个UNI,买入UNI后价格变为1ETH=19.606UNI,或者1UNI=0.051ETH,即UNI的价格略微上涨。

在Uniswap交易对中,两种资产的地位是等价的,且任何人都可以注入流动性充当做市商。对做市商来说,任何时候,都可以按照当前价格的比例注入两种资产,Uniswap会计算新注入的LP的占比,并返回给做市商一个LP代币作为做市凭证。做市商稍后可以将LP凭证兑换出两种资产,并在兑换时一次性获得累计的手续费。

做市商的风险在于,做市的价格和退出做市的价格很可能不一样,例如做市时注入了100ETH和2000UNI,退出时取出了50ETH和4000UNI,价格变化可能造成做市商损失,这种损失被称为无常损失:Impermanent Loss。

对交易员来说,往一个交易池里扔进去一种资产,就自动获得另一种资产,数量由Uniswap计算后确定,这就是Swap。

每次Swap交易都会改变交易池里两种资产的数量,从而引起价格的变动。那么,影响滑点大小的因素有哪些呢?由Uniswap价格公式可知,如果做Swap交易的数量较少,则滑点较小。如果流动性池子的资产数量越多,则滑点越小。因此,注入到池子里的资产数量至关重要,Uniswap用总锁仓量TVL:Total Value Locked表示池子的大小,可以从Uniswap交易页看到TVL排名靠前的交易池:

如果交易员想要交易UNI/WBTC,但是Uniswap没有这个交易池,或者交易池太小,怎么办?可以找两个较大的交易池,例如UNI/ETH和WBTC/ETH,做两次Swap,完成UNI和WBTC的交易:

┌─────────┐

────>│ UNI/ETH │

└─────────┘

┌─────────┐

<────│WBTC/ETH │

└─────────┘

这种借助中介资产的交易方式会付出更多的手续费,但滑点会大大降低。

由于流动性池子的大小对于滑点有重要影响,而通常价格只会在某个范围波动,为了更好地利用做市商的流动性池子,Uniswap的V3协议允许做市时指定价格区间,超过区间后该做市商的资金就不会被使用。这种改进的目的在于使用更少的资金提供相同的流动性:

除了Uniswap,还有其他基于链上Swap的DEX。

Balancer允许多个资产在同一个池子中交易,它采用的公式如下:

V=\prod_{}B_t^{W_t}

Curve也允许多个资产在同一个池子中交易,它采用的公式如下:

\prod_{}x_i=\left(\frac Dn\right)^n

数学功底过硬的同学可以自行推导价格变化曲线,这里我们就不展开细讲了。

术语

自动化做市商AMM:Automated Market Maker

流动性提供商LP:Liquidity Provider

无常损失:Impermanent Loss

总锁仓量TVL:Total Value Locked

小结

Uniswap开创了自动化做市的AMM机制,使得程序化在链上做市成为可能。

Comments

Make a comment

Sign in to make a comment

Author: 廖雪峰

Publish at: ...

关注公众号不定期领红包:

加入知识星球社群:

关注微博获取实时动态:

廖雪峰的官方网站

©Copyright 2019-2021

Powered by iTranswarp

Feedback

License

深度解读Uniswap V4:一个“集大成者”的去中心化交易所_腾讯新闻

深度解读Uniswap V4:一个“集大成者”的去中心化交易所_腾讯新闻

深度解读Uniswap V4:一个“集大成者”的去中心化交易所

作者:蒋海波,PANews

在美国监管加强,Binance、Coinbase等头部中心化交易所(CEX)遭到SEC打击,做市商撤出美国市场的背景下,去中心化交易所中(DEX)势必会不可或缺。

6月13日,Uniswap宣布了即将推出的Uniswap V4,并公开了白皮书的草稿版本,PANews对该白皮书和Uniswap V4的更新进行了分析。

Uniswap的演进史从V1到V4

Uniswap从2018年上线以来,经过了多个版本的更新。Uniswap V1引入了恒定乘积的做市商模型,支持无需许可的代币交换;Uniswap V2增加了ERC-20/ERC-20 对的交易(V1只支持ERC-20/ETH对的交易);Uniswap V3引入了“集中流动性”并让做市商可以自行选择费用层级(V2固定为0.3%),大大提高了资金利用率。

本次更新的Uniswap V4仍是一个无需托管、无需许可、无需升级的AMM协议,它基于Uniswap V3的集中流动性模型,更新了钩子(Hooks)功能、Singleton合约、瞬时账户、支持原生ETH、支持ERC-1155账户、增加治理功能和donate()功能,从而提高Uniswap的灵活性和可组合性,大大降低提供流动性和交易所需的Gas费。

V4版本更新细节和影响

通过钩子实现TWAMM、限价单、动态费用、内部MEV机制、自定义预言机等功能

在计算机中,钩子(Hooks)是一段处理系统消息的程序,通过系统调用,把它挂入系统。

Uniswap V4的钩子使得整合者可以创建更加灵活和可定制执的集中流动性池。钩子可以修改池参数,或者添加新的功能和特性,从而实现包括加权平均做市商(TWAMM)、限价单、动态费用、内部MEV机制、将多余的流动性存放到借贷协议中、自定义预言机在内的功能,可由钩子合约动态管理。

开发者可以基于Uniswap的钩子构建各种DApp,这将使得Uniswap V4的功能更加丰富,钩子合约也可以将交易手续费的一部分分配给自己。但用户在使用中也要更加小心,可能带来新的挑战和风险管理需求。

用Singleton合约取代Factory合约

Uniswap V4在架构设计上进行了重大改变,用Singleton(单例)模式取代Factory/Pool模式。在原来的版本中,每一个流动性池都是通过Factory合约独立部署的,那么在一个多步交易中,就要经过多个合约(如将ETH兑换为DAI,可能会经过ETH-USDC、USDC-DAI两个流动性池的合约)。

在新的Singleton合约中,所有流动性池都被包含在单个合约中,上述的多步交易可以通过一次合约交互完成,降低交易所需的Gas费。

瞬时账户在坎昆升级后大大降低Gas费

瞬时账户(Flash Accounting)是另一项架构转变。在Uniswap以前的版本中,每一步操作都会转移代币。而在Uniswap V4中,每个操作只会更新一个“内部净余额”,直至结束时才进行外部转账,从而简化多步交易、添加流动性和原子交易的复杂性,降低Gas费。

以太坊下一次的坎昆升级已确认包含EIP-1153,后者将引入“瞬态”存储,不需要瞬时账户在每次每次余额变动时都更新存储,进一步降低Gas费。

恢复对原生ETH的支持

Uniswap V1曾只支持ERC20/ETH的交易,从Uniswap V2开始不再进行限制,但考虑到代码的复杂性和对WETH/ETH之间流动性碎片化的担忧,Uniswap V2和V3在交易前会将ETH封装成WETH,WETH的转账费用也比原生ETH高,这就引来额外的Gas费。

Singleton和瞬时账户通过对架构的重新设计,已经解决了对复杂性了流动性的担忧,Uniswap V4也将重新支持原生ETH,降低Gas费。

支持ERC-1155代币

Uniswap V4将支持ERC-1155(将值作为数组传递,只有transferFrom,没有transfer )代币的铸造和销毁,用户可以在Singleton中保留代币,而不用进行ERC-20代币的转入、转出,对于操作频繁的流动性提供者和交易者有价值。

增加治理机制

Uniswap V4增加了新的治理机制,允许对交易和提款(从流动性池中提取资金)收费,并允许治理体系对这些费用进行分配,以奖励对Uniswap做出贡献的用户和开发者。

这项功能在钩子合约中可能有作用,如让钩子合约的开发者对LP收取一定的费用。但参考目前Uniswap在对交易收费上的缓慢进展,若协议从中收费,也可能会先对开发者的收入进行收费,这部分费用相对于用户持有的UNI代币价值也相对较低。

donate()功能

Uniswap V4引入了新的donate()功能,允许其他人向位于交易范围内的流动性提供者支付资金。这项功能可能对于项目方和TWAMM订单中的流动性激励有帮助。

Uniswap V4的更新对于Uniswap本身意义重大,钩子的引入将大大提高协议的灵活性,Singleton合约、瞬时账户、ERC-1155账户、对原生ETH的支持也都有利于降低Gas费。目前影响Uniswap使用的正是高昂的Gas费,预计Uniswap V4会将创建流动性池的Gas成本降低99%,这将进一步提高Uniswap在DEX中的地位。

博采众长,借鉴其它DEX已实现的功能

本次更新的某些功能,在已有的DEX中也有部分已经实现。而Uniswap v4是使用BSL许可证发布的,这点也受到诟病。

如架构调整中的Singleton合约和瞬时账户,在Balancer V2中,已经采用单一的Vault管理所有流动性池中的资产。在跨流动性池的交易中,Balancer V2也直接转移最终的Token数量,节省Gas费。由于钩子功能会带来各种各样的流动性池,对账户的调整在Uniswap V4中更加重要。钩子合约的创建者可以分配费用,这一点也和Balancer允许流动性池的创建者收费类似。

通过钩子实现的TWAMM,也有多个项目正在构建类似的方案,如Pulsar、Integral等,由于该功能正是这些项目的特色,品牌效应更加强大的Uniswap的更新可能使这类项目直接失去竞争力。

小结

Uniswap V4更新可能大幅提高Uniswap的竞争力,可借此实现TWAMM、限价单、动态费用、将流动性存入借贷协议、自动将手续费复投等功能。对于流动性提供者和交易者,所需的Gas费也可因为新的架构而大幅降低。虽然在Balancer、Integral等DEX中已经实现了部分功能,但凭借Uniswap的品牌效应,可能将这些功能发扬光大,成为一个集大成者。

而对于UNI持币者期待的费用调整,虽然Uniswap V4允许治理提取交易手续费和赎回费用,但优先考虑的分配对象可能也是对应的开发者。考虑到协议向流动性提供者收费的难度,如果仅向开发者收取一小部分费用,价值也不会太高。

Binance 与 Uniswap: 2024年使用哪一个交易所?

Binance 与 Uniswap: 2024年使用哪一个交易所?

ZH-CN

EN

DE

ES

FR

IT

PT

RU

JA

KO

HI

AR

ID

TR

VI

搜索

类别

交易所

项目

钱包

媒体

加密货币

工具

挖矿

游戏

软件

生态

DeFi

工具

Wallet

比较

Cryptocurrency market

List on exchange

P2P Money Transfer

Exchange

Market watch

NFT market

Top Addresses

添加公司

博客

博客

Price predictions

文章

Staking

Best companies

Best crypto wallets

ZH-CN

EN

DE

ES

FR

IT

PT

RU

JA

KO

HI

AR

ID

TR

VI

Keep your crypto safe. Get Web3 Wallet now!

Home

比较

Binance 与 Uniswap 比较

Binance 与 Uniswap 比较

比较公司

Hanbitco

HBTC

ZUBR

Ebitcoinics Review

Bitlish

VS

Rarible

EUCX

PayBitoPro

Bitclover

VELIC

比较公司

明确

在此页面上,您可以将 Binance 与 Uniswap 比较。 哪个交易所更好用? 您可以比较交易特征,费用, 交易功能, 安全 和社交 媒体存在。

在此页面的最后,您可以看到我们对这两家交易所的最终评级。

在下表中,您可以看到有关两个交易所的一般信息。

Binance 在 China 成立于 2017。

选择交易所进行交易时,应考虑活跃的市场参与者多长时间。 较早的交易所通常具有良好的声誉,并且往往更可靠。 使用旧的交换机可能是一个不错的决定。

您还应该注意交易所的位置。 一些国家/地区有严格的财务规定,因此在需要时难以寻求法院协助。

Binance 有一个适用于 iOS和Android 的移动应用程序。

Uniswap 没有移动应用。

Binance 提供 19 种语言,包括 English, Portuguese, German, French, Spanish, Italian, Korean, Russian, Traditional Chinese, Simplified Chinese, Dutch, Vietnamese, Turkish, Indonesian, Filipino, Polish, Slovene, Ukrainian和Bulgarian。

公司

Binance

Uniswap

User rating

User rating

2.9 / 5

44 用户评论

User rating

4.6 / 5

5 用户评论

Cryptogeek rating

Cryptogeek rating

4.6 / 5

Cryptogeek rating

0.0 / 5

TrustScore

其如何运作

TrustScore

4.05 / 5

TrustScore

1.81 / 5

User rating

2.9 / 5

44 用户评论

4.6 / 5

5 用户评论

Cryptogeek rating

4.6 / 5

0.0 / 5

TrustScore

其如何运作

4.05 / 5

1.81 / 5

关于

币安目前是最大和最受欢迎的加密货币交易所之一。该公司最初来自香港,最近将其总部迁至马耳他。早在其发展之初,该公司就进行了ICO,并筹集了1500万美元。参与者获得了Binance Coin(BNB),可用于交易加密货币并支付Binance交易所费用。

Uniswap是一个分散的协议,用于在以太坊上自动提供流动性。

成立日期

2017

没有数据

国家

China

没有数据

类型

Centralized

没有数据

语言

English, Portuguese, German, French, Spanish, Italian, Korean, Russian, Traditional Chinese, Simplified Chinese, Dutch, Vietnamese, Turkish, Indonesian, Filipino, Polish, Slovene, Ukrainian, Bulgarian

没有数据

移动应用

iOS, Android

-

完整地址

Valletta, NA - Malta, Malta (The exchange does not disclose the exact location of its office)

没有数据

费用

Trading fees:

Level | 30d Trade Volume (BTC) &/or BNB Balance | Maker/Taker | Maker/Taker (BNB 25%off) | Maker/Taker (BNB x Referral 25% off x Kickback 20%)

VIP 0 < 50 BTC or ≥ 0 BNB 0.100% / 0.100% 0.0750% / 0.0750% 0.0600% / 0.0600%

VIP 1 ≥ 50 BTC & ≥ 50 BNB 0.090% / 0.100% 0.0675% / 0.0750% 0.0540% / 0.0600%

VIP 2 ≥ 500 BTC & ≥ 200 BNB 0.080% / 0.100% 0.0600% / 0.0750% 0.0480% / 0.0600%

VIP 3 ≥ 1500 BTC & ≥ 500 BNB 0.070% / 0.100% 0.0525% / 0.0750% 0.0420% / 0.0600%

VIP 4 ≥ 4500 BTC & ≥ 1000 BNB 0.070% / 0.090% 0.0525% / 0.0675% 0.0420% / 0.0540%

VIP 5 ≥ 10000 BTC & ≥ 2000 BNB 0.060% / 0.080% 0.0450% / 0.0600% 0.0360% / 0.0480%

VIP 6 ≥ 20000 BTC & ≥ 3500 BNB 0.050% / 0.070% 0.0375% / 0.0525% 0.0300% / 0.0420%

VIP 7 ≥ 40000 BTC & ≥ 6000 BNB 0.040% / 0.060% 0.0300% / 0.0450% 0.0240% / 0.0360%

VIP 8 ≥ 80000 BTC & ≥ 9000 BNB 0.030% / 0.050% 0.0225% / 0.0375% 0.0180% / 0.0300%

VIP 9 ≥ 150000 BTC & ≥ 11000 BNB 0.020% / 0.040% 0.0150% / 0.0300% 0.0120% / 0.0240%

Full fee schedule: https://www.binance.com/en/fee/schedule

没有数据

关于

币安目前是最大和最受欢迎的加密货币交易所之一。该公司最初来自香港,最近将其总部迁至马耳他。早在其发展之初,该公司就进行了ICO,并筹集了1500万美元。参与者获得了Binance Coin(BNB),可用于交易加密货币并支付Binance交易所费用。

Uniswap是一个分散的协议,用于在以太坊上自动提供流动性。

成立日期

成立日期

2017

成立日期

没有数据

国家

国家

China

国家

没有数据

类型

类型

Centralized

类型

没有数据

语言

语言

English, Portuguese, German, French, Spanish, Italian, Korean, Russian, Traditional Chinese, Simplified Chinese, Dutch, Vietnamese, Turkish, Indonesian, Filipino, Polish, Slovene, Ukrainian, Bulgarian

语言

没有数据

移动应用

移动应用

iOS, Android

移动应用

-

完整地址

完整地址

Valletta, NA - Malta, Malta (The exchange does not disclose the exact location of its office)

完整地址

没有数据

费用

费用

Trading fees:

Level | 30d Trade Volume (BTC) &/or BNB Balance | Maker/Taker | Maker/Taker (BNB 25%off) | Maker/Taker (BNB x Referral 25% off x Kickback 20%)

VIP 0 < 50 BTC or ≥ 0 BNB 0.100% / 0.100% 0.0750% / 0.0750% 0.0600% / 0.0600%

VIP 1 ≥ 50 BTC & ≥ 50 BNB 0.090% / 0.100% 0.0675% / 0.0750% 0.0540% / 0.0600%

VIP 2 ≥ 500 BTC & ≥ 200 BNB 0.080% / 0.100% 0.0600% / 0.0750% 0.0480% / 0.0600%

VIP 3 ≥ 1500 BTC & ≥ 500 BNB 0.070% / 0.100% 0.0525% / 0.0750% 0.0420% / 0.0600%

VIP 4 ≥ 4500 BTC & ≥ 1000 BNB 0.070% / 0.090% 0.0525% / 0.0675% 0.0420% / 0.0540%

VIP 5 ≥ 10000 BTC & ≥ 2000 BNB 0.060% / 0.080% 0.0450% / 0.0600% 0.0360% / 0.0480%

VIP 6 ≥ 20000 BTC & ≥ 3500 BNB 0.050% / 0.070% 0.0375% / 0.0525% 0.0300% / 0.0420%

VIP 7 ≥ 40000 BTC & ≥ 6000 BNB 0.040% / 0.060% 0.0300% / 0.0450% 0.0240% / 0.0360%

VIP 8 ≥ 80000 BTC & ≥ 9000 BNB 0.030% / 0.050% 0.0225% / 0.0375% 0.0180% / 0.0300%

VIP 9 ≥ 150000 BTC & ≥ 11000 BNB 0.020% / 0.040% 0.0150% / 0.0300% 0.0120% / 0.0240%

Full fee schedule: https://www.binance.com/en/fee/schedule

费用

没有数据

交易

Binance 个交易量为 1,023,663,221.0 个。该交易所有 603 个可用交易对。 菲亚特交易可在交易所进行。

可用保证金交易。

Uniswap 个交易量为 0 个。该交易所有 0 个可用交易对。 交易所未提供菲亚特交易。

保证金交易不可用。

请注意:可用交易对的数量更多,为您提供了更多的交易机会,但可能同时导致技术缺陷。

交易量

1023663221

0

交易对

603

0

代币

173

0

法定货币交易

-

存款

Free

没有数据

费用

Percentage

没有数据

保证金

-

交易量

交易量

1023663221

交易量

0

交易对

交易对

603

交易对

0

代币

代币

173

代币

0

法定货币交易

法定货币交易

法定货币交易

-

存款

存款

Free

存款

没有数据

费用

费用

Percentage

费用

没有数据

保证金

保证金

保证金

-

安全

双因素认证

-

已验证

Verified

-

双因素认证

双因素认证

双因素认证

-

已验证

已验证

Verified

已验证

-

社交

网站

www.binance.com

app.uniswap.org

Twitter

@binance

没有数据

关注者人数

1039263

0

网站

网站

www.binance.com

网站

app.uniswap.org

Twitter

Twitter

@binance

Twitter

没有数据

关注者人数

关注者人数

1039263

关注者人数

0

评分

User rating

User rating

2.9 / 5

44 用户评论

User rating

4.6 / 5

5 用户评论

Cryptogeek rating

Cryptogeek rating

4.6 / 5

Cryptogeek rating

0.0 / 5

Cryptogeek摘要

基于 44 条用户评论, Binance 条用户评分为 2.9 条。 基于 5 位用户的评论, Uniswap 位用户的评分是 4.6 位。

我们还会根据每个交易所的特征来计算特殊的Cryptogeek TrustScore。

我们根据TrustScore评分选择获胜者

请记住,选择哪家公司仍然取决于您的选择!

我们如何计算信任分数?

我们根据TrustScore评分选择获胜者

请记住,选择哪家公司仍然取决于您的选择!

我们如何计算信任分数?

Binance

Winner

TrustScore:

4.05 / 5

与之比较:

HitBTC, Changelly, Upbit

书写您的评论

Uniswap

TrustScore:

1.81 / 5

与之比较:

Bitbns, bitFlyer, CryptoMarket

书写您的评论

在此页面上,您可以将 Binance 与 Uniswap 比较。 哪个交易所更好用? 您可以比较交易特征,费用, 交易功能, 安全 和社交 媒体存在。

在此页面的最后,您可以看到我们对这两家交易所的最终评级。

在下表中,您可以看到有关两个交易所的一般信息。

Binance 在 China 成立于 2017。

选择交易所进行交易时,应考虑活跃的市场参与者多长时间。 较早的交易所通常具有良好的声誉,并且往往更可靠。 使用旧的交换机可能是一个不错的决定。

您还应该注意交易所的位置。 一些国家/地区有严格的财务规定,因此在需要时难以寻求法院协助。

Binance 有一个适用于 iOS和Android 的移动应用程序。

Uniswap 没有移动应用。

Binance 提供 19 种语言,包括 English, Portuguese, German, French, Spanish, Italian, Korean, Russian, Traditional Chinese, Simplified Chinese, Dutch, Vietnamese, Turkish, Indonesian, Filipino, Polish, Slovene, Ukrainian和Bulgarian。

Binance vs Bitfinex

Binance vs Bittrex

Binance vs Digifinex

Binance vs BTC Alpha

Binance vs CoinEx

Uniswap vs Nash Exchange (NEX)

Uniswap vs Tokens.net

Uniswap vs CoinTandem

Uniswap vs MixCoins

Uniswap vs CoinGate

© 2019-2023年。Cryptogeek。保留所有权利。

关于我们

小部件

Terms

Privacy policy

Cookies policy

Editorial policy

Advertise

Sitemap

Top companies

HitBTC

LOBSTR

Cash App

Latoken

Advcash

Top stories

Coinbase连接银行账户安全吗? /Coinbase安全分析

如何从Coinbase转移到Binance-5简单步骤

Holo价格预测2022-2025-这是一个很好的投资吗?

在哪些国家可以在没有身份证的情况下购买比特币?

如何开设Coinbase商业账户?

关闭

Uniswap-v2 合约概览 | ethereum.org

wap-v2 合约概览 | ethereum.org跳转至主要内容学习用法构建参与研究搜索​​​​语言 ZH帮助更新此页面本页面有新版本,但现在只有英文版。请帮助我们翻译最新版本。翻译页面没有错误!此页面未翻译,因此特意以英文显示。不再显示Uniswap-v2 合约概览solidity中级Ori Pomerantz 2021年5月1日81 分钟阅读 minute read在本页面介绍Uniswap 是做什么的?为什么选择 v2? 而不是 v3?核心合约与外围合约数据和控制流程兑换增加流动资金撤回流动资金核心合约UniswapV2Pair.solUniswapV2Factory.solUniswapV2ERC20.sol外围合约UniswapV2Router01.solUniswapV2Router02.solUniswapV2Migrator.sol程序库数学定点小数 (UQ112x112)UniswapV2Library转账帮助结论介绍Uniswap v2(opens in a new tab) 可以在任何两个 ERC-20 代币之间创建一个兑换市场。 在本文中,我们将深入探讨实现此协议的合约的源代码,了解为何要如此编写协议。Uniswap 是做什么的?一般来说有两类用户:流动资金提供者和交易者。流动性提供者为资金池提供两种可以兑换的代币(称为 Token0 和 Token1)。 作为回报,他们会收到第三种叫做流动性代币的代币,代表他们对资金池的部分所有权。交易者将一种代币发送到资金池,并从资金池中接收流动性提供者提供的另一种代币(例如,发送 Token0 并获得 Token1)。 兑换汇率由资金池中 Token0 和 Token1 的相对数量决定。 此外,资金池将收取汇率的一小部分作为流动性资金池的奖励。当流动性提供者想要收回他们的代币资产时,他们可以销毁资金池代币并收回他们的代币,其中包括属于他们的奖励。点击此处查看更完整的描述(opens in a new tab)。为什么选择 v2? 而不是 v3?Uniswap v3(opens in a new tab) 是 v2 的升级,远比 v2 复杂得多。 比较容易的方法是先学习 v2,然后再学习 v3。核心合约与外围合约Uniswap v2 可以分为两个部分,一个为核心部分,另一个为外围部分。 核心合约存放着资产,因而必须确保安全,这种分法就使核心合约更加简洁且更便于审核。 而所有交易者需要的其它功能可以通过外围合约提供。数据和控制流程执行 Uniswap 的三个主要操作时,会出现以下数据和控制流程:兑换不同代币将资金添加到市场中提供流动性,并获得兑换中奖励的流动池 ERC-20 代币消耗流动池 ERC-20 代币并收回交易所允许交易者兑换的 ERC-20 代币兑换这是交易者最常用的流程:调用者向外围帐户提供兑换额度。调用外围合约中的一个兑换函数。外围合约有多种兑换函数,调用哪一个取决于是否涉及以太币、交易者是指定了存入的代币金额还是提取的代币金额等。 每个兑换函数都接受一个 path,即要执行的一系列兑换。在外围合约 (UniswapV2Router02.sol) 中确定兑换路径中,每次兑换所需交易的代币数额。沿路径迭代。 对于路径上的每次兑换,首先发送输入代币,然后调用交易所的 swap 函数。 在大多数情况下,代币输出的目的地址是路径中下一个配对交易。 在最后一个交易所中,该地址是交易者提供的地址。在核心合约 (UniswapV2Pair.sol) 中验证核心合约没有被欺骗,可在兑换后保持足够的流动资金。检查除了现有的储备金额外,还有多少额外的代币。 此数额是我们收到的要用于兑换的输入代币数量。将输出代币发送到目的地址。调用 _update 来更新储备金额回到外围合约 (UniswapV2Router02.sol)执行所需的必要清理工作(例如,消耗包装以太币代币以返回以太币给交易者)增加流动资金调用者向外围帐户提交准备加入流动资金池的资金额度。调用外围合约的其中一个 addLiquidity 函数。在外围合约 (UniswapV2Router02.sol) 中必要时创建一个新的配对交易如果有现有的币对交易所,请计算要增加的代币金额。 该金额对于两种代币应该是相同的,因此新代币对现有代币的比率是相同的。检查金额是否可接受(调用者可以指定一个最低金额,低于此金额他们就不增加流动性)调用核心合约。在核心合约 (UniswapV2Pair.sol) 中生成流动池代币并将其发送给调用者调用 _update 来更新储备金额撤回流动资金调用者向外围帐户提供一个流动池代币的额度,作为兑换底层代币所需的消耗。调用外围合约的其中一个 removeLiquidity 函数。在外围合约 (UniswapV2Router02.sol) 中将流动池代币发送到该配对交易在核心合约 (UniswapV2Pair.sol) 中向目的地址发送底层代币,金额与销毁的代币成比例。 例如,如果资金池里有 1000 个 A 代币,500 个 B 代币和 90 个流动性代币,而我们收到请求销毁 9 个流动性代币,那么,我们将销毁 10% 的流动性代币,然后将返还用户 100 个 A 代币和 50 个 B 代币。销毁流动性代币调用_update来更新储备金额核心合约这些是持有流动资金的安全合约。UniswapV2Pair.sol本合约(opens in a new tab)实现用于交易代币的实际资金池。 这是 Uniswap 的核心功能。1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Pair.sol';4import './UniswapV2ERC20.sol';5import './libraries/Math.sol';6import './libraries/UQ112x112.sol';7import './interfaces/IERC20.sol';8import './interfaces/IUniswapV2Factory.sol';9import './interfaces/IUniswapV2Callee.sol';显示全部 复制这些都是该合约需要知道的接口,因为该合约实现了它们(IUniswapV2Pair 和 UniswapV2ERC20),或因为该合约调用了实现它们的合约。1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { 复制此合约继承自 UniswapV2ERC20,为流动池代币提供 ERC-20 代币功能。1 using SafeMath for uint; 复制SafeMath 库(opens in a new tab)用于避免整数上溢和下溢。 这很重要,否则最终可能会出现这样的情况:本该是 -1 的值,结果却成了 2^256-1。1 using UQ112x112 for uint224; 复制流动池合约中的许多计算都需要分数。 但是,以太坊虚拟机本身不支持分数。 Uniswap 找到的解决方案是使用 224 位数值,整数部分为 112 位,小数部分为 112 位。 因此,1.0 用 2^112 表示,1.5 用 2^112 + 2^111 表示,以此类推。关于这个函数库的更详细内容在文档的稍后部分。变量1 uint public constant MINIMUM_LIQUIDITY = 10**3; 复制为了避免分母为零的情况,始终存在最低数量的流动性代币(但为帐户零所拥有)。 该数字,即 MINIMUM_LIQUIDITY,为 1000。1 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)'))); 复制这是 ERC-20 传输函数的应用程序二进制接口选择程序。 它用于在两个代币帐户中转移 ERC-20 代币。1 address public factory; 复制这就是由工厂合约创造的资金池地址。 每个资金池都是两种 ERC-20 代币之间的交易所,工厂是连接所有这些资金池的中心点。1 address public token0;2 address public token1; 复制这两个地址是该资金池可以交易的两种 ERC-20 代币的合约地址。1 uint112 private reserve0; // uses single storage slot, accessible via getReserves2 uint112 private reserve1; // uses single storage slot, accessible via getReserves 复制每个代币类型都有储备的资源库。 我们假定两者代表相同数量的值,因此每个 token0 的价值都等同于 reserve1/reserve0 token1。1 uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves 复制发生兑换的最后一个区块的时间戳,用来追踪一段时间内的汇率。以太坊合约中燃料消耗量最大的一项是存储,这种燃料消耗从一次合约调用持续到下一次调用。 每个存储单元长度为 256 位。 因此,reserve0、reserve1 和 blockTimestampLast 三个变量的分配方式让单个存储值可以包含全部这三个变量 (112+112+32=256)。1 uint public price0CumulativeLast;2 uint public price1CumulativeLast; 复制这些变量存放每种代币的累计成本(每种代币在另一种代币的基础上计算)。 可以用来计算一段时间内的平均汇率。1 uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event 复制币对交易所决定 token0 和 token1 之间汇率的方式是在交易中保持两种储备金的乘数恒定不变。 即 kLast 这个值。 当流动性提供者存入或提取代币时,该乘数就会变化,由于兑换市场的费用为 0.3%,它会略有增加。下面是一个示例。 请注意,为了简单起见,表格中的数字仅保留了小数点后三位,我们忽略了 0.3% 交易费,因此数字并不准确。事件reserve0reserve1reserve0 * reserve1平均汇率 (token1 / token0)初始设置1,000.0001,000.0001,000,000交易者 A 用 50 个 token0 兑换 47.619 个 token11,050.000952.3811,000,0000.952交易者 B 用 10 个 token0 兑换 8.984 个 token11,060.000943.3961,000,0000.898交易者 C 用 40 个 token0 兑换 34.305 个 token11,100.000909.0901,000,0000.858交易者 D 用 100 个 token1 兑换 109.01 个 token0990.9901,009.0901,000,0000.917交易者 E 用 10 个 token0 兑换 10.079 个 token11,000.990999.0101,000,0001.008由于交易者提供了更多 token0,token1 的相对价值增加了,反之亦然,这取决于供求。锁定1 uint private unlocked = 1; 复制有一类基于重入攻击(opens in a new tab)的安全漏洞。 Uniswap 需要转让不同数值的 ERC-20 代币,这意味着调用的 ERC-20 合约可能会导致调用合约的 Uniswap 市场遭受攻击。 通过在合约中使用 unlocked 变量,我们可以防止函数在运行时被调用(同一笔交易中)。1 modifier lock() { 复制此函数是一个修改器(opens in a new tab),它对正常函数进行包装数,以便以某种方式改变其行为。1 require(unlocked == 1, 'UniswapV2: LOCKED');2 unlocked = 0; 复制如果 unlocked 变量值为 1,将其设置为 0。 如果已经是 0,则撤销调用,返回失败。1 _; 复制在修饰符中,_; 是原始函数调用(含所有参数)。 此处,这意味着仅在 unlocked 变量值为 1 时调用函数,该函数调用才有效;而当函数运行时,unlocked 值为 0。1 unlocked = 1;2 } 复制当主函数返回后,释放锁定。其他 函数1 function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {2 _reserve0 = reserve0;3 _reserve1 = reserve1;4 _blockTimestampLast = blockTimestampLast;5 } 复制此函数返回给调用者当前的兑换状态。 请注意,Solidity 函数可以返回多个值(opens in a new tab)。1 function _safeTransfer(address token, address to, uint value) private {2 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); 复制此内部函数可以从交易所转账一定数额的 ERC20 代币给其他帐户。 SELECTOR 指定我们调用的函数是 transfer(address,uint)(参见上面的定义)。为了避免必须为代币函数导入接口,我们需要使用其中一个应用程序二进制接口函数(opens in a new tab)来“手动”创建调用。1 require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');2 } 复制ERC-20 的转移调用有两种方式可能失败:回滚 如果对外部合约的调用回滚,则布尔返回值为 false正常结束但报告失败。 在这种情况下,返回值的缓冲为非零长度,将其解码为布尔值时,其值为 false一旦出现这两种情况,转移调用就会回退。事件1 event Mint(address indexed sender, uint amount0, uint amount1);2 event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 复制当流动资金提供者存入流动资金 (Mint) 或提取流动资金 (Burn) 时,会发出这两个事件。 在这两种情况下,存入或提取的 token0 和 token1 金额是事件的一部分,以及调用合约的帐户身份 (Sender) 也是事件的一部分。 在提取资金时,事件中还包括获得代币的目的地址 (to),这个地址可能与发送人不同。1 event Swap(2 address indexed sender,3 uint amount0In,4 uint amount1In,5 uint amount0Out,6 uint amount1Out,7 address indexed to8 ); 复制当交易者用一种代币交换另一种代币时,会激发此事件。 同样,代币发送者和兑换后代币的存入目的帐户可能不一样。 每种代币都可以发送到交易所,或者从交易所接收。1 event Sync(uint112 reserve0, uint112 reserve1); 复制最后,无论出于何种原因,每次存入或提取代币时都会触发 Sync 事件,以提供最新的储备金信息(从而提供汇率)。设置函数这些函数应在建立新的配对交易时调用。1 constructor() public {2 factory = msg.sender;3 } 复制构造函数确保我们能够跟踪产生配对的工厂合约的地址。 initialize 函数和工厂交易费(如果有)需要此信息1 // called once by the factory at time of deployment2 function initialize(address _token0, address _token1) external {3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check4 token0 = _token0;5 token1 = _token1;6 } 复制这个函数允许工厂(而且只允许工厂)指定配对中进行兑换的两种 ERC-20 代币。内部更新函数_update1 // update reserves and, on the first call per block, price accumulators2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { 复制每次存入或提取代币时,会调用此函数。1 require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); 复制如果 balance0 或 balance1 (uint256) 大于 uint112(-1) (=2^112-1)(因此当转换为 uint112 时会溢出并返回 0),拒绝继续执行 _update 以防止溢出。 一般的代币可以细分成 10^18 个单元,这意味在每个交易所,每种代币的限额为 5.1*10^15 个。 迄今为止,这并不是一个问题。1 uint32 blockTimestamp = uint32(block.timestamp % 2**32);2 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { 复制如果流逝的时间值不是零,这意味着本交易是此区块上的第一笔兑换交易。 在这种情况下,我们需要更新累积成本值。1 // * never overflows, and + overflow is desired2 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;3 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;4 } 复制每个累积成本值都用最新成本值(另一个代币的储备金额/本代币的储备金额)与以秒为单位的流逝时间的乘积加以更新。 要获得平均兑换价格,需要读取两个时间点的累积价格并除以两个时间点之间的时间差。 例如,假设下面这些事件序列:事件reserve0reserve1时间戳边际汇率 (reserve1 / reserve0)price0CumulativeLast初始设置1,000.0001,000.0005,0001.0000交易者 A 存入 50 个代币 0 获得 47.619 个代币 11,050.000952.3815,0200.90720交易者 B 存入 10 个代币 0 获得 8.984 个代币 11,060.000943.3965,0300.8920+10*0.907 = 29.07交易者 C 存入 40 个代币 0 获得 34.305 个代币 11,100.000909.0905,1000.82629.07+70*0.890 = 91.37交易者 D 存入 100 个代币 0 获得 109.01 个代币 1990.9901,009.0905,1101.01891.37+10*0.826 = 99.63交易者 E 存入 10 个代币 0 获得 10.079 个代币 11,000.990999.0105,1500.99899.63+40*1.1018 = 143.702比如说我们想要计算时间戳 5,030 到 5,150 之间代币 0 的平均价格。 price0Cumulative 的差值为 143.702-29.07=114.632。 此为两分钟(120 秒)间的平均值。 因此,平均价格为 114.632/120 = 0.955。此价格计算是我们需要知道原有资金储备规模的原因。1 reserve0 = uint112(balance0);2 reserve1 = uint112(balance1);3 blockTimestampLast = blockTimestamp;4 emit Sync(reserve0, reserve1);5 } 复制最后,更新全局变量并发布一个 Sync 事件。_mintFee1 // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)2 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) { 复制在 Uniswap 2.0 的合约中规定交易者为使用兑换市场支付 0.30% 的费用。 这笔费用的大部分(交易的 0.25%)支付给流动性提供者。 余下的 0.05% 可以支付给流动性提供者或支付给工厂指定的地址作为协议费,用于支付 Uniswap 团队的开发费用。为了减少计算次数(因此减少燃料费用),仅在向资金池中增加或减少流动性时才计算该费用,而不是在每次兑换交易时都计算。1 address feeTo = IUniswapV2Factory(factory).feeTo();2 feeOn = feeTo != address(0); 复制读取工厂的费用支付地址。 如果返回值为零,则代表没有协议费,也不需要计算这笔费用。1 uint _kLast = kLast; // gas savings 复制kLast 状态变量位于内存中,所以在合约的不同调用中都有一个值。 虽然易失性内存每次在函数调用合约结束后都会清空,但由于访问存储的费用比访问内存高得多,所以我们使用内部变量,以降低燃料费用。1 if (feeOn) {2 if (_kLast != 0) { 复制流动资金提供者仅仅因为提供流动性代币而得到所属的费用。 但是协议费用要求铸造新的流动性代币,并提供给 feeTo 地址。1 uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));2 uint rootKLast = Math.sqrt(_kLast);3 if (rootK > rootKLast) { 复制如果有新的流动性变化需要收取协议费。 你可以在本文后面部分看到平方根函数。1 uint numerator = totalSupply.mul(rootK.sub(rootKLast));2 uint denominator = rootK.mul(5).add(rootKLast);3 uint liquidity = numerator / denominator; 复制这种复杂的费用计算方法在白皮书(opens in a new tab)第 5 页中作了解释。 从计算 kLast 的时间到当前为止,流动性没有增加或减少(因为每次计算都是在流动性增加或减少并发生实际变化之前进行),所以 reserve0 * reserve1 的任何变化一定是从交易费用中产生(如果没有交易费,reserve0 * reserve1 值为常量)。1 if (liquidity > 0) _mint(feeTo, liquidity);2 }3 } 复制使用 UniswapV2ERC20._mint 函数产生更多的流动池代币并发送到 feeTo 地址。1 } else if (_kLast != 0) {2 kLast = 0;3 }4 } 复制如果不需收费则将 klast 设为 0(如果 klast 不为 0)。 编写该合约时,有一个燃料返还功能(opens in a new tab),用于鼓励合约将其不需要的存储释放,从而减少以太坊上状态的整体存储大小。 此段代码在可行时返还。外部可访问函数请注意,虽然任何交易或合约都可以调用这些函数,但这些函数在设计上是从外围合约调用。 如果直接调用,您无法欺骗币对交易所,但可能因为错误而丢失价值。铸币1 // this low-level function should be called from a contract which performs important safety checks2 function mint(address to) external lock returns (uint liquidity) { 复制当流动资金提供者为资金池增加流动资金时,将会调用此函数。 它铸造额外的流动性代币作为奖励。 应从外围合约中调用该函数,在同一笔交易中增加流动性后外围合约就调用该函数(因此其他人都不能在合法所有者之前提交要求新增加流动性的交易)。1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 复制这是 Solidity 函数中读取多个返回值的方式。 我们丢弃了最后返回的值区块时间戳,因为不需要它。1 uint balance0 = IERC20(token0).balanceOf(address(this));2 uint balance1 = IERC20(token1).balanceOf(address(this));3 uint amount0 = balance0.sub(_reserve0);4 uint amount1 = balance1.sub(_reserve1); 复制获取当前余额并查看每个代币类型中添加的数量。1 bool feeOn = _mintFee(_reserve0, _reserve1); 复制如果有协议费用的话,计算需要收取的费用,并相应地产生流动池代币。 因为输入 _mintFee 函数的参数是原有的储备金数值,相应费用仅依据费用导致的资金池变化来精确计算。1 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee2 if (_totalSupply == 0) {3 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);4 _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens 复制如果这是第一笔存款,会创建数量为 MINIMUM_LIQUIDITY 的代币并将它们发送到地址 0 进行锁定。 这些代币永远无法赎回,也就是说资金池永远不会完全变空(避免某些情况下出现分母为零错误)。 MINIMUM_LIQUIDITY 的值是 1000,因为考虑到大多数 ERC-20 细分成 1 个代币的 10^-18 个单位,而以太币则被分为 wei,为 1 个代币价值的 10^-15。 成本不高。在首次存入时,我们不知道两种代币的相对价值,所以假定两种代币都具有相同的价值,只需要两者数量的乘积并取一下平方根。我们可以相信这一点,因为提供同等价值、避免套利符合存款人的利益。 比方说,这两种代币的价值是相同的,但我们的存款人存入的 Token1 是 Token0 的四倍。 交易者可以利用币对交易所认为 Token0 的价值更高这种情况,减少其价值。事件reserve0reserve1reserve0 * reserve1流动池价值 (reserve0 + reserve1)初始设置83225640交易者存入 8 个 Token0 代币,获得 16 个 Token1 代币161625632正如您可以看到的,交易者额外获得了 8 个代币,这是由于流动池价值下降造成的,损害了拥有流动池的存款人。1 } else {2 liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); 复制对于随后每次存入,我们已经知道两种资产之间的汇率。我们期望流动性提供者提供等值的两种代币。 如果他们没有,我们根据他们提供的较低价值代币来支付他们的流动池代币以做惩罚。无论是最初存入还是后续存入,流动性代币的数量均等于 reserve0*reserve1 变化的平方根,而流动性代币的价值不变(除非存入的资金为不等值的代币类型,那么就会分派“罚金”)。 下面是另一个示例,两种代币具有相同价值,进行了三次良性存入和一次不良存入(即只存入一种类型的代币,所以不会产生任何流动性代币)。事件reserve0reserve1reserve0 * reserve1流动池价值 (reserve0 + reserve1)存入资金而产生的流动池代币流动池代币总值每个流动池代币的值初始设置8.0008.0006416.000882.000每种代币存入 4 个12.00012.00014424.0004122.000每种代币存入 2 个14.00014.00019628.0002142.000不等值的存款18.00014.00025232.000014~2.286套利后~15.874~15.874252~31.748014~2.2671 }2 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');3 _mint(to, liquidity); 复制使用 UniswapV2ERC20._mint 函数产生更多流动池代币并发送到正确的帐户地址。12 _update(balance0, balance1, _reserve0, _reserve1);3 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date4 emit Mint(msg.sender, amount0, amount1);5 } 复制更新相应的状态变量(reserve0、reserve1,必要时还包含 kLast)并激发相应事件。销毁1 // this low-level function should be called from a contract which performs important safety checks2 function burn(address to) external lock returns (uint amount0, uint amount1) { 复制当流动资金被提取且相应的流动池代币需要被销毁时,将调用此函数。 还需要从外围帐户调用。1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings2 address _token0 = token0; // gas savings3 address _token1 = token1; // gas savings4 uint balance0 = IERC20(_token0).balanceOf(address(this));5 uint balance1 = IERC20(_token1).balanceOf(address(this));6 uint liquidity = balanceOf[address(this)]; 复制外围合约在调用函数之前,首先将要销毁的流动资金转到本合约中。 这样,我们知道有多少流动资金需要销毁,并可以确保它被销毁。1 bool feeOn = _mintFee(_reserve0, _reserve1);2 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee3 amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution4 amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); 复制流动资金提供者获得等值数量的两种代币。 这样不会改变兑换汇率。1 _burn(address(this), liquidity);2 _safeTransfer(_token0, to, amount0);3 _safeTransfer(_token1, to, amount1);4 balance0 = IERC20(_token0).balanceOf(address(this));5 balance1 = IERC20(_token1).balanceOf(address(this));67 _update(balance0, balance1, _reserve0, _reserve1);8 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date9 emit Burn(msg.sender, amount0, amount1, to);10 }11显示全部 复制burn 函数的其余部分是上述 mint 函数的镜像。兑换1 // this low-level function should be called from a contract which performs important safety checks2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { 复制此函数也应该从外围合约调用。1 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');2 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');45 uint balance0;6 uint balance1;7 { // scope for _token{0,1}, avoids stack too deep errors 复制本地变量可以存储在内存中,或者如果变量数目不太多,直接存储进堆栈。 如果我们可以限制变量数量,那么建议使用堆栈以减少燃料消耗。 欲了解更多详情,请参阅以太坊黄皮书(以前的以太坊规范)(opens in a new tab)第 26 页上的“方程式 298”。1 address _token0 = token0;2 address _token1 = token1;3 require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');4 if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens 复制这种转移应该是会成功的,因为在转移之前我们确信所有条件都得到满足。 在以太坊中这样操作是可以的,原因在于如果在后面的调用中条件没有得到满足,我们可以回滚操作和造成的所有变化。1 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); 复制如果收到请求,则通知接收者要进行兑换。1 balance0 = IERC20(_token0).balanceOf(address(this));2 balance1 = IERC20(_token1).balanceOf(address(this));3 } 复制获取当前余额。 外围合约在调用交换函数之前,需要向合约发送要兑换的代币。 这让合约可以方便检查它有没有受到欺骗,这是在核心合约中必须进行的检查(因为除外围合约之外的其他实体也可以调用该函数)。1 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;2 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;3 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');4 { // scope for reserve{0,1}Adjusted, avoids stack too deep errors5 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));6 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));7 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); 复制这是一项健全性检查,确保我们不会因兑换而损失代币。 在任何情况下兑换都不应减少 reserve0*reserve1。 这也是我们确保为兑换发送 0.3% 费用的方式;在对 K 值进行完整性检查之前,我们将两个余额乘以 1000 减去 3 倍的金额,这意味着在将其 K 值与当前准备金 K 值进行比较之前,从余额中扣除 0.3% (3/1000 = 0.003 = 0.3%)。1 }23 _update(balance0, balance1, _reserve0, _reserve1);4 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);5 } 复制更新 reserve0 和 reserve1 的值,并在必要时更新价格累积值和时间戳并激发相应事件。同步或提取实际余额有可能与配对交易所认为的储备金余额没有同步。 没有合约的认同,就无法撤回代币,但存款却不同。 帐户可以将代币转移到交易所,而无需调用 mint 或 swap。在这种情况下,有两种解决办法:sync,将储备金更新为当前余额skim,撤回额外的金额。 请注意任何帐户都可以调用 skim 函数,因为无法知道是谁存入的代币。 此信息是在一个事件中发布的,但这些事件无法从区块链中访问。1 // force balances to match reserves2 function skim(address to) external lock {3 address _token0 = token0; // gas savings4 address _token1 = token1; // gas savings5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));7 }891011 // force reserves to match balances12 function sync() external lock {13 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);14 }15}显示全部 复制UniswapV2Factory.sol此合约(opens in a new tab)创建币对交易所。1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Factory.sol';4import './UniswapV2Pair.sol';56contract UniswapV2Factory is IUniswapV2Factory {7 address public feeTo;8 address public feeToSetter; 复制这些状态变量是执行协议费用所必需的(请见白皮书(opens in a new tab)的第 5 页)。 feeTo 地址用于累积流动性代币以收取协议费,而 feeToSetter 地址可用于将 feeTo 更改为不同地址。1 mapping(address => mapping(address => address)) public getPair;2 address[] public allPairs; 复制这些变量用以跟踪配对,即两种代币之间的兑换。第一个变量 getPair 是一个映射,它根据兑换的两种 ERC-20 代币来识别币对交易所合约。 ERC-20 代币通过实现它们的合约的地址来识别,因此关键字和值都是地址。 为了获取币对交易所地址,以便能够将 tokenA 兑换成 tokenB,可以使用 getPair [](或其他方式)。第二个变量 allPairs 是一个数组,其中包括该工厂创建的所有币对交易所的地址。 在以太坊中,无法迭代映射内容,或获取所有关键字的列表,所以,该变量是了解此工厂管理哪些交易所的唯一方式。注意:不能迭代所有映射关键字的原因是合约数据存储费用昂贵,所以我们越少用存储越好,且越少改变 越好。 可以创建支持迭代的映射(opens in a new tab),但它们需要额外存储关键字列表。 但在大多数应用程序中并不需要。1 event PairCreated(address indexed token0, address indexed token1, address pair, uint); 复制当新的配对交易创建时,将激发此事件。 它包括代币地址、币对交易所地址以及工厂管理的交易所总数。1 constructor(address _feeToSetter) public {2 feeToSetter = _feeToSetter;3 } 复制构造函数做的唯一事情是指定 feeToSetter。 工厂开始时没有费用,只有 feeSetter 可以改变这种情况。1 function allPairsLength() external view returns (uint) {2 return allPairs.length;3 } 复制此函数返回交易配对的数量。1 function createPair(address tokenA, address tokenB) external returns (address pair) { 复制这是工厂的主要函数,可以在两个 ERC-20 代币之间创建配对交易。 注意,任何人都可以调用此函数。 不需要 Uniswap 许可就能创建新的币对交易所。1 require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');2 (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 复制我们希望新交易所的地址是可以确定的,这样就可以在链下提前计算(这对于二层网络交易来说比较有用)。 为此,无论收到代币地址的顺序如何,我们需要代币地址始终按顺序排列,因此我们在此处对它们排序。1 require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');2 require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient 复制大流动资金池优于小流动资金池,因为其价格比较稳定。 我们不希望每一对代币有多个流动性池。 如果已经有一个交易所,则无需为相同代币对创建另一个交易所。1 bytes memory bytecode = type(UniswapV2Pair).creationCode; 复制要创建新合约,我们需要使用创建它的代码(包括构造函数和写入用于存储实际合约以太坊虚拟机字节码的代码)。 在 Solidity 语言中,通常只需使用 addr = new () 的格式语句,然后编译器就可以完成所有的工作,不过为了获取一个确定的合约地址,需要使用 CREATE2 操作码(opens in a new tab)。 在编写这个代码时,Solidity 还不支持操作码,因此需要手动获取该代码。 目前这已经不再是问题,因为 Solidity 现已支持 CREATE2(opens in a new tab)。1 bytes32 salt = keccak256(abi.encodePacked(token0, token1));2 assembly {3 pair := create2(0, add(bytecode, 32), mload(bytecode), salt)4 } 复制当 Solidity 不支持操作码时,我们可以通过内联汇编(opens in a new tab)来调用。1 IUniswapV2Pair(pair).initialize(token0, token1); 复制调用 initialize 函数来告诉新兑换交易可以兑换哪两种代币。1 getPair[token0][token1] = pair;2 getPair[token1][token0] = pair; // populate mapping in the reverse direction3 allPairs.push(pair);4 emit PairCreated(token0, token1, pair, allPairs.length);5 } 复制在状态变量中保存新的配对信息,并激发一个事件来告知外界新的配对交易合约已生成。1 function setFeeTo(address _feeTo) external {2 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');3 feeTo = _feeTo;4 }56 function setFeeToSetter(address _feeToSetter) external {7 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');8 feeToSetter = _feeToSetter;9 }10}显示全部 复制这两个函数允许 feeSetter 管理费用接收人(如果有)并将 feeSetter 更改为新地址。UniswapV2ERC20.sol本合约(opens in a new tab)实现 ERC-20 流动性代币。 它与 OpenZeppelin ERC-20 合约相似,因此这里仅解释不同的部分,即 permit 的功能。以太坊上的交易需要消耗以太币 (ETH),相当于实际货币。 如果你有 ERC-20 代币但没有以太币,就无法发送交易,因而不能用代币做任何事情。 避免该问题的一个解决方案是元交易(opens in a new tab)。 代币的所有者签署一个交易,允许其他人从链上提取代币,并通过网络发送给接收人。 接收人拥有以太币,可以代表所有者提交许可。1 bytes32 public DOMAIN_SEPARATOR;2 // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");3 bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; 复制此哈希值是这种交易类型的标识(opens in a new tab)。 在这里,我们仅支持带有这些参数的 Permit。1 mapping(address => uint) public nonces; 复制接收人无法伪造数字签名。 但是,可以将同一笔交易发送两次(这是一种重放攻击(opens in a new tab))。 为防止发生这种情况,我们使用了随机数(opens in a new tab)。 如果新 Permit 的随机数不是上次使用的随机数加一,我们认为它无效。1 constructor() public {2 uint chainId;3 assembly {4 chainId := chainid5 } 复制这是获取链标识符(opens in a new tab)的代码。 它使用一种名为 Yul(opens in a new tab) 的以太坊虚拟机汇编语言。 请注意,在当前版本 Yul 中,必须使用 chainid(),而非 chainid。1 DOMAIN_SEPARATOR = keccak256(2 abi.encode(3 keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),4 keccak256(bytes(name)),5 keccak256(bytes('1')),6 chainId,7 address(this)8 )9 );10 }显示全部 复制计算 EIP-712 的域分隔符(opens in a new tab)。1 function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external { 复制这是实现批准功能的函数。 它接收相关字段作为参数,并将三个标量值(v、r 和 s)作为签名(opens in a new tab)。1 require(deadline >= block.timestamp, 'UniswapV2: EXPIRED'); 复制截止日期后请勿接受交易。1 bytes32 digest = keccak256(2 abi.encodePacked(3 '\x19\x01',4 DOMAIN_SEPARATOR,5 keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))6 )7 ); 复制abi.encodePacked(...) 是我们预计将收到的信息。 我们知道随机数应该是什么,所以不需要将它作为一个参数。以太坊签名算法预计获得 256 位用于签名,所以我们使用 keccak256 哈希函数。1 address recoveredAddress = ecrecover(digest, v, r, s); 复制从摘要和签名中,我们可以用 ecrecover(opens in a new tab) 函数计算出签名的地址。1 require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');2 _approve(owner, spender, value);3 }4 复制如果一切正常,则将其视为 ERC-20 批准(opens in a new tab)。外围合约外围合约是用于 Uniswap 的 API(应用程序接口)。 它们可用于其他合约或去中心化应用程序进行的外部调用。 你可以直接调用核心合约但更为复杂,如果你出错,则可能会损失价值。 核心合约只包含确保它们不会遭受欺骗的测试,不会对其他调用者进行健全性检查。 它们在外围,因此可以根据需要进行更新。UniswapV2Router01.sol本合约(opens in a new tab)存在问题,不应该再使用(opens in a new tab)。 幸运的是,外围合约无状态,也不拥有任何资产,弃用外围合约比较容易。建议使用 UniswapV2Router02 来替代。UniswapV2Router02.sol在大多数情况下,您会通过该合约(opens in a new tab)使用 Uniswap。 有关使用说明,您可以在这里(opens in a new tab)找到。1pragma solidity =0.6.6;23import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';4import '@uniswap/lib/contracts/libraries/TransferHelper.sol';56import './interfaces/IUniswapV2Router02.sol';7import './libraries/UniswapV2Library.sol';8import './libraries/SafeMath.sol';9import './interfaces/IERC20.sol';10import './interfaces/IWETH.sol';显示全部 复制其中大部分我们都曾遇到过,或相当明显。 一个例外是 IWETH.sol。 Uniswapv2 允许兑换任意一对 ERC-20 代币,但以太币 (ETH) 本身并不是 ERC-20 代币。 它早于该标准出现,并采用独特的机制转换。 为了在适用于 ERC-20 代币的合约中使用以太币,人们制定出包装以太币 (WETH)(opens in a new tab) 合约。 你发送以太币到该合约,它会为您铸造相同金额的包装以太币。 或者您可以销毁包装以太币,然后换回以太币。1contract UniswapV2Router02 is IUniswapV2Router02 {2 using SafeMath for uint;34 address public immutable override factory;5 address public immutable override WETH; 复制路由需要知道使用哪个工厂,以及对于需要包装以太币的交易,要使用什么包装以太币合约。 这些值是不可修改(opens in a new tab)的,意味着它们只能在构造函数中设置。 这使得用户相信没有人能够改变这些值,让它们指向有风险的合约。1 modifier ensure(uint deadline) {2 require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');3 _;4 } 复制此修改函数确保有时间限制的交易(如果可以,请在 Y 之前执行 X)不会在时限后发生。1 constructor(address _factory, address _WETH) public {2 factory = _factory;3 WETH = _WETH;4 } 复制构造函数仅用于设置不可变的状态变量。1 receive() external payable {2 assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract3 } 复制当我们将代币从包装以太币合约换回以太币时,需要调用此函数。 只有我们使用的包装以太币合约才有权完成此操作。增加流动资金这些函数添加代币进行配对交易,从而增大了流动资金池。12 // **** ADD LIQUIDITY ****3 function _addLiquidity( 复制此函数用于计算应存入币对交易所的 A 代币和 B 代币的金额。1 address tokenA,2 address tokenB, 复制这些是 ERC-20 代币合约的地址。1 uint amountADesired,2 uint amountBDesired, 复制这些是流动资金提供者想要存入的代币数额。 它们也是要存入的代币 A 和 B 的最大金额。1 uint amountAMin,2 uint amountBMin 复制这些是可接受的最低存款数额。 如果在达到最小金额或更高金额时交易无法完成,则会回滚交易。 如果不想要此功能,将它们设定为零即可。流动性提供者指定最低金额,往往是因为他们想要限制交易汇率,使其在与当前汇率接近。 如果汇率波动太大,可能意味着基础价值可能发生改变,流动性提供者需要自己决定采取什么措施。例如,想象汇率是一比一时,流动性提供者指定了以下值:参数值amountADesired1000amountBDesired1000amountAMin900amountBMin800只要汇率保持在 0.9 至 1.25 之间,交易就会进行。 如果汇率超出这个范围,交易将被取消。这种预防措施的原因是交易不是即时的,你提交交易,最后验证者才会将它们包含在一个区块中(除非你的燃料价格非常低,在这种情况下你需要提交另一个具有相同随机数的交易以及更高的燃料价格来覆盖它)。 在提交交易和交易包含到区块中之间发生的事情是无法控制的。1 ) internal virtual returns (uint amountA, uint amountB) { 复制该函数返回流动性提供者应存入的金额,存入该金额是为了让比率等于当前储备金之间的比率。1 // create the pair if it doesn't exist yet2 if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {3 IUniswapV2Factory(factory).createPair(tokenA, tokenB);4 } 复制如果还没有此代币对的兑换交易,则创建一个。1 (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB); 复制获取配对中的当前储备金。1 if (reserveA == 0 && reserveB == 0) {2 (amountA, amountB) = (amountADesired, amountBDesired); 复制如果当前储备金为空,那么这是一笔新的配对交易。 存入的金额应与流动性提供者想要提供的金额完全相同。1 } else {2 uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB); 复制如果我们需要知道这些金额是多少,可以使用此函数(opens in a new tab)获得最佳金额。 我们想要与当前储备相同的比率。1 if (amountBOptimal <= amountBDesired) {2 require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');3 (amountA, amountB) = (amountADesired, amountBOptimal); 复制如果 amountBOptimal 小于流动性提供者想要存入的金额,意味着代币 B 目前比流动性存款人所认为的价值更高,所以需要更少的金额。1 } else {2 uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);3 assert(amountAOptimal <= amountADesired);4 require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');5 (amountA, amountB) = (amountAOptimal, amountBDesired); 复制如果 B 代币的最佳金额大于想要存入的 B 代币金额,意味着代币 B 目前比流动性存款人所认为的价值更低,所以需要更多的金额。 然而,需要存入的金额是最大值,意味着我们无法存入更多金额的 B 代币。 可以选择的另一种方法是,我们计算所需 B 代币数额对应的最佳 A 代币数额。把数值汇总起来,我们就会得到这张图表。 假定你正在试图存入 1000 个 A 代币(蓝线)和 1000 个 B 代币(红线)。 X 轴是汇率,A/B。 如果 x=1,两种代币价值相等,每种代币各存入 1000 个。 如果 x=2,A 的价值是 B 的两倍(每个 A 代币可换两个 B 代币),因此你存入 1000 个 B 代币,但只能存入 500 个 A 代币。 如果是 x=0.5,情况就会逆转,即可存 1000 个 A 代币或 500 个 B 代币。可以将流动资金直接存入核心合约(使用 UniswapV2Pair::mint(opens in a new tab)),但核心合约只是检查自己没有遭受欺骗。因此,如果汇率在提交交易至执行交易之间发生变化,您将面临损失资金价值的风险。 如果使用外围合约,它会计算你应该存入的金额并会立即存入,所以汇率不会改变,你不会有任何损失。1 function addLiquidity(2 address tokenA,3 address tokenB,4 uint amountADesired,5 uint amountBDesired,6 uint amountAMin,7 uint amountBMin,8 address to,9 uint deadline显示全部 复制此函数可以在交易中调用,用于存入流动资金。 大多数参数与上述 _addLiquidity 中相同,但有两个例外:. to 是会获取新流动池代币的地址,这些代币铸造用于显示流动资金提供者在池中所占比率 deadline 是交易的时间限制1 ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {2 (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);3 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB); 复制我们计算实际存入的金额,然后找到流动资金池的帐户地址。 为了节省燃料,我们不是通过询问工厂执行此操作,而是使用库函数 pairFor(参见如下程序库)1 TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);2 TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB); 复制将正确数额的代币从用户帐户转到配对交易。1 liquidity = IUniswapV2Pair(pair).mint(to);2 } 复制反过来,将流动资金池的部分所有权赋予 to 地址的流动性代币。 核心合约的 mint 函数查看合约有多少额外代币(与上次流动性发生变化时合约持有的金额比较),并相应地铸造流动性代币。1 function addLiquidityETH(2 address token,3 uint amountTokenDesired, 复制当流动资金提供者想要向代币/以太币配对交易提供流动资金时,存在一些差别。 合约为流动性提供者处理以太币包装。 用户不需要指定想要存入多少以太币,因为用户直接通过交易发送以太币(金额在 msg.value 中)。1 uint amountTokenMin,2 uint amountETHMin,3 address to,4 uint deadline5 ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {6 (amountToken, amountETH) = _addLiquidity(7 token,8 WETH,9 amountTokenDesired,10 msg.value,11 amountTokenMin,12 amountETHMin13 );14 address pair = UniswapV2Library.pairFor(factory, token, WETH);15 TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);16 IWETH(WETH).deposit{value: amountETH}();17 assert(IWETH(WETH).transfer(pair, amountETH));显示全部 复制为了将以太币存入合约,首先将其包装成包装以太币,然后将包装以太币转入配对。 请注意转账在 assert 中包装。 这意味着如果转账失败,此合约调用也会失败,因此包装不会真正发生。1 liquidity = IUniswapV2Pair(pair).mint(to);2 // refund dust eth, if any3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);4 } 复制用户已经向我们发送了以太币,因此,如果还有任何额外以太币剩余(因为另一种代币比用户所认为的价值更低),我们需要发起退款。撤回流动资金下面的函数将撤回流动资金并还给流动资金提供者。1 // **** REMOVE LIQUIDITY ****2 function removeLiquidity(3 address tokenA,4 address tokenB,5 uint liquidity,6 uint amountAMin,7 uint amountBMin,8 address to,9 uint deadline10 ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {显示全部 复制最简单的流动资金撤回案例。 对于每种代币,都有一个流动性提供者同意接受的最低金额,必须在截止时间之前完成。1 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);2 IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair3 (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to); 复制核心合约的 burn 函数处理返还给用户的代币。1 (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB); 复制某个函数返回多个值时,如果我们只对其中部分值感兴趣,以下便是我们只获取那些值的方式。 从消耗燃料的角度来说,这样比读取那些从来不用的值更加经济。1 (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); 复制将按从核心合约返回代币的路径(低位代币地址优先)调整为用户期望的方式(对应于 tokenA 和 tokenB)。1 require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');2 require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');3 } 复制可以首先进行转账,然后再核实转账是否合法,因为如果不合法,我们可以回滚所有的状态更改。1 function removeLiquidityETH(2 address token,3 uint liquidity,4 uint amountTokenMin,5 uint amountETHMin,6 address to,7 uint deadline8 ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {9 (amountToken, amountETH) = removeLiquidity(10 token,11 WETH,12 liquidity,13 amountTokenMin,14 amountETHMin,15 address(this),16 deadline17 );18 TransferHelper.safeTransfer(token, to, amountToken);19 IWETH(WETH).withdraw(amountETH);20 TransferHelper.safeTransferETH(to, amountETH);21 }显示全部 复制撤回以太币流动性的方式几乎是一样的,区别在于我们首先会收到包装以太币代币,然后将它们兑换成以太币并退还给流动性提供者。1 function removeLiquidityWithPermit(2 address tokenA,3 address tokenB,4 uint liquidity,5 uint amountAMin,6 uint amountBMin,7 address to,8 uint deadline,9 bool approveMax, uint8 v, bytes32 r, bytes32 s10 ) external virtual override returns (uint amountA, uint amountB) {11 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);12 uint value = approveMax ? uint(-1) : liquidity;13 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);14 (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);15 }161718 function removeLiquidityETHWithPermit(19 address token,20 uint liquidity,21 uint amountTokenMin,22 uint amountETHMin,23 address to,24 uint deadline,25 bool approveMax, uint8 v, bytes32 r, bytes32 s26 ) external virtual override returns (uint amountToken, uint amountETH) {27 address pair = UniswapV2Library.pairFor(factory, token, WETH);28 uint value = approveMax ? uint(-1) : liquidity;29 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);30 (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);31 }显示全部 复制这些函数转发元交易,通过许可证机制使没有以太币的用户能够从资金池中提取资金。12 // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****3 function removeLiquidityETHSupportingFeeOnTransferTokens(4 address token,5 uint liquidity,6 uint amountTokenMin,7 uint amountETHMin,8 address to,9 uint deadline10 ) public virtual override ensure(deadline) returns (uint amountETH) {11 (, amountETH) = removeLiquidity(12 token,13 WETH,14 liquidity,15 amountTokenMin,16 amountETHMin,17 address(this),18 deadline19 );20 TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));21 IWETH(WETH).withdraw(amountETH);22 TransferHelper.safeTransferETH(to, amountETH);23 }24显示全部 复制此函数可以用于在传输或存储时收取费用的代币。 当代币有这类费用时,我们无法依靠 removeLiquidity 函数来告诉我们可以撤回多少代币。因此,我们需要先提取然后查询余额。123 function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(4 address token,5 uint liquidity,6 uint amountTokenMin,7 uint amountETHMin,8 address to,9 uint deadline,10 bool approveMax, uint8 v, bytes32 r, bytes32 s11 ) external virtual override returns (uint amountETH) {12 address pair = UniswapV2Library.pairFor(factory, token, WETH);13 uint value = approveMax ? uint(-1) : liquidity;14 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);15 amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(16 token, liquidity, amountTokenMin, amountETHMin, to, deadline17 );18 }显示全部 复制最后这个函数将存储费用计入元交易。交易1 // **** SWAP ****2 // requires the initial amount to have already been sent to the first pair3 function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual { 复制公开给交易者的函数可以调用此函数以执行内部处理。1 for (uint i; i < path.length - 1; i++) { 复制在撰写此教程时,已有 388,160 个 ERC-20 代币(opens in a new tab)。 如果每个代币对都有币对交易所,币对交易所将超过 1500 亿个。 目前,整个链上的帐户数量仅为该数量的 0.1%(opens in a new tab)。 实际上,兑换函数支持路径这一概念。 交易者可以将 A 代币兑换成 B、B 代币兑换成 C、C 代币兑换成 D,因此不需要直接的 A-D 币对交易所。这些市场上的价格往往是同步的,因为当价格不同步时,就会为套利创造机会。 设想一下,例如有三种代币 A、B 和 C。有三个币对交易所,每对代币一个。初始情况交易者出售 24.695 A 代币,获得 25.305 B 代币。交易者卖出 24.695 个 B 代币得到 25.305 个 C 代币,大约获得 0.61 个 B 代币的利润。随后,该交易者卖出 24.695 个 C 代币得到 25.305 个 A 代币,大约获得 0.61 个 C 代币的利润。 该交易者还多出了 0.61 个 A 代币(交易者最终拥有的 25.305 个 A 代币,减去原始投资 24.695 个 A 代币)。步骤A-B 兑换B-C 兑换A-C 兑换1A:1000 B:1050 A/B=1.05B:1000 C:1050 B/C=1.05A:1050 C:1000 C/A=1.052A:1024.695 B:1024.695 A/B=1B:1000 C:1050 B/C=1.05A:1050 C:1000 C/A=1.053A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1050 C:1000 C/A=1.054A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1024.695 C:1024.695 C/A=11 (address input, address output) = (path[i], path[i + 1]);2 (address token0,) = UniswapV2Library.sortTokens(input, output);3 uint amountOut = amounts[i + 1]; 复制获取我们当前处理的配对,排序后(以便与配对一起使用)获得预期的输出金额。1 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0)); 复制获得预期的金额后,按配对交易所需方式排序。1 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to; 复制这是最后一次兑换吗? 如果是,将收到用于交易的代币发送到目的地址。 如果不是,则将代币发送到下一个币对交易所。12 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(3 amount0Out, amount1Out, to, new bytes(0)4 );5 }6 } 复制真正调用配对交易来兑换代币。 我们不需要回调函数来了解交易信息,因此没有在该字段中发送任何字节。1 function swapExactTokensForTokens( 复制交易者直接使用此函数来兑换代币。1 uint amountIn,2 uint amountOutMin,3 address[] calldata path, 复制此参数包含 ERC-20 合约的地址。 如上文所述,此参数是一个数组,因为可能需要通过多个币对交易所将现有资产变为想要的资产。Solidity 中的函数参数可以存入 memory 或者 calldata。 如果此函数是合约的入口点,在由用户(通过交易)直接调用或从另一个合约调用时,那么参数的值可以直接从调用数据中获取。 如果函数是内部调用,如上述 _swap 函数,则参数必须存储在 memory 中。 从所调用合约的角度来看,calldata 为只读变量。对于标量类型,如 uint 或 address,编译器可以为我们处理存储选择,但对于数组,由于它们需要更多的存储空间也消耗更多的燃料,我们需要指定要使用的存储类型。1 address to,2 uint deadline3 ) external virtual override ensure(deadline) returns (uint[] memory amounts) { 复制返回值总是返回内存中。1 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);2 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); 复制计算每次兑换时要购买的代币金额。 如果金额低于交易者愿意接受的最低金额,则回滚该交易。1 TransferHelper.safeTransferFrom(2 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]3 );4 _swap(amounts, path, to);5 } 复制最后,将初始的 ERC-20 代币转到第一个配对交易的帐户中,然后调用 _swap。 所有这些都发生在同一笔交易中,因此币对交易所知道任何意料之外的代币都是此次转账的一部分。1 function swapTokensForExactTokens(2 uint amountOut,3 uint amountInMax,4 address[] calldata path,5 address to,6 uint deadline7 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {8 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);9 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');10 TransferHelper.safeTransferFrom(11 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]12 );13 _swap(amounts, path, to);14 }显示全部 复制前一个函数 swapTokensForTokens,使交易者可以指定自己愿意提供的输入代币的准确数量和愿意接受的输出代币的最低数量。 此函数可以撤销兑换,使交易者能够指定想要的输出代币数量以及愿意支付的输入代币最大数量。在这两种情况下,交易者必须首先给予此外围合约一定的额度,用于转账。1 function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)2 external3 virtual4 override5 payable6 ensure(deadline)7 returns (uint[] memory amounts)8 {9 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');10 amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);11 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');12 IWETH(WETH).deposit{value: amounts[0]}();13 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));14 _swap(amounts, path, to);15 }161718 function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)19 external20 virtual21 override22 ensure(deadline)23 returns (uint[] memory amounts)24 {25 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');26 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);27 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');28 TransferHelper.safeTransferFrom(29 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]30 );31 _swap(amounts, path, address(this));32 IWETH(WETH).withdraw(amounts[amounts.length - 1]);33 TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);34 }35363738 function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)39 external40 virtual41 override42 ensure(deadline)43 returns (uint[] memory amounts)44 {45 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');46 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);47 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');48 TransferHelper.safeTransferFrom(49 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]50 );51 _swap(amounts, path, address(this));52 IWETH(WETH).withdraw(amounts[amounts.length - 1]);53 TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);54 }555657 function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)58 external59 virtual60 override61 payable62 ensure(deadline)63 returns (uint[] memory amounts)64 {65 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');66 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);67 require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');68 IWETH(WETH).deposit{value: amounts[0]}();69 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));70 _swap(amounts, path, to);71 // refund dust eth, if any72 if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);73 }显示全部 复制这四种转换方式都涉及到以太币和代币之间的交易。 唯一不同的是,我们要么从交易者处收到以太币,并使用以太币铸造包装以太币,要么从路径上的最后一个交易所收到包装以太币并销毁,然后将产生的以太币再发送给交易者。1 // **** SWAP (supporting fee-on-transfer tokens) ****2 // requires the initial amount to have already been sent to the first pair3 function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual { 复制此内部函数用于兑换有转账或存储费用的代币,以解决(此问题(opens in a new tab))。1 for (uint i; i < path.length - 1; i++) {2 (address input, address output) = (path[i], path[i + 1]);3 (address token0,) = UniswapV2Library.sortTokens(input, output);4 IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));5 uint amountInput;6 uint amountOutput;7 { // scope to avoid stack too deep errors8 (uint reserve0, uint reserve1,) = pair.getReserves();9 (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);10 amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);11 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);显示全部 复制由于有转账费用,我们不能依靠 getAmountsOut 函数来告诉我们每次转账完成后的金额(调用原来的 _swap 函数之前可以这样做)。 相反,我们必须先完成转账然后再查看我们获得的代币数量。注意:理论上我们可以使用此函数而非 _swap,但在某些情况下(例如,如果因为在最后无法满足所需最低金额而导致转账回滚),最终会消耗更多燃料。 有转账费用的代币很少见,所以,尽管我们需要接纳它们,但不需要让所有的兑换都假定至少需要兑换一种需要收取转账费用的代币。1 }2 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));3 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;4 pair.swap(amount0Out, amount1Out, to, new bytes(0));5 }6 }789 function swapExactTokensForTokensSupportingFeeOnTransferTokens(10 uint amountIn,11 uint amountOutMin,12 address[] calldata path,13 address to,14 uint deadline15 ) external virtual override ensure(deadline) {16 TransferHelper.safeTransferFrom(17 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn18 );19 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);20 _swapSupportingFeeOnTransferTokens(path, to);21 require(22 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,23 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'24 );25 }262728 function swapExactETHForTokensSupportingFeeOnTransferTokens(29 uint amountOutMin,30 address[] calldata path,31 address to,32 uint deadline33 )34 external35 virtual36 override37 payable38 ensure(deadline)39 {40 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');41 uint amountIn = msg.value;42 IWETH(WETH).deposit{value: amountIn}();43 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));44 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);45 _swapSupportingFeeOnTransferTokens(path, to);46 require(47 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,48 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'49 );50 }515253 function swapExactTokensForETHSupportingFeeOnTransferTokens(54 uint amountIn,55 uint amountOutMin,56 address[] calldata path,57 address to,58 uint deadline59 )60 external61 virtual62 override63 ensure(deadline)64 {65 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');66 TransferHelper.safeTransferFrom(67 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn68 );69 _swapSupportingFeeOnTransferTokens(path, address(this));70 uint amountOut = IERC20(WETH).balanceOf(address(this));71 require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');72 IWETH(WETH).withdraw(amountOut);73 TransferHelper.safeTransferETH(to, amountOut);74 }显示全部 复制这些方式与用于普通代币的相同,区别在于它们调用的是_swapSupportingFeeOnTransferTokens。1 // **** LIBRARY FUNCTIONS ****2 function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {3 return UniswapV2Library.quote(amountA, reserveA, reserveB);4 }56 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)7 public8 pure9 virtual10 override11 returns (uint amountOut)12 {13 return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);14 }1516 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)17 public18 pure19 virtual20 override21 returns (uint amountIn)22 {23 return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);24 }2526 function getAmountsOut(uint amountIn, address[] memory path)27 public28 view29 virtual30 override31 returns (uint[] memory amounts)32 {33 return UniswapV2Library.getAmountsOut(factory, amountIn, path);34 }3536 function getAmountsIn(uint amountOut, address[] memory path)37 public38 view39 virtual40 override41 returns (uint[] memory amounts)42 {43 return UniswapV2Library.getAmountsIn(factory, amountOut, path);44 }45}显示全部 复制这些函数仅仅是调用 UniswapV2Library 函数的代理。UniswapV2Migrator.sol这个合约用于将交易从旧版 v1 迁移至 v2。 目前版本已经迁移,便不再相关。程序库SafeMath 库(opens in a new tab)是一个文档很完备的程序库,这里便无需赘述了。数学此库包含一些 Solidity 代码通常不需要的数学函数,因而它们不是 Solidity 语言的一部分。1pragma solidity =0.5.16;23// a library for performing various math operations45library Math {6 function min(uint x, uint y) internal pure returns (uint z) {7 z = x < y ? x : y;8 }910 // babylonian method (https://wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)11 function sqrt(uint y) internal pure returns (uint z) {12 if (y > 3) {13 z = y;14 uint x = y / 2 + 1;显示全部 复制首先赋予 x 一个大于平方根的估值(这是我们需要把 1-3 当作特殊情况处理的原因)。1 while (x < z) {2 z = x;3 x = (y / x + x) / 2; 复制获取一个更接近的估值,即前一个估值与我们试图找到其方根值的数值的平均数除以前一个估值。 重复计算,直到新的估值不再低于现有估值。 欲了解更多详情,请参见此处(opens in a new tab)。1 }2 } else if (y != 0) {3 z = 1; 复制我们永远不需要零的平方根。 1、2 和 3 的平方根大致为 1(我们使用的是整数,所以忽略小数)。1 }2 }3} 复制定点小数 (UQ112x112)该库处理小数,这些小数通常不属于以太坊计算的一部分。 为此,它将数值编码x为 x*2^112。 这使我们能够使用原来的加法和减法操作码,无需更改。1pragma solidity =0.5.16;23// a library for handling binary fixed point numbers (https://wikipedia.org/wiki/Q_(number_format))45// range: [0, 2**112 - 1]6// resolution: 1 / 2**11278library UQ112x112 {9 uint224 constant Q112 = 2**112;显示全部 复制Q112 是 1 的编码。1 // encode a uint112 as a UQ112x1122 function encode(uint112 y) internal pure returns (uint224 z) {3 z = uint224(y) * Q112; // never overflows4 } 复制因为 y 是uint112,所以最多可以是 2^112-1。 该数值还可以编码为 UQ112x112。1 // divide a UQ112x112 by a uint112, returning a UQ112x1122 function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {3 z = x / uint224(y);4 }5} 复制如果我们需要两个 UQ112x112 值相除,结果不需要再乘以 2^112。 因此,我们为分母取一个整数。 我们需要使用类似的技巧来做乘法,但不需要将 UQ112x112 的值相乘。UniswapV2Library此库仅被外围合约使用1pragma solidity >=0.5.0;23import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';45import "./SafeMath.sol";67library UniswapV2Library {8 using SafeMath for uint;910 // returns sorted token addresses, used to handle return values from pairs sorted in this order11 function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {12 require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');13 (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);14 require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');15 }显示全部 复制按地址对这两个代币排序,所以我们将能够获得相应的配对交易地址。 这很有必要,否则就会出现两种可能性,一种是参数 A、B,而另一种是参数 B、A,这导致两次交易而非一次。1 // calculates the CREATE2 address for a pair without making any external calls2 function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {3 (address token0, address token1) = sortTokens(tokenA, tokenB);4 pair = address(uint(keccak256(abi.encodePacked(5 hex'ff',6 factory,7 keccak256(abi.encodePacked(token0, token1)),8 hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash9 ))));10 }显示全部 复制此函数计算两种代币的配对交易地址。 此合约使用 CREATE2 操作码(opens in a new tab)创建,如果我们知道它使用的参数,我们可以使用相同的算法计算地址。 这比查询工厂便宜得多,而且1 // fetches and sorts the reserves for a pair2 function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {3 (address token0,) = sortTokens(tokenA, tokenB);4 (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();5 (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);6 } 复制此函数返回配对交易所拥有的两种代币的储备金。 请注意,它可以任意顺序接收代币并将代币排序,以便内部使用。1 // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset2 function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {3 require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');4 require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');5 amountB = amountA.mul(reserveB) / reserveA;6 } 复制如果不涉及交易费用的话,此函数将返回给您代币 A 兑换得到的代币 B。 此计算考虑到转账可能会改变汇率。1 // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset2 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { 复制如果使用配对交易没有手续费,上述 quote 函数非常有效。 然而,如果有 0.3% 的手续费,您实际得到的金额就会低于此值。 此函数可以计算缴纳交易费用后的金额。12 require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');3 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');4 uint amountInWithFee = amountIn.mul(997);5 uint numerator = amountInWithFee.mul(reserveOut);6 uint denominator = reserveIn.mul(1000).add(amountInWithFee);7 amountOut = numerator / denominator;8 } 复制Solidity 本身不能进行小数计算,所以不能简单地将金额乘以 0.997。 作为替代方法,我们将分子乘以 997,分母乘以 1000,也能取得相同的效果。1 // given an output amount of an asset and pair reserves, returns a required input amount of the other asset2 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {3 require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');4 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');5 uint numerator = reserveIn.mul(amountOut).mul(1000);6 uint denominator = reserveOut.sub(amountOut).mul(997);7 amountIn = (numerator / denominator).add(1);8 } 复制此函数大致完成相同的功能,但它会获取输出数额并提供输入代币的数量。12 // performs chained getAmountOut calculations on any number of pairs3 function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {4 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');5 amounts = new uint[](path.length);6 amounts[0] = amountIn;7 for (uint i; i < path.length - 1; i++) {8 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);9 amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);10 }11 }1213 // performs chained getAmountIn calculations on any number of pairs14 function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {15 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');16 amounts = new uint[](path.length);17 amounts[amounts.length - 1] = amountOut;18 for (uint i = path.length - 1; i > 0; i--) {19 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);20 amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);21 }22 }23}显示全部 复制在需要进行数次配对交易时,可以通过这两个函数获得相应数值。转账帮助此库(opens in a new tab)添加了围绕 ERC-20 和以太坊转账的成功检查,并以同样的方式处理回退和返回 false 值。1// SPDX-License-Identifier: GPL-3.0-or-later23pragma solidity >=0.6.0;45// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false6library TransferHelper {7 function safeApprove(8 address token,9 address to,10 uint256 value11 ) internal {12 // bytes4(keccak256(bytes('approve(address,uint256)')));13 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));14显示全部 复制我们可以通过以下两种方式调用不同的合约:使用一个接口定义创建函数调用使用应用程序二进制接口 (ABI)(opens in a new tab)“手动”创建调用。 这是代码作者的决定。1 require(2 success && (data.length == 0 || abi.decode(data, (bool))),3 'TransferHelper::safeApprove: approve failed'4 );5 } 复制为了与之前的 ERC-20 标准创建的代币反向兼容,ERC-20 调用失败可能有两种情况:回退(在这种情况下 success 即是 false),或者调用成功但返回 false 值(在这种情况下有输出数据,将其解码为布尔值,会得到 false)。123 function safeTransfer(4 address token,5 address to,6 uint256 value7 ) internal {8 // bytes4(keccak256(bytes('transfer(address,uint256)')));9 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));10 require(11 success && (data.length == 0 || abi.decode(data, (bool))),12 'TransferHelper::safeTransfer: transfer failed'13 );14 }显示全部 复制此函数实现了 ERC-20 的转账功能(opens in a new tab),可使一个帐户花掉由不同帐户所提供的额度。12 function safeTransferFrom(3 address token,4 address from,5 address to,6 uint256 value7 ) internal {8 // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));9 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));10 require(11 success && (data.length == 0 || abi.decode(data, (bool))),12 'TransferHelper::transferFrom: transferFrom failed'13 );14 }显示全部 复制此函数实现了 ERC-20 的 transferFrom 功能(opens in a new tab),可使一个帐户花掉由不同帐户所提供的额度。12 function safeTransferETH(address to, uint256 value) internal {3 (bool success, ) = to.call{value: value}(new bytes(0));4 require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');5 }6} 复制此函数将以太币转至一个帐户。 任何对不同合约的调用都可以尝试发送以太币。 因为我们实际上不需要调用任何函数,就不需要在调用中发送任何数据。结论本篇文章较长,约有 50 页。 如果您已读到此处,恭喜您! 希望你现在已经了解编写真实应用程序(相对于短小的示例程序)时的考虑因素,并且能够更好地为自己的用例编写合约。现在去写点实用的东西吧,希望您能给我们惊喜。f上次修改时间: @finereganyue(opens in a new tab), Invalid DateTime查看贡献者本教程对你有帮助吗?是否编辑页面(opens in a new tab)在本页面介绍Uniswap 是做什么的?为什么选择 v2? 而不是 v3?核心合约与外围合约数据和控制流程兑换增加流动资金撤回流动资金核心合约UniswapV2Pair.solUniswapV2Factory.solUniswapV2ERC20.sol外围合约UniswapV2Router01.solUniswapV2Router02.solUniswapV2Migrator.sol程序库数学定点小数 (UQ112x112)UniswapV2Library转账帮助结论网站最后更新: 2024年2月16日(opens in a new tab)(opens in a new tab)(opens in a new tab)使用以太坊查找钱包获取以太币Dapps - 去中心化应用二层网络运行节点稳定币质押ETH学习学习中心什么是以太坊?什么是以太币 (ETH)?以太坊钱包Gas fees以太坊安全和预防欺诈措施什么是 Web3?智能合约以太坊能源消耗以太坊路线图以太坊改进提案 (Eip)以太坊的历史以太坊白皮书以太坊词汇表以太坊治理区块链桥零知识证明测试中心开发者开始体验相关文档教程通过编码来学习设置本地环境生态系统社区中心以太坊基金会以太坊基金会的博客(opens in a new tab)生态系统支持方案(opens in a new tab)以太坊漏洞悬赏计划生态系统资助计划以太坊品牌资产Devcon(opens in a new tab)企业级应用主网以太坊私密以太坊企业级应用关于ethereum.org关于我们工作机会参与贡献语言支持隐私政策使用条款缓存政策联系我们(opens in a new t