imtoken钱包下载新版本|uniswap

作者: imtoken钱包下载新版本
2024-03-07 21:41:40

什么是Uniswap? 史上最全新手导读 - 知乎

什么是Uniswap? 史上最全新手导读 - 知乎切换模式写文章登录/注册什么是Uniswap? 史上最全新手导读达瓴智库Uniswap是一个建立在以太坊上的主流分布式加密交易平台。绝大部分的加密资产交易发生在中心化交易平台,如Coinbase和Binance。这些平台受约束于单一中心机构(运转交易公司),用户需按其要求放入资金,并用传统的订单簿系统来促进交易。订货单交易是买入和卖出订单以其各自的金额呈现在一张表单中。一种资产未完成的买卖订单量被称为“市场深度”。为了在这个系统中促成成功的交易,订单中的一个买单需要匹配相对应的一个卖单,反之亦然。 例如,如果你想在中心化交易平台中以33000美元的价位卖出一个BTC,你需要等到一个买家出现,他希望以这个价格去购买同等或更高价格的BTC。这种系统的主要问题是流动性,在该种情况下,流动性指的是在任何给定时间内订单簿上的订单深度和数量。如果流动性比较低,意味着交易者无法执行他们买卖的订单。从另个角度思考流动性,想象你在一个集市上有一个自己的小摊,如果这个集市很繁忙,不断有人买卖商品,则这个就可以被认为是一个“流动性”。如果这个市场安静并且只有少量交易,则可被认为是个“狭窄的市场”。一、Uniswap是什么?Uniswap 是一种完全不同的交易平台,完全的分布式意味着它自己并不被单一实体所有并且运营,而是运用了名为自动流动性协议的一个全新交易模式。Uniswap平台于2018年建立在全球市值第二大的加密资产项目以太坊区块链之上,这使得它与所有ERC-20Token和基础设施(如MetaMask和MyEtherWallet等钱包服务)兼容。Uniswap也是一个完全开源的项目,意味着任何人都能复制代码去创造他们自己的分布式交易平台。它甚至允许用户免费上新Token。一般中心化交易所都是受利益驱动,并对上新Token收取高额费用。所以这是一个显著的差异。因为Uniswap是一个分布式交易平台(DEX),这也意味着用户任何时候都保持对他们基金的控制,而不是像中心化交易平台那样,要求交易者放弃控制自己的私钥,因此订单可以登录一个内部数据库,而不是在区块链上执行,两者比较起来,中心化交易平台更耗时和成本更高。通过保留对私钥的控制,它消除了交易平台遭到黑客攻击而损失资产的风险。根据2月初的数据,Uniswap是第四大分布式金融(DeFi)平台,在其协议上锁定了价值超过30亿美元的加密资产。二、Uniswap如何工作Uniswap运行在两个智能合约上; 一个“交换”合同和一个“工厂”合同。 这是一种自动计算机程序,当满足某些条件时,它被设计来执行特定的功能。 在这种情况下,工厂智能合约用于向平台添加新的Token,而交换合约为所有Token交换或“交易”提供便利。 任何基于ERC-20的Token都可以在更新的Uniswap v.2平台上与另一个Token交换。 三、自动化的流动性协议Uniswap用自动化流动性协议来解决中心化交易平台的流动性问题。这是通过激励人们在交易平台进行交易,从而成为流动性提供者(LPs)来实现的:Uniswap用户将他们的资金聚集在一起,来创建一个基金,用于执行平台上发生的所有交易。每一个登记的Token都有他们自己的用户可以贡献的基金pool,并且每个Token的价格用由电脑通过数学算法计算出来(在下面解释:Token价格如何决定的)。有了这个系统,买方或卖方不必等待对方出现来完成交易。相反,他们可以立即以已知价格执行任何交易,前提是特定pool中有足够的流动性为其提供便利。作为提供资金的交换,每个参与者都会收到一个Token,代表他们对投资pool的贡献。例如,如果你向一个总计持有10万美元的流动性pool贡献了1万美元,你将获得该流动性pool10%的Token。这个Token可以兑换一部分交易费用。Uniswap对用户在平台上进行的每笔交易收取0.30%的固定费用,并自动将其发送到一个流动性储备中。每当流动性提供者决定退出时,他们就会从准备金中获得相对于其在pool中持股金额的一部分费用。他们收到的Token记录了他们相对应的权益然后被销毁。在Uniswap v.2升级后,引入了一种新的协议费用,它可以通过社区投票来开启或关闭,基本上是将每0.30%的交易费用的0.05%发送给Uniswap基金,为未来的发展提供资金。目前,这一收费选项已被关闭,但一旦开启,这意味着参与者将开始收取pool交易费用的0.25%。四、Token的价格是如何决定的这个系统中另一个重要的原理是每个Token的价格是如何决定的。不同于最高买家和最低卖家决定每种资产的价格的订单系统,Uniswap使用的是自动做市商系统。这个替代的方法是基于供需的一个长期数学公式来调整资产价格。它的工作原理是根据每个资金pool中Token数量的比例来增加和减少Token的价格。这是一个非常重要的点,即每当有人向Uniswap添加一个新的ERC-20Token时,这个人就必须添加一定量的选定的ERC-20Token和等量的另一种ERC-20Token,以启动流动pool。每个Token价格的计算公式是x*y=k,TokenA的数量是X,TokenB的数量是Y,K是常数,是一个不变的数字。例如,Bob 想在Uniswap LINK/ETH资金pool中交易chainlink (LINK)Token, Bob将大量的LINK Token投入资金pool中,从而增加了资金pool中LINK相对ETH的比例。由于K值必须保持不变,这意味着ETH的成本增加,而资金pool中LINK Token的成本降低。因此,Bob LINKToken放入越多,他获取另外的Token就越少。流动性pool的规模也决定了Token价格在交易期间的变化幅度。在一个流动性pool中,资金越多,就越容易实施大规模的操作,而不语导致价格大幅下降。五、套利套利交易者是Uniswap生态系统中一个不可或缺的组成部分。这些交易员擅长发现多个交易平台之间的价格差异,并利用它们来获取利润。例如,如果BTC在Kraken上的交易价格是3.55万美元,Binance的价格是3.45万美元,你可以在Binance上购买BTC,然后在Kraken上出售,从而轻松获利。如果交易量大,就有可能以相对低的风险获得可观的利润。套利交易者在Uniswap上所做的是找到高于或低于其平均价格的Token交易——这是由于大量交易在pool中造成失衡并降低或提高价格的结果——并相应地买卖它们。六、如何使用Uniswap使用Uniswap相对简单,但是,你需要确保你已经有了支持ERC-20的钱包设置,如MetaMask、WalletConnect、Coinbase钱包、Portis或Fortmatic。一旦你有了这样一个钱包,你就需要添加ETH,以便在Uniswap上进行交易并支付GAS费(以太坊交易费用名称)。GAS费在价格方面的变化依赖于正在使用的网络的数量。在通过以太坊区块链进行支付时,大多数ERC-20兼容的钱包服务提供三种选择:慢速、中速或快速。慢速是最便宜的选择,快速是最贵的选择,而中速是介于中间。你交易快慢是靠以太坊网络节点维护者处理交易的速度决定的。翻译:Miki丨达瓴智库校对:Jane丨达瓴智库排版:炯炯丨达瓴智库审核:Amber丨达瓴智库原文:Ollie Leech, P. (2021, Feb 4). What Is Uniswap? A Complete Beginner’s Guide. CoinDesk. https://www.coindesk.com/business/2021/02/04/what-is-uniswap-a-complete-beginners-guide/发布于 2021-12-16 20:10区块链(Blockchain)去中心化金融(DeFi)​赞同 26​​3 条评论​分享​喜欢​收藏​申请

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使用教程 - 知乎

小白课堂 | 一文读懂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 Wallet on iOS and Android

Uniswap Wallet on iOS and Android

Uniswap in your pocket.A wallet built for swappingLearn moreJoin the waitlist for the Uniswap ExtensionJoin the waitlist for the Uniswap ExtensionJoin the waitlist for the Uniswap ExtensionClaim your uni.eth username in the Uniswap mobile app to join the waitlist for the Uniswap Extension.Claim your uni.eth username in the Uniswap mobile app to join the waitlist for the Uniswap Extension.Claim your uni.eth username in the Uniswap mobile app to join the waitlist for the Uniswap Extension.Learn MoreSwap on multiple chains.Supports Ethereum, Optimism, Polygon, Arbitrum, Base, and BNBOne simple wallet.All your NFTs, tokens, and wallets in one placeSafe and secure by design.Built by the most trusted team in DeFi.Swap on multiple chains.Supports Ethereum, Optimism, Polygon, Arbitrum, Base, and BNBOne simple wallet.All your NFTs, tokens, and wallets in one placeSafe and secure by design.Built by the most trusted team in DeFi.Swap on multiple chains.Supports Ethereum, Optimism, Polygon, Arbitrum, Base, and BNBOne simple wallet.All your NFTs, tokens, and wallets in one placeSafe and secure.Built by the most trusted team in DeFi.ResourcesDownload SupportOther ProductsSwap TokensExplore NFTsLearn moreBlogDiscordTwitter© 2024 – Uniswap Privacy PolicyTrademark PolicyResourcesDownload SupportOther ProductsSwap TokensExplore NFTsLearn moreBlogDiscordTwitter© 2024 – Uniswap Privacy PolicyTrademark PolicyResourcesDownload SupportOther ProductsSwap TokensExplore NFTsLearn moreBlogDiscordTwitter© 2024 – Uniswap Privacy PolicyTrademark Policy

Uniswap

wap+

Subscribe

* indicates required

Email Address *

First Name

What do you want to learn about? *

Liquidity Provisioning

Grants

Developer tools

Community / Hackathon information

Governance Information

All of the above

DEVELOPERSGRANTSOUR APPROACHOPPORTUNITIESFUNDED GRANTSAPPLY FOR GRANTEVENT SPONSORSHIPSGOVERNANCERESOURCESUNISWAP 101RESEARCHLIQUIDITYFAQEVENTSBLOGHACKER HOMEINTEREST FORMBuilders, Liquidity Providers, and DelegatesContribute to Uniswap V4 This SummerLearn howTaglineMedium length section heading goes hereLorem ipsum dolor sit amet, consectetur adipiscing elit. 01Feature oneFeature oneShort heading goes hereLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.02Feature twoFeature twoShort heading goes hereLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.03Feature threeFeature threeShort heading goes hereLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.04Feature fourFeature fourShort heading goes hereLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.You'rebuildingsomthingcool.How can we help?What is the Uniswap Protocol?The Uniswap Protocol is a set of persistent, non-upgradable smart contracts that allows its users to provide liquidity, create liquidity pools, and swap between tokens in a liquidity pool without requiring any trusted intermediaries or a central authority. It is an implementation of a noncustodial automated market maker (AMM), in that it pools liquidity and makes pooled tokens available at a price set by an algorithm.Uniswap is built to prioritize censorship resistance, permissionlessness, security, decentralization, and self-custody. It makes market creation, liquidity creation, and swapping accessible, fair, and secure to all.How Can I Participate?You can participate by applying for a grant, participating in governance, or joining the community.Grants. The Uniswap Foundation (UF) provides grant funding to contributors. If you have an idea that fits into a category of focus, you can apply for a grant. Alternately, take a look at our RFPs.Participate. You can shape the future of the protocol by participating in governance.Community. Join the community on Discord, or find us at an event around the world.what is the foundation's mission?The Uniswap Foundation supports a community of individuals and organizations dedicated to a more open, fair and decentralized financial system. We achieve this mission through three pillars of work:1. Growth2. Innovation3. StewardshipBy providing funding and resources for the continued development and growth of the DeFi ecosystem, the Uniswap Foundation seeks to contribute to the advancement of the DeFi space as a whole, driving greater accessibility, efficiency, and transparency in the financial industry.You'rebuildingsomthingcool.How can we help?Grant Funded ProjectsVIEW ALL FUNDED PROJECTSCommunity GrowthUniswap v3 Development CourseThis Decentralized Exchange Development Course is a comprehensive educational program for developers interested in learning about DEXs and Solidity by building a Uniswap V3 clone from scratch.CompletedGovernance StewardshipAgoraAgora is developing a governance app to improve participation and decision-making through features such as liquid delegation, delegate platform pages, and a governance activity feed.CompletedCommunity GrowthUCSB-ECON DeFi SeminarThis grant supports the hosting of research seminars at UC Santa Barbara. These seminars aim to build a community of academics and to bring together academics with an interest in DEX and DeFi research.CompletedWhat Grantees are SayingThrough hackathons, bounties, and the UniswappeR grant, we’ve been able to share insights into blockchain data and help develop tools that have enhanced the usability of the Uniswap protocol and enable easier data driven decision making for the Uniswap community.Lawrence, Omni Analytics[The UF’s] support elevated my work in DeFi, giving me the confidence to explore new solutions and opportunities. This initial support was crucial in helping me develop Panoptic, my new protocol inspired by the ideas from my grant, and will continue shaping my future work.Guillaume, PanopticWorking with the UF has been a an amazing experience. UF is very clear on the protocol's needs when drafting RFPs and seeking work done, but simultaneously open to feedback from external parties with different points of view.Omer, Chaos LabsThrough hackathons, bounties, and the UniswappeR grant, we’ve been able to share insights into blockchain data and help develop tools that have enhanced the usability of the Uniswap protocol and enable easier data driven decision making for the Uniswap community.Lawrence @Omni Analytics[The UF’s] support elevated my work in DeFi, giving me the confidence to explore new solutions and opportunities. This initial support was crucial in helping me develop Panoptic, my new protocol inspired by the ideas from my grant, and will continue shaping my future work.Guillaume Lambert @PanopticWorking with the UF has been a an amazing experience. UF is very clear on the protocol's needs when drafting RFPs and seeking work done, but simultaneously open to feedback from external parties with different points of view.Omer @Chaos LabsThrough hackathons, bounties, and the UniswappeR grant, we’ve been able to share insights into blockchain data and help develop tools that have enhanced the usability of the Uniswap protocol and enable easier data driven decision making for the Uniswap community.Lawrence @Omni Analytics[The UF’s] support elevated my work in DeFi, giving me the confidence to explore new solutions and opportunities. This initial support was crucial in helping me develop Panoptic, my new protocol inspired by the ideas from my grant, and will continue shaping my future work.Guillaume Lambert @PanopticWorking with the UF has been a an amazing experience. UF is very clear on the protocol's needs when drafting RFPs and seeking work done, but simultaneously open to feedback from external parties with different points of view.Omer @Chaos Labs

Frequently Asked QuestionsWhat is the Uniswap Foundation?The Uniswap Foundation is a Delaware non-profit organization founded in 2022 whose mission is to support the growth, decentralization, and sustainability of Decentralized Finance. Its funding was provided by the Uniswap community, through the approval of a community governance proposal. ‍It supports the DeFi community through three pillars of work:1. Growth. The Foundation provides financial resources and other support to developers and community members working on tools and initiatives to increase usage of DeFi.2. Innovation. We fund researchers on the bleeding edge of crypto to seek ways to make DeFi more accessible.3. Stewardship. We provide resources and support initiatives that drive long term DeFi sustainability.What is UF’s relationship with the rest of the Uniswap ecosystem?The Uniswap Foundation operates independently from other entities working within the Uniswap ecosystem, including Uniswap Labs and other organizations that are working to advance the goals of decentralized finance.What is the best way to get in touch with the UF?There are several ways to reach us:Jump in Discord and head to the #grants channel. We have a team member staffed to answer questions and help route you to the right person.Follow us on Twitter, or message us there.Lastly, if you are a potential grantee building an open source, public good project on Uniswap, you can sign up for office hours here.view allJoin our mailing listKeep In TouchSIGN ME UPPrivacy policyTerms of usework with us+

Subscribe

* indicates required

Email Address *

First Name

What do you want to learn about? *

Liquidity Provisioning

Grants

Developer tools

Community / Hackathon information

Governance Information

All of the above

目前大火的去中心化交易所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入门 - 廖雪峰的官方网站

廖雪峰的官方网站

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: 30455 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

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深度科普 - 知乎

Uniswap深度科普 - 知乎首发于区块随笔切换模式写文章登录/注册Uniswap深度科普JasonW喜欢思考,博而不专的铲屎官一、Uniswap是什么首先要搞清楚一下相关的名词都是什么意思: Uniswap Labs:负责开发Uniswap协议、网络接口的公司。 The Uniswap Protocol:一个实现自动化做市商的智能合约全家桶,促进点对点做市和以太坊上ERC-20 token的交易的协议(即Uniswap核心技术,后续工作原理介绍也都是针对协议内容的解释)。 The Uniswap Interface:为了方便使用Uniswap protocol而开发的网络接口,是与Uniswap protocol交互的众多方式之一(也可以直接与智能合约交互)。 Uniswap Governance:一个Uniswap Protocol的民主治理系统(社区式治理方式,论坛模式)。 按官网上的介绍,Uniswap协议是一个用来在以太坊区块链上交易加密货币(ERC-20代币)的点对合约系统。这个协议通过一个持久化、不可更改的智能合约集合来实现,旨在优先考虑抗审查性、安全性、自我监管,以及在没有任何可能有选择地限制访问的可信中介的情况下运行。简单点说就是通过智能合约实现了一个去中心化的ERC-20代币的自动交易系统。 二、Uniswap的发展历程Uniswap诞生至今也不过两年多的时间,但是却创造了很多令人惊叹的记录: 2018年11月2日,Uniswap公开宣布上线并部署到以太坊主网,推出第一个版本Uniswap v1,但这个v1版本只能算是一个新型去中心化交易方式的概念验证,可实用性并不强。 2020年1月31日,经过1年多的沉淀,Uniswap锁仓金额突破5000万美元,成为DeFi龙头。 2020年5月19日,Uniswap v2版本上线,增加了自由组合交易对、价格预言机、闪贷、最优化交易路径等功能,对v1版本进行了全面的技术升级。 2020年7月27日,Uniswap 24小时交易额突破1亿美元,DeFi在2020年迎来爆发式增长。 2020年8月7日,Uniswap官方宣布已完成1100万美元的A轮融资,由Andreessen Horowitz领投。 2020年8月31日,Uniswap锁仓金额突破10亿美元。 2020年9月1日,Uniswap总交易量超过100亿美元。 2020年9月3日,Uniswap锁仓金额突破20亿美元,距离10亿美元仅仅过了3天,可见市场之火爆。 2020年9月17日,Uniswap宣布其协议治理代币UNI已在以太坊主网上发布,针对每一个之前使用过Uniswap protocol的区块链地址空投400个UNI,UNI的持有者有对平台新的发展及改变的提议的投票权。 2021年5月5日,Uniswap v3版本上线,提供了集中流动性、多重收费层次、高级价格预言机、流动性预言机等技术升级,核心是提升资本效率,具体实现可关注另一篇文章Uniswap v3 设计详解。有一点需要注意的是v1和v2版本都遵循的是GPL开源协议,但是v3使用的是Business Source License 1.1(有效时限GPL-2.0或更高版本的许可证)。该许可证将v3源代码在商业环境或生产环境中的使用期限限制为两年,届时它将永久转换为GPL许可证。 三、Uniswap中的自动化做市商(Automated Market Maker,AMM)在开始介绍Uniswap之前,先来说说中心化的交易所是怎么交易的在传统中心化交易所中,你以一个价格发出买单,系统会在order book寻找合适的卖单进行撮合成交,如果当前没有合适的对手单,则将新的订单暂存到order book中等待合适的对手单出现。这个order book以中心系统的形式保存了所有买单、买单的信息,如下图所示:Order Book在这种中心化交易平台上,每笔交易的撮合并不需要通过区块链,而是在中心化系统中实现,保证了交易的高并发和低时延,但如果平台上的买卖双方不够活跃,用户发出的买单或者卖单无法快速找到交易对手方进行撮合,就会出现长时间的挂单,交易效率低下,这时就出现了做市商。 做市商是指在传统证券市场上,由具备一定实力和信誉的独立证券经营法人作为特许交易商,不断向公众投资者报出某些特定证券的买卖价格(即双向报价),并在该价位上接受公众投资者的买卖要求,以其自有资金和证券与投资者进行证券交易。买卖双方不需等待交易对手出现,只要有做市商出面承担交易对手方既可达成交易。 做市商通过做市制度来维持市场的流动性,满足公众投资者的投资需求。做市商通过买卖报价的适当差额来补偿所提供服务的成本费用,并实现一定的利润,但是在中心化平台中,买方/卖方并不确定做市商是否真的在区块链上有实际的资产(账户中的余额只是中心化数据库中的一个数字),而且用户的资产都保存在中心化交易所的钱包里,自己没有绝对的控制权,而中心化交易所也没接受任何监管机构的监管,很容易发生监守自盗的事件。 基于以上种种弊端,Uniswap提出了一种通过智能合约实现自动化做市商(Automated Market Maker,AMM)来与用户进行交易的去中心化交易协议,用户资产完全由自己控制,而智能合约中锁定的做市资产也是公开可查,是一种更安全透明的交易方式。 四、Uniswap技术原理由于v1版本主要是概念验证,因此不做过多介绍,主要就v2版本来深度解析一下Uniswap的技术原理,关于v3版本的更新,会再单独解析。 AMM要实现能自动跟买方/卖方完成交易,需要满足几个特性: AMM要持有资产,由于要做双向报价,所以要持有两种资产; AMM资产池要能充值/提现; AMM可以根据市场情况自动调整价格; AMM要能通过交易赚取利润; 接下我们通过核心的几个智能合约来看看Uniswap是怎么实现AMM的这些特性的: UniswapV2ERC20: 由于Uniswap是部署在以太坊上的,而且支持的是同质化代币之间的交易,因此可交易的资产是符合ERC-20标准的Token.我们在开发去中心化应用时,通常是通过智能合约来执行交易,当我们需要从一个用户的地址中转移一部分token到另外一个地址时,需要地址拥有者授权,就像是你用微信发红包,直接关联到银行卡的话,就需要你先授权微信可以从你的银行账户里划款一样,用户要先给银行一个指令,告诉银行可以给微信授权一个额度,然后微信才能去用户在银行的账户中划款,如下图所示: 如果在区块链上实现一个去中心化的微信应用,分别用智能合约来实现微信(DApp)和银行(ERC-20)的功能,那么用户就要发起至少两次链上交易,如下图中红色部分所示,第1步owner授权DApp可以从他的地址提取100个token,第3步DApp智能合约执行交易从ERC-20合约中owner的地址余额提取不多于100个的token,也就是要消耗两次gas手续费,极大地提升了链上交易的成本。 为了降低成本,减少链上交易次数,可以通过链下签名授权的方式来实现第1步的授权,首先owner在链下进行对某个DApp授权操作的签名,一起发给DApp的智能合约,在DApp智能合约中发起的ERC-20的执行交易中,先验证授权签名(permit),然后再调用提取函数(withdraw)进行owner账户下的token提取,这样整个交易流程就只有一个链上交易,只需要消耗1次gas。 作为Uniswap核心合约之一,UniswapV2ERC20合约定义了Uniswap中所有交易资产的标准。 UniswapV2Pair: 有了资产之后就到了最核心的交易部分了,Uniswap提供了一个UniswapV2Pair的智能合约来交易任意两种ERC-20代币,pairs交易对主要提供三个功能: 流动性追踪,追踪交易池中的代币余额,并且提供流动性代币;自动做市,根据特定算法自动计算出来的价格来撮合交易;去中心化预言机,暴露相关数据给外部使用;接下来分别详细介绍一下这几个功能:流动性追踪首先我们也是要先了解几个重要概念: 流动性:指的是pair合约里的两种ERC-20代币的总和,如果同时质押两种代币,则称为增加(提供)流动性 流动性池(Pool):所有流动性汇集成的池子,即AMM的资产池,Uniswap协议通过流动性池提供个人对合约的交易撮合 流动性提供者(Liquidity Provider/LP):向流动性池中提供流动性的人 流动性代币(Pool Token也叫做Liquidity Token):UniswapV2Pair本身也是一种ERC-20合约,它的代币用来代表流动性供给,即为流动性代币,在LP提供流动性时自动增发(mint)代币给LP,提取流动性时燃烧(burn)LP的代币 流动性池份额(Liquidity Pool Share/LPS):计算出来代表所占有的流通的流动型代币的份额值,用来记录每个LP的流动性贡献比例 在初始化一个pair合约之后,其中两种代币的初始值都是0,为了使流动性池可以开始促成交易,必须有流动性提供者(LP)质押一定量的两种代币来启动流动性池,第一个LP就是设定这个流动性池初始价格的人,并且获得流动性池份额(LPS)。流动性池中两种代币的相对价格是通过池子中两种代币的数量比来决定的,直观的理解就是两种代币的总价值是相同的,每次交易完之后由于两种代币的数量会发生变化,相对价格也会变化,价格的调整遵循如下公式:x和y代表两种代币的数量,具体在交易中这个公式发挥的作用会在后文详细介绍。如果第一个LP初始化质押的两种代币量分别为x_0和y_0,则获得的流动性池份额(Liquidity Pool Share/LPS)为s_0: 使用几何平均数计算的好处是可以使LPS在任何时候都不受质押的两种代币的比例影响,因为两种代币在流动性池中的比例可能与市场价格不符。如下图所示,假设初始质押量为x_0 = 100,000,y_0 = 1,000,则s_0 = 10,000,在LP质押完X和Y代币之后会收到10,000LPS,此时s_current也同样是10,000,相当于第一个LP持有100%的LPS(除去锁定到零地址的LPS),Liquidity Pool中当前Y相对于X的价格为 1 Y = 100000 / 1000 X = 100 X,例如X是USDT,Y是ETH的话,那么 1 ETH = 100 USDT。*:Output数据在第一次质押中实际会做一些调整,这部分的介绍在Uniswap白皮书中比较简略,但其实蕴含的内容和机制还是较多较复杂,接下来深入挖掘一下。 按照LPS初始值的计算公式,一个LPS的价值不会低于Pair中两种质押代币的几何平均数,而且随着交易手续费的积累或者“捐赠”会使LPS的价值升高,因为交易手续费在流动性池中积累,针对这部分手续费并不会产生新的LPS,效果就是池子变大了,但是LPS总量没变,两者的比值即LPS的价值就升高了。 Pair智能合约对应的LPS是有18位小数的(以太坊中最大的小数位数),理论上有一种情况是LPS的最小量(即1e-18 LPS)价值非常大,导致后续小流动性提供者很难再提供流动性了,因为提供流动性的成本太高了,例如1e-18 LPS = $100的话,因为这个是最小单位了,所以要增加流动性就至少质押$100美金才能获得LPS,而且随着LPS增值,流动性成本越来越高,不利于维持交易的流动性。在Uniswap白皮书中把这种极端情况认为是一种可能的人为攻击,为了提高这种攻击的成本,在新创建流动性池的时候,设置了一个最小流动性值MINIMUM_LIQUIDITY=1e-15,即LPS最小单位的1000倍,任何流动性池在启用之初都要在零地址中锁定1e-15的LPS,所以上面流动性池初始化的图修订后为: 在这种机制之下,如果人为把LPS价值提升到1e-18 = $100的话,就需要在零地址中锁定价值 $100 * 1e3 = $100000 的LPS,这样就极大地提升了攻击成本,而且在通常情况下,1e-15的LPS的价值是很小的,甚至可以忽略,所以修订图中第一次质押后获得的LPS虽然要减少1e-15LPS,但约等于10000不变。当然也会有极端情况,例如Pair中质押的两种代币都没有小数,而且单价很高,那么1e-15LPS的价值还是可以感知到的,不过这种类型的代币也不太适合用Uniswap协议来交易。接下来如果有LP继续添加流动性,则按新增的流动性等比例增发LPS,假设当前Pool中X的量为x_current,Y的量为y_current,存量LPS为s_current,新增加的流动性中的X为x_add,Y为y_add(通常情况下x_current/y_current = x_add/y_add,即等比例增加流动性),则新增发的LPS为s_add:如下图所示,增加2000 X和20 Y之后,获取200 LPS,此时LPS都在各个LP自己的地址里,他们可以自由转账,流动性池里只是记录了目前LPS总量的值。通常情况下,LP会按照目前流动性池里的X和Y的比例来增加流动性,获取LPS,Uniswap也提供了周边辅助性智能合约来完成增加流动性的操作,如果新质押的X和Y比例与流动性池中不一样,会按照少的代币量等比例质押,另一种多出来的不会去质押,避免损失,如果是直接去操作Pair合约,需要自己校验,否则还是按少的代币量计算LPS,但另一种多出来的就不会返还了,当是捐赠了。 如果是减少流动性,例如减少LPS为s_remove,存量X为x_current,Y为y_current,LPS为s_current,则LP可以提出去的两种代币量分别为x_withdraw和y_withdraw: 整个流动性相关还会涉及到协议手续费的问题,默认是不收取的,此处暂不讨论。 自动做市Uniswap的流动性池是通过一个恒定乘积公式来计算价格的,以x和y来代表流动性池中两种ERC-20代币(假设为X和Y)的数量,则: 如果我们想要用X从流动性池中交换Y,假设输入X的量为deltaX,交易换回的Y为deltaY,在交易池中的资产足够的前期下,满足: 也就是说交易前后,流动性池中两种代币的乘积是恒定不变的,基于此,如果交易的量相对于流动池中的量很小的话,那么交易价格就近似为当前两种代币的比: 在实际交易过程中,还会有0.3%的交易手续费,扣除方式是先扣掉手续费,再利用乘积公式进行计算,由于最终兑换出来的交易数量是跟交易量有关的,因此实际交易价格并不等于当前两种代币的比例,而且同一个区块里可能会有多笔交易,同一区块里前面的交易对后续的交易也都会有一定的影响,我们来看一下单笔交易的过程: 如上图所示,原流动性池中两种代币余额为100 X和1 Y,可认为Y相对X的价格为1 Y = 100 X,此时要通过流动性池交易20个X,如果按照当前价格全量交易的话,应该换回 20 / 100 = 0.2 个Y,再减去0.3%的手续费,最后返回0.1994个Y,但实际返回了0.1658个Y,我们来逐步分析一下: 输入20个X,先扣除0.3%的手续费,即实际交易量为19.94个X; 按照x*y=k(k=1*100=100)的公式进行计算: 3. Uniswap pair会给交易者地址返回0.1658个Y,此交易平均交易价格为 1 Y = 20 / 0.1658 X = 120.6273 X,比交易开始时的100要高20%多,主要是因为交易量20个X相对于流动性池的比例较大(20%),相当于大额交易,对价格会产生较大影响,直观的感觉就是市场上有人大量买入Y,从而导致Y的价格上涨,此笔交易之后,交易池中Y相对于X的价格变为了143.8504; 4. 更新流动性池中的余额,虽然交易的时候扣掉了0.3%的手续费,但实际上这个手续费依然会放到流动性池中,作为流动性提供者的收益,因此X更新后的余额还是100 + 20 = 120,Y的余额是1 - 0.1658 = 0.8342,Liquidity Shares的值不变。随着交易手续费的积累,整个流动性池的总价值在上涨,LPS的总量不变,则LPS的单位价值上涨。 以上基本介绍了Uniswap协议中的最核心设计原理,涉及到了流动性池和AMM机制等,除了基础核心部分,Uniswap还提供了一些高级功能,同时也带来了一些很有挑战性的问题,包括上文提到的手续费的收取、滑点损失、无偿损失等LP收益相关问题等等,都是很值得深入挖掘和探讨的。编辑于 2022-01-20 10:22去中心化金融(DeFi)Uniswap​赞同 161​​16 条评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录区块随笔区块链技术领域的一些知

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