Skip to content

第九章:浅谈自动做市协议

ch09

Q链支持智能合约后,生态内迅速涌现出一大批应用(DApp),其中最具代表性的便是各类代币相关应用。其中包括形态多样的FT代币,例如稳定币和模因币,还有各种NFT数字藏品。尤其是FT领域,凭借其可分割、可互换的特性,一度呈现出极为繁荣的发展态势。而随着FT代币种类和流通量的激增,如何在链上安全、高效地实现不同FT代币之间的直接兑换,自然成为了生态发展中亟待解决的核心问题。

初期市场上曾出现过多种代币兑换方案,但尚未形成统一标准。直到一款名为QSwap的代币交换协议横空出世,凭借其创新机制迅速成为行业标杆,彻底改变了此前分散的局面。此后,众多项目纷纷效仿QSwap的模式推出各类Swap协议,直接推动了去中心化金融(DeFi)应用在Q链生态内的爆发式增长。

本章将介绍QSwap协议的基本工作原理,包括其核心的自动做市机制、流动性池的构建逻辑,以及用户如何通过协议完成代币兑换、添加/移除流动性等关键操作。同时也会简要说明协议如何通过手续费分配等设计吸引流动性提供者,以及无偿损失和三明治攻击等潜在风险,为后续理解Q链DeFi生态的运行逻辑奠定基础。

订单账簿

在介绍自动做市协议之前,我们先来看“非自动”做市的工作逻辑及存在的问题。我们知道,传统交易所采用的是经典的订单簿(Order Book)模型:卖家根据预期价格挂出限价卖单,买家根据目标价格挂出限价买单,这些限价单(Limit Order)会由交易所实时记录,并按“买单价高优先、卖单价低优先”的规则排序,最终形成直观的订单簿。比如下面这张表格,就展示了一个模拟的订单簿形态,能清晰看到不同价格对应的委托数量:

卖单数量价格价格数量买单
卖三40102
卖二45101
卖一50100
9955买一
9860买二
9765买三

当卖家挂出的卖单价格与买家挂出的买单价格一致,或卖单价格低于买单价格时,系统就会自动撮合双方成交,成交价格也由此产生。当然,着急完成交易的用户也可以挂市价单(Market Order)。挂市价卖单会以当前市场最优的买单价格(即买一价)优先成交,挂市价买单则会以当前市场最优的卖单价格(即卖一价)优先成交,以此尽可能快速吃掉对手方的挂单。我们把市场中这种“能快速促成交易、让资产顺利买卖的能力”叫做流动性(Liquidity)。

订单簿系统本身是能正常运转的,但它存在一个关键问题:当市场参与者较少时,容易出现流动性不足的情况,导致订单难以快速成交甚至无法成交。所以为解决这一问题,就出现了做市商(Market Maker),他们会主动在订单簿上同时挂出买单和卖单,为市场提供流动性。当其他用户吃掉他们的挂单时,做市商通过“低价位买入、高价位卖出”的差价,来赚取利润。

最先在Q链上出现的,就是采用订单簿模型的智能合约,这些合约和传统交易所逻辑类似,支持用户挂单、系统撮合交易。但受限于Q链早期生态中用户数量较少、交易活跃度不足的情况,这类合约逐渐暴露了明显问题:一方面,订单簿常因流动性不足导致成交缓慢,甚至出现用户挂单后长期无法匹配的情况;另一方面,智能合约的燃料费成本叠加订单簿模型的操作复杂度,也降低了普通用户的使用意愿。最终,这类订单簿模型的智能合约并未在Q链生态中流行起来。为了更好适配区块链去中心化环境下的交易需求,以QSwap为代表的自动做市商模型应运而生,这也是本章剩余部分将要重点讲解的内容。

这里有个小情况要跟读者提前说下:一个坏消息和一个好消息。坏消息是,之前我们一直尽量避开公式,力求用通俗的语言讲清楚内容,但这一章涉及自动做市商的核心逻辑,公式确实无法避免;不过好消息是,这一章会出现的公式都非常基础,只要会四则运算就能懂,完全不用担心理解门槛,跟着步骤走很容易看明白。

自动做市

如前所述,传统交易所普遍采用订单簿机制,价格由市场中的买家与卖家共同决定。买家挂出“买单”、卖家挂出“卖单”,系统通过匹配供需双方的报价完成交易,本质是“人与人”之间的价格博弈。

而QSwap则完全不同,它基于恒定乘积自动做市(Constant Product Market Maker,简称CPMM)协议运行。这是自动做市(Automated Market Maker,简称AMM)协议的典型类型,核心特点是用数学公式替代人工定价。交易价格无需依赖买卖双方手动报价,而是由资金池中两种代币的数量关系自动计算得出。为了更直观地和中心化交易所做类比,我们通常会把这类基于区块链运行的链上兑换协议称为去中心化交易所(Decentralized Exchange,简称DEX)。

虽然听起来有些高深莫测,但它的底层逻辑其实很直观。只要掌握基本的乘法和除法,就能理解代币数量与价格之间的联动关系,轻松搞懂其运作原理。简单来说,如果我们想在两个代币(用大写字母X和Y来表示)之间做兑换(Swap),就建立一个存放X和Y的池子。如果你想从池子里拿走X就必须放入Y,反之亦然。兑换的时候,必须保证X和Y的数量的乘积保持不变。比如说有个池子,X(蓝)和Y(绿)的数量都是100,此时乘积是10000,如下图(中间)所示:

pool

如果我们想从池子购买60个Y,需要花多少个X呢?答案是150个,因为我们必须保证池子里X和Y的数量的乘积不变,如上图(左)所示。那么回到初始状态,如果我们想卖给池子100个Y,可以拿到多少个X呢?答案是50,如上图(右)所示。是不是很简单?我们用小写字母x和y分别来代表池子里X和Y的数量,用k来代表数量的乘积,那么恒定乘积约束可以用下面的公式表示:

x×y=k

那么这个公式起到了什么作用呢?答案是,给X和Y定价。给定一个池子的状态,也就是知道x和y的值,我们可以用Y来给X定价。我们用大写字母P来表示价格,写成公式是:

Px=yx

比如说上图所示的三个池子,X(蓝)的价格从左到右逐渐升高,分别是40÷250=0.16100÷100=1200÷50=4。反过来也一样,我们也可以用X来给Y定价,写成公式是:

Py=xy

还是上图所示的三个池子,Y(绿)的价格从左到右逐渐降低,分别是250÷40=6.25100÷100=150÷200=0.25。不难看出,X和Y的价格互为倒数,下面是证明:

Px×Py=yx×xy=1

为更清晰地查看池内X与Y的数量、恒定乘积k,以及X和Y各自的价格,我们将上方图表转化为表格形式,具体如下:

X数量Y数量kX价格Y价格
2504010,0000.166.25
10010010,0001.001.00
5020010,0004.000.25

恒定乘积公式简单粗暴地模拟了市场的供需关系,但却非常好使。想象一下,在现实生活中,物以稀为贵,某种东西越多就越不值钱。而池子也是这样,里面某种代币越多,我们购买它就越便宜。在我们的生活中,商品是靠市场来定价的。在CPMM池子里,代币靠公式定价。

通过恒定乘积不仅可以直接推导出价格公式,稍作修改就可以得到买卖的公式。假设我们想卖给池子一定数量的X,可以拿到多少数量的Y呢?兑换后,池子里的X多了一点,数量用 Δx 表示;Y少了一点,数量用 Δy 表示。根据恒定乘积公式可知:

(x+Δx)×(yΔy)=kyΔy=kx+ΔxΔy=ykx+Δx

反过来,如果我们想从池子里买一定数量的X,需要给池子多少Y呢?根据恒定乘积公式可知:

(xΔx)×(y+Δy)=ky+Δy=kxΔxΔy=kxΔxy

以上两个公式是通过X算Y,反过来通过Y算X也是一样的,这里就不列公式了,留给读者作为练习。

价格滑点

恒定乘积法看起来挺美的,简单、有效,但是却有一个很大的问题。我们继续前面的例子,假设上面这个XY池子处于初始状态,也就是X和Y的数量都是100,此时X和Y的价格都是100÷100=1。如果我们卖给池子100个Y,能拿到多少X呢?上一小节已经给出了答案:50。好的,那我们最终以什么样的价格向池子出售了Y呢?简单计算一下就可以知道:

pΔy=ΔxΔy=50100=0.5

我们本来打算以价格1.0向池子出售100个Y,但实际上最终的成交价格却是0.5,比预期价格低了50%!反过来,当我们从池子里购买Y的时候,价格也会比预期贵一点。这种向池子兑换某种代币时成交价格和预期价格之间的差异叫做价格滑点(Slippage)。这肯定不是用户所希望的,但是我们无法避免这个问题。

那怎么才能缓解这个问题呢?简单思考一下就知道,池子里X和Y的数量越多,即k越大,滑点就越低。这和我们现实生活中的市场也是吻合的,如果市面上有大量苹果,即使你买一点也不会影响苹果的价格。假如池子里分别有一千个X和Y,根据前面的介绍可知,出售100个Y可以得到X的数量为:

(xΔx)×(y+Δy)=k(1000Δx)×(1000+100)=1000×10001000Δx=1000×10001100Δx=10001000×1000110090.90

可以看到,我们大约能拿到90.9个X,相比于之前,滑点大幅降低了。于是,要想缓解滑点过高、价格不稳定等问题,只要让池子足够大就行。池子越大,代币交易时的价格冲击越小,兑换体验也越稳定。在这里,我们借用前面订单簿模型中“流动性”的概念,把池子里用于支撑代币兑换的XY两种代币的总量及可即时交易的储备量称为流动性(Liquidity),将按比例往池子里注入XY代币的用户称为流动性提供者(Liquidity Provider,简称LP)。另外,我们将池子里X和Y的价值总和称为锁仓总价值(Total Value Locked,简称TVL)。

激励机制

好的,新的问题来了。谁愿意主动去往某个池子里注入XY代币,也就是说,去提供流动性呢?所谓“无利不起早”,为了鼓励大家提供流动性,降低用户兑换代币时的价格滑点,QSwap协议设计了一种激励机制。简单来说,每次有用户使用池子进行兑换时,池子都会收取一定比例的手续费。这个手续费通常都很低,例如千分之一,用户还是能够接受的。

举例来说,当用户通过X购买Y时,池子会扣掉少量的Y,当作手续费。我们假设手续费的比例为fee%(比如0.1%),于是我们可以更新前面的买卖公式:

(x+Δx)×(y(1fee%)Δy)=k

注意,手续费也会导致兑换价格产生少量的滑点,但是相比于鼓励大家提供流动性减少的滑点来说,就可以忽略不计了。另外,这个作为手续费的Y会被放回池子里,由LP按比例分配。于是,每次兑换完毕的时候,池子的XY乘积实际上是会变大那么一点点。顺便说一下,注入XY流动性的时候,池子的乘积k也会变大。注入流动性的公式为:

(x+Δx)×(y+Δy)>k

此外,在添加流动性时,需严格按照资金池中X、Y两种代币的当前比例操作,否则会导致池内价格偏移,进而给用户带来潜在资金损失。当LP撤回流动性时,池子会按其持有份额比例归还对应数量的X、Y代币,此时恒定乘积k的数值会随流动性减少而相应变小。整个过程中,池子需精准记录每位LP的份额,以确保权益分配准确。QSwap池子合约需要记录的状态如下图所示:

state

无偿损失

那么滑点问题是不是QSwap唯一的缺点?很遗憾,也不是。这个小节我们会讨论流动性提供者面临的一个问题:无偿损失。下一小节会介绍用户面临的另外一个问题:三明治攻击。

想象一下,如果我们作为流动性提供者(LP),在XY池子价格是1的时候,注入了一笔流动性。为了简化讨论,不妨假设我们自己就是唯一的LP,分别注入了100个X和Y。如果我们按X来作为计价单位,此时我们注入了100个X,以及价值为100X的Y。

假如此时价格发生了波动,Y的价格暴涨,涨到了6.25,那么池子中X和Y的数量(先不考虑手续费)分别是25040,大家可以回去看一下本章第一小节的图。如果我们自己拿着X和Y,在价格暴涨后,我们拥有的代币价值为725个X:

100+100×6.25=725

但是由于我们把X和Y注入了池子,此时我们的代币价值只有500个X:

250+40×6.25=500

换句话说,我们亏了。那么反过来,假如Y的价格暴跌,跌到了0.25,那么池子中X和Y的数量分别是50200。如果我们自己拿着X和Y,在价格暴跌后,我们拥有的代币价值为125个X:

100+100×0.25=125

但是由于我们把X和Y注入了池子,此时我们的代币价值只有100个X:

50+200×0.25=100

结果依然是亏的!这说明,添加流动性后,无论价格涨落,提供者(LP)都会面临亏损。为直观呈现这一损失,我们将上文例子总结为下表:

状态X数量Y数量价格(X:Y)总价值(X)损失
初始状态1001001:12000%
(参考)1001006.25:1725
Y价格上涨250406.25:150031%
(参考)1001000.25:1125
Y价格下跌502000.25:110020%

但是价格始终是在波动的,只要我们不止损,总有一天价格会回到1,那时候我们就是赚的(因为有手续费)。我们把LP面临的这种暂时性的损失称为无偿损失(Impermanent Loss,简称IL)。这个翻译好像不太恰当,但是木已成舟,已经被社区广泛使用,所以本书也采用这个译法。总之,LP们是在用时间来对抗价格的波动。只要时间足够长,波动足够大,那么总体来说,手续费收益就会远远超过价格波动导致的损失。

抢跑攻击

现在我们来考虑另外一个问题。假设目前池子里分别有100个X和Y,价格是1:1。如果我们想花150个X来购买Y,根据第一节的介绍可知,可以拿到60个Y(暂时忽略手续费),之后池子中Y的最新价格为6.25。但是如果有一个坏人,通过各种途径知道了我们的购买意图,先把Y的价格哄抬到6.25怎么办?此时池子里已经有250个X和40个Y了,根据公式,我们只能买到大约15个Y:

(x+Δx)×(yΔy)=kΔy=ykx+Δx=40250×40250+150=15

然后这个坏人再把低价买到的Y卖给池子,把价格降回到正常情况,于是坏人获利,我们吃亏蒙受损失。也许公式和文字描述都不够直观,我们通过表格来具体分析,下面是正常的兑换交易。可以看出,我们实际购买Y的价格是2.5

交易正常情况XY价格(X:Y)
池子初始状态1001001:1
1用户兑换-150+602.50:1
池子最新状态250406.25:1

再来看一下抢跑攻击。黑客在我们的交易前面插入一笔抢跑交易(1),先将Y的价格抬高到6.25。这个时候我们用同样的X只能买到15个Y,实际的购买价格为10,是期望的四倍!然后黑客在我们的交易后面再插入一笔卖出交易(3),将低价买到的Y卖给池子,最终获利282-150=132个X,如下表所示:

交易抢跑攻击XY价格(X:Y)
池子初始状态1001001:1
1攻击者抬高价格-150+602.50:1
池子最新状态250406.25:1
2用户兑换-150+1510.00:1
池子最新状态4002516.00:1
3攻击者获利+282-604.70:1
池子最新状态118851.38:1

注意,当池子的价格和外面的世界发生偏离时,通过买卖把池子的价格跟外面的价格拉平而获利的行为是有益的,我们把这种行为叫做套利(Arbitrage)。但是这种发现有利可图的交易,然后在前面插入一笔交易的套利行为是有害的,会严重影响用户体验,我们把这种行为叫做抢跑攻击(Front-Running Attack)。

还有一些情况,单独在某些交易的后面插入交易也是可以获利的,比如利用正常交易引发的价格变化赚取差价的套利行为。很自然地,我们把这种行为叫做后跑(Back-Running)。后跑对于正常交易者的伤害没有抢跑那么严重,有时候甚至是有利的,所以通常不被称为攻击。

而更常见的是上面例子中的这种情况,黑客不仅在前面插入了一笔交易(低价买入),还在后面也插入了一笔交易(高价卖出),把我们的交易夹在了中间,看起来就像是一个三明治,于是我们把这种特殊的抢跑攻击叫做三明治攻击(Sandwich Attack),如下图所示:

attack

大家一定听过臭名昭著的“黄牛党”吧?想象一下:你喜欢的歌星终于要在你的城市开演唱会,500元的普通票性价比很高,你满心期待地等着开票,想买一张去现场感受氛围。可等你打开售票软件,却发现所有门票已经“售罄”。不是真的抢完了,而是黄牛早已通过各种手段提前锁定票源,把大部分门票囤积在自己手里。如果你实在不想错过这场演出,就只能去二手平台找黄牛,花远超原价的钱买“黄牛票”。这种靠提前抢占资源、囤积后抬高价格牟利的行为,本质上就是现实生活里的“抢跑攻击”。

由于Q链上的交易具备公开透明的特性,所有发起的交易都会先进入节点的内存池排队等候打包,且区块内的交易严格按顺序逐一执行,这就让攻击者能轻松监测到内存池中待处理的关键交易(如QSwap上的大额兑换),并通过调整燃料费抢先插入操作,为三明治攻击等抢跑行为创造了得天独厚的条件。QSwap在为Q链生态提供便捷代币交换服务的同时,也无意间让链上交易环境变成了“黑暗森林”(出自著名作家刘慈欣创作的长篇科幻小说《三体2》)。普通用户的交易意图暴露在外,易成为攻击目标,甚至催生出围绕矿工可提取价值(Miner Extractable Value,简称MEV)的灰色产业链,不过这部分内容此处暂不展开讨论。

闪电贷款

在现实生活中想要贷款是很麻烦的,这里就不赘述细节了,想必大家都了解。但是QSwap利用Q链交易原子执行的特性,提出了一种非常创新的快速贷款方式——闪电贷(Flash Loan)。它有两个特点:第一,速度极快;第二,无需抵押。听起来太酷了,究竟快到什么程度呢?快到必须在一笔交易内完成贷款的发放、使用、归还。考虑到交易执行的时间,称之为闪电贷也不算夸张了。

QSwap本质是Q链上的一款普通智能合约,而智能合约具备被其他合约调用的特性。也就是说,QSwap既可以由普通用户通过私钥签发交易直接使用,也能被其他智能合约触发调用。闪电贷正是专门为合约场景设计的借贷功能:若某合约需要临时使用某种代币,可在同一笔原子交易内,先调用QSwap合约从资金池借出代币,完成操作后再连本带利归还至池子。QSwap在收到归还的代币后,会校验资金池内两种代币的数量乘积是否满足预设要求,满足则交易成功,否则整笔交易直接失败。闪电贷的具体概念示意图如下:

flash

需要注意的是,闪电贷全程在单笔交易内完成,贷款方(资金池)的收益由恒定乘积公式保障,因此借款方无需抵押其他代币,这正是闪电贷区别于传统借贷的核心特点之一。下一章我们将介绍另一类借贷模式:需通过抵押资产实现的链上借贷协议。

另外,闪电贷也是一把双刃剑:它既可以被合理利用,比如通过兑换套利拉平不同市场的代币价格、参与下一章将要介绍的清算套利等场景,保证市场的价格稳定与健康;也可能被黑客恶意滥用,例如临时操纵代币价格实施攻击,或利用合约漏洞窃取资金,给项目方和用户带来损失。具体细节我们就不展开讨论了。

到这里,QSwap的核心工作原理就全部介绍完毕了。为了进一步加深理解,我们将上述所有合约交互和池子状态的变化总结为下面这张表:

交互由谁发起池子里X和Y的数量K的值
添加流动性流动性提供者按价格同时增加变大
减少流动性流动性提供者按份额同时减少变小
兑换用户一个增加一个减少变大一点点(手续费)
闪电贷用户(只能是合约)至少一个增加变大一点点(手续费)

本章小结

本章首先介绍了Q链现象级应用QSwap的基本工作原理,包括其底层依赖的恒定乘积自动做市机制(以乘法公式维持流动性池内两种代币数量乘积恒定),以及基于该机制的价格形成逻辑(通过池内代币数量比例自动定价),用户买卖代币的交易流程等。同时也提及了与这些操作相关的数学公式,为理解协议运行逻辑打下基础。

随后,本章介绍了QSwap运行中存在的滑点问题,即大额交易因流动性池规模限制导致实际成交价格偏离预期的情况。以及协议如何通过收取代币兑换手续费,激励其他用户向池内注入资产以提供流动性。此外,也提及了添加/移除流动性等核心操作,以及支撑这些操作的数学公式。

最后,我们讨论了QSwap生态中的关键问题。一是流动性提供者可能面临的无偿损失(因池内代币价格波动导致资产价值低于直接持有的潜在亏损),二是利用链上交易特性发起的三明治攻击等抢跑行为。还简单介绍了QSwap极具开创性的闪电贷特性(其无需抵押、一笔交易内完成借贷、使用、归还),以及背后的核心原理。

和前面侧重概念介绍的章节不同,本章因涉及QSwap协议的底层机制,出现了不少数学公式。不过这些公式大多围绕恒定乘积、价格计算等基础逻辑展开,比如核心的xy=k公式、滑点或兑换金额的推导式,都不难,只需要掌握基本的乘法和除法运算就能理解。相信读者在读完本章内容后,能够对以QSwap为代表的自动做市协议的运行逻辑形成一个清晰的初步认识。

本书以 CC0 1.0 协议发布,可自由使用