From 180d80be7d89ce1c45cfa172724fecd487ff0ec8 Mon Sep 17 00:00:00 2001 From: Jon Cinque Date: Wed, 31 Aug 2022 14:34:25 +0200 Subject: [PATCH] token-swap: Remove stable-swap simulation tests (#3555) --- Cargo.lock | 110 -------------- token-swap/program/Cargo.toml | 1 - token-swap/program/sim/Cargo.toml | 10 -- token-swap/program/sim/simulation.py | 188 ------------------------ token-swap/program/sim/src/lib.rs | 193 ------------------------- token-swap/program/src/curve/stable.rs | 49 ------- 6 files changed, 551 deletions(-) delete mode 100644 token-swap/program/sim/Cargo.toml delete mode 100644 token-swap/program/sim/simulation.py delete mode 100644 token-swap/program/sim/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2e106b51..a86590f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/token-swap/program/Cargo.toml b/token-swap/program/Cargo.toml index 990a1516..5a1b00f0 100644 --- a/token-swap/program/Cargo.toml +++ b/token-swap/program/Cargo.toml @@ -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" diff --git a/token-swap/program/sim/Cargo.toml b/token-swap/program/sim/Cargo.toml deleted file mode 100644 index 0c12e61a..00000000 --- a/token-swap/program/sim/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "sim" -version = "0.1.0" -authors = ["michaelhly "] -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"] } diff --git a/token-swap/program/sim/simulation.py b/token-swap/program/sim/simulation.py deleted file mode 100644 index e1b6baa3..00000000 --- a/token-swap/program/sim/simulation.py +++ /dev/null @@ -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 diff --git a/token-swap/program/sim/src/lib.rs b/token-swap/program/sim/src/lib.rs deleted file mode 100644 index 7181c0f5..00000000 --- a/token-swap/program/sim/src/lib.rs +++ /dev/null @@ -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, - pub n_coins: u8, - pub fee: u128, - pub target_prices: Vec, - pub pool_tokens: u128, -} - -impl StableSwapModel { - pub fn new(amp_factor: u128, balances: Vec, 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, - 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 { - 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 { - 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 { - 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>, - ) -> Result { - 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 { - 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); - } -} diff --git a/token-swap/program/src/curve/stable.rs b/token-swap/program/src/curve/stable.rs index 9ec7b2fd..d990d975 100644 --- a/token-swap/program/src/curve/stable.rs +++ b/token-swap/program/src/curve/stable.rs @@ -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;