-
Notifications
You must be signed in to change notification settings - Fork 54
/
math.rs
393 lines (353 loc) · 14.7 KB
/
math.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
///! Calculator to maintain the invariant on adding/removing liquidity and on swapping.
///! Large part of the code was taken from https://github.com/saber-hq/stable-swap/blob/master/stable-swap-math/src/curve.rs
use near_sdk::{Balance, Timestamp};
use crate::admin_fee::AdminFees;
use crate::utils::{FEE_DIVISOR, U256, u128_ratio};
/// Minimum ramp duration, in nano sec.
pub const MIN_RAMP_DURATION: Timestamp = 86400 * 1_000_000_000;
/// Min amplification coefficient.
pub const MIN_AMP: u128 = 1;
/// Max amplification coefficient.
pub const MAX_AMP: u128 = 1_000_000;
/// Max amplification change.
pub const MAX_AMP_CHANGE: u128 = 10;
/// Stable Swap Fee calculator.
pub struct Fees {
pub trade_fee: u32,
pub admin_fee: u32,
}
impl Fees {
pub fn new(total_fee: u32, fees: &AdminFees) -> Self {
Self {
trade_fee: total_fee,
admin_fee: fees.admin_fee_bps,
}
}
pub fn zero() -> Self {
Self {
trade_fee: 0,
admin_fee: 0,
}
}
pub fn trade_fee(&self, amount: Balance) -> Balance {
u128_ratio(amount, self.trade_fee as u128, FEE_DIVISOR as u128)
}
pub fn admin_trade_fee(&self, amount: Balance) -> Balance {
u128_ratio(amount, self.admin_fee as u128, FEE_DIVISOR as u128)
}
/// Used to normalize fee applid on difference amount with ideal balance, This logic is from
/// https://github.com/saber-hq/stable-swap/blob/5db776fb0a41a0d1a23d46b99ef412ca7ccc5bf6/stable-swap-program/program/src/fees.rs#L73
pub fn normalized_trade_fee(&self, num_coins: u32, amount: Balance) -> Balance {
let adjusted_trade_fee = (self.trade_fee * num_coins) / (4 * (num_coins - 1));
u128_ratio(amount, adjusted_trade_fee as u128, FEE_DIVISOR as u128)
}
}
/// Encodes all results of swapping from a source token to a destination token.
#[derive(Debug)]
pub struct SwapResult {
/// New amount of source token.
pub new_source_amount: Balance,
/// New amount of destination token.
pub new_destination_amount: Balance,
/// Amount of destination token swapped.
pub amount_swapped: Balance,
/// Admin fee for the swap.
pub admin_fee: Balance,
/// Fee for the swap.
pub fee: Balance,
}
/// The StableSwap invariant calculator.
pub struct StableSwap {
/// Initial amplification coefficient (A)
initial_amp_factor: u128,
/// Target amplification coefficient (A)
target_amp_factor: u128,
/// Current unix timestamp
current_ts: Timestamp,
/// Ramp A start timestamp
start_ramp_ts: Timestamp,
/// Ramp A stop timestamp
stop_ramp_ts: Timestamp,
}
impl StableSwap {
pub fn new(
initial_amp_factor: u128,
target_amp_factor: u128,
current_ts: Timestamp,
start_ramp_ts: Timestamp,
stop_ramp_ts: Timestamp,
) -> Self {
Self {
initial_amp_factor,
target_amp_factor,
current_ts,
start_ramp_ts,
stop_ramp_ts,
}
}
/// Compute the amplification coefficient (A)
pub fn compute_amp_factor(&self) -> Option<Balance> {
if self.current_ts < self.stop_ramp_ts {
let time_range = self.stop_ramp_ts.checked_sub(self.start_ramp_ts)?;
let time_delta = self.current_ts.checked_sub(self.start_ramp_ts)?;
// Compute amp factor based on ramp time
if self.target_amp_factor >= self.initial_amp_factor {
// Ramp up
let amp_range = self
.target_amp_factor
.checked_sub(self.initial_amp_factor)?;
let amp_delta = (amp_range as u128)
.checked_mul(time_delta as u128)?
.checked_div(time_range as u128)?;
self.initial_amp_factor
.checked_add(amp_delta)
.map(|x| x as u128)
} else {
// Ramp down
let amp_range = self
.initial_amp_factor
.checked_sub(self.target_amp_factor)?;
let amp_delta = (amp_range as u128)
.checked_mul(time_delta as u128)?
.checked_div(time_range as u128)?;
self.initial_amp_factor
.checked_sub(amp_delta)
.map(|x| x as u128)
}
} else {
// when stop_ramp_ts == 0 or current_ts >= stop_ramp_ts
Some(self.target_amp_factor as u128)
}
}
/// Compute stable swap invariant (D)
/// Equation:
/// A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
pub fn compute_d(&self, c_amounts: &Vec<Balance>) -> Option<U256> {
let n_coins = c_amounts.len() as u128;
let sum_x = c_amounts.iter().fold(0, |sum, i| sum + i);
if sum_x == 0 {
Some(0.into())
} else {
let amp_factor = self.compute_amp_factor()?;
let mut d_prev: U256;
let mut d: U256 = sum_x.into();
for _ in 0..256 {
// $ D_{k,prod} = \frac{D_k^{n+1}}{n^n \prod x_{i}} = \frac{D^3}{4xy} $
let mut d_prod = d;
for c_amount in c_amounts {
d_prod = d_prod.checked_mul(d)?
.checked_div((c_amount * n_coins).into())?;
}
d_prev = d;
let ann = amp_factor.checked_mul(n_coins.checked_pow(n_coins as u32)?.into())?;
let leverage = (U256::from(sum_x)).checked_mul(ann.into())?;
// d = (ann * sum_x + d_prod * n_coins) * d_prev / ((ann - 1) * d_prev + (n_coins + 1) * d_prod)
let numerator = d_prev.checked_mul(
d_prod
.checked_mul(n_coins.into())?
.checked_add(leverage.into())?,
)?;
let denominator = d_prev
.checked_mul(ann.checked_sub(1)?.into())?
.checked_add(d_prod.checked_mul((n_coins + 1).into())?)?;
d = numerator.checked_div(denominator)?;
// Equality with the precision of 1
if d > d_prev {
if d.checked_sub(d_prev)? <= 1.into() {
break;
}
} else if d_prev.checked_sub(d)? <= 1.into() {
break;
}
}
Some(d)
}
}
/// Compute the amount of LP tokens to mint after a deposit
/// return <lp_amount_to_mint, lp_fees_part>
pub fn compute_lp_amount_for_deposit(
&self,
deposit_c_amounts: &Vec<Balance>, // deposit tokens in comparable precision,
old_c_amounts: &Vec<Balance>, // current in-pool tokens in comparable precision,
pool_token_supply: Balance, // current share supply
fees: &Fees,
) -> Option<(Balance, Balance)> {
let n_coins = old_c_amounts.len();
// Initial invariant
let d_0 = self.compute_d(old_c_amounts)?;
let mut new_balances = vec![0_u128; n_coins];
for (index, value) in deposit_c_amounts.iter().enumerate() {
new_balances[index] = old_c_amounts[index].checked_add(*value)?;
}
// Invariant after change
let d_1 = self.compute_d(&new_balances)?;
if d_1 <= d_0 {
None
} else {
// Recalculate the invariant accounting for fees
for i in 0..new_balances.len() {
let ideal_balance = d_1
.checked_mul(old_c_amounts[i].into())?
.checked_div(d_0)?
.as_u128();
let difference = if ideal_balance > new_balances[i] {
ideal_balance.checked_sub(new_balances[i])?
} else {
new_balances[i].checked_sub(ideal_balance)?
};
let fee = fees.normalized_trade_fee(n_coins as u32, difference);
new_balances[i] = new_balances[i].checked_sub(fee)?;
}
let d_2 = self.compute_d(&new_balances)?;
// d1 > d2 > d0,
// (d2-d0) => mint_shares (charged fee),
// (d1-d0) => diff_shares (without fee),
// (d1-d2) => fee part,
// diff_shares = mint_shares + fee part
let mint_shares = U256::from(pool_token_supply)
.checked_mul(d_2.checked_sub(d_0)?)?
.checked_div(d_0)?
.as_u128();
let diff_shares = U256::from(pool_token_supply)
.checked_mul(d_1.checked_sub(d_0)?)?
.checked_div(d_0)?
.as_u128();
Some((mint_shares, diff_shares-mint_shares))
}
}
/// Compute new amount of token 'y' with new amount of token 'x'
/// return new y_token amount according to the equation
pub fn compute_y(
&self,
x_c_amount: Balance, // new x_token amount in comparable precision,
current_c_amounts: &Vec<Balance>, // in-pool tokens amount in comparable precision,
index_x: usize, // x token's index
index_y: usize, // y token's index
) -> Option<U256> {
let n_coins = current_c_amounts.len() as u128;
let amp_factor = self.compute_amp_factor()?;
let ann = amp_factor.checked_mul(n_coins.checked_pow(n_coins as u32)?.into())?;
// invariant
let d = self.compute_d(current_c_amounts)?;
let mut s_ = x_c_amount;
let mut c = d.checked_mul(d)?.checked_div(x_c_amount.into())?;
for (idx, c_amount) in current_c_amounts.iter().enumerate() {
if idx != index_x && idx != index_y {
s_ += *c_amount;
c = c.checked_mul(d)?
.checked_div((*c_amount).into())?;
}
}
c = c
.checked_mul(d)?
.checked_div(ann.checked_mul((n_coins as u128).checked_pow(n_coins as u32)?.into())?.into())?;
let b = d.checked_div(ann.into())?.checked_add(s_.into())?; // d will be subtracted later
// Solve for y by approximating: y**2 + b*y = c
let mut y_prev: U256;
let mut y = d;
for _ in 0..256 {
y_prev = y;
// $ y_{k+1} = \frac{y_k^2 + c}{2y_k + b - D} $
let y_numerator = y.checked_pow(2.into())?.checked_add(c)?;
let y_denominator = y.checked_mul(2.into())?.checked_add(b)?.checked_sub(d)?;
y = y_numerator.checked_div(y_denominator)?;
if y > y_prev {
if y.checked_sub(y_prev)? <= 1.into() {
break;
}
} else if y_prev.checked_sub(y)? <= 1.into() {
break;
}
}
Some(y)
}
/// given token_out user want get and total tokens in pool and lp token supply,
/// return <lp_amount_to_burn, lp_fees_part>
/// all amounts are in c_amount (comparable amount)
pub fn compute_lp_amount_for_withdraw(
&self,
withdraw_c_amounts: &Vec<Balance>, // withdraw tokens in comparable precision,
old_c_amounts: &Vec<Balance>, // in-pool tokens comparable amounts vector,
pool_token_supply: Balance, // total share supply
fees: &Fees,
) -> Option<(Balance, Balance)> {
let n_coins = old_c_amounts.len();
// Initial invariant, D0
let d_0 = self.compute_d(old_c_amounts)?;
// real invariant after withdraw, D1
let mut new_balances = vec![0_u128; n_coins];
for (index, value) in withdraw_c_amounts.iter().enumerate() {
new_balances[index] = old_c_amounts[index].checked_sub(*value)?;
}
let d_1 = self.compute_d(&new_balances)?;
// compare ideal token portions from D1 with withdraws, to calculate diff fee.
if d_1 >= d_0 {
None
} else {
// Recalculate the invariant accounting for fees
for i in 0..new_balances.len() {
let ideal_balance = d_1
.checked_mul(old_c_amounts[i].into())?
.checked_div(d_0)?
.as_u128();
let difference = if ideal_balance > new_balances[i] {
ideal_balance.checked_sub(new_balances[i])?
} else {
new_balances[i].checked_sub(ideal_balance)?
};
let fee = fees.normalized_trade_fee(n_coins as u32, difference);
// new_balance is for calculation D2, the one with fee charged
new_balances[i] = new_balances[i].checked_sub(fee)?;
}
let d_2 = self.compute_d(&new_balances)?;
// d0 > d1 > d2,
// (d0-d2) => burn_shares (plus fee),
// (d0-d1) => diff_shares (without fee),
// (d1-d2) => fee part,
// burn_shares = diff_shares + fee part
let burn_shares = U256::from(pool_token_supply)
.checked_mul(d_0.checked_sub(d_2)?)?
.checked_div(d_0)?
.as_u128();
let diff_shares = U256::from(pool_token_supply)
.checked_mul(d_0.checked_sub(d_1)?)?
.checked_div(d_0)?
.as_u128();
Some((burn_shares, burn_shares-diff_shares))
}
}
/// Compute SwapResult after an exchange
/// all tokens in and out with comparable precision
pub fn swap_to(
&self,
token_in_idx: usize, // token_in index in token vector,
token_in_amount: Balance, // token_in amount in comparable precision (1e18),
token_out_idx: usize, // token_out index in token vector,
current_c_amounts: &Vec<Balance>, // in-pool tokens comparable amounts vector,
fees: &Fees,
) -> Option<SwapResult> {
let y = self.compute_y(
token_in_amount + current_c_amounts[token_in_idx],
current_c_amounts,
token_in_idx,
token_out_idx,
)?.as_u128();
// https://github.com/curvefi/curve-contract/blob/b0bbf77f8f93c9c5f4e415bce9cd71f0cdee960e/contracts/pool-templates/base/SwapTemplateBase.vy#L466
let dy = current_c_amounts[token_out_idx].checked_sub(y)?.checked_sub(1).unwrap_or(0_u128);
let trade_fee = fees.trade_fee(dy);
let admin_fee = fees.admin_trade_fee(trade_fee);
let amount_swapped = dy.checked_sub(trade_fee)?;
let new_destination_amount = current_c_amounts[token_out_idx]
.checked_sub(amount_swapped)?
.checked_sub(admin_fee)?;
let new_source_amount = current_c_amounts[token_in_idx]
.checked_add(token_in_amount)?;
Some(SwapResult {
new_source_amount,
new_destination_amount,
amount_swapped,
admin_fee,
fee: trade_fee,
})
}
}