Cedric Fung

Mixin me 25566

Return of Different Uniswap Fee Distribution Models

Oct 11, 2020

DeFi is hot for awhile, the significant star of DeFi is Uniswap without doubt. But automated market maker is not a new thing, Bancor was born years earlier than Uniswap, yet not gains enough usage despite its famous ICO. We are not able to get the answer why Uniswap succeeds so much than Bancor, but I believe Uniswap is almost the simplest yet most powerful AMM solution over the years.

Despite its huge success, I’m still trying to figure out potential improvements on the simple constant product formula used by Uniswap. Last night I was drinking some beer with friends and they inspired me that the core difference between Uniswap and traditional centralized exchanges is not order book, it is the fee distribution mechanism. A traditional exchange deduces the fee from every trade, and the fee is hold by the exchange itself, and they rare puts the fee into the order book. Uniswap also charges the fee from every trade, however Uniswap puts the charged fee into the liquidity pool instead of holds it in a separate pool.

May this small difference make great impact? I have tried to make a mathematic model to predict the potential impact, but too complicated for me to make it in a short time. So I wrote some program to do many return simulations on different Uniswap fee distribution models.

My simulation code is very simple, it first defines the Swap engine with different Trade logic based on the fee distribution models. Then simulate batch of trades, where positive number means swap X to Y, and negative vice verse. The simulation also assumes static liquidity pool without any addition or reduction besides the fee.

func (s *Swap) Simulate(trades []float64, verbose bool) (float64, float64) {
	for _, amount := range trades {
		s.Trade(amount)
	}

	dX := (s.X + s.FX - s.IX) / s.IX
	dY := (s.Y + s.FY - s.IY) / s.IY
	if verbose {
		fmt.Printf("X: %f %f%%\nY: %f %f%%\n",
			s.X+s.FX, dX*100, s.Y+s.FY, dY*100)
	}
	return dX, dY
}

The Simulate function produces the changes of X and Y as result, i.e. the outcome of providing two assets X and Y as liquidity in the Swap engine. One simulation uses 10,000 trades, and I pass 1,000 these trades groups to two fresh swap engines, one engine with the original Uniswap fee distribution model, while the other with another separate fee model.

func Benchmark(x, y float64, tradesGroup [][]float64, model int) {
	var winX, winY, winXY, failXY int
	for _, trades := range tradesGroup {
		os := NewSwap(x, y, FeeModelOriginal)
		ss := NewSwap(x, y, model)
		osdX, osdY := os.Simulate(trades, false)
		ssdX, ssdY := ss.Simulate(trades, false)
		if ssdX >= osdX {
			winX += 1
		}
		if ssdY >= osdY {
			winY += 1
		}
		if ssdX >= osdX && ssdY >= osdY {
			winXY += 1
		}
		if ssdX < osdX && ssdY < osdY {
			failXY += 1
		}
	}
	fmt.Printf("X WIN: %d\nY WIN: %d\nX Y WIN: %d\nX Y FAIL: %d\n",
		winX, winY, winXY, failXY)
}

I produces some data set with different behaviors, and run the benchmark.

  • All random trades, but with a threshold to limit the trade size, e.g. I generate some random numbers, but they never exceed 1/100 of the pool size. Benchmark shows that the two fee separate model wins in both X and Y returns, and the larger the threshold, the better they perform. A bonus is that all these simulations produce positive returns.
  • Random round-robin trades, and with a threshold to limit the trade size, e.g. I generate a random positive number, follows a random negative number, again and gain, but they never exceed 1/100 of the pool size. The result is very similar to all random trades.
  • Monotonic increased price, and with a threshold to limit the trade size, e.g. I generate all negative numbers, that means people keep swapping Y to X. Simply separate the fee with the liquidity pool performs worse than the original Uniswap model, all fails. But if I make trade at first, then charge the fee and separate the fee pool, it wins all simulations.
  • Strictly static prices, and with a threshold to limit the trade size, e.g. I swap some random amount of X to some Y, then I swap these Y immediately back to X, again and gain. Both two fee separate models win all the simulations.

These rough simulations show that separating the fee pool with the liquidity pool will increase return on providing liquidity to Uniswap. Full code is on my GitHub, run the code to get full report.

About the Author

Core developer of Mixin Network. Passionate about security and privacy. Strive to formulate elegant code, simple design and friendly machine.

25566 @ Mixin Messenger

https://vec.io