# Batched Bonding Curves

Three Solutions to Prevent Front-Running

The goal of this document is to outline various implementations of front-running resistant Bonding Curves. For a refresher on the finer points of Bonding Curves, check out this great deep dive from Slava Balasanov. A Batched Bonding Curve would accept a group of buy and sell orders over a span of blocks and calculate a clearing price after that span of blocks. This would prevent front-running scenarios that since being published about have increased dramatically in the wild and affect almost all decentralized exchanges (DEX). It takes the Gnosis DutchX as inspiration, the only DEX that completely thwarts front-running attacks and does so by batching orders. This technique would furthermore mitigate the mass exit issue with Bonding Curves. Depending on the number of blocks included in a batch, it would allow for a mass exit to occur at a uniform price rather than the last out eating their hat.

Finding a uniform price for all buy and sell orders is difficult to do as the order of the orders will have an effect on the resulting prices. The math required to do so isn’t practical on the Ethereum Virtual Machine (EVM). Below are two solutions that find a separate price for all buyers and all sellers as well as a third method that finds a unified price for both groups without being restricted by the EVM. All methods begin by combining buys together and sells together while keeping track of individual amounts. This results in computations that incorporate one large buy order and one large sell. The different implementations diverge at that point with different features that may be desired for different scenarios.

At the end of this document is a link to a github repository where all three implementations have been built. Please take a look at the code and make an issue if you have any questions or comments. Furthermore this document is also available as an Observable that contains values and functions which can be edited to see how different scenarios would play out.

# Initial State

`s = {  balance: 100,  totalSupply: 1000,   connectorWeight: .5,  currentPrice: 0.2,  slope: 0.0002,  power: 1}`

Now consider a series of buys and sells. The buys are amounts of collateral (like Eth or Dai) to be spent in exchange for some amount of new tokens, and the sells are amounts of tokens to be sold for some amount of collateral.

`buys = Array(3) [10, 20, 5]sells = Array(3) [100, 200, 50]`

These can be thought of as a single large buy order and a single large sell order. This will make it easier to calculate clearing prices for each of the following scenarios.

`allBuys = 35allSells = 350`

# Implementation #1 Batched & Ordered

## First all the buys are executed and then all the sells:

`{  balance: 65.9173,   totalSupply: 811.895,   connectorWeight: .5,  currentPrice: 0.1624,  slope: 0.00020000000880536034,  power: 1}`

While the buys began pushing the price per token upwards in this scenario, the sells ultimately capitalized on this high price and were able to extract a larger amount of collateral per token than would have been possible had the order of the orders been switched. This means that in general the buys resulted in fewer yielded tokens, and the sells resulted in greater yielded collateral. This situation benefits the group making sell orders over the group making buy orders.

## First all the sells are executed and then all the buys:

`{  balance: 77.25,   totalSupply: 878.9198,   connectorWeight: .5,  currentPrice: 0.1758,  slope: 0.0002,  power: 1}`

While the sells began pushing the price per token downwards in this scenario, the buys ultimately capitalized on this low price and were able to extract a larger amount of token per collateral. This means that in general the sells resulted in fewer yielded collateral, and the buys resulted in greater yielded tokens. This situation benefits the group making buy orders over the group making sell orders.

If a token model incorporated the desire to reward buyers over sellers it might be beneficial to use the seller-first Batched & Ordered implementation. This result is preferable for a scenario where the goal of the token is to appreciate in value. It also rewards users entering into the token ecosystem by buying new tokens rather than reward the users that are leaving.

Here are both scenarios laid out with price averages to give you an idea of what each would look like. Keep in mind that buyers want low prices and sellers want high prices:

• The price for the sellers would have been 0.1650 👎
• The price for the buyers would have been 0.1529 👍
• The final market price would be 0.1758 👍
• The non-weighted average of the two would have been 0.159

• The price for the sellers would have been 0.1974 👍
• The price for the buyers would have been 0.2162 👎
• The final market price would be 0.1624 👎
• The non-weighted average of the two would have been 0.2068

# Implementation #2 Match & Fill

Collect and combine all buys and all sells over a span of some blocks like before then take the current price per token and execute as many as possible of the orders at that price. Of course with a Bonding Curve, executing any amount would change the current price. For this scenario we are pretending that the current price would not change when some amount of orders were executed, because they’re essentially being matched buyers to sellers:

`price = 0.2 collateral per tokentotalBuy / price = 35 / 0.2 = 175 tokens as a result of the buystotalSell * price = 350 * 0.2 = 70 collateral as a result of the sells`

The next step depends on the outcome of this fixed price execution.

## If the sells outweigh the buys

`totalSell - (totalBuy / price) = 350 - (35 / 0.2) = 350 - 175 = 175 token that still need to be sold`

This remaining 175 tokens to be sold would go into the normal bonding curve price calculator:

`saleReturn(175, s) = 31.937500000000007 collateral`

This bonding curve sell is combined with the earlier match order sell to give all sellers a uniform price:

`(matchedSellResult + curvedSellResult) / totalSell =(35 + 31.937500000000007) / 350 = 0.19125 collateral/token`

In Summary

• The price for the sellers would be 0.1913
• The price for the buyers would be 0.2
• The final market price would be 0.165
• The non-weighted average of the two would have been 0.1956

# Implementation #3 Common Clearing Price

One method for calculating the uniform price is to first assume some universal price (_p) and then describe the group of buys and sells as part of a total change in tokens and a total change in collateral. This can be written as Δc (total change in collateral) and Δx (total change in tokens).

The total change of collateral (Δc) is a combination of the addition of new collateral from buy orders (_c) and the subtraction of collateral from the sell orders. Sell orders can be thought of as some number of tokens (_x) times that universal price _p.

The total change of tokens (Δx) is a combination of the subtraction of tokens from sell orders (_x) and the addition of new tokens from the buy orders. Buy orders can be thought of as some amount of collateral (_c) divided by that universal price _p.

When dealing with a slope formula Bonding Curve it is possible to calculate the cost of purchasing some amount of tokens by taking the integral of the slope between two different token supplies. This looks like:

We can use this same method but substitute Δc for the purchase (which is really just a change in collateral) and Δx for the value k (which is the difference in token supplies). This will give us a formula that can be solved for _p:

We can substitute all of the variables with values from our example state `s` from earlier:

`_c = 35_x = 350m = 0.0002n = 1totalSupply = 1000`

When you put this equation into Wolfram Alpha you get a variety of answers depending on the value of n. Unfortunately Wolfram Alpha can’t handle solving the function for _p, so we have to do with specific examples.

In the scenario where m = 0.0002, n = 1, s = 1000, _x = 350, _c = 35 this yields the following approximate solutions for _p:

`p_1 = -0.0190197p_2 = 0.1p_3 = 0.18402`

We can try executing orders with each of these prices to see what happens. We should be able to tell whether or not they worked based on whether or not the slope of our Bonding Curve was altered from the original 0.0002.

## p_1

`{  balance: 141.656895,  totalSupply: -1190.1972691472524,  connectorWeight: 0.5,  currentPrice: 0.2,  slope: 0.0002,  power: 1}`

The slope of this state would be 0.00019999991677280065 👍

## p_2

`{  balance: 100,   totalSupply: 1000,   connectorWeight: 0.5,   currentPrice: 0.2,   slope: 0.0002,   power: 1}`

The slope of this state would be 0.0002 👍

## p_3

`{  balance: 70.593,   totalSupply: 840.1967177480708,   connectorWeight: 0.5,  currentPrice: 0.2,   slope: 0.0002,   power: 1}`

The slope of this state would be 0.0001999998513976622 👍

## Selecting a Solution

p_1 would result in a negative totalSupply, which doesn’t seem good.

p_2 would result in no change to the state of the bonding curve at all which also doesn’t seem good.

p_3 seems to be the best candidate after process of elimination. The net difference of the state of the contract would result in a little lower totalSupply and a little lower balance. That would imply that there were more sells than buys, which also seems to be the case based on the scenarios outlined in the previous implementations using the same initial state.

Finally this solution needs to be implemented on the EVM. Instead of trying to compute this answer, it would be better to allow anyone to provide the solution to the contract and let the contract confirm that

• it does not result in a negative token supply
• it does not result in no change at all
• it does not change the slope of the contract (within some margin of error for rounding)

# Conclusion

The second scenario, Match & Fill might be best if it is preferable to have a more unified price between buyers and sellers, determined by the order quantities rather than a specific design decision.

The third scenario, Common Clearing Price might be best if it is preferable to have a unified buy and sell price for all buyers and sellers. This scenario comes with a UX overhead of orders not being cleared until a suitable price has been supplied and verified, but it gives the most intuitive price for users.

All three scenarios involve an asynchronous order execution which is also not a great user experience. However each order could come with an extra fee that could be used to reward a third party for finalizing an order on behalf of another user. This would allow a user to “set it and forget it” so that the results of their trade would show up without further interaction.

As mentioned before all three scenarios have been implemented in the github repository @okwme/BatchedBondingCurves. This solution is not implemented in the wild but if you’re interested in incorporating it please let me know! I also welcome any critiques or feedback on the solutions I’ve proposed via github or here.

My name is Billy Rennekamp. I’m a co-founder of Bin Studio, a multidisciplinary research, design and development studio based in Berlin. We’re currently developing Clovers Network, a novel proof-of-work game supported by the ECF — Ethereum Community Fund. I work on a lot of other great projects including Cosmos Network, Gnosis, ENS Nifty, Meme Lordz and Doneth. If any of that sounds interesting to you, follow my twitter, my github or just say hello 👋

https://okw.me