SIP-120: Atomic Exchange Function
Author | |
---|---|
Status | Implemented |
Type | Governance |
Network | Ethereum |
Implementor | JJ |
Release | Alkaid |
Created | 2021-02-24 |
Implementors
Justin Moses (@justinjmoses), Brett Sun (@sohkai)
Simple Summary
Provide a new exchange function allowing users to atomically exchange assets without fee reclamation by pricing synths via a combination of Chainlink and DEX oracles (Uniswap V3). This functionality will only be available on Ethereum's base layer (L1).
Abstract
This SIP proposes a new exchange function for L1 that enables atomic transactions between synths by eschewing the current fee reclamation mechanism. The price selection method is designed to be resistant against both frontrunning oracle latency and flashloan attacks by sourcing prices from Chainlink and DEX oracles.
Motivation
Fee reclamation prevents atomic transactions between synths, degrading the composability of synths in the wider DeFi ecosystem. An attempt at improving fee reclamation came with SIP-89 (Virtual Synths), which was designed to enable atomic transactions between synths without removing the frontrunning protection provided by fee reclamation. The intent was to enable cross-asset swaps between AMM pools within protocols like Curve by tokenizing the claim to an exchange requiring fee reclamation. While virtual synths have now been enabled and have proved fairly successful, they continue to present significant UX friction for implementers and users due to a second transaction still being required to settle the exchange.
DEX-based oracles have come a long way in DeFi, with on-chain TWAP oracles introduced in Uniswap V2, expanded upon in Uniswap V3, and seen increasing adoption by DeFi protocols. For Synthetix, they present an opportunity to utilize an alternative source of prices for fully atomic, one transaction exchanges of synths. Used correctly, these TWAP oracles are difficult to technically frontrun, as one would have to frontrun an active market, and thereby do not expose clean, “pure profit” frontrunning opportunities akin to those based on oracle latency. Furthermore, these TWAP oracles have been carefully constructed to be resilient to manipulation from both flashloan and longer-window attacks.
Initially, the Synthetix system will source DEX-based pricing from Uniswap V3 with the expectation that only large-liquidity markets such as WETH/WBTC and WETH/USDC will be queried. The reported DEX-based price of a synth will be an aggregation of Uniswap V3's latest price, "spot", and a TWAP based on a configurable window, and then compared against the current Chainlink rate. In the future, the DEX-based price may be re-configured to include other sources or pricing models based on the assets enabled for atomic exchanging and evolving market conditions.
The proposed functionality in this SIP is intended only for L1. The current reality, and ongoing hope, of the L2 Synthetix system is to mitigate oracle latency frontrunning, and thereby avoid non-atomic transactions altogether (i.e. no fee reclamation), with the much faster transaction processing capabilities of L2.
Specification
Overview
Taken altogether, this SIP introduces three major changes to the current system:
- A refactoring of the
Exchanger
andExchangeRates
contracts, splitting each into a shared base class and environment-specific (L1) variant - A new
exchangeAtomically()
function, implementing atomic exchanges - A new
IDexPriceAggregator
concept, to plug in DEX-based pricing of synths
And additionally, a number of new configurable values in SystemSettings
.
Environment-specific Exchanger
and ExchangeRates
L1-specific functionality in Exchanger
and ExchangeRates
will be spun out into ExchangerWithFeeReclamationAlternatives
and ExchangeRatesWithDexPricing
, respectively. ExchangerWithFeeReclamationAlternatives
will amalgamate and replace the current ExchangerWithVirtualSynth
variant of Exchanger
.
On L1, these variants will be deployed instead of the base Exchanger
and ExchangeRates
contracts. To the system (via AddressResolver
), these variants will still be known as Exchanger
and ExchangeRates
.
exchangeAtomically()
To facilitate atomic exchanges, the Synthetix
and ExchangerWithFeeReclamationAlternatives
contracts will expose a new function exchangeAtomically()
. This new function will act in a similar manner to the current Exchanger.exchange()
flow but with primary differences in:
- The execution price, detailed below
- Not having a fee reclamation window and therefore not minting any virtual synths
- Restrictions on source and destination synths, configurable by SCCPs
Unlike Exchanger.exchange()
, which relies solely on Chainlink oracles, the execution price for atomic exchanges is selected between the prices given by Chainlink oracles and DEX-based oracles. Two distinct prices are considered, P_CLBUF
and P_DEX
, with the selected execution price being the one that outputs the minimum amount of destination synths:
P_CLBUF
: current Chainlink price, with a buffer of N bps applied against the trading direction (specified per-synth)P_DEX
: aggregated DEX price, over a window of N seconds (if a TWAP is considered)
P_CLBUF
can be calculated internally within the current Synthetix system. P_DEX
is provided externally from an oracle contract implementing IDexPriceAggregator
.
IDexPriceAggregator
To source DEX-based prices, a new IDexPriceAggregator
interface is introduced. Its interface consists of a single pricing function, assetToAsset()
:
function assetToAsset(
address tokenIn,
uint amountIn,
address tokenOut,
uint twapPeriod
) external view returns (uint amountOut);
The external oracle contract implementing IDexPriceAggregator
can source prices from any DEX and use any internal measure to aggregate multiple pricing methods. A complex example of an IDexPriceAggregator
oracle contract would source prices from Uniswap V3 and Sushiswap, and aggregate their spot and TWAP prices together via a liquidity-weighted mean.
Initially, this SIP proposes to use an IDexPriceAggregator
that sources prices solely from Uniswap V3, returning the worst price between the "spot" and TWAP. This initial oracle has been deployed to 0xf120F029Ac143633d1942e48aE2Dfa2036C5786c
(DexPriceAggregatorUniswapV3
).
SystemSettings
Several new configuration settings are proposed:
SystemSettings.atomicMaxVolumePerBlock
: the max volume for atomic exchanges accepted in a block, specified in sUSDSystemSettings.atomicTwapWindow
: the time window to consider for a DEX-based TWAP, specified in number of secondsSystemSettings.atomicEquivalentForDexPricing
: a synth's equivalent on-chain asset with higher on-chain liquidity to poll DEX-based prices from. Having this specified for a synth will also allow it to be used as a source synth or destination synth in atomic exchanges.SystemSettings.atomicExchangeFeeRate
: the exchange fee rate to be paid to the debt pool on each atomic exchange, specified in bps and per-synth. If not set, the regular exchange fee rate will be applied.SystemSettings.atomicPriceBuffer
(CL_BUFFER
): the buffer to be applied against the current Chainlink price in the direction detrimental to the trade, specified in bps and per-synthSystemSettings.atomicVolatilityConsiderationWindow
: the time window to evaluate whether a synth is too volatile to atomically exchange, specified in number of secondsSystemSettings.atomicVolatilityUpdateThreshold
: the maximum number of Chainlink updates in the consideration window before a synth is deemed too volatile to atomically exchange, specified in number of updates
Rationale
The most important considerations concern the price selection method. Ideally, the chosen method will strike a balance between preventing both flashloan style price attacks and frontrunning oracle latency attacks while enabling low-slippage execution of atomic exchanges for high-value, cross-asset swaps involving synths across multiple equivalent-asset AMM pools (e.g. Curve).
The prices of P_CLBUF
and P_DEX
have their own strengths and weaknesses:
P_CLBUF
:P_CL
, the current Chainlink price, is the official “internal” price used for all other exchanges and system debt calculations. However, its update latency is easily gamed via technical frontrunning on L1.P_CLBUF
provides a buffer of N bps (CL_BUFFER
) fromP_CL
to provide a safety net from the deviation threshold for Chainlink oracle updates.CL_BUFFER
is configurable per-synth andP_CLBUF
is calculated using the largerCL_BUFFER
of the source and destination synth.P_DEX
:P_DEX
is the worst price between two separate pricing methods from Uniswap V3:P_DEX_TWAP
: Uniswap V3 TWAPs are designed to only update based on the state at the beginning of the block, preventing flashloan style attacks and making longer-term manipulation costly to the attacker. However, by their construction, TWAPs always lag behind spot.P_DEX_SPOT
: Uniswap V3 spot prices generally follow CEX spot but can be easy to manipulate via flashloan or sandwich style attacks when used improperly. The proposedIDexPriceAggregator
's implementation sources spot from the prior block's observed price rather than the current block's, mitigating intra-block flashloan and sandwich style attacks. However, this measurement is still subject to short-term volatility, such as a large trade occurring in the prior block.
By selecting the worst price between P_CLBUF
, P_DEX_TWAP
, and P_DEX_SPOT
at any given time, the hope is that a “good enough but not exploitable” price can be obtained for the vast majority of situations. In the remainder, there may be periods of high volatility where one price dramatically lags behind, whereby traders will likely forgo atomic execution to obtain a better price through the fee reclamation route--or be prevented from atomic exchanges altogether via the volatility circuit breaker detailed below.
To show that the price selection method of choosing the price that gives the minimum output is safe, we note various potential market and exploit situations:
- If any price provides better output than
P_CL
, a trader can immediately arbitrage back through a fee reclamation exchange - If
P_CL
is about to be updated (i.e. oracle latency), traders will, at best, receive an output that is dampened by theCL_BUFFER
rate. This essentially provides the same defense as fee reclamation, but taken in advance at a fixed rate. - If
P_DEX_TWAP
orP_DEX_SPOT
is used, a synth trader's execution price could be negatively impacted by a price manipulation attack across multiple blocks on Uniswap. However, such attacks only grief the synth trader, not the debt pool, and come at large risk for the griefer due to the need to hold a position across multiple blocks and a lack of atomic execution (i.e. no Flashbots). This scenario would also be similar to a griefer risking capital in one or more CEXes to briefly grief participants by changingP_CL
. - On-chain prices usually follow CEX, so a trader could “frontrun the on-chain market” at the expense of the debt pool in periods of clear market directionality. However, it could be argued that this also applies with fee reclamation, only that it requires more careful timing.
The final concern was backtested with front-running strategies over a few periods of clear market directionality, showing that traders were likely to obtain positive results--at the expense of the debt pool--in such conditions if no price dampeners were included. Adding exchange fees into the model effectively hindered most strategies from taking profit, although it was observed that the necessary rates would be different between BTC and ETH, which prompted for per-synth configuration of the CL_BUFFER
and exchange fee rates.
To further decrease the risk to the debt pool during periods of clear market directionality, a volatility circuit breaker is included to automatically disable and re-enable atomic exchanges for a given synth based on its perceived volatility. This circuit breaker uses the number of Chainlink updates over the past N seconds as a proxy for volatility; if there are more updates than the configured threshold, the synth is deemed too volatile.
To reduce the risk of CL_BUFFER
not providing adequate protection in multi-hop exchanges, this SIP proposes to initially require sUSD as the source or destination synth in an atomic exchange. For example, exchanging between sETH and sBTC involves two reads from Chainlink (ETH:USD and BTC:USD), increasing the potential for oracle latency abuse when updates to both prices are expected.
Finally, as further backstops to decrease the risk associated with this new exchange mechanism, the proposed configuration parameters allow the system to be gradually eased in by increasing per-block volume limits, asset whitelisting, and pricing-related parameter tweaks.
Technical Specification
First, refactor Exchanger
, ExchangerWithVirtualSynth
, and ExchangeRates
into their shared base versions and L1-variants.
Then, add the following interfaces and storage variables:
ExchangerWithFeeReclamationAlternatives.exchangeAtomically()
andSynthetix.exchangeAtomically()
ExchangeRatesWithDexPricing.effectiveAtomicValueAndRates()
and related setters to manage theIDexPriceAggregator
to useExchangerWithFeeReclamationAlternatives.lastAtomicVolume
(struct { uint64 time, uint192 volume }
),SystemSettings.atomicMaxVolumePerBlock
(uint256
), and related setterSystemSettings.setAtomicMaxVolumePerBlock()
, configurable by SCCPsSystemSettings.atomicTwapWindow
(uint256
) and related setterSystemSettings.setAtomicTwapWindow()
, configurable by SCCPsSystemSettings.atomicEquivalentForDexPricing
(mapping (bytes32 => address)
) and related setterSystemSettings.setAtomicEquivalentForDexPricing()
, configurable by SCCPsSystemSettings.atomicExchangeFeeRate
(mapping (bytes32 => uint256)
) and related setterSystemSettings.setAtomicExchangeFeeRate()
, configurable by SCCPsSystemSettings.atomicPriceBuffer
(mapping (bytes32 => uint256)
) and related setterSystemSettings.setAtomicPriceBuffer()
, configurable by SCCPsSystemSettings.atomicVolatilityConsiderationWindow
(mapping (bytes32 => uint256)
) and related setterSystemSettings.setAtomicVolatilityConsiderationWindow()
, configurable by SCCPsSystemSettings.atomicVolatilityUpdateThreshold
(mapping (bytes32 => uint256)
) and related setterSystemSettings.setAtomicVolatilityUpdateThreshold()
, configurable by SCCPs
In detail, ExchangerWithFeeReclamationAlternatives.exchangeAtomically()
will:
- Use the
onlySynthetixorSynth
modifier and other user-input related sanity checks - Ensure the source and destination synths are not deemed too volatile
- Settle any previous fee reclamation exchanges on the source synth
- Select the execution price between
P_CLBUF
andP_DEX
viaExchangeRatesWithDexPricing.effectiveAtomicValueAndRates()
- Sanity check the current exchange's Chainlink rates against the internal circuit breaker (SIP-65) as well as the obtained atomic rate against the current Chainlink rate
- Ensure both the source synth and destination synth can be atomically exchanged and that one of them is sUSD
- Update the volume counter,
ExchangerWithFeeReclamationAlternatives.lastAtomicVolume
, for the current exchange’s sUSD value and ensure the per-block volume limit for atomic exchanges is not exceeded - Execute the exchange by burning source synth, issuing destination synth, and collecting fees. Crucially, this step issues destination synths directly to the account executing the exchange and does not create new virtual synths, bypassing fee reclamation. Fees collected will be derived from the amount of destination synths issued at this step.
- If required, remit the fee with any required conversions back to sUSD (priced via internal Chainlink rate) using either the default fee rate or atomic fee rate.
- Update internal bookkeeping with the new exchange and debt snapshot (priced via internal Chainlink rate), and emit related events
- Process trading rewards
Note that internal system updates arising from the exchange, e.g. updating the debt cache or circuit breaker, will continue to use the current Chainlink rate rather than the selected execution price in 4.
ExchangeRatesWithDexPricing.effectiveAtomicValueAndRates()
selects the execution price by:
- Applying
CL_BUFFER
toP_CL
to obtainP_CLBUF
- Querying the
IDexPriceAggregator
contract (initiallyDexPriceAggregatorUniswapV3
(0xf120F029Ac143633d1942e48aE2Dfa2036C5786c
)) to obtainP_DEX
- Outputting whichever of
P_CLBUF
andP_DEX
provides the minimum output
For step 2, note that due to the low liquidity of synths on DEXes, queries to IDexPriceAggregator
use an equivalent non-synth version with high liquidity instead. This mapping can be configured via SCCPs by calling SystemSettings.setAtomicEquivalentForDexPricing()
.
Additional considerations
It is worth noting that the debt cache may be adversely impacted by this SIP due to synths being priced differently (lower) than their internal, Chainlink-based price during atomic exchanges. Over time, it is likely extended usage of atomic exchanges will produce a skew in the debt cache opposite the direction of the majority of atomic exchanges.
However, as of writing this SIP, it is expected that the current debt cache mechanism will be replaced by an off-chain snapshotting mechanism in the near future.
Test Cases
Included with implementation.
Of interest may be the price selection method, so several examples are included in this SIP.
The cases below assume no trading fees or rebates are applied. Other configuration, such as the volume limit and TWAP window, are also ignored.
On a sUSD -> sETH trade of 1000 sUSD (prices reported in sUSD:sETH):
- Given
P_DEX
of 0.01,P_CL
of 0.011, andCL_BUFFER
of 50bps- Choose 0.01 (
P_DEX
) to output 10 sETH
- Choose 0.01 (
- Given
P_DEX
of 0.01,P_CL
of 0.0099, andCL_BUFFER
of 50bps- Choose 0.0098505 (
P_CLBUF
) to output 9.8505 sETH
- Choose 0.0098505 (
- Given
P_DEX
of 0.01,P_CL
of 0.01, andCL_BUFFER
of 50bps- Choose 0.00995 (
P_CLBUF
) to output 9.95 sETH
- Choose 0.00995 (
- Given
P_DEX
of 0.0099,P_CL
of 0.01, andCL_BUFFER
of 200bps- Choose 0.0098 (
P_CLBUF
) to output 9.8 sETH
- Choose 0.0098 (
- Given
P_DEX
of 0.0099,P_CL
of 0.01, andCL_BUFFER
of 0bps- Choose 0.0099 (
P_DEX
) to output 9.9 sETH
- Choose 0.0099 (
- Given
P_DEX
of 0.01,P_CL
of 0.01, andCL_BUFFER
of 0bps- Choose 0.01 (
P_DEX
orP_CLBUF
) to output 10 sETH
- Choose 0.01 (
Conversely, on a sETH -> sUSD trade of 10 sETH (prices reported in sETH:sUSD):
- Given
P_DEX
of 100,P_CL
of 110, andCL_BUFFER
of 50bps:- Choose 100 (
P_DEX
) to output 1000 sUSD
- Choose 100 (
- Given
P_DEX
of 100,P_CL
of 99, andCL_BUFFER
of 50bps- Choose 98.505 (
P_CLBUF
) to output 985.05 sUSD
- Choose 98.505 (
- Given
P_DEX
of 100,P_CL
of 100, andCL_BUFFER
of 50bps- Choose 99.5 (
P_CLBUF
) to output 995 sUSD
- Choose 99.5 (
- Given
P_DEX
of 99,P_CL
of 100, andCL_BUFFER
of 200bps- Choose 98 (
P_CLBUF
) to output 980 sUSD
- Choose 98 (
- Given
P_DEX
of 99,P_CL
of 100, andCL_BUFFER
of 0bps- Choose 99 (
P_DEX
) to output 990 sUSD
- Choose 99 (
- Given
P_DEX
of 100,P_CL
of 100, andCL_BUFFER
of 0bps- Choose 100 (
P_DEX
orP_CLBUF
) to output 1000 sUSD
- Choose 100 (
Configurable Values (Via SCCP)
Relevant only for atomic exchanges:
- Per-block volume limit, specified in sUSD
- TWAP time window, specified in number of seconds
- Synth equivalents for DEX-basd price queries, specified in token addresses
- Synths allowed, specified with a synth having a mapped equivalent (above)
- Fee override for atomic exchanges, specified in bps and per-synth
- Price buffer against Chainlink, specified in bps and per-synth
- Volatility threshold based on Chainlink updates, specified in number of updates over a number of seconds and per-synth
Initially, this SIP proposes the following system configuration:
SystemSettings.atomicMaxVolumePerBlock
: 200,000 sUSDSystemSettings.atomicTwapWindow
: 1800 (i.e. 30min)SystemSettings.atomicEquivalentForDexPricing
(and thereby also allowing these synths to be exchanged atomically):- sUSD: USDC
- sETH: WETH9
- sBTC: WBTC
SystemSettings.atomicExchangeFeeRate
:- sETH: 30bps
- sBTC: 30bps
SystemSettings.atomicPriceBuffer
:- sETH: 15bps
- sBTC: 15bps
SystemSettings.atomicVolatilityConsiderationWindow
:- sETH: 600 (i.e. 10min)
- sBTC: 600 (i.e. 10min)
SystemSettings.atomicVolatilityUpdateThreshold
:- sETH: 3
- sBTC: 3
Historical context
SIP-120 was initially proposed with Keep3r's TWAP oracles, whose liveliness and correctness relied on an external network of incentivized actors to provide rates of whitelisted assets from Uniswap V2 and Sushiswap liquidity pools.
However, near the end of this SIP's initial development, Uniswap V3 was announced and, important to this SIP, included expanded oracle functionality that simplified the retrieval of TWAP rates by allowing other contracts to directly retrieve TWAP rates from a Uniswap V3 pool. Upgrading to this new version allowed the Synthetix system to both save gas and minimize external dependencies.
A simplified proof-of-concept of this earlier version of the exchange mechanism can be found in this SynthetixAMM
contract which sourced DEX-based prices from (now defunct) Keep3r TWAP oracles.
Closer towards deployment, a sandbox version of the updated exchange mechanism in this SIP was deployed to another SynthetixAMM
contract. This version piggybacked on the existing L1 Synthetix deployment to be as close to a real deployment as possible.
Copyright
Copyright and related rights waived via CC0.