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

[Protocol3] New fee model and examples #197

Closed
Brechtpd opened this issue May 17, 2019 · 41 comments

Comments

@Brechtpd
Copy link
Contributor

commented May 17, 2019

New Fee Model Design

@Brechtpd Staking has been moved to #208.

Protocol Fee

This fee is proportionally applied on every token transfer part of the trade. The protocol fee can be lowered by staking LRC. A different protocol fee can be used for maker orders and taker orders.
A protocol fee value is stored in a single byte in bips/10. This allows us to set the protocol fee up to 0.255% in steps of 0.001% (0.1 bips).

Different treatment of maker and taker orders

The ring-matcher chooses which order is the maker and which order is the taker.
The order at position 0 in the ring is always the taker order, the order at position 1 is always the maker order. We always use the rate of the second order for the trade.

Removal of the fee token

No more fee token/fee amount. The protocol fee is applied on the amount traded. A possible negative caused by this is that it's not possible to do fee payments in security tokens, NFTs, ... but we don't support these yet in protocol 3 anyway. Can be added back in a future version of the protocol when it makes sense.

Fee payments

Paid in the tokenB of the order. These are not taxed or do not have any other disadvantage. The order owner pays the fee to the ring-matcher respecting feeBips. These values are calculated on the complete amount bought, the protocol fee does not change this in any way.

private calculateFees(
    fillB: BN,
    protocolFeeBips: number,
    feeBips: number,
    rebateBips: number
{
    const protocolFee = fillB.mul(new BN(protocolFeeBips)).div(new BN(100000));
    const fee = fillB.mul(new BN(feeBips)).div(new BN(10000));
    const rebate = fillB.mul(new BN(rebateBips)).div(new BN(10000));
    return [fee, protocolFee, rebate];
  }

feeBips's max value is 0.63% in steps of 0.01% (1 bips).

Buy/Sell orders

Users can decide if they want to buy a fixed number of tokens (a buy order) of if they want to sell a fixed number of tokens (a sell order). A buy order contains the maximum price the user wants to spend (by increasing amountS), a sell order contains the minimum price the users wants to get (by decreasing amountB). This allows us to do market orders. It only matters if an order is a buy or a sell order when the order is used as the taker order, the price for the maker order is always the price defined in the order.

So if there is a positive spread between the taker and the maker order we either use the spread to buy more tokens (sell order) for the taker, or let the taker keep the spread (buy order).

Rebates

The ring-matcher can choose to give an order a rebate. This is a percentage of amountB.

rebateBips's max value is 0.63% in steps of 0.01% (1 bips).

Ring-matcher pays all actual costs for a trade

The ring-matcher is in the best position to decide if a trade is profitable (and if not, he can still choose to do the trade for other reasons):

  • The ring-matcher receives fees from both orders
  • The ring-matcher pays the operator for settlement on-chain
  • The ring-matcher actively monitors the side-chain/main-chain and cost for settlement

So it makes sense to also let the ring-matcher pay:

  • The protocol fee for both orders
  • The rebates

By letting the ring-matcher pay the protocol fees for the orders we decouple this from the fee paid by the order owners. This is useful because the order.feeBips can be lower than the protocol fee. Otherwise this could give issues when the protocol fee changes or when the order is used as maker/taker and different protocol fees apply.

Note that the ring-matcher normally receives fees from both orders which he can use to pay the protocol fees and/or rebates.

But if the ring-matcher pays the protocol fee, why have different protocol fee rates? You could argue that a 0.05% taker fee and a 0.01% maker fee is the same as a fixed 0.03% rate because the same entity pays the fee. That is true, but in general the ring-matcher will get a larger fee for the taker order than for the maker order so he will receive more tokens in takerOrder.amountB than in makerOrder.amountB. By having different rates it's more likely that the ring-matcher can pay the complete protocol fee just by using the tokens he receives as fee.

Data

Order

Order {
  exchangeID (32bit)
  orderID (20bit)
  accountID (20bit)
  dualAuthPublicKeyX (254bit)
  dualAuthPublicKeyY (254bit)
  tokenS (8bit)
  tokenB (8bit)
  amountS (96bit)
  amountB (96bit)
  allOrNone (1bit)
  validSince (32bit)
  validUntil (32bit)
  maxFeeBips (6bit)
  buy (1bit)
}

The order contains the maximum fee paid by the order owner.

Ring

Ring {
  orderA_hash (254bit)
  orderB_hash (254bit)
  orderA_feeBips (6bit)
  orderB_feeBips (6bit)
  orderA_rebateBips (6bit)
  orderB_rebateBips (6bit)
  accountID (20bit)
  tokenID (8bit)
  fee (96bit)
  nonce (32bit)
}

The ring contains the actual fee paid by the order owner (which needs to be <= order.maxFeeBips). This allows, for example, for DEXs to temporarily/permanently lower the fees on their DEX without having users recreate their orders so they can get the lower fee. This also allows the same order to get the taker fee and the maker fee, because the same order could be used as both.

feeBips and rebateBips cannot both be non-zero for an order.

Token Transfers for a single ring

The order we do the transfers in is important. The ring-matcher can directly pay his fees using the fees he gets from the orders.

There is little point in minimizing the number of 'token transfers' in the circuit. That's not expensive. The number of accounts and balances that need to be updated is the expensive part.

// Actual trade
orderA.tokenS.transferFrom(accountA, accountB, fillAmountSA)
orderB.tokenS.transferFrom(accountB, accountA, fillAmountSB)
// Fees
orderA.tokenB.transferFrom(accountA, ringMatcher, feeA)
orderB.tokenB.transferFrom(accountB, ringMatcher, feeB)
// Rebates
orderA.tokenB.transferFrom(ringMatcher, accountA, rebateA)
orderB.tokenB.transferFrom(ringMatcher, accountB, rebateB)
// Protocol fees
orderA.tokenB.transferFrom(ringMatcher, protocolFeePool, protocolFeeA)
orderB.tokenB.transferFrom(ringMatcher, protocolFeePool, protocolFeeB)
// Operator fee
ring.feeToken.transferFrom(ringMatcher, operator, ring.fee)

Fee Model Examples

Buy taker order

Protocol fees:

  • Taker: 0.05%
  • Maker: 0.025%

Taker order:

  • Buy order
  • Sell: 10.1ETH
  • Buy: 10,000LRC
  • Fee: 0.2%
    -> Pays: 10.0ETH (less than the 10.1ETH specified in the order)
    -> Receives: 10,000LRC - 20LRC (fee)
    -> Trading fee: 20LRC
    -> Protocol fee: 5LRC

Maker order:

  • Buy/Sell order (doesn't matter for the maker order)
  • Sell: 20,000LRC
  • Buy: 20ETH
  • Fee: 0.1%
    -> Pays: 10,000LRC
    -> Receives: 10ETH - 0.01ETH (fee)
    -> Trading fee: 0.01ETH
    -> Protocol fee: 0.0025ETH

Sell taker order

Protocol fees:

  • Taker: 0.05%
  • Maker: 0.025%

Taker order:

  • Sell order
  • Sell: 10.1ETH
  • Buy: 10,000LRC
  • Fee: 0.2%
    -> Pays: 10.1ETH
    -> Receives: 10,100LRC (more than the 10,000LRC specified in the order) - 20.2LRC (fee)
    -> Trading fee: 20.2LRC
    -> Protocol fee: 5.05LRC

Maker order:

  • Buy/Sell order (doesn't matter for the maker order)
  • Sell: 20,000LRC
  • Buy: 20ETH
  • Rebate: 0.1%
    -> Pays: 10,100LRC
    -> Receives: 10.1ETH + 0.0101ETH (rebate)
    -> Rebate: 0.0101ETH
    -> Protocol fee: 0.002525ETH

@dong77 dong77 pinned this issue May 18, 2019

@dong77

This comment has been minimized.

Copy link
Contributor

commented May 18, 2019

Here are some of my feedback and questions regarding New Fee Model:

  • regarding Different treatment of maker and taker orders:

The first order in the ring is always the maker order, the second order is always the taker order. We always use the rate of the second order for the trade.

I think we should always use the maker's price, instead of the taker's price. This is how it works on CEXes.

  • regarding Removal of the fee token
    The reason to remove fee-token is to reduce the number of constraints in the circuits, is it?

  • regarding Allow matching orders regardless of price
    I actually had this idea a while ago but never push this forward (one reason is that we have to do the math right, ant the calculation will be more complex). It seems to me the ring-matcher, wallet, and DEX operator subside the taker together to make the trade happen. When we settle a trade, we do need to consider fees before-hand, instead of simply deducting fees afterward. I'm not sure if it is too complex to do the math in circuits.

But from a user's perspective, what he get in a trade should always satisfy the order's price, regardless if the spread is negative or positive.

  • regarding Ring-matcher pays all actual costs for a trade

The ring-matcher receives the spread if spread > 0 (we called this the margin) but the ring-matcher needs to pay the spread if spread < 0.

This is an interesting approach. Basically if a ring is signed by the matcher, the matcher is forced to subside whatever amount needed to make the settlement price satisfy both order's prices, given the matcher still have enough balance. We may need to apply some kind of cap on the amount though. For example, 10% of the order'size, etc.

One possible challange for this appoach is that the ring-matcher may not have so many altcoins to pay feesl for all rings he built. We'll proabaly need to deduct protocol fees from trading fees the ring-matcher make out of the both orders first, if the trading fees < protocol fees, then we deduct from matcher's account balance, and if the fund is insufficient, we reject the settlement request.

If we do enforce ring-matcher to pay protocol fee, should we force them to pay in LRC instead of any token? And the protocol fee can be paid in fix amount instead of a percentage of the trading volume as well.

@dong77

This comment has been minimized.

Copy link
Contributor

commented May 18, 2019

Regarding 3. Exchange protocol fee reduction staking (Per exchange)

I guess the idea is to let the exchange owner figure out how to reward people who stake LRC for his/her exchange, from the protocol level, we treat those staked tokens as a whole, is it?

This type of stakes is different from type-2 staking (2. Exchange owner staking (Per exchange)) in that the weight is 50% and the tokens can be withdrawn at any time without restrictions. Correct me if I'm wrong.

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 18, 2019

Different treatment of maker and taker orders:

The first order in the ring is always the maker order, the second order is always the taker order. We always use the rate of the second order for the trade.

I think we should always use the maker's price, instead of the taker's price. This is how it works on CEXes.

I think that's fine, because 2 order rings are symmetrical there should be no problem with making the first order the taker and the second order the maker, it should still be the same ring.

Removal of the fee token:
The reason to remove fee-token is to reduce the number of constraints in the circuits, is it?

That's right, it reduces the number of constraints and makes the protocol fees/spread a lot easier because we only have to deal with 2 tokens. If we do a protocol fee on the amount traded we would also have to do that on the fee that is paid in fee token and so on...

regarding Allow matching orders regardless of price:
I actually had this idea a while ago but never push this forward (one reason is that we have to do the math right, ant the calculation will be more complex). It seems to me the ring-matcher, wallet, and DEX operator subside the taker together to make the trade happen. When we settle a trade, we do need to consider fees before-hand, instead of simply deducting fees afterward. I'm not sure if it is too complex to do the math in circuits.

Unless I made a mistake it's actually very simple to do. All fees are calculated as if the trade happens at the rate specified in the order. The only difference is that the second order receives tokens from not just the first order, but also the ring-matcher. Because the ring-matcher also pays the protocol fee for both orders there is no difference in who needs to pay the protocol fee on the complete fillB of the second order. If you check the sequence in which the token transfers are done you will see that the first order pays the second order at the start before all fees are paid, so the number of tokens received here can still be < fillB, but should still be more than enough to pay the wallet/ring-matcher fee in tokenB (max fee is currently 2.55%, so the spread would have to be > 97% for this to fail!).

But from a user's perspective, what he get in a trade should always satisfy the order's price, regardless if the spread is negative or positive.

Yes, that's how it would work. It does not matter for the order owners either way. They will get their tokens at the rate specified in the order.

The ring-matcher receives the spread if spread > 0 (we called this the margin) but the ring-matcher needs to pay the spread if spread < 0.

This is an interesting approach. Basically if a ring is signed by the matcher, the matcher is forced to subside whatever amount needed to make the settlement price satisfy both order's prices, given the matcher still have enough balance. We may need to apply some kind of cap on the amount though. For example, 10% of the order'size, etc.

I though about this, and I'm not against a cap, but I think leaving this uncapped is not really dangerous. The ring-matcher needs to know what he's doing when matching orders. There's a lot of ways to lose money if you use the system incorrectly (like paying a huge fee to the operator for the settlement).

One possible challange for this appoach is that the ring-matcher may not have so many altcoins to pay feesl for all rings he built. We'll proabaly need to deduct protocol fees from trading fees the ring-matcher make out of the both orders first, if the trading fees < protocol fees, then we deduct from matcher's account balance, and if the fund is insufficient, we reject the settlement request.

That's how it would work. If the ring-matcher balance is insufficient the settlement cannot be included in a block.

There's actually no need to do any checks like 'trading fees < protocol fees' etc in the circuit... the balance is just updated for every 'token transfer' and every 'transfer' ensures that balance >= 0. This is different then in solidity where you can save gas by not having to check balances and minimizing the number of token transfers you have to do. In the circuits the cost is just the balance in the leaf that needs to be updated, and this leaf needs to be updated anyway, all other calculation are insignificant so can be kept as simple as possible.

But in most normal cases the ring-matcher can pay everything using the fees he gets.

If we do enforce ring-matcher to pay protocol fee, should we force them to pay in LRC instead of any token? And the protocol fee can be paid in fix amount instead of a percentage of the trading volume as well.

I think it makes sense to use percentages. Trading fees are percentages and people are okay with that. Otherwise the fixed protocol fee is either extremely low or makes the protocol unattractive for small trades. I think making it a very low percentage is better than making it a fixed fee, but that's just my opinion, I don't know if that's the smartest thing to do. A fixed fee in LRC will be a bit cheaper in the circuit. Doing both is also an option I guess: very low percentage + fixed fee in LRC.

When we support simple token transfers we'll probably have to do the protocol fee in a percentage because users will have to pay it directly.

If we use percentages that kind of rules out using LRC as the only payment because we don't know the price of LRC in tokenS/tokenB. Well, we could try and get the price in the circuits some way, but even if that is possible it would be pretty expensive to do it that way.

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 18, 2019

Regarding 3. Exchange protocol fee reduction staking (Per exchange)

I guess the idea is to let the exchange owner figure out how to reward people who stake LRC for his/her exchange, from the protocol level, we treat those staked tokens as a whole, is it?

Correct. Just the reduction in protocol fee could be reason enough for ring-matchers/wallets/etc... to stake for the exchange. But the exchange owner can give extra rewards if he wants.
And all this staked LRC is seen as a single amount for the protocol.

This type of stakes is different from type-2 staking (2. Exchange owner staking (Per exchange)) in that the weight is 50% and the tokens can be withdrawn at any time without restrictions. Correct me if I'm wrong.

That's the idea. 3. staking is a lot more flexible in that the LRC can be withdrawn at any time (from the protocol's perspective, the exchange owner contract can impose more restrictions). 2. stake is stuck until shutdown so is riskier for the staker, but better for the users so this staking is incentivized by giving it a larger weight for the protocol fee reduction.

@dong77

This comment has been minimized.

Copy link
Contributor

commented May 19, 2019

I feel comfortable moving forward.

@coreycaplan3

This comment has been minimized.

Copy link
Contributor

commented May 24, 2019

Regarding:

Maker: 0.025% -> 0.010% (0.0% most likely creates a loophole)
Can you explain how going to 0% creates a loophole?

Is there a way that you can allow for the exchange to reimburse the maker to bring their effective fee down to 0%?

How is there a loophole also? Can you explain please?

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 24, 2019

Thanks for the feedback @coreycaplan3 !

Maker: 0.025% -> 0.010% (0.0% most likely creates a loophole)
Can you explain how going to 0% creates a loophole?

Is there a way that you can allow for the exchange to reimburse the maker to bring their effective fee down to 0%?

Yes, the protocol fee is not paid by the order owner itself, it's completely paid by the ring-matcher. The protocol fee is not paid by the order owner by using a part of the fee. The protocol fee percentage and fee percentage are completely unrelated. If the fee is 0% the order owner will not pay any fees at all, the ring-matcher will have to pay the protocol fee.

It's also very likely it will be possible for the maker fee to be reduced to 0%.

How is there a loophole also? Can you explain please?

My current thinking is that there isn't any realistic loophole. My biggest fear is/was the asymmetry. You could create a ring that matches an order selling $100 worth of tokens with an order selling $0 worth of tokens, the protocol doesn't know (and should not care) about this. The ring-matcher can then decide to only pay the protocol fees for the order worth $0, effectively evading the protocol fees.

The question remains how this asymmetry could be exploited. I can't think of any that are problematic but some examples:

  • You could try to do the trade in multiple rings, exploiting this with dummy orders not worth anything but which are used to pay the protocol fees on. Even if this works in some way I think it's highly unlikely you could still do the trade trustless (e.g. using an intermediate account or by colluding with the ring-matcher or something like that).
  • You could use it to transfer tokens between accounts without paying the protocol fee. This is actually fine because we don't want to charge users a protocol fee on the amount transferred for doing token transfers, the user will just pay a fixed fee for the transfer like he would expect from doing on-chain transfers (and this fixed fee to the operator could perhaps be subject to a protocol fee).

But it's of course possible I'm missing a case that's actually a problem. :)

@coreycaplan3

This comment has been minimized.

Copy link
Contributor

commented May 24, 2019

Great explanation! I think one thing to keep in mind is that most exchanges operate like regular trading platforms. The idea that Dolomite could settle a trade worth $0 to avoid fees with a $100 trade is far fetched when you think about how businesses like Dolomite operate.

I definitely see what you're saying but even still I don't think it's reasonably possible to setup a taker trade for effectively $0 if it's being matched logically.

Yes, the protocol fee is not paid by the order owner itself, it's completely paid by the ring-matcher. The protocol fee is not paid by the order owner by using a part of the fee. The protocol fee percentage and fee percentage are completely unrelated. If the fee is 0% the order owner will not pay any fees at all, the ring-matcher will have to pay the protocol fee.

So does this mean that, similar to v2 of Loopring, Dolomite needs to effectively reimburse itself via the trading fees?

Last recommendation: Protocol pool staking (Global)
I think having the flexibility to withdraw your stake before the time period you specify, (IE 6 months) is pretty important. Users should be able to withdraw while facing some small penalty for doing so early. I'm not sure what the penalty should be, but this should be considered at least and I'm happy to discuss it.

Thanks for all of the answers. Everything seems logical otherwise :)

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 24, 2019

Great explanation! I think one thing to keep in mind is that most exchanges operate like regular trading platforms. The idea that Dolomite could settle a trade worth $0 to avoid fees with a $100 trade is far fetched when you think about how businesses like Dolomite operate.

I definitely see what you're saying but even still I don't think it's reasonably possible to setup a taker trade for effectively $0 if it's being matched logically.

Oh I definitely agree. But because the protocol fee can still be a significant loss of revenue for exchanges it may be worthwhile for them to exploit far fetched things like this.

Yes, the protocol fee is not paid by the order owner itself, it's completely paid by the ring-matcher. The protocol fee is not paid by the order owner by using a part of the fee. The protocol fee percentage and fee percentage are completely unrelated. If the fee is 0% the order owner will not pay any fees at all, the ring-matcher will have to pay the protocol fee.

So does this mean that, similar to v2 of Loopring, Dolomite needs to effectively reimburse itself via the trading fees?

I'm not sure what you mean with that. The ring-matcher can pay the protocol fees using his own funds. But because of the order we do the token transfers in (the ring-matcher receives the trading fees/margin first, afterwards he pays the protocol fees), he can use the trading fees he receives to do so if possible. Does that answer your question?

There are 3 possible sources of income:

  • The trading fees (paid by the order owners to the wallet/ring-matcher)
  • The positive spread (aka the margin paid to the ring-matcher)
  • The operator fee (paid by the ring-matcher to the operator)

As a DEX you can choose to be the wallet, ring-matcher and operator all at the same time, which makes the operator fee redundant.

Last recommendation: Protocol pool staking (Global)
I think having the flexibility to withdraw your stake before the time period you specify, (IE 6 months) is pretty important. Users should be able to withdraw while facing some small penalty for doing so early. I'm not sure what the penalty should be, but this should be considered at least and I'm happy to discuss it.

The exact mechanism for this still needs to worked out. But this seems reasonable, though I think the penality should be rather high if we would do this. It's a risk/reward exercise for the users, nobody is forcing you to lock up LRC for a long time. :)

Thanks for all of the answers. Everything seems logical otherwise :)

Thank you!

@coreycaplan3

This comment has been minimized.

Copy link
Contributor

commented May 25, 2019

I'm not sure what you mean with that. The ring-matcher can pay the protocol fees using his own funds. But because of the order we do the token transfers in (the ring-matcher receives the trading fees/margin first, afterwards he pays the protocol fees), he can use the trading fees he receives to do so if possible. Does that answer your question?

Yes this answers my question! I was wondering at what point in the settlement process does that occur. This makes sense. Considering DEXs (like Dolomite) can effectively cover the protocol fee for market makers and (presumably) issue rebates then too to them in "real-time".

There are 3 possible sources of income:

This makes sense.

It's a risk/reward exercise for the users, nobody is forcing you to lock up LRC for a long time.
Definitely in agreement with you but sometimes people like the flexibility of paying a (large) penalty. Clearly we're in agreement here though.

What's your thoughts on this scenario: I say I'm going to lock my LRC for 12 months. After the 6 month tranche goes by, I decide I want to withdraw, which is in-line with the other people who were going to withdraw in 6 months, but it still breaks my "agreement" of staking for 12 months. What's your thoughts on situations like this and how we should penalize the withdrawal? The same way as withdrawing at a random time?

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 25, 2019

Yes this answers my question! I was wondering at what point in the settlement process does that occur. This makes sense. Considering DEXs (like Dolomite) can effectively cover the protocol fee for market makers and (presumably) issue rebates then too to them in "real-time".

The only way to do rebates is by negative spreads. This was actually something I discussed with Matthew and Daniel today and may be a source of confusion (see the 'Negative fees for market makers (rebates)' part in the issue). Matt will ask market makers if they are okay with this.

Having actual rebate payments for orders has the strange side effect of the same order having multiple representations that behave differently if you implement it in a straightforward way. Having the market maker simply set a higher price (combination of his expected price + rebate) is more flexible, though perhaps less traditional. If this is too confusing we will add a rebate parameter to the order, but it will simply be used to set the price higher in the protocol. I will be making some examples showcasing how the fee model works in different cases (probably tomorrow).

What's your thoughts on this scenario: I say I'm going to lock my LRC for 12 months. After the 6 month tranche goes by, I decide I want to withdraw, which is in-line with the other people who were going to withdraw in 6 months, but it still breaks my "agreement" of staking for 12 months. What's your thoughts on situations like this and how we should penalize the withdrawal? The same way as withdrawing at a random time?

I would say the same as at a random time, because you're still withdrawing earlier than expected. Users are expecting the LRC to remain locked up for another 6 months and they may have made a decision to buy/lock-up LRC as a result of that. Though Matt actually came with an idea for this that may render this discussion moot (locked up LRC could be 'used' in a different way). Lots of different possibilities!

@coreycaplan3

This comment has been minimized.

Copy link
Contributor

commented May 25, 2019

The only way to do rebates is by negative spreads. This was actually something I discussed with Matthew and Daniel today and may be a source of confusion (see the 'Negative fees for market makers (rebates)' part in the issue). Matt will ask market makers if they are okay with this.

Maybe I misunderstood what was originally written but there are circumstances in which negative fees would be given out organically and not just for negative spreads. Dolomite is using it as an incentive mechanism, no matter what the order books look like. Simply put, it's an incentive mechanism for all market makers. The way you described the rebates and the way I understood them lend them to being "conditional" on negative spreads, whereas the way Dolomite would like to use them is not conditional on anything - if you are a maker, you get the company-specified rebate. Can you elaborate if I misunderstood?

I would say the same as at a random time, because you're still withdrawing earlier than expected. Users are expecting the LRC to remain locked up for another 6 months and they may have made a decision to buy/lock-up LRC as a result of that. Though Matt actually came with an idea for this that may render this discussion moot (locked up LRC could be 'used' in a different way). Lots of different possibilities!

Very interesting, looking forward to learning more and I agree with the result so far.

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 25, 2019

The only way to do rebates is by negative spreads. This was actually something I discussed with Matthew and Daniel today and may be a source of confusion (see the 'Negative fees for market makers (rebates)' part in the issue). Matt will ask market makers if they are okay with this.

Maybe I misunderstood what was originally written but there are circumstances in which negative fees would be given out organically and not just for negative spreads. Dolomite is using it as an incentive mechanism, no matter what the order books look like. Simply put, it's an incentive mechanism for all market makers. The way you described the rebates and the way I understood them lend them to being "conditional" on negative spreads, whereas the way Dolomite would like to use them is not conditional on anything - if you are a maker, you get the company-specified rebate. Can you elaborate if I misunderstood?

I added some examples in the issue how it would work. If you want to do rebates you have to use negative spreads. But I think the confusion is probably how this is currently exposed in the protocol/order. Even though not needed for the protocol, it makes sense to add a rebate parameter to the order to make this more clear for users using traditional order books.

@coreycaplan3

This comment has been minimized.

Copy link
Contributor

commented May 27, 2019

Even though not needed for the protocol, it makes sense to add a rebate parameter to the order to make this more clear for users using traditional order books.

Yes that's a great idea and will make it clearer!

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 28, 2019

Updated the main post:

  • Removed a lot of implementation details. Should be easier to understand for everyone!
  • Removed the wallet address from orders! And all things related to the fees paid to the wallet (walletSplitPercentage). Fees go completely to the ring-matcher. A fee sharing scheme can be done outside the protocol. This makes the protocol more efficient.
  • The margin is not paid to the ring-matcher, the order owner keeps the margin. This is done to support 'market orders'. We still use the margin value to correctly update the trading history for the order though.
  • Added a rebate field to the Ring data. The rebate is specified in bips of amountB and is paid to the order owner in tokenB.
  • To support a proving service we need to be able to split the operator fee with the account of that service. Because this service generates the proof this account is baked into the proof itself, so the operator cannot simply change it or remove it. (Though this payment could also be done outside the protocol?)
@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented May 29, 2019

To support a proving service we need to be able to split the operator fee with the account of that service. Because this service generates the proof this account is baked into the proof itself, so the operator cannot simply change it or remove it. (Though this payment could also be done outside the protocol?)

This can actually be done by using a contract as the owner of the operator account. The funds are withdrawn to this account and can be split between the operator and the proving service using any logic.

@dong77

This comment has been minimized.

Copy link
Contributor

commented May 31, 2019

To support a proving service we need to be able to split the operator fee with the account of that service. Because this service generates the proof this account is baked into the proof itself, so the operator cannot simply change it or remove it. (Though this payment could also be done outside the protocol?)

This can actually be done by using a contract as the owner of the operator account. The funds are withdrawn to this account and can be split between the operator and the proving service using any logic.

Great, then this will be like an extension to the protocol. The same approach can be used to make sure matching fees can also be split between multiple parties including the operator and wallets (therefore the protocol does not need to support a percentage operator fee).

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jun 6, 2019

Updated the main post:

  • Added buy/sell orders to support market orders
  • Updated examples

@dong77 dong77 modified the milestones: 3.0beta2, 3.0beta3 Jul 10, 2019

@dong77 dong77 added the discussion label Jul 10, 2019

@dong77 dong77 self-assigned this Jul 10, 2019

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 10, 2019

The following discussions are for beta3 and further versions.

@dong77

This comment was marked as outdated.

Copy link
Contributor

commented Jul 10, 2019

Current in beta2, we still have walletSplitPercentage for OFFCHAIN_WITHDRAWALAL and ORDER_CANCELLATION. I suggest to remove all wallet split related parameters and calculation and allow wallets to negotiate revenue-share agreements with DEX owners offchain. Wallet account ids should be part of orders and other requests to make sure data aggregation is possible for calculating fees payable to wallets.

Revenue share between DEX owner and operators can is preferably done in realtime by the protocol. Currently, this is done for settlements, but not other requests including deposits and withdrawals.

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 11, 2019

The summary of a discussion between Brecht and myself regarding what will likely change in our fee model:

  1. We can remove all in-circuit fee-split. All fees will be paid to one or more operators; operators pay protocol fee in the circuits out of fee revenue, then split the profit with the DEX owner off-chain, for example, on a weekly or monthly basis, according to their business agreement. The DEX owner still owns the DEX and has the power to add/remove operators. But the operators control the cash flow.
  2. Use uint8 or uint16 in orders, rings, and probably other requests to label different parties involved in DEX operations. These uint-labels will be used to calculate off-chain fee-distribution; labels are transparent to the protocol.

A team who provide technical support should be seen as operators; people who take legal responsibility, marketing, operations are DEX owners.

We should also limit the crypt asset of the operator's hot wallet, because that's all that hackers can steal if they hacked into the backend. The idea is to use the hot wallet to pay Ethereum tx gas; use another account (fee-recipient) to receive trading fees and pays protocol fees. The private key of the fee-recipient can be kept in a cold wallet.

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jul 23, 2019

Ring-matcher removal

The Operator now receives the fees form users but he now also pays the protocol fee/rebates. The operator didn't need to sign anything with the ring-matcher because the operator only received fees, the operator never had to pay anything using his account. The ring-matcher needed to sign the ring to authorize these payments.

All the things a ring-matcher could decide are now the responsibility of the operator, but the operator will probably just do whatever the DEX wants using some non-protocol related data:

  • The order the rings are settled
  • The actual fee paid by the order owner (needs to be <= order.maxFeeBips)
  • The rebate given to the order owner

The fees earned by the operator can be withdrawn to a contract that splits the fees in a trust-less way or the operator can have complete control over the fees that were earned.

The operator needs 2 online keypairs:

  • The operator needs an account with a valid EdDSA keypair which is used to sign [blockIdx, merkleRoot] per block (no need to sign every ring independently).
  • The operator needs to submit blocks on the Ethereum blockchain. A normal Ethereum ECDSA keypair is used for this . This address should NOT be the owner of the account described above to minimize risk (otherwise having access to the private key of this account also allows doing onchain withdrawal requests for the account above).

Having 2 different sets of online keypairs gives better security than just a single keypair set.

We could also come up with a method that only uses a single ECDSA keypair:

  • The operator creates an account with the ECDSA keypair. This address is the owner of the account. This account would not need a valid EdDSA keypair because it wouldn't have to sign anything offchain.
  • If the operator submits a block we check that operator == accounts[operatorAccount].owner as a way to authorize potential payments (instead of signing [blockIdx, merkleRoot]) done in the circuit.

If the attacker has access to the ECDSA private key in this scheme however he could drain both the account receiving the fees as well as the ETH in the Ethereuem account used for transactions.

Token Transfers for a single ring

Updated token transfers, though not much has changed (operator simply replaces the ring-matcher). The biggest performance gain here is that we don't have to update a ring-matcher account/trade and we don't have to store ring-matcher and ring-matcher to operator fee info in DA.

// Actual trade
orderA.tokenS.transferFrom(accountA, accountB, fillAmountSA)
orderB.tokenS.transferFrom(accountB, accountA, fillAmountSB)
// Fees
orderA.tokenB.transferFrom(accountA, operator, feeA)
orderB.tokenB.transferFrom(accountB, operator, feeB)
// Rebates
orderA.tokenB.transferFrom(operator, accountA, rebateA)
orderB.tokenB.transferFrom(operator, accountB, rebateB)
// Protocol fees
orderA.tokenB.transferFrom(operator, protocolFeePool, protocolFeeA)
orderB.tokenB.transferFrom(operator, protocolFeePool, protocolFeeB)

Security

Both EdDSA and ECDSA keypairs hacked

For an attacker to successfully spend funds from the operator account he needs both the EdDSA private key and the ECDSA private key of the operator:

  • The EdDSA private key to sign some request transferring funds from the account to another account, we don't support offchain transfers yet and withdrawals go to account owner (which is fixed onchain), so the hacker will have to setup dummy trades to do this and create valid proofs so these trades can be verified onchain!
  • The ECDSA private key so the attacker is able to submit blocks. If the attacker can't submit blocks he can't do anything with the EdDSA private key because the server committing the blocks can detect malicious use of the operator account (e.g. any use of the account not for a withdrawal).

-> The EdDSA private key and the ECDSA private key should be stored on different servers (or some setup that is even more secure than this).

Only the EdDSA keypair hacked

When an attacker only has the EdDSA private key he can only sign offchain requests using the operator account. These requests can be blocked by the server committing blocks.
It's still a good idea to minimize the amount of funds in the account to be safe when also the ECDSA private key gets stolen. Either a withdrawal can be done to the account owner (could be a contract) or (when implemented) an offchain transfer to another offline account.

Only the ECDSA keypair hacked

When an attacker only has the ECDSA private key he could

  • in theory process the requests and receive fees on his own account. This is fine and even seems unlikely because the attacker also needs to generate the proofs for these blocks.
  • withdraw the block fee of blocks containing onchain requests to some ETH account. These block fees need to be withdrawn regularly to some offline ETH account.
  • spend all the ETH in the account (used to pay the gas cost for the Ethereum transactions). There are ways we can minimize the risk here and the protocol does not need to care how this is handled:
    1. This account gets topped up regularly from a different offline account. This minimizes the amount of ETH that can be stolen.
    2. The online account only contains enough gas for a single transaction. All transactions go through a contract that refunds the online account for the gas that was spent (so the balance of the online account remains constant). The operator contract can store a large amount of ETH securely and can be managed by an offline account.

Labels

For labelling requests we have two options:

  1. We simply put all the label data onchain. If we do this we should really limit the size of this data (e.g. just 4 bits for a label/order).
  2. We don't actually put all the labels of the requests onchain, only the hash of the labels is put onchain. The operator should share the actual label data with all necessary parties (and the hash can be used to validate it). Because this data is not critical (no funds can be lost by anyone) and we already expect the operator to act in good faith to share the fees that were earned I think this is a good and cheap way to support this.

I think 2. this makes the most sense because we want to really limit the data that is put onchain as much as possible (as it is the main bottleneck).

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 25, 2019

Labels

I also prefer using hashes. But we need to make sure the hash is as short as possible, hash collisions should be fine as we have some data being hashed are available as part of the onchain DA. For simplicity, should we also use Poseidon or we can use any hashing function?

My understanding is the hashing function has nothing to do with circuits so we may even use murmur hash in relayer to generate an int64 hash.

Also, infrequent hash collisions should not be a problem as many data fields of settlements are actually available on-chain.

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 25, 2019

Keypair Schemes

I prefer the 2-keypairs scheme. In the future, we may refer to these private keys as:

  • fee recipient block-signing (EdDSA) private key: to sign [blockIdx, merkleRoot] per block
  • fee recipient (ECDSA) private key: to do onchain withdrawal and to manage the block signing EdDSA private key.
  • operator (ECDSA) private key: sign Ethereum transactions.

In our smart contract, only the 3rd private key’s corresponding address need to be registered as an operator. (Is my understanding correct? @Brechtpd )

In your 2-keypairs scheme, fee recipient (ECDSA) private key != operator (ECDSA) private key, but we don't have to enforce it on the protocol level, and instead, use as the best practices.

@Brechtpd @letsgoustc

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jul 25, 2019

Labels

I also prefer using hashes. But we need to make sure the hash is as short as possible, hash collisions should be fine as we have some data being hashed are available as part of the onchain DA. For simplicity, should we also use Poseidon or we can use any hashing function?

My understanding is the hashing function has nothing to do with circuits so we may even use murmur hash in relayer to generate an int64 hash.

I would make the label part of the order/requests, so it's part of the the data a user signs. Then all labels of all orders would be bundled together and hashed using Poseidon/Pedersen in the circuit. This way the operator cannot lie about the label data of the orders/requests used. The length of the hash is not important because it's just a single hash/block.

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jul 25, 2019

In our smart contract, only the 3rd private key’s corresponding address need to be registered as an operator. (Is my understanding correct? @Brechtpd )

Yes! Can be registered either directly as the exchange operator or indirectly using an operator contract.

In your 2-keypairs scheme, fee recipient (ECDSA) private key != operator (ECDSA) private key, but we don't have to enforce it on the protocol level, and instead, use as the best practices.

Correct.

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 25, 2019

Labels

I also prefer using hashes. But we need to make sure the hash is as short as possible, hash collisions should be fine as we have some data being hashed are available as part of the onchain DA. For simplicity, should we also use Poseidon or we can use any hashing function?
My understanding is the hashing function has nothing to do with circuits so we may even use murmur hash in relayer to generate an int64 hash.

I would make the label part of the order/requests, so it's part of the data a user signs. Then all labels of all orders would be bundled together and hashed using Poseidon/Pedersen in the circuit. This way the operator cannot lie about the label data of the orders/requests used. The length of the hash is not important because it's just a single hash/block.

I would assume 1) calculating the order hash will take O(n) time where n is the number of orders and 2) the calculation of hash is not part of any circuits. If my understanding is correct, I'd say go with this approach.

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jul 25, 2019

  1. calculating the order hash will take O(n) time where n is the number of orders

That is correct. The label will just be ~1 byte/order so the hashing cost in the circuit will be very very low, maybe an extra 50-100 constraints/trade in total for hashing the extra order data for the order signature and hashing all the labels of all the orders to a single hash.

  1. the calculation of hash is not part of any circuits.

I think it should be part of the circuit, otherwise the operator can put any hash on-chain that he wants. The extra cost in the circuit will be insignificant. If we do it in the circuit the operator is forced to use the correct labels to generate the hash (so he needs to share the correct label data, otherwise it won't match the hash onchain). If we don't do it in the circuit I don't see a lot of benefit in putting anything onchain. There isn't that much data of the order onchain that someone can easily see which trade used which orders.

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 25, 2019

What about we use 32 bytes (uint256) for the label as Poseidon takes 4N inputs?

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jul 25, 2019

Poseidon can take different number of inputs, but you can only use 1536bits/(number of inputs)bits per input.

It may make more sense to use Pedersen here because Pedersen is bit based. we just make a bitstream of all the labels of all the orders and hash it that way once for the complete block. There are ways Poseidon could also be used this way, though I'd have to check what's possible. In any case, the extra constraints/trade will be low.

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 25, 2019

Regardless of the hash function, I don't think 1 byte is large enough for the label, we should use at least a uint64, if not uint256.

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jul 25, 2019

Any reason why it needs to be that large? A simple DEX specific contract where people can get a short uint16/uint24/... label can be made to make these labels short and make it possible to map to some larger amount of data (like an Ethereum address).

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 25, 2019

sorry I was massing up the label concept with the hashing. Uint24 should be enough.

@letsgoustc

This comment has been minimized.

Copy link

commented Jul 26, 2019

I'm not quite understanding what's meaning for the "label" in above discussion? :(

@Brechtpd

This comment has been minimized.

Copy link
Contributor Author

commented Jul 26, 2019

It's just an identifier for a request e.g. an identifier for the wallet the request was created in.

In previous fee models we had wallets and ring-matchers that were paid directly as part of the trade. This produces a lot of token transfers which increases the cost in the circuit (constraints) but also increases the amount of data-availability data (all transactions need to be reconstructible using this data).

So instead of doing all these costly token transfers we just publish a label on-chain for the order/request that was used by the operator. The operator (who receives all the fees from users) can then make agreements with wallets/etc... outside of the protocol how they get paid for the use of their requests.

@letsgoustc

This comment has been minimized.

Copy link

commented Jul 26, 2019

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 26, 2019

I'm not quite understanding what's meaning for the "label" in above discussion? :(

The label can also be used to keep any arbitrary data that can be interpreted by the DEX and their partners, entirely transparent to the protocol.

@dong77 dong77 pinned this issue Jul 29, 2019

@dong77

This comment has been minimized.

Copy link
Contributor

commented Jul 29, 2019

We only need to hashes all the labels as a labelHash per off-chain block.

@dong77

This comment has been minimized.

Copy link
Contributor

commented Aug 2, 2019

All features have been implemented.

@dong77 dong77 closed this Aug 2, 2019

Loopring Protocol v3 automation moved this from In progress to Done Aug 2, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.