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: Fee Reclamations #57

Closed
justinjmoses opened this issue Dec 2, 2019 · 9 comments
Closed

Proposal: Fee Reclamations #57

justinjmoses opened this issue Dec 2, 2019 · 9 comments

Comments

@justinjmoses
Copy link
Member

@justinjmoses justinjmoses commented Dec 2, 2019

Problem

Refer to "Problem" definition via Synthetixio/synthetix#298 for details on the front-running situation in Synthetix.

The Exchanging Queuing Proposal suggests solving front-running with the use of queue, to be processed at some future stage after prices have been received. The concern with this approach is that it turns a synchronous process - Synthetix.exchange() - into an ansynchoronous one. That is, when Synthetix.exchange() completes, the account will not yet have synths, and has to wait for the queue to be processed at a later block before they appear in the user's balance. As mentioned in the issue, this breaks composibility; any smart contract that tries to do a Synthetix.exchange() immediately followed by another action that relies on the balance being there will fail.

Proposal

Instead of using a queue, and instead of using the current max gwei limitation introduced in SIP-12, we allow all exchanges to be processed at whatever gas price the user wishes. Immediately thereafter, there is a small waiting period (of M minutes, say) within which exchanging or transfering any of that synth will fail for the user.

After the waiting period, if a price was received by the oracle during that window that would have led to more profit than the exchange fee, then this profit is marked as reclaimable. The next exchange of that synth after the waiting period will always send any reclaimable amount to the fee pool before processing the exchange. For transfers, in order to not break ERC20 conventions, if there is any reclaimable on that synth, instead of reclaiming the fees during transfer we propose to always fail the transaction (not unlike when a user attempts to transfer locked SNX). The onus is then user to invoke a function on the synth to repay their reclaimable amount - if any.

Workflow

  • Synthetix.exchange() invoked from synth src to dest by user for amount

    • Are we currently within a waiting period for src?
      • Yes: Fail the transaction
      • No
        • Does user have any reclaimable on src?
          • Yes
            • Is amount > reclaimable?
              • Yes: Invoke Synthetix.settleFeesReclaimed(src) for synth src then continue to exchange amount - reclaimable
              • No: Fail the transaction
          • No: Proceed with exchange as usual
  • Synthetix.settleFeesReclaimed(src) invoked with synth src by user

    • Does user have any reclaimable on src?
      • Yes: Then send reclaimable to the fee pool and set reclaimable to 0
      • No: Fail the transaction
  • Synth.transfer() invoked from synth src by user for amount

    • Are we currently within a waiting period for src?
      • Yes: Fail the transaction
      • No: Does user have any reclaimable on src?
        • Yes: Fail the transaction
        • No: Proceed with transfer as usual

Concerns

1. Legitimate users have to pay front-running fees

While this post-trade fee reclamation is designed to prevent intentional front-running, it is possible that legitimate users get caught out unintentionally front-running. That is, someone is trading unawares of market volatility, and they happen to place a trade right before an oracle update yields them additional profit. However, this proposal only penalizes them for these immediate profits made. Future oracle updates after this waiting period of M minutes have no impact.

2. Friction to exchange & transfer synths

Under this proposal, two points of friction are introduced:

  1. Exchanging a synth soon after it was exchanged into will fail (if less than M minutes). This is a suboptimal user experience, but it's arguably better than the max gas price solution (SIP-12) which impacts all trades. Instead, this limitation only strikes exchanges out of a synth that was recently traded into.

  2. Transferring a synth could now fail if there are any reclamable fees owing. While this introduces extra friction to the Synthetix ecosystem, it is not dissimilar to locked SNX that cannot be transferred. For the sake of protecting the system against front-runners making sprofit from SNX stakers, we propose that this is a worthwhile tradeoff.

3. Limitations to composability

The final concern with this approach is that any atomic (i.e. within the same transaction) exchanges or transfers that proceed an exchange will fail - causing the entire transaction to fail. As more an more DeFi projects are composed together in smart contracts, we have legitimate concerns that this friction could impede future compositions which have yet to be created.

We have some ideas around how to prevent this - such as a additional exchange function that circumvents this logic but always pays higher fees. Alternatively, we can take into account on-chain volatility of the src synth and if below some volatility threshold (say 50bps) over some time window (say 24 hours), allow any exchange of src to bypass the reclamation fee process.

We welcome any constructive input you may have around this issue.

Examples

Given the following preconditions:

  • Jessica has a wallet which holds 100 sUSD and this wallet has never exchanged before,

  • and the price of ETHUSD is 100:1, and BTCUSD is 10000:1

  • and the reclamation fee waiting period (M) is set to 3 minutes.


    When

    • she exchanges 100 sUSD into 1 sETH.

    Then

    • it succeeds as sETH has no reclamation fees for this wallet.

    When

    • she exchanges 100 sUSD into 1 sETH
    • and she immediately attempts to transfer 0.1 sETH

    Then

    • it fails as the waiting period has not expired

    When

    • she exchanges 100 sUSD into 1 sETH
    • and she immediately attempts to exchange 1 sETH for sBTC

    Then

    • it fails as the waiting period has not expired

    When

    • she exchanges 50 sUSD into 0.5 sETH.
    • and she immediately attempts to exchange 50 sUSD into 0.005 sBTC

    Then

    • it succeeds as sBTC has no reclamation fees for this wallet

    When

    • she exchanges 100 sUSD into 1 sETH (paying a 30bps fee)
    • and 2 minutes later the price of ETHUSD goes up to 100.25:1
    • and another minute later she attempts to transfer this sETH

    Then

    • the transfer succeeds because the profit made from the oracle update is less than the fee she already paid

    When

    • she exchanges 100 sUSD into 1 sETH (paying a 30bps fee)
    • and 1 minute later the price of ETHUSD goes up to 103:1
    • and 2 more minutes later she attempts to transfer any of this sETH

    Then

    • the transfer fails because she profited 3% - 0.3% = 2.7%.

    When

    • she exchanges 100 sUSD into 1 sETH (paying a 30bps fee)
    • and a minute later the price of ETHUSD goes up to 103:1
    • and 2 more minutes later she invokes settle for sETH
    • and immediately transfers this sETH to another wallet

    Then

    • the transfer succeeds as the prior settle invocation sent 2.7% of her exchange amount (0.027) to the fee pool, and transfer detected no reclaimable fees remaining.

    When

    • she exchanges 100 sUSD into 1 sETH (paying a 30bps fee)
    • and a minute later the price of ETHUSD goes up to 103:1
    • and 2 more minutes later she attempts to exchange 1 sETH for sBTC

    Then

    • the exchange succeeds, sending 2.7% of her exchange amount (0.027 sETH) to the fee pool, and converting the rest into sBTC (minus the exchange fee).

    When

    • she exchanges 100 sUSD into 1 sETH (paying a 30bps fee)
    • and no oracle update for ETHUSD occurs after 3 minutes
    • once 3 minutes from exchange have elapsed she attempts to exchange

    Then

    • the exchange succeeds and no reclaimable fee is charged

@thebkr7

This comment has been minimized.

Copy link

@thebkr7 thebkr7 commented Dec 10, 2019

@justinjmoses What if each trade sends an oracle request to chainlink with an ID specific to that trade.
The oracle then uses the callback function to return the price for the trade thus completing the creation of the trade.

@justinjmoses

This comment has been minimized.

Copy link
Member Author

@justinjmoses justinjmoses commented Dec 10, 2019

@thebkr7 because that would introduce a delay (of unknown length) while the oracles fetched prices off-chain and before reporting back with the latest price. Like queuing, it breaks composability completely as the execution of the order would no longer happen atomically (i.e. within the same transaction).

@brian0641

This comment has been minimized.

Copy link

@brian0641 brian0641 commented Jan 17, 2020

I propose extending this proposal to both directions: gains due to luck or soft front-running are reclaimable and losses due to bad luck are rebatable. If only the reclaimable portion were implemented, lucky trades would be penalized while traders that make unlucky trades would bear the full loss. To non-front running traders, it would average out to be an effective fee increase.

Rebatable Implementation: During an exchange or transfer, the rebatable flow is similar to the reclaimable flow, except rebatable amounts are credited to a per-account rebate variable. The user would manually need to call Synthetix.claimRebates() in order to claim their rebate from the fee pool. If the fee pool didn't have enough, the call would fail.

In discussion on discord, one issue mentioned with this implementation of rebatable is that the fee pool may sometimes not have enough funds, which would be a poor user experience. Some possible solutions to this: (1) Instead of fully distributing the fee pool every week to SNX holders, always leave a minimum amount in the fee pool (e.g., 20k sUSD); (2) deduct the outstanding rebates in real-time from the fee pool, so that there will always be enough to cover calls to Synthetix.claimRebates(); (3) when needed, new sUSD can simply be minted and added to the fee pool. As long as the amount minted is offset by corresponding creating of new debt, everything balances.; (4) some combination of (1)-(3).

@justinjmoses

This comment has been minimized.

Copy link
Member Author

@justinjmoses justinjmoses commented Jan 17, 2020

@brian0641 I think adding rebates are a reasonable compromise for this proposal, and I've come around to them (as this was discussed a few weeks ago on our Discord in the #sips-wips channel). I think however instead of a separate function, that we include the rebates in settleFeesReclaimed() - naming it settle() - rather than making another function. Creating a separate function makes the logic more convoluted with respect to storage and lookup.

As for the fee pool size - I think with the addition of fee reclamation to the fee pool, there should be a sufficient amount to pay for rebates. This feels like quite an edge case regardless (user has negative front-run - presumably by accident - and the fee pool isn't big enough to cover the losses). If we implement and find it is impacting the user experience, we can revisit this (or if griefing does start to occur - which seems unlikely due to cost to the user).

That being said, I appreciate the possible suggestions. Of them, (1) is a possible addition. (2) is too tough and doesn't work in all cases - it's possible the first exchange in a new period (where fees are currently 0) has a rebate; we can't deduct it from the fee pool until there's a transaction to settle() - which unless we write an oracle to process these settlements, could be never. (3) seems problematic in that it introduces minting into a new scenario - adding more complexity to a complex system, and I don't feel the trade-off is worth it. It makes sense that we use the fee pool as the place where debt is repaid to SNX stakers, and that this same pool be used to rebate them.

@ACE0999

This comment has been minimized.

Copy link

@ACE0999 ACE0999 commented Jan 17, 2020

Hey Justin,
Just read this fee reclamation proposal, really amazing job with the concept. I am worried though how will the time delay be decided upon? Meaning, let's say we set it now to 1 minutes. Where is the vulnerability is my concern...

Also on the second friction point you raised above, regarding the failure of swap transactions due to unclaimed fees... I don't know about the complexity of the implementation where triggers payment/receipt of unclaimed fees when swapping the synth automatically...

@justinjmoses

This comment has been minimized.

Copy link
Member Author

@justinjmoses justinjmoses commented Jan 20, 2020

@ACE0999 thanks 🙏

how will the time delay be decided upon

We will initially decide upon a conservative time frame, based on known oracle updates, and monitor it. We also need to integrate this with the remainder of the chainlink migration - so we will need thread the needle a little. I expect the initial rate to be somewhere in the vicinity of 5 minutes, but that is subject to change.

regarding the failure of swap transactions due to unclaimed fees... I don't know about the complexity of the implementation where triggers payment/receipt of unclaimed fees when swapping the synth automatically...

I'm not sure I follow you? If you mean exchange when you say swap, then these will automatically be deducted or credited following successive exchanges.

@ACE0999

This comment has been minimized.

Copy link

@ACE0999 ACE0999 commented Jan 20, 2020

Perfect, on the first point.... Thanks
For the second point I meant, if I buy something like sETH, and I want to swap it on uniswap, I'm assuming I'll be blocked due to the reclamation fees?
So if it is the case, will I have an ability to settle these fees on MintR, or maybe automatically on swapping... That's what I meant...
Thanks a lot

@justinjmoses

This comment has been minimized.

Copy link
Member Author

@justinjmoses justinjmoses commented Jan 21, 2020

This is now being discussed in this PR: #77 - please direct comments over there.

@justinjmoses

This comment has been minimized.

Copy link
Member Author

@justinjmoses justinjmoses commented Jan 21, 2020

Update: this is now SIP-37 - link to discussions in Discord in the SIP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.