在上篇文章当中,我们讲解了在Defi应用当中的一种去中心化撮合交易形式——Liquidity Pool。这次,我们来通过详细地了解它的模型来分析它是如何工作的,以及有哪些利与弊。
首先,作为一个在区块链上的数学模型,我认为很重要的一件事情就是,它必须很简单+很可靠,作为一个交易资产的项目,每一行代码都关乎到用户的钱,如果出了问题就会影响到大家对这个项目的观感,对项目的影响是非常直接的。
在ETH的智能合约里,用的基本上都是integer arithmetic(整数运算),所以有时候会遇到计算之后得到的是浮点数导致的误差,有些时候这种误差是很致命的,但是在文章里有讲到如何把这种运算结果进行取整同时将影响控制在有限的范围内,这些我们在后面会讲到。
下面的部分参考自Uniswap的Whitepaper,网址:https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig(需要翻墙)
Uniswap是一个简单易用、使用gas效率高、能通过审查且零租金的链上交易系统。
Lidquidity pool交易模型
1.y×x=k模型
一个交易对是基于两个Token进行的,我们假设他们为X和Y,那么x和y则是X和Y在这个交易对当中作为储备金的token个数。
这个交易对的比率(也就是价格),是基于交易对当中X和Y的数量决定的。在整个交易的过程当中,这个y×x=k的k是一定的。
我们假设在交易过程当中,当你买入了Δx的币,并且卖出了Δy的币,那么公式如下:
推导公式可得出X+Δx和Y-Δy,以及Δx和Δy的值:
这是最基础的自动交易模型,它可以保持这两个币种能一直有一个对应的价格和数量一直进行交易(前提是交易的数量不超过池子内币种的数量)。
2.加入fee的连续乘积模型
在之前的模型当中,我们默认交易前后的乘积k是不变的。但是这样很难刺激大家把自己的钱放入到市场当中作为流动提供者,为了刺激持币者,我们需要在交易的时候收取一些手续费来作为提供流动性的报酬。
收取手续费的比率在0~1之间,我们可以设置为0.3%,手续费的符号是𝜌,除了手续费外的交易金额为𝛾=1-𝜌。
同样的,我们可以得到交易后的X与Y的token数量,以及交易兑换变化的Δx和Δy的值。
现在所得到的这个数值就是我们在实际应用过程当中使用的计算公式了,当𝛾=1也就是没有手续费的时候,这个公式跟上面那个公式就是一样的了。
这个公式的除了能让提供流动性的做市商获得盈利之外,在经济学上还有一个显著的变化,就是这个模型的k不是恒定的了,而是会随着交易有一个缓慢地增长。
流动性份额计算与更新
我们使用充值和提币两种形式来代表对这个交易对的流动性增加和减少。(这里要注意的是,充值和提币的操作都是对这个交易对的两边的币种同时进行操作,一次提取和存入的都是两种货币)
在这里我就以充币为例介绍一下在程序里的实现公式注意事项,在这里会涉及到之前说过的integer arithmetic(整数运算)所带来的误差取整,如果有对智能合约安全感兴趣或者想自己做项目的可以仔细看看,没有兴趣的同学可以直接跳到最后的tips了。
1.增加流动性数学公式
在开始之前先介绍一下我们会用到的符号,e代表ether的数量(以wei计,1eth=1,000,000,000,000,000,000wei),t代表交易对另外一个币的数量,l代表增加流动性的数量,带'的符号代表状态改变了之后(存完币)的数值。
在我们提供了流动性之后,uniswap会返回给你一个erc20的代币,代表你占流动性池子的份额。
在数学公式里,一般都会有一个限定条件,这里的限定条件就是,这个公式里的所有的东西都是处于静态状态的,也就是只有从这个状态变到下个状态之后里面的值才会改变。
α=Δe/e,这个公式很好理解,就是往里面存的X币和Y币的数量一定是等比例的,这个比例取决于之前池子里X和Y的比例,在你提供了这些流动性之后,池子里的流动性l就增加了,增加的这部分l就归你了。
同时也保证了,在整个计算过程当中,e:t:l的比例都是固定的。
通过上面这个式子的额外推导,我们可以得出以下的几条定理:
2.增加流动性的代码实现
这一部分就牵扯到之前说的integer arithmetic(整数运算),由于α=Δe/e得到的数字是一个浮点数,取整之后会有一个误差。在Uniswap当中,他们利用了这个误差<1的特点,设计了下面这个方式来消除误差的影响。
Δe的数量是优先决定的,Δt的数量在之前那个计算之后向下取整,最后提供的总体流动性也是向下取整的。
这个设计可以得到下面这些定理:
首先要保证的是,用户得到的流动性代币<=用户充值后提供的流动性。这里l的比较久可以说明这一点,l''代表的是在程序当中最后计算出置换给用户的流动性代币,这个代币后面会用于提币的时候的凭证,通过这个代币可以从池子当中提取流动性。
同时,这个取整+1的操作保证了对用户的价值减少会控制在一个很小很小的范围内,因为数字货币的有效数字基本上都在10位以上,所以可以说是没有影响。
关于增加流动性的操作就讲到这里,提币(减少流动性)也就是将刚刚我说的这些公式反过来罢了,如果很有兴趣的朋友可以直接去看上面链接的文章。
交易价格计算
我们一开始讲了关于整个交易的k变化的计算,但是在交易的时候,我们总是要提供给交易者他交易的价格,才能让交易者知道自己的代币是以一个什么价格成交的。与普通的报价单不同,交易价格是随着交易者选择交易货币的数量变化而变化的。
其实算价格很简单,price=Δx/Δy,我们所需要计算的就是在已知其中一个情况下计算另外一个是多少。
这里有两种算法,一个叫getInputPrice(通过限定Δx来算出Δy),另外一个叫getOutputPrice(通过限定Δy来算出Δx)。
getInputPrice
当手续费为0.3%时,直接代入下列公式,就可以得到Δy的值
getOutputPrice
同样带入公式,就可以得到Δx的值
这里的代码计算同样需要用到我们之前提到的那个整数运算向下取整的方法来规避套利者利用漏洞攻击合约的行为。
基本上关于合约模型就到这里结束了,在论文当中后面的第四部分还就合约交易的3种模式进行了讲解(ETHtoToken、TokentoETH、TokentoToken),其中用到的东西基本上就是上面我所说的这些数值了,基本上都是比较重复的概念。
写在最后的tips:
看到这里的同志都不容易,其实这篇论文是在2018年发表的,所以其中的技术都是Uniswap v1的概念,Uniswap v2在此之上还进行了一些改动,取消了完全以eth作为桥梁的交易方式,可以直接进行token-token之间的交易,这样可以降低这种交易所消耗的gas。
在合约里,数学完备性是最重要的一条,即使是像我上面分析的这么简单的公式,也很难适应于所有的代币。
比如这个通缩模型的BOMB币,在每笔交易过后会销毁整个网络当中0.2%交易量的代币,在Uniswap上线之后被人发现可以用于套利,由此发展出各种各样通缩类型的代币。
上面讲的这个例子也只是冰山一角,在Uniswap上多次被攻击提取走eth的示例也还有很多。其实这件事告诉我们,一个代币的经济模型有可能对这个交易模型产生影响,如果在设计交易模型的时候不考虑代币的经济模型,很容易产生bug。所以如果想参与这种池子类型交易的同学,最好选择固定总量的代币或者是通胀代币。
Uniswap其实也并没有这篇文章讲的这么简单,它在设计的理念当中还涉及到一个“闪贷”的套利方式,可以在一个区块内实现转换+套利+反转换的操作,有兴趣的同学也可以去看看。
作为一个API比较完备的项目,很多其他的Defi项目会使用Uniswap作为交易媒介,这样整个智能合约都可以在链上实现,完全规避主体风险。
但是我们也要考虑到这种全部都在智能合约上实现的系统性风险,毕竟就如2008年的次贷危机一样,这种套利机器有可能给市场上想撬动杠杆的人一个可乘之机,触发CTA信号或者质押的平仓线产生突发性的行情。