token-swap: Remove stable-swap simulation tests (#3555)

This commit is contained in:
Jon Cinque 2022-08-31 14:34:25 +02:00 committed by GitHub
parent 8d1e57018a
commit 180d80be7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 0 additions and 551 deletions

110
Cargo.lock generated
View File

@ -2100,29 +2100,6 @@ dependencies = [
"regex",
]
[[package]]
name = "indoc"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8"
dependencies = [
"indoc-impl",
"proc-macro-hack",
]
[[package]]
name = "indoc-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0"
dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.43",
"quote 1.0.21",
"syn 1.0.99",
"unindent",
]
[[package]]
name = "inout"
version = "0.1.2"
@ -3043,25 +3020,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "paste"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
dependencies = [
"paste-impl",
"proc-macro-hack",
]
[[package]]
name = "paste-impl"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
dependencies = [
"proc-macro-hack",
]
[[package]]
name = "pbkdf2"
version = "0.4.0"
@ -3328,12 +3286,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "0.4.30"
@ -3487,54 +3439,6 @@ dependencies = [
"autotools",
]
[[package]]
name = "pyo3"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf01dbf1c05af0a14c7779ed6f3aa9deac9c3419606ac9de537a2d649005720"
dependencies = [
"cfg-if 1.0.0",
"indoc",
"libc",
"parking_lot 0.11.2",
"paste",
"pyo3-build-config",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf9e4d128bfbddc898ad3409900080d8d5095c379632fbbfbb9c8cfb1fb852b"
dependencies = [
"once_cell",
]
[[package]]
name = "pyo3-macros"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67701eb32b1f9a9722b4bc54b548ff9d7ebfded011c12daece7b9063be1fd755"
dependencies = [
"pyo3-macros-backend",
"quote 1.0.21",
"syn 1.0.99",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f44f09e825ee49a105f2c7b23ebee50886a9aee0746f4dd5a704138a64b0218a"
dependencies = [
"proc-macro2 1.0.43",
"pyo3-build-config",
"quote 1.0.21",
"syn 1.0.99",
]
[[package]]
name = "qstring"
version = "0.7.2"
@ -4398,13 +4302,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788"
[[package]]
name = "sim"
version = "0.1.0"
dependencies = [
"pyo3",
]
[[package]]
name = "simpl"
version = "0.1.0"
@ -6356,7 +6253,6 @@ dependencies = [
"num-traits",
"proptest",
"roots",
"sim",
"solana-program",
"solana-sdk",
"spl-math",
@ -7218,12 +7114,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unindent"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
[[package]]
name = "universal-hash"
version = "0.4.1"

View File

@ -28,7 +28,6 @@ roots = { version = "0.0.7", optional = true }
[dev-dependencies]
proptest = "1.0"
roots = "0.0.7"
sim = { path = "./sim" }
solana-sdk = "1.11.6"
test-case = "2.2"

View File

@ -1,10 +0,0 @@
[package]
name = "sim"
version = "0.1.0"
authors = ["michaelhly <michaelhly@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pyo3 = { version = "0.15.1", features = ["auto-initialize"] }

View File

@ -1,188 +0,0 @@
# Source from: https://github.com/curvefi/curve-contract/blob/master/tests/simulation.py
class Curve:
"""
Python model of Curve pool math.
"""
def __init__(self, A, D, n, fee = 10 ** 7, p=None, tokens=None):
"""
A: Amplification coefficient
D: Total deposit size
n: number of currencies
p: target prices
"""
self.A = A # actually A * n ** (n - 1) because it's an invariant
self.n = n
self.fee = fee
if p:
self.p = p
else:
self.p = [10 ** 18] * n
if isinstance(D, list):
self.x = D
else:
self.x = [D // n * 10 ** 18 // _p for _p in self.p]
self.tokens = tokens
def xp(self):
return [x * p // 10 ** 18 for x, p in zip(self.x, self.p)]
def D(self):
"""
D invariant calculation in non-overflowing integer operations
iteratively
A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
Converging solution:
D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1)
"""
Dprev = 0
xp = self.xp()
S = sum(xp)
D = S
Ann = self.A * self.n
counter = 0
while abs(D - Dprev) > 1:
D_P = D
for x in xp:
D_P = D_P * D // (self.n * x + 1)
Dprev = D
D = (Ann * S + D_P * self.n) * D // ((Ann - 1) * D + (self.n + 1) * D_P)
counter += 1
if counter > 1000:
break
return D
def y(self, i, j, x):
"""
Calculate x[j] if one makes x[i] = x
Done by solving quadratic equation iteratively.
x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
x_1**2 + b*x_1 = c
x_1 = (x_1**2 + c) / (2*x_1 + b)
"""
D = self.D()
xx = self.xp()
xx[i] = x # x is quantity of underlying asset brought to 1e18 precision
xx = [xx[k] for k in range(self.n) if k != j]
Ann = self.A * self.n
c = D
for y in xx:
c = c * D // (y * self.n)
c = c * D // (self.n * Ann)
b = sum(xx) + D // Ann - D
y_prev = 0
y = D
counter = 0
while abs(y - y_prev) > 1:
y_prev = y
y = (y ** 2 + c) // (2 * y + b)
counter += 1
if counter > 1000:
break
return y # the result is in underlying units too
def y_D(self, i, _D):
"""
Calculate x[j] if one makes x[i] = x
Done by solving quadratic equation iteratively.
x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
x_1**2 + b*x_1 = c
x_1 = (x_1**2 + c) / (2*x_1 + b)
"""
xx = self.xp()
xx = [xx[k] for k in range(self.n) if k != i]
S = sum(xx)
Ann = self.A * self.n
c = _D
for y in xx:
c = c * _D // (y * self.n)
c = c * _D // (self.n * Ann)
b = S + _D // Ann
y_prev = 0
y = _D
counter = 0
while abs(y - y_prev) > 1:
y_prev = y
y = (y ** 2 + c) // (2 * y + b - _D)
counter += 1
if counter > 1000:
break
return y # the result is in underlying units too
def dy(self, i, j, dx):
# dx and dy are in underlying units
xp = self.xp()
return xp[j] - self.y(i, j, xp[i] + dx)
def exchange(self, i, j, dx):
xp = self.xp()
x = xp[i] + dx
y = self.y(i, j, x)
dy = xp[j] - y
fee = dy * self.fee // 10 ** 10
#assert dy > 0
if dy == 0:
return 0
self.x[i] = x * 10 ** 18 // self.p[i]
self.x[j] = (y + fee) * 10 ** 18 // self.p[j]
return dy - fee
def remove_liquidity_imbalance(self, amounts):
_fee = self.fee * self.n // (4 * (self.n - 1))
old_balances = self.x
new_balances = self.x[:]
D0 = self.D()
for i in range(self.n):
new_balances[i] -= amounts[i]
self.x = new_balances
D1 = self.D()
self.x = old_balances
fees = [0] * self.n
for i in range(self.n):
ideal_balance = D1 * old_balances[i] // D0
difference = abs(ideal_balance - new_balances[i])
fees[i] = _fee * difference // 10 ** 10
new_balances[i] -= fees[i]
self.x = new_balances
D2 = self.D()
self.x = old_balances
token_amount = (D0 - D2) * self.tokens // D0
return token_amount
def calc_withdraw_one_coin(self, token_amount, i):
xp = self.xp()
if self.fee:
fee = self.fee - self.fee * xp[i] // sum(xp) + 5 * 10 ** 5
else:
fee = 0
D0 = self.D()
D1 = D0 - token_amount * D0 // self.tokens
dy = xp[i] - self.y_D(i, D1)
return dy - dy * fee // 10 ** 10

View File

@ -1,193 +0,0 @@
use pyo3::prelude::*;
use pyo3::types::PyTuple;
use std::fs::File;
use std::io::prelude::*;
const FILE_NAME: &str = "simulation.py";
const FILE_PATH: &str = "sim/simulation.py";
const MODULE_NAME: &str = "simulation";
const DEFAULT_POOL_TOKENS: u128 = 0;
const DEFAULT_TARGET_PRICE: u128 = 1000000000000000000;
pub const MODEL_FEE_NUMERATOR: u128 = 1;
pub const MODEL_FEE_DENOMINATOR: u128 = 1000;
pub struct StableSwapModel {
py_src: String,
pub amp_factor: u128,
pub balances: Vec<u128>,
pub n_coins: u8,
pub fee: u128,
pub target_prices: Vec<u128>,
pub pool_tokens: u128,
}
impl StableSwapModel {
pub fn new(amp_factor: u128, balances: Vec<u128>, n_coins: u8) -> StableSwapModel {
let mut src_file = File::open(FILE_PATH).unwrap();
let mut src_content = String::new();
let _ = src_file.read_to_string(&mut src_content);
Self {
py_src: src_content,
amp_factor,
balances,
n_coins,
fee: 0,
target_prices: vec![DEFAULT_TARGET_PRICE, DEFAULT_TARGET_PRICE],
pool_tokens: DEFAULT_POOL_TOKENS,
}
}
pub fn new_with_pool_tokens(
amp_factor: u128,
balances: Vec<u128>,
n_coins: u8,
pool_token_amount: u128,
) -> StableSwapModel {
let mut src_file = File::open(FILE_PATH).unwrap();
let mut src_content = String::new();
let _ = src_file.read_to_string(&mut src_content);
Self {
py_src: src_content,
amp_factor,
balances,
n_coins,
fee: 0,
target_prices: vec![DEFAULT_TARGET_PRICE, DEFAULT_TARGET_PRICE],
pool_tokens: pool_token_amount,
}
}
pub fn sim_d(&self) -> u128 {
let gil = Python::acquire_gil();
return self
.call0(gil.python(), "D")
.unwrap()
.extract(gil.python())
.unwrap();
}
pub fn sim_dy(&self, i: u128, j: u128, dx: u128) -> u128 {
let gil = Python::acquire_gil();
return self
.call1(gil.python(), "dy", (i, j, dx))
.unwrap()
.extract(gil.python())
.unwrap();
}
pub fn sim_exchange(&self, i: u128, j: u128, dx: u128) -> u128 {
let gil = Python::acquire_gil();
return self
.call1(gil.python(), "exchange", (i, j, dx))
.unwrap()
.extract(gil.python())
.unwrap();
}
pub fn sim_xp(&self) -> Vec<u128> {
let gil = Python::acquire_gil();
return self
.call0(gil.python(), "xp")
.unwrap()
.extract(gil.python())
.unwrap();
}
pub fn sim_y(&self, i: u128, j: u128, x: u128) -> u128 {
let gil = Python::acquire_gil();
return self
.call1(gil.python(), "y", (i, j, x))
.unwrap()
.extract(gil.python())
.unwrap();
}
pub fn sim_y_d(&self, i: u128, d: u128) -> u128 {
let gil = Python::acquire_gil();
return self
.call1(gil.python(), "y_D", (i, d))
.unwrap()
.extract(gil.python())
.unwrap();
}
pub fn sim_remove_liquidity_imbalance(&self, amounts: Vec<u128>) -> u128 {
let gil = Python::acquire_gil();
return self
.call1(
gil.python(),
"remove_liquidity_imbalance",
PyTuple::new(gil.python(), amounts.to_vec()),
)
.unwrap()
.extract(gil.python())
.unwrap();
}
pub fn sim_calc_withdraw_one_coin(&self, token_amount: u128, i: u128) -> u128 {
let gil = Python::acquire_gil();
return self
.call1(gil.python(), "calc_withdraw_one_coin", (token_amount, i))
.unwrap()
.extract(gil.python())
.unwrap();
}
fn call0(&self, py: Python, method_name: &str) -> Result<PyObject, PyErr> {
let sim = PyModule::from_code(py, &self.py_src, FILE_NAME, MODULE_NAME).unwrap();
let model = sim
.getattr("Curve")?
.call1((
self.amp_factor,
self.balances.to_vec(),
self.n_coins,
self.fee,
self.target_prices.to_vec(),
self.pool_tokens,
))
.unwrap()
.to_object(py);
let py_ret = model.as_ref(py).call_method0(method_name);
self.extract_py_ret(py, py_ret)
}
fn call1(
&self,
py: Python,
method_name: &str,
args: impl IntoPy<Py<PyTuple>>,
) -> Result<PyObject, PyErr> {
let sim = PyModule::from_code(py, &self.py_src, FILE_NAME, MODULE_NAME).unwrap();
let model = sim
.getattr("Curve")?
.call1((
self.amp_factor,
self.balances.to_vec(),
self.n_coins,
self.fee,
self.target_prices.to_vec(),
self.pool_tokens,
))
.unwrap()
.to_object(py);
let py_ret = model.as_ref(py).call_method1(method_name, args);
self.extract_py_ret(py, py_ret)
}
fn extract_py_ret(&self, py: Python, ret: PyResult<&PyAny>) -> Result<PyObject, PyErr> {
match ret {
Ok(v) => v.extract(),
Err(e) => {
e.print_and_set_sys_last_vars(py);
panic!("Python execution failed.")
}
}
}
pub fn print_src(&self) {
println!("{}", self.py_src);
}
}

View File

@ -388,7 +388,6 @@ mod tests {
RoundDirection, INITIAL_SWAP_POOL_AMOUNT,
};
use proptest::prelude::*;
use sim::StableSwapModel;
#[test]
fn initial_pool_amount() {
@ -437,54 +436,6 @@ mod tests {
assert_eq!(result.destination_amount_swapped, 0);
}
proptest! {
#[test]
fn swap_no_fee(
swap_source_amount in 100..1_000_000_000_000_000_000u128,
swap_destination_amount in 100..1_000_000_000_000_000_000u128,
source_amount in 100..100_000_000_000u128,
amp in 1..150u64
) {
prop_assume!(source_amount < swap_source_amount);
let curve = StableCurve { amp };
let model: StableSwapModel = StableSwapModel::new(
curve.amp.into(),
vec![swap_source_amount, swap_destination_amount],
N_COINS,
);
let result = curve.swap_without_fees(
source_amount,
swap_source_amount,
swap_destination_amount,
TradeDirection::AtoB,
);
let result = result.unwrap();
let sim_result = model.sim_exchange(0, 1, source_amount);
let diff =
(sim_result as i128 - result.destination_amount_swapped as i128).abs();
// tolerate a difference of 2 because of the ceiling during calculation
let tolerance = std::cmp::max(2, sim_result as i128 / 1_000_000_000);
assert!(
diff <= tolerance,
"result={}, sim_result={}, amp={}, source_amount={}, swap_source_amount={}, swap_destination_amount={}, diff={}",
result.destination_amount_swapped,
sim_result,
amp,
source_amount,
swap_source_amount,
swap_destination_amount,
diff
);
}
}
#[test]
fn pack_curve() {
let amp = 1;