Blame
c3a770 | kovalev_v | 2024-09-10 05:27:40 | 1 | # SF Swap Core |
2 | ||||
3 | September 2024 | |||
4 | ||||
5 | #### Abstract | |||
6 | ||||
7de75b | kovalev_v | 2024-09-11 17:27:12 | 7 | This technical whitepaper explains some of the design decisions behind the Stochastic.Finance Swap (further: SFSwap) core contracts. It covers the contracts’ features including arbitrary pairs between ERC20 (further: Stable Coin) and ERC1155 (further: Trade Coin), an AMM that allowes traders to watch liquidity of pairs and collect a price information, "flash swaps" that allow traders to buy/sell options produced on Stochastic.Finance DAPP. This whitepaper describes the mechanics of SFSwap “core” contracts including the pair contract that stores liquidity providers’ funds—and the factory contract used to instantiate pair contracts. |
c3a770 | kovalev_v | 2024-09-10 05:27:40 | 8 | |
9 | ## 1. Introduction | |||
10 | ||||
11 | SFSwap is an on-chain system of smart contracts on the Ethereum blockchain, implementing an automated liquidity protocol based on a “constant product formula” with additional features. Mostly it enables the creation only of arbitrary ERC20/ERC1155 pairs. | |||
12 | Each SFSwap pair stores pooled reserves of two assets (Stable Coin and Trade Coin), and provides liquidity for those two assets, maintaining the invariant that the product of the reserves cannot decrease. Traders pay zero fee (read in ??? "Zero fee policy" section) on trades, which goes to liquidity providers. The contracts are non-upgradeable. | |||
13 | As discussed in section ???, SFSwap also has all safety and security fixes, as well as rearchitecting the implementation, reducing SFSwap’s attack surface. | |||
14 | ||||
15 | This paper describes the mechanics of that core contract, as well as the factory contract used to instantiate those contracts. Actually using SFSwap will require calling the pair contract through a “router” contract that computes the trade or deposit amount and transfers funds to the pair contract | |||
16 | ||||
17 | ## 2. Features | |||
18 | ### 2.1 ERC-20/ERC-1155 pairs | |||
19 | ||||
7de75b | kovalev_v | 2024-09-11 17:27:12 | 20 | SFSwap allows liquidity providers to create pair contracts only for Stable Coin and any parts (ERC-1155 ids) or Trade Coin. There are could be many pairs between ERC-20/ERC1155 that could make it somewhat more difficult |
21 | to find the best path to trade a particular option/pair, but routing can be handled with help from the UI of DAPP. | |||
22 | ||||
23 | ### 2.2 Price oracle | |||
24 | ||||
25 | The marginal price offered by SFSwap (with zero trades) at time can be computed by dividing the reserves of asset $a$ by the reserves of asset $b$. | |||
26 | ||||
27 | $$p_t = \frac{r^a_t}{r^b_t}$$ | |||
28 | ||||
29 | But SFSwap improves this oracle functionality by measuring and recording the price before the first trade of each block (or equivalently, after the last trade of the previous block). This price is more difficult to manipulate than prices during a block. If the attacker submits a transaction that attempts to manipulate the price at the end of a block, some other arbitrageur may be able to submit another transaction to trade back immediately afterward in the same block. A miner (or an attacker who uses enough gas to fill an entire block) could manipulate the price at the end of a block, but unless they mine the next block as well, they may not have a particular advantage in arbitraging the trade back. | |||
30 | ||||
31 | Specifically, SFSwap accumulates this price, by keeping track of the cumulative sum of prices at the beginning of each block in which someone interacts with the contract. Each price is weighted by the amount of time that has passed since the last block in which it was updated, according to the block timestamp. This means that the accumulator value at any given time (after being updated) should be the sum of the spot price at each second in the history of the contract. | |||
32 | ||||
33 | $$a_t = \sum_{i=1}^t{p_i}$$ | |||
34 | ||||
35 | To estimate the time-weighted average price from time t1 to t2, an external caller can checkpoint the accumulator’s value at t1 and then again at t2, subtract the first value from the second, and divide by the number of seconds elapsed. (Note that the contract itself does not store historical values for this accumulator—the caller has to call the contract at the beginning of the period to read and store this value.) | |||
36 | ||||
37 | $$p_{t_1,t_2} = \frac{\sum_{i=t_1}^{t_2} p_i}{t_2 - t_1} = \frac{\sum_{i=1}^{t_2} p_i - \sum_{i=1}^{t_1} p_i}{t_2 - t_1} = \frac{a_{t_2} - a_{t_1}}{t_2 - t_1}$$ | |||
38 | ||||
39 | Users of the oracle can choose when to start and end this period. Choosing a longer period makes it more expensive for an attacker to manipulate the TWAP, although it results in a less up-to-date price. | |||
40 | ||||
41 | One complication: should we measure the price of asset A in terms of asset B, or the price of asset B in terms of asset A? While the spot price of A in terms of B is always the reciprocal of the spot price of B in terms of A, the mean price of asset A in terms of asset B over a particular period of time is not equal to the reciprocal of the mean price of asset B in terms of asset A.3 For example, if the USD/ETH price is 100 in block 1 and 300 in block 2, the average USD/ETH price will be 200 USD/ETH, but the average ETH/USD price will be 1/150 ETH/USD. Since the contract cannot know which of the two assets users would want to use as the unit of account, Uniswap v2 tracks both prices. | |||
42 | ||||
43 | Another complication is that it is possible for someone to send assets to the pair contract—and thus change its balances and marginal price—without interacting with it, and thus without triggering an oracle update. If the contract simply checked its own balances and updated the oracle based on the current price, an attacker could manipulate the oracle by sending an asset to the contract immediately before calling it for the first time in a block. If the last trade was in a block whose timestamp was X seconds ago, the contract would incorrectly multiply the new price by X before accumulating it, even though nobody has had an opportunity to trade at that price. To prevent this, the core contract caches its reserves after each interaction, and updates the oracle using the price derived from the cached reserves rather than the current reserves. In addition to protecting the oracle from manipulation, this change enables the contract re-architecture described below in section 3.2. | |||
44 | ||||
45 | #### 2.2.1 Precision | |||
46 | ||||
47 | Because Solidity does not have first-class support for non-integer numeric data types, the Uniswap v2 uses a simple binary fixed point format to encode and manipulate prices. Specifically, prices at a given moment are stored as UQ112.112 numbers, meaning that 112 fractional bits of precision are specified on either side of the decimal point, with no sign. | |||
48 | These numbers have a range of $[0, 2^{112} - 1]$ and a precision of $\frac {1} {2^{112}}$. | |||
49 | ||||
50 | The UQ112.112 format was chosen for a pragmatic reason — because these numbers can be stored in a uint224, this leaves 32 bits of a 256 bit storage slot free. It also happens that the reserves, each stored in a uint112, also leave 32 bits free in a (packed) 256 bit storage slot. These free spaces are used for the accumulation process described above. Specifically, the reserves are stored alongside the timestamp of the most recent block with at least one trade, modded with $2^{32}$ so that it fits into 32 bits. Additionally, although the price at any given moment (stored as a UQ112.112 number) is guaranteed to fit in 224 bits, the accumulation of this price over an interval is not. The extra 32 bits on the end of the storage slots for the accumulated price of A/B and B/A are used to store overflow bits resulting from repeated summations of prices. This design means that the price oracle only adds an additional three SSTORE operations (a current cost of about 15,000 gas) to the first trade in each block. | |||
51 | ||||
52 | The primary downside is that 32 bits isn’t quite enough to store timestamp values that will reasonably never overflow. In fact, the date when the Unix timestamp overflows a uint32 is 02/07/2106. To ensure that this system continues to function properly after this date, and every multiple of 232 − 1 seconds thereafter, oracles are simply required to check prices at least once per interval (approximately 136 years). This is because the core method of accumulation (and modding of timestamp), is actually overflow-safe, meaning that trades across overflow intervals can be appropriately accounted for given that oracles are using the proper (simple) overflow arithmetic to compute deltas. | |||
53 | ||||
54 | ### 2.3 Flash Swaps | |||
411db0 | kovalev_v | 2024-09-11 17:53:26 | 55 | |
56 | SFSwap adds a feature that allows a user to receive and use an asset before paying for it, as long as they make the payment within the same atomic transaction. The swap function makes a call to an optional user-specified callback contract in between transferring out the tokens requested by the user and enforcing the invariant. Once the callback is complete, the contract checks the new balances and confirms that the invariant is satisfied (after adjusting for fees on the amounts paid in). If the contract does not have sufficient funds, it reverts the entire transaction. | |||
57 | ||||
58 | ### 2.4 "Zero fee" Policy | |||
59 | ||||
60 | TBD | |||
61 | ||||
62 | ## 3 Contract structure | |||
63 | ||||
64 | ### 3.1 Contract architecture | |||
65 | ||||
66 | One design priority for SFSwap is to minimize the surface area and complexity of the core pair contract—the contract that stores liquidity providers’ assets. Any bugs in this contract could be disastrous, since millions of dollars of liquidity might be stolen or frozen. | |||
67 | ||||
68 | When evaluating the security of this core contract, the most important question is whether it protects liquidity providers from having their assets stolen or locked. Any feature that is meant to support or protect traders—other than the basic functionality of allowing one asset in the pool to be swapped for another—can be handled in a “router” contract. | |||
69 | ||||
70 | In fact, even part of the swap functionality can be pulled out into the router contract. As mentioned above, SFSwap stores the last recorded balance of each asset (in order to prevent a particular manipulative exploit of the oracle mechanism). | |||
71 | ||||
72 | In SFSwap, the seller sends the asset to the core contract before calling the swap function. Then, the contract measures how much of the asset it has received, by comparing the last recorded balance to its current balance. This means the core contract is agnostic to the way in which the trader transfers the asset. Instead of transferFrom, it could be a meta transaction, or any other future mechanism for authorizing the transfer of ERC-20s | |||
73 | ||||
74 | ### 3.2 Initialization of liquidity token supply | |||
75 | ||||
76 | When a new liquidity provider deposits tokens into an existing SFSwap pair, SFSwap initially mints shares equal to the geometric mean of the amounts deposited: | |||
77 | ||||
78 | $$s_{\text{minted}} = \sqrt{x_{\text{deposited}} \cdot y_{\text{deposited}}}$$ | |||
79 | ||||
80 | This formula ensures that the value of a liquidity pool share at any time is essentially independent of the ratio at which liquidity was initially deposited. For example, suppose that the price of 1 ABC is currently 100 XYZ. If the initial deposit had been 2 ABC and 200 XYZ (a ratio of 1:100), the depositor would have received $\sqrt{2 \cdot 200} = 20$ shares. Those shares should now still be worth 2 ABC and 200 XYZ, plus accumulated fees. | |||
81 | ||||
82 | If the initial deposit had been 2 ABC and 800 XYZ (a ratio of 1:400), the depositor would have received $\sqrt{2 \cdot 800} = 40$ pool shares. | |||
83 | ||||
84 | The above formula ensures that a liquidity pool share will never be worth less than the geometric mean of the reserves in that pool. However, it is possible for the value of a liquidity pool share to grow over time, either by accumulating trading fees or through “donations” to the liquidity pool. In theory, this could result in a situation where the value | |||
85 | of the minimum quantity of liquidity pool shares (1e-18 pool shares) is worth so much that it becomes infeasible for small liquidity providers to provide any liquidity. | |||
86 | ||||
87 | To mitigate this, SFSwap burns the first 1e-15 (0.000000000000001) pool shares that are minted (1000 times the minimum quantity of pool shares), sending them to the zero address instead of to the minter. This should be a negligible cost for almost any token pair. But it dramatically increases the cost of the above attack. In order to raise the value of a liquidity pool share to $100, the attacker would need to donate $100,000 to the pool, which would be permanently locked up as liquidity | |||
88 | ||||
89 | ### 3.3 Deterministic pair addresses (Clones) | |||
90 | ||||
91 | TBD From 3.6. | |||
92 | ||||
93 | ### 3.4 Maximum token balance | |||
94 | ||||
95 | In order to efficiently implement the oracle mechanism, SFSwap only support reserve balances of up to $2^{112} − 1$. This number is high enough to support 18-decimal-place tokens with a totalSupply over 1 quadrillion. | |||
96 | ||||
97 | If either reserve balance does go above $2^{112} − 1$, any call to the **swap** function will begin to fail (due to a check in the **_update()** function). | |||
98 | ||||
99 | ## 4 Disclaimer | |||
100 | ||||
101 | This paper is for general information purposes only. It does not constitute investment advice or a recommendation or solicitation to buy or sell any investment and should not be used in the evaluation of the merits of making any investment decision. It should not be relied upon for accounting, legal or tax advice or investment recommendations. This paper reflects current opinions of the authors and is not made on behalf of Stochastic.Finance or its affiliates and does not necessarily reflect the opinions of Stochastic.Finance, its affiliates or individuals associated with Paradigm. The opinions reflected herein are subject to change without being updated. |