Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Making liquidity provision a first class citizen in the smart contracts #744

Open
josojo opened this issue May 7, 2020 · 7 comments
Labels
Version2 Proposals for the version 2 contract

Comments

@josojo
Copy link
Contributor

josojo commented May 7, 2020

Liquidity provision seems to be so important for a number of use-cases. It's a shame that it is soo gas expensive and so cumbersome and that we can't build pools on the current protocol.

Imagine the liquidity provision could be done with one account instead of 1 account per bracket. That would make withdraws and deposits so much cheaper and the order placement as well.

The following proposal describes a technical solution for this scenario:

In this scenario, users should place the same orders, currently placed by our brackets-strategy, in one validFromOrders tx plus a bool flag indicating that this account is in the bracket-strategy mode.
All orders from these accounts must be sorted so that the orders at index 0,1 would be the same as the bracket trading at the lowest price, and the orders at index n-1, n would be the same as the bracket trading at the highest price, if n orders are placed from one particular account.

Then the smart contract could calculate automatically during the solution submission, minimal balances of sellTokens, that the account would need to have in order to allow the i-th order to be touched, just as shown in this sheet:
https://docs.google.com/spreadsheets/d/10Et3GeH97ovyAyVaus04YLUFXI1uzimQCD16EkmyHLM/edit?usp=sharing

An example is: if the bracket with the index 1 - bracket -9 - is touched by selling DAI for ETH, then after the sell is complete, at least 1005 DAI still needs to be in the account. If this would not be the case, then this order should not be touched. This prevents that the same order is touched several times unless the account balance was refilled with trade selling the opposite token.

On the other hand, if we are selling ETH for DAI, we are checking that enough ETH is still in the account after the i-th order is processed.

This logic could be checked in the smart contract very easily during the solution submission like that:

// Perform all subtractions after additions to avoid negative values
        for (uint256 i = 0; i < owners.length; i++) {
            Order memory order = orders[owners[i]][orderIds[i]];
            (, uint128 executedSellAmount) = getTradedAmounts(buyVolumes[i], order);
            subtractBalance(owners[i], tokenIdToAddressMap(order.sellToken), executedSellAmount);
            if(isBracketAccount(owners[i])){ <-- here starts the new code
               unit totalValueInSellToken=calculateSellTokenValueOfAccountWithCurrentAuctionPrice(...)
                require(balanceOfOwner(owners[i], sellToken)>=orderIds[i]/lengthOfOrders(owner[i])*totalValueInSellToken, "bracket order was not allowed to be touched")
             }. <-- here ends the new code
        }

Probably, there are much smarter ways to do it. But maybe it is worth thinking in this direction...
I think this feature is a hard requirement for implementing pools, as currently deposit transactions for 20 brackets cost 6 m gas.


Update:
instead of placing the orders directly by the account, we could also allow users to only specify the highestLimitPrice, lowestLimitPrice and number of simulated orders, and then we can calculate the actual order price during the solution submission.

order_limit_price = lowestLimitPrice + (highestLimitPrice- lowestLimitPrice) * index/#orders

This would allow us to place with one transaction 1000 order for a pool, and hence a rediculous low spread. If we pool enough funds into one pool, then even a higher amount of orders might even become economically viable.

@josojo josojo added the Version2 Proposals for the version 2 contract label May 7, 2020
@josojo josojo changed the title Proposal only: Making liquidity provision a first class citizen in the smart contracts Proposal: Making liquidity provision a first class citizen in the smart contracts May 8, 2020
@marcovc
Copy link

marcovc commented May 8, 2020

This might be related to something I have been thinking about, but can't really articulate it very well yet. The idea was to model this liquidity provision thing in the solver (so one step further from the suggestion above, where if I understood correctly, the liquidity order is a first class citizen at the smart contract level, but it is realized into 1000 normal orders at the solver level).

So, the idea is to try to come up with a more expressive way to declare the max sell amount of an order. Instead of y<yb as it is currently, something like y<f(r), where r is the exhange rate r=y/x<pi. A simple f would be a line: y<m×r+b, where m and b would be user defined parameters. Maybe I am confused, but what I am trying to do is to replace a stair with 1000 steps with just one line. In a way, it says that my liquidity for business increases as a function of the exhange rate.

Tom and I were briefly discussing these the other day, and he mentioned that this was already discussed at some point. I am also unsure how feasible this is solver-wise. Anyway, just raising it here.

Edit: I think r should be x/y but you get the point. Also it is not really a line since r is a function of y. It is more of a curve.

@koeppelmann
Copy link
Member

The category I see this under is "order expressibility".
Check "Automated MM" here: https://forum.gnosis.pm/t/list-of-protocol-improvements-for-dfusion/650

@josojo
Copy link
Contributor Author

josojo commented May 10, 2020

yes Marco, having linear orders is even better. I was thinking about it from a perspective minimizing current changes to the protocol, but these linear orders are way cooler.

Having a zero-spread pool - of course, fees must be in the spread - with opposite linear orders (selling WETH-DAI with one linear order and DAI-WETH with the other) would be so cool. Uniswap or curve.finance could create a ton of different pools with different parameters, but they would always have trouble combining them. But with our protocol, we could allow anyone to create their individual, customized pool, and our genius solvers would easily combine their liquidity. As long as our fee is competitive, their models can't be better. Their only advantage would be to be atomic.

If we would really build zero-spread pools - pools making a profit due to the fact that they are getting a better price than they asked for, we would be sure to always have in one or the other way a better price than our competition.

Martin, I really like the vision of creating more dimensional orders, with dimensions like price, available balance, volatility, etc. Well said.
Especially the dimension of volatility could be interesting for pools to minimize permanent loss etc.

Understanding the GP as a protocol to combine all kinds of liquidity from a variety of pools with different parameters in the best interest of the selling orders, is a very powerful perspective. This should be very appealing to customers.


The logic for the zero-spread pool is really not that complicated. I think we could code it down quite quickly as well.

Screen Shot 2020-05-11 at 07 52 11

@marcovc
Copy link

marcovc commented May 10, 2020

From the brief discussion with Tom about this, we had the feeling that this might be doable in the standard solver. If this is confirmed, it will be much more efficient to solve a problem with one "linear order" instead of what is roughly a 1000 piecewise approximation.

In any case I agree that will be significantly more disruptive than your proposal to keep it in the contract level. It requires changing the APIs at almost every level.

@marcovc
Copy link

marcovc commented May 10, 2020

Just to make it even more general: now we have the constraint y<yb. Even if we accept y<p(buy token)/p(sell token) × m + b we still need the first one to have a "hard cap". But since the first one is just a special case of the linear one (with m=0), then a more general setting would be a list of lines per order. That allows for approximating more complex functions, such as polynomials or exponentials, while still possible solver-wise.

@twalth3r
Copy link

twalth3r commented Jul 6, 2020

Since we were discussing "linear orders" in our solver weekly today, just want to describe what we currently see as doable for the solver (basically repeating and detailing out what @marcovc wrote above):

(current) fixed max-sell-amount:

Let ymax be the maximum sell amount of an order, and y denote the executed sell amount. Then, the constraint that a solution needs to satisfy reads

limit price violated   =>  y = 0
limit price satisfied  =>  y <= ymax

However, in our linear optimization model, we are not working with traded amounts directly, but with so-called trading volumes.
Let pB and pS be the prices of the buy- and sell-token of an order.
We define the trading volume of a sell order to be v := y * pS.

Then, our above constraint can be reformulated as

limit price violated   =>  v = 0
limit price satisfied  =>  v <= vmax = ymax * pS

(future) linear max-sell-amount:

Instead of a fixed maximum sell amount ymax, the idea as described by @josojo is to use a maximum sell amount that is linear in the exchange rate between the two tokens involved:

ymax = ymax(pB, pS) = a*(pB/pS) + b

with a and b being constants.

In the world of trading volumes, this translates to

vmax = ymax(pB, pS) * pS = a*pB + b*pS

which defines a hyperplane in the space of (v, pB, pS), hence is a linear expression.

Without any major changes, we can simply change the constraint above to read

limit price violated   =>  v = 0
limit price satisfied  =>  v <= vmax = a*pB + b*pS

(Note: we can even add v <= vmax = ymax * pS with a fixed max-sell-amount for an order on top)

Notice that the exchange rate quotient pB/pS must not be used in the inverted sense (i.e., pS/pB), since then we end up with some nonlinear/quadratic inequality.

@twalth3r
Copy link

twalth3r commented Jul 6, 2020

As an example, let's consider an order with a fixed sell amount of 1000 DAI:

Sell 1000 DAI for at least 5 ETH,

which translates to

Sell 1000 DAI for ETH if p[ETH] / p[DAI] <= 200.

With "linear" orders, we could generalize this to

Sell  1000 DAI for ETH if p[ETH] / p[DAI] <= 190
Sell     0 DAI for ETH if p[ETH] / p[DAI] >= 210
Sell  ymax DAI for ETH if p[ETH] / p[DAI] in [190, 210]

where ymax in the last case is defined as a line between (190, 1000) and (210, 0), i.e.,
ymax = -50 * p[ETH] / p[DAI] + 10500.

IMG_20200706_171433

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Version2 Proposals for the version 2 contract
Projects
None yet
Development

No branches or pull requests

4 participants