From cbecaaaff14d7059b67bf035638ba509b93c0ba6 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 11:35:59 -0400 Subject: [PATCH 01/23] patch g3m swap fee --- box-core/src/membrane.rs | 332 ++++++++++++++--------------- box-simulation/src/setup/deploy.rs | 17 +- 2 files changed, 179 insertions(+), 170 deletions(-) diff --git a/box-core/src/membrane.rs b/box-core/src/membrane.rs index 871e90a5e..cef5a6057 100644 --- a/box-core/src/membrane.rs +++ b/box-core/src/membrane.rs @@ -119,169 +119,169 @@ pub trait Membrane: Sync + Send + Debug { async fn get_weights(&self) -> anyhow::Result<(f64, f64), anyhow::Error>; } -#[cfg(test)] -mod tests { - - use super::*; - use anyhow::anyhow; - use bindings::counter::{Counter, COUNTER_ABI, COUNTER_BYTECODE}; - use ethers::{prelude::*, types::transaction::eip2718::TypedTransaction}; - use std::sync::Arc; - - /// Mock membrane has two fields: - /// - client - The middleware client. - /// - contract - The Counter smart contract instance. Mutable so it can be overridden after deployed. - /// M type must be restricted to the middleware client type. - #[derive(Debug, Clone)] - pub struct MockMembrane - where - M: Middleware + Clone + Send + Sync + 'static, - { - client: Arc, - contract: Counter, - } - - impl MockMembrane - where - M: Middleware + Clone + Send + Sync + 'static, - { - /// Creates a new mock membrane. - pub fn new(client: Arc) -> Self { - let contract = Counter::new(ethers::types::Address::zero(), client.clone()); - Self { client, contract } - } - - /// Deploys the smart contract to the network. - /// - returns: The address of the deployed smart contract. - /// - errors: If the deployment fails, an error is returned. - /// - notes: This is only used for testing. - pub async fn deploy(mut self) -> Self { - let contract = Counter::deploy(self.client.clone(), ()) - .unwrap() - .send() - .await - .unwrap(); - - // Override contract to use the address. - self.contract = contract; - - self - } - } - - /// Implements the Membrane trait for the MockMembrane. - /// This is where the middleware client is used to call the smart contract. - /// The middleware methods act as the standardized api. - /// - /// Two methods are implemented that call it's Mock "contract" (Counter smart contract): - /// - rebalance() - Calls "increment()": Increments the counter by 1. - /// - get_spot_price() - Calls "number()": Fetches the current count. - #[async_trait] - impl Membrane for MockMembrane, LocalWallet>> { - type Client = Arc, LocalWallet>>; - async fn rebalance( - &self, - _swap_direction: bool, - _amount: f64, - ) -> anyhow::Result { - // Increments the counter by 1. - let res = self.contract.increment().send().await?.await?; - - // Returns "1.0" if success, errors otherwise. - let outcome = match res { - Some(_) => Ok(1.0), - None => Err(anyhow!("No result returned from contract call.")), - }?; - - Ok(outcome) - } - - async fn add_liquidity( - &self, - asset_1: &str, - asset_2: &str, - amount_1: f64, - amount_2: f64, - ) -> anyhow::Result { - Ok(0.0) - } - - async fn remove_liquidity( - &self, - asset_1: &str, - asset_2: &str, - amount: f64, - ) -> anyhow::Result<(f64, f64), anyhow::Error> { - Ok((0.0, 0.0)) - } - - async fn update_weights( - &self, - asset: &str, - weight: f64, - ) -> anyhow::Result<(&str, f64, &str, f64), anyhow::Error> { - Ok(("", 0.0, "", 0.0)) - } - - async fn get_spot_price(&self) -> anyhow::Result { - let count = self.contract.clone().number().await.unwrap(); - let count = count.as_u64() as f64; - - Ok(count) - } - - async fn get_weights(&self) -> anyhow::Result<(f64, f64), anyhow::Error> { - Ok((0.0, 0.0)) - } - } - - #[tokio::test] - async fn test_counter() { - // 1. Create an anvil instance. - let anvil = ethers::core::utils::Anvil::new().spawn(); - - // 2. Get a wallet using the anvil instance developer wallets. - let wallet: LocalWallet = anvil.keys()[0].clone().into(); - - // 3. Connect to the network - let provider = Provider::::try_from(anvil.endpoint()) - .unwrap() - .interval(std::time::Duration::from_millis(10u64)); - - // 4. Instantiate the client with the wallet - let client = SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id())); - let client = Arc::new(client); - - // 5. Instantiate the membrane implementation with the client. - let mut membrane = MockMembrane::new(client.clone()); - - // 6. Deploy the smart contract of the membrane implementation using the client. - println!( - "membrane contract address: {:?}", - membrane.contract.clone().address() - ); - membrane = membrane.deploy().await; - println!( - "membrane contract address: {:?}", - membrane.contract.clone().address() - ); - - // 7. Check the initial count is zero. - let count = membrane.get_spot_price().await.unwrap(); - assert_eq!(count, 0.0); - println!("count: {}", count); - - // 8. Calls rebalance(), which implements the increment() call. - let is_success = membrane.rebalance(true, 0.0).await.unwrap(); - assert_eq!(is_success, 1.0); // Returns 1.0 if success, just to make things easy for us. - - // 9. Check the count is now one. - let count = membrane.get_spot_price().await.unwrap(); - assert_eq!(count, 1.0); - println!("count: {}", count); - } - - async fn test_counter_arbiter() { - todo!("Test the arbiter membrane implementation.") - } -} +// #[cfg(test)] +// mod tests { + +// use super::*; +// use anyhow::anyhow; +// use bindings::counter::{Counter, COUNTER_ABI, COUNTER_BYTECODE}; +// use ethers::{prelude::*, types::transaction::eip2718::TypedTransaction}; +// use std::sync::Arc; + +// /// Mock membrane has two fields: +// /// - client - The middleware client. +// /// - contract - The Counter smart contract instance. Mutable so it can be overridden after deployed. +// /// M type must be restricted to the middleware client type. +// #[derive(Debug, Clone)] +// pub struct MockMembrane +// where +// M: Middleware + Clone + Send + Sync + 'static, +// { +// client: Arc, +// contract: Counter, +// } + +// impl MockMembrane +// where +// M: Middleware + Clone + Send + Sync + 'static, +// { +// /// Creates a new mock membrane. +// pub fn new(client: Arc) -> Self { +// let contract = Counter::new(ethers::types::Address::zero(), client.clone()); +// Self { client, contract } +// } + +// /// Deploys the smart contract to the network. +// /// - returns: The address of the deployed smart contract. +// /// - errors: If the deployment fails, an error is returned. +// /// - notes: This is only used for testing. +// pub async fn deploy(mut self) -> Self { +// let contract = Counter::deploy(self.client.clone(), ()) +// .unwrap() +// .send() +// .await +// .unwrap(); + +// // Override contract to use the address. +// self.contract = contract; + +// self +// } +// } + +// /// Implements the Membrane trait for the MockMembrane. +// /// This is where the middleware client is used to call the smart contract. +// /// The middleware methods act as the standardized api. +// /// +// /// Two methods are implemented that call it's Mock "contract" (Counter smart contract): +// /// - rebalance() - Calls "increment()": Increments the counter by 1. +// /// - get_spot_price() - Calls "number()": Fetches the current count. +// #[async_trait] +// impl Membrane for MockMembrane, LocalWallet>> { +// type Client = Arc, LocalWallet>>; +// async fn rebalance( +// &self, +// _swap_direction: bool, +// _amount: f64, +// ) -> anyhow::Result { +// // Increments the counter by 1. +// let res = self.contract.increment().send().await?.await?; + +// // Returns "1.0" if success, errors otherwise. +// let outcome = match res { +// Some(_) => Ok(1.0), +// None => Err(anyhow!("No result returned from contract call.")), +// }?; + +// Ok(outcome) +// } + +// async fn add_liquidity( +// &self, +// asset_1: &str, +// asset_2: &str, +// amount_1: f64, +// amount_2: f64, +// ) -> anyhow::Result { +// Ok(0.0) +// } + +// async fn remove_liquidity( +// &self, +// asset_1: &str, +// asset_2: &str, +// amount: f64, +// ) -> anyhow::Result<(f64, f64), anyhow::Error> { +// Ok((0.0, 0.0)) +// } + +// async fn update_weights( +// &self, +// asset: &str, +// weight: f64, +// ) -> anyhow::Result<(&str, f64, &str, f64), anyhow::Error> { +// Ok(("", 0.0, "", 0.0)) +// } + +// async fn get_spot_price(&self) -> anyhow::Result { +// let count = self.contract.clone().number().await.unwrap(); +// let count = count.as_u64() as f64; + +// Ok(count) +// } + +// async fn get_weights(&self) -> anyhow::Result<(f64, f64), anyhow::Error> { +// Ok((0.0, 0.0)) +// } +// } + +// #[tokio::test] +// async fn test_counter() { +// // 1. Create an anvil instance. +// let anvil = ethers::core::utils::Anvil::new().spawn(); + +// // 2. Get a wallet using the anvil instance developer wallets. +// let wallet: LocalWallet = anvil.keys()[0].clone().into(); + +// // 3. Connect to the network +// let provider = Provider::::try_from(anvil.endpoint()) +// .unwrap() +// .interval(std::time::Duration::from_millis(10u64)); + +// // 4. Instantiate the client with the wallet +// let client = SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id())); +// let client = Arc::new(client); + +// // 5. Instantiate the membrane implementation with the client. +// let mut membrane = MockMembrane::new(client.clone()); + +// // 6. Deploy the smart contract of the membrane implementation using the client. +// println!( +// "membrane contract address: {:?}", +// membrane.contract.clone().address() +// ); +// membrane = membrane.deploy().await; +// println!( +// "membrane contract address: {:?}", +// membrane.contract.clone().address() +// ); + +// // 7. Check the initial count is zero. +// let count = membrane.get_spot_price().await.unwrap(); +// assert_eq!(count, 0.0); +// println!("count: {}", count); + +// // 8. Calls rebalance(), which implements the increment() call. +// let is_success = membrane.rebalance(true, 0.0).await.unwrap(); +// assert_eq!(is_success, 1.0); // Returns 1.0 if success, just to make things easy for us. + +// // 9. Check the count is now one. +// let count = membrane.get_spot_price().await.unwrap(); +// assert_eq!(count, 1.0); +// println!("count: {}", count); +// } + +// async fn test_counter_arbiter() { +// todo!("Test the arbiter membrane implementation.") +// } +// } diff --git a/box-simulation/src/setup/deploy.rs b/box-simulation/src/setup/deploy.rs index 6fbd8d6ba..8a0ba80b7 100644 --- a/box-simulation/src/setup/deploy.rs +++ b/box-simulation/src/setup/deploy.rs @@ -44,9 +44,10 @@ pub async fn deploy_contracts(env: &Environment) -> Result Result Date: Tue, 10 Oct 2023 09:24:56 -0700 Subject: [PATCH 02/23] feat(init): initial init function to setup state of contracts --- Cargo.lock | 1 - box-simulation/src/main.rs | 1 + box-simulation/src/setup/deploy.rs | 3 +- box-simulation/src/setup/init.rs | 124 ++++++++++++++++++++++++++++- 4 files changed, 125 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88a21a871..ac638265d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,7 +344,6 @@ name = "bindings" version = "0.1.0" dependencies = [ "ethers", - "serde", ] [[package]] diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index 590dfd3c3..d1f4fea21 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -37,6 +37,7 @@ async fn main() -> Result<()> { contracts.exchanges.g3m.address(), ) .await?; + setup::init::init(&contracts).await; Ok(()) } diff --git a/box-simulation/src/setup/deploy.rs b/box-simulation/src/setup/deploy.rs index 8a0ba80b7..95f53d355 100644 --- a/box-simulation/src/setup/deploy.rs +++ b/box-simulation/src/setup/deploy.rs @@ -26,6 +26,7 @@ pub struct Exchanges { } pub struct Contracts { + pub deployer: Arc, pub tokens: TokensDeployed, pub exchanges: ExchangesDeployed, } @@ -63,7 +64,7 @@ pub async fn deploy_contracts(env: &Environment) -> Result Result<(), anyhow::Error> { + // Mint INITIAL_BALANCES of tokens to deployer. + contracts + .tokens + .arbx + .mint( + contracts.deployer.address(), + parse_ether(INITIAL_BALANCES.0).unwrap(), + ) + .send() + .await? + .await?; + contracts + .tokens + .arby + .mint( + contracts.deployer.address(), + parse_ether(INITIAL_BALANCES.1).unwrap(), + ) + .send() + .await? + .await?; + + // Get the parsed amounts for the portfolio deposit. + let amounts = ( + parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap(), + parse_ether(INITIAL_PORTFOLIO_BALANCES.1).unwrap(), + ); + + // Approve tokens to be spent by the G3M contract. + contracts + .tokens + .arbx + .approve(contracts.exchanges.g3m.address(), amounts.0) + .send() + .await? + .await?; + contracts + .tokens + .arby + .approve(contracts.exchanges.g3m.address(), amounts.1) + .send() + .await? + .await?; + + // Call init pool to setup the portfolio + // Needs an amount of both tokens, the amounts can be anything but note that they affect the spot price. + let init_pool = contracts + .exchanges + .g3m + .init_pool(amounts.0.into(), amounts.1.into()) + .send() + .await? + .await?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::settings; + use crate::setup; + use arbiter_core::environment::builder::EnvironmentBuilder; + use arbiter_core::environment::Environment; + + /// Initializes the environment and deploys the contracts before calling the init function. + async fn setup_tests() -> Result<(Contracts, Environment), anyhow::Error> { + let config = settings::params::SimulationConfig::new()?; + + let env = EnvironmentBuilder::new().build(); + let contracts = setup::deploy::deploy_contracts(&env).await?; + + let _ = init(&contracts).await?; + + Ok((contracts, env)) + } + + #[tokio::test] + /// Should return the correct initial token balances of the contracts + agents. + async fn test_initial_balances() -> Result<(), anyhow::Error> { + let (contracts, env) = setup_tests().await?; + + let balance_0 = contracts + .tokens + .arbx + .balance_of(contracts.deployer.address()) + .call() + .await?; + let balance_1 = contracts + .tokens + .arby + .balance_of(contracts.deployer.address()) + .call() + .await?; + + assert_eq!( + balance_0, + parse_ether(INITIAL_BALANCES.0 - INITIAL_PORTFOLIO_BALANCES.0).unwrap() + ); + assert_eq!( + balance_1, + parse_ether(INITIAL_BALANCES.1 - INITIAL_PORTFOLIO_BALANCES.1).unwrap() + ); + + Ok(()) + } + + #[tokio::test] + /// Should return the correct constructor arguments of the contracts. + async fn test_constructor_args() {} -fn _init() { - todo!(); + #[tokio::test] + /// Should return the desired start price of the pool. + async fn test_initial_pool_price() {} } From 20e5a3bdacfe46483877eb69f2aee4c124073125 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Oct 2023 11:00:38 -0700 Subject: [PATCH 03/23] refactor(fmt): formats and refactors --- box-simulation/src/settings/params.rs | 4 +- box-simulation/src/setup/deploy.rs | 41 +++++-- box-simulation/src/setup/init.rs | 155 +++++++++++++++++++++++--- 3 files changed, 175 insertions(+), 25 deletions(-) diff --git a/box-simulation/src/settings/params.rs b/box-simulation/src/settings/params.rs index 9d692117f..ebd583be2 100644 --- a/box-simulation/src/settings/params.rs +++ b/box-simulation/src/settings/params.rs @@ -35,8 +35,8 @@ pub struct TokenParameters { pub struct PortfolioPoolParameters { /// fee in bips pub fee_basis_points: u16, - pub weight_token_0: u16, - pub weight_token_1: u16, + pub weight_token_0: f64, + pub weight_token_1: f64, pub liquidity_mantissa: u64, pub liquidity_exponent: u32, diff --git a/box-simulation/src/setup/deploy.rs b/box-simulation/src/setup/deploy.rs index 95f53d355..833a18a3b 100644 --- a/box-simulation/src/setup/deploy.rs +++ b/box-simulation/src/setup/deploy.rs @@ -10,6 +10,8 @@ use ethers::types::U256; use ethers::utils::parse_ether; use std::sync::Arc; +use crate::settings::params::SimulationConfig; + #[derive(Deploy)] pub struct Tokens { pub arbx: @@ -31,9 +33,12 @@ pub struct Contracts { pub exchanges: ExchangesDeployed, } -pub async fn deploy_contracts(env: &Environment) -> Result { - let deployer = RevmMiddleware::new(env, "deployer".into())?; - let decimals = 18u8; +pub async fn deploy_contracts( + env: &Environment, + config: &SimulationConfig, +) -> Result { + let deployer = RevmMiddleware::new(&env, "deployer".into())?; + let decimals = u8::from(18); let arbx_args = ("Arbiter Token X".to_string(), "arbx".to_string(), decimals); let arby_args = ("Arbiter Token Y".to_string(), "arby".to_string(), decimals); @@ -44,17 +49,33 @@ pub async fn deploy_contracts(env: &Environment) -> Result Result Result Result<(), anyhow::Error> { +pub async fn init(contracts: &Contracts, config: &SimulationConfig) -> Result<(), anyhow::Error> { + // Setup the initial token state by minting tokens to the deployer and approving the G3M contract to spend them. + setup_token_state(&contracts).await?; + + // Setup initial contract state of G3M by initializing the pool. + setup_pool_state(&contracts, config).await?; + + Ok(()) +} + +/// All the stateful calls to the tokens used in the simulation. +pub async fn setup_token_state(contracts: &Contracts) -> Result<(), anyhow::Error> { // Mint INITIAL_BALANCES of tokens to deployer. contracts .tokens @@ -53,6 +66,52 @@ pub async fn init(contracts: &Contracts) -> Result<(), anyhow::Error> { .await? .await?; + Ok(()) +} + +/// Initializes the pool with the desired amounts of tokens, making it have an initial spot price. +pub async fn setup_pool_state( + contracts: &Contracts, + config: &SimulationConfig, +) -> Result<(), anyhow::Error> { + // Initial weight is set in the simulation config, but it can be overridden with setWeightX() function. + let initial_weight_0 = parse_ether(config.portfolio_pool_parameters.weight_token_0).unwrap(); + let initial_weight_1 = parse_ether(1) + .unwrap() + .checked_sub(initial_weight_0) + .unwrap(); + // Using the initial weight, initial price, and initial reserve x, we can compute reserve y. + let initial_price = config.portfolio_pool_parameters.initial_price; + let initial_reserve_x = parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap(); + + // p = (x / w_x) / (y / w_y) + // y / w_y = (x / w_x) / p + // y = (x / w_x) / p * w_y + let one_ether = parse_ether(1).unwrap(); + let initial_reserve_y = initial_reserve_x + .checked_mul(one_ether) + .unwrap() + .checked_div(initial_weight_0) + .unwrap() + .checked_mul(one_ether) + .unwrap() + .checked_div(parse_ether(initial_price).unwrap()) + .unwrap() + .checked_mul(initial_weight_1) + .unwrap() + .checked_div(one_ether) + .unwrap(); + println!( + "initial reserves: {}, {}", + initial_reserve_x, initial_reserve_y + ); + + // Get the parsed amounts for the portfolio deposit. + let amounts = ( + parse_ether(initial_reserve_x).unwrap(), + parse_ether(initial_reserve_y).unwrap(), + ); + // Call init pool to setup the portfolio // Needs an amount of both tokens, the amounts can be anything but note that they affect the spot price. let init_pool = contracts @@ -70,26 +129,29 @@ pub async fn init(contracts: &Contracts) -> Result<(), anyhow::Error> { mod tests { use super::*; use crate::settings; + use crate::settings::params::SimulationConfig; use crate::setup; use arbiter_core::environment::builder::EnvironmentBuilder; use arbiter_core::environment::Environment; /// Initializes the environment and deploys the contracts before calling the init function. - async fn setup_tests() -> Result<(Contracts, Environment), anyhow::Error> { + async fn setup_test_environment( + ) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> { let config = settings::params::SimulationConfig::new()?; let env = EnvironmentBuilder::new().build(); - let contracts = setup::deploy::deploy_contracts(&env).await?; + let contracts = setup::deploy::deploy_contracts(&env, &config).await?; - let _ = init(&contracts).await?; - - Ok((contracts, env)) + Ok((contracts, env, config)) } #[tokio::test] /// Should return the correct initial token balances of the contracts + agents. async fn test_initial_balances() -> Result<(), anyhow::Error> { - let (contracts, env) = setup_tests().await?; + let (contracts, env, config) = setup_test_environment().await?; + + // Setup the token state to assert the balances are set and spent correctly. + setup_token_state(&contracts).await?; let balance_0 = contracts .tokens @@ -104,6 +166,69 @@ mod tests { .call() .await?; + assert_eq!(balance_0, parse_ether(INITIAL_BALANCES.0).unwrap()); + assert_eq!(balance_1, parse_ether(INITIAL_BALANCES.1).unwrap()); + + Ok(()) + } + + #[tokio::test] + /// Should return the correct constructor arguments of the contracts. + async fn test_constructor_args() -> Result<(), anyhow::Error> { + todo!() + } + + #[tokio::test] + /// Should return the desired start price of the pool. + async fn test_initial_pool_price() -> Result<(), anyhow::Error> { + let (contracts, env, config) = setup_test_environment().await?; + init(&contracts, &config).await?; + + let price = contracts.exchanges.g3m.get_spot_price().call().await?; + println!("Price: {}", price); + + let target_price = parse_ether(config.portfolio_pool_parameters.initial_price).unwrap(); + println!("Target Price: {}", target_price); + assert_eq!(price, target_price); + + Ok(()) + } + + #[tokio::test] + /// Should return the correct balances of the pool after initialization. + async fn test_initial_pool_balances() -> Result<(), anyhow::Error> { + let (contracts, env, config) = setup_test_environment().await?; + + // Setup the token state to assert the balances are set and spent correctly. + setup_token_state(&contracts).await?; + // Setup the pool state to assert the tokens are sent into the pool on initialization. + setup_pool_state(&contracts, &config).await?; + + let balance_0 = contracts + .tokens + .arbx + .balance_of(contracts.deployer.address()) + .call() + .await?; + let balance_1 = contracts + .tokens + .arby + .balance_of(contracts.deployer.address()) + .call() + .await?; + let balance_2 = contracts + .tokens + .arbx + .balance_of(contracts.exchanges.g3m.address()) + .call() + .await?; + let balance_3 = contracts + .tokens + .arby + .balance_of(contracts.exchanges.g3m.address()) + .call() + .await?; + assert_eq!( balance_0, parse_ether(INITIAL_BALANCES.0 - INITIAL_PORTFOLIO_BALANCES.0).unwrap() @@ -112,15 +237,15 @@ mod tests { balance_1, parse_ether(INITIAL_BALANCES.1 - INITIAL_PORTFOLIO_BALANCES.1).unwrap() ); + assert_eq!( + balance_2, + parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap() + ); + assert_eq!( + balance_3, + parse_ether(INITIAL_PORTFOLIO_BALANCES.1).unwrap() + ); Ok(()) } - - #[tokio::test] - /// Should return the correct constructor arguments of the contracts. - async fn test_constructor_args() {} - - #[tokio::test] - /// Should return the desired start price of the pool. - async fn test_initial_pool_price() {} } From 5e050d456b2cadbd256800f1147721c147557c82 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Oct 2023 11:03:43 -0700 Subject: [PATCH 04/23] fix(initial-pool): fixes double parsing of input values --- box-simulation/src/setup/init.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/box-simulation/src/setup/init.rs b/box-simulation/src/setup/init.rs index a06ae489b..5a1a76e4c 100644 --- a/box-simulation/src/setup/init.rs +++ b/box-simulation/src/setup/init.rs @@ -101,16 +101,9 @@ pub async fn setup_pool_state( .unwrap() .checked_div(one_ether) .unwrap(); - println!( - "initial reserves: {}, {}", - initial_reserve_x, initial_reserve_y - ); // Get the parsed amounts for the portfolio deposit. - let amounts = ( - parse_ether(initial_reserve_x).unwrap(), - parse_ether(initial_reserve_y).unwrap(), - ); + let amounts = (initial_reserve_x, initial_reserve_y); // Call init pool to setup the portfolio // Needs an amount of both tokens, the amounts can be anything but note that they affect the spot price. From e382fda0016e84d2073c9c51f1f86bc147f5d45d Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Oct 2023 11:13:04 -0700 Subject: [PATCH 05/23] test(constructor-args): tests the constructor args are set correctly --- box-simulation/src/setup/init.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/box-simulation/src/setup/init.rs b/box-simulation/src/setup/init.rs index 5a1a76e4c..0b504f7a7 100644 --- a/box-simulation/src/setup/init.rs +++ b/box-simulation/src/setup/init.rs @@ -167,8 +167,33 @@ mod tests { #[tokio::test] /// Should return the correct constructor arguments of the contracts. + /// Since the constructor arguments are derived from the simulation config, + /// this is a good test for making sure the config is integrated correctly. async fn test_constructor_args() -> Result<(), anyhow::Error> { - todo!() + let (contracts, env, config) = setup_test_environment().await?; + init(&contracts, &config).await?; + + let initial_weight_x_wad = parse_ether(config.portfolio_pool_parameters.weight_token_0)?; + let initial_swap_fee_bps = U256::from(config.portfolio_pool_parameters.fee_basis_points); + let initial_admin = contracts.deployer.address(); + let initial_token_x = contracts.tokens.arbx.address(); + let initial_token_y = contracts.tokens.arby.address(); + + let actual_token_x = contracts.exchanges.g3m.token_x().call().await?; + let actual_token_y = contracts.exchanges.g3m.token_y().call().await?; + let actual_weight_x = contracts.exchanges.g3m.weight_x().call().await?; + let actual_weight_y = contracts.exchanges.g3m.weight_y().call().await?; + let actual_swap_fee_bps = contracts.exchanges.g3m.swap_fee().call().await?; + let actual_admin = contracts.exchanges.g3m.admin().call().await?; + + assert_eq!(actual_token_x, initial_token_x); + assert_eq!(actual_token_y, initial_token_y); + assert_eq!(actual_weight_x, initial_weight_x_wad); + assert_eq!(actual_weight_y, parse_ether(1)? - initial_weight_x_wad); + assert_eq!(actual_swap_fee_bps, initial_swap_fee_bps); + assert_eq!(actual_admin, initial_admin); + + Ok(()) } #[tokio::test] From 333095047bda6f67802bca09d3decd02efc7afb2 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Oct 2023 11:20:19 -0700 Subject: [PATCH 06/23] chore(ci): adds ci to run tests --- .github/workflows/test.yaml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000..42fadd568 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,27 @@ +name: test + +on: push + +jobs: + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.71.0 + override: true + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Build bindings + run: FOUNDRY_PROFILE=default ./build.sh + + - name: test + run: cargo test --workspace --exclude bindings \ No newline at end of file From 31de8e7042036280f5d7e65c63959a794ebb298f Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 15:03:03 -0400 Subject: [PATCH 07/23] refactor math lib --- Cargo.lock | 23 ++-- Cargo.toml | 1 + box-core/Cargo.toml | 2 +- box-core/src/math.rs | 106 +++++++++++----- box-simulation/Cargo.toml | 1 + box-simulation/src/agents/mod.rs | 2 + box-simulation/src/agents/rebalancer.rs | 158 ++++++++++++++++++++++++ box-simulation/src/main.rs | 2 + box-simulation/src/setup/deploy.rs | 4 +- 9 files changed, 255 insertions(+), 44 deletions(-) create mode 100644 box-simulation/src/agents/rebalancer.rs diff --git a/Cargo.lock b/Cargo.lock index ac638265d..716a90f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -411,34 +411,35 @@ dependencies = [ ] [[package]] -name = "box-core" +name = "box-simulation" version = "0.1.0" dependencies = [ "anyhow", + "arbiter-core", + "arbiter-derive", "async-trait", - "auto_impl", "bindings", + "box_core", + "config", "ethers", + "serde", + "serde_json", "tokio", + "toml 0.8.2", + "tracing", + "tracing-subscriber", ] [[package]] -name = "box-simulation" +name = "box_core" version = "0.1.0" dependencies = [ "anyhow", - "arbiter-core", - "arbiter-derive", "async-trait", + "auto_impl", "bindings", - "config", "ethers", - "serde", - "serde_json", "tokio", - "toml 0.8.2", - "tracing", - "tracing-subscriber", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a1ceb2acb..0431b67c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ members = ["box-core", "box-simulation"] [workspace.dependencies] bindings = { path = "./box-contracts/out/bindings", version = "0.1.0" } +box_core = { path = "./box-core", version = "0.1.0" } diff --git a/box-core/Cargo.toml b/box-core/Cargo.toml index 0497f5407..c4616e4b4 100644 --- a/box-core/Cargo.toml +++ b/box-core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "box-core" +name = "box_core" version = "0.1.0" edition = "2021" diff --git a/box-core/src/math.rs b/box-core/src/math.rs index fe4d16bfe..e16dbbbd7 100644 --- a/box-core/src/math.rs +++ b/box-core/src/math.rs @@ -11,31 +11,55 @@ pub fn compute_returns(values: Vec) -> Vec { returns } -/// Computes the mean of a series of values (arithmetic average), finds the mean -/// of the summed differences of the values vs. the mean. -pub fn compute_variance(values: Vec) -> f64 { - let mean = values.iter().sum::() / values.len() as f64; - let variance = values - .iter() - .map(|&return_| (return_ - mean).powi(2)) - .sum::() - / values.len() as f64; - variance +pub trait ComputeReturns { + fn compute_log_returns(self) -> Vec; + fn compute_variance(self) -> f64; + fn compute_std_deviation(self) -> f64; + fn compute_realized_volatility(self) -> f64; + fn compute_sharpe_ratio(self) -> f64; } -/// Computes the square root of the variance. -pub fn compute_std_deviation(values: Vec) -> f64 { - let variance = compute_variance(values); - variance.sqrt() -} - -/// The sharpe ratio is the ratio of the mean of the returns to the standard -/// deviation of the returns. -pub fn compute_sharpe_ratio(values: Vec) -> f64 { - let returns = compute_returns(values); - let mean = returns.iter().sum::() / returns.len() as f64; - let std_deviation = compute_std_deviation(returns); - mean / std_deviation +impl ComputeReturns for I +where + I: IntoIterator, +{ + fn compute_log_returns(self) -> Vec { + let mut previous_value = 0.0_f64; + let mut returns = Vec::new(); + for value in self { + if previous_value != 0.0 { + returns.push((value / previous_value).ln()); + } + previous_value = value; + } + returns + } + fn compute_variance(self) -> f64 { + let values = self.into_iter().collect::>(); + let mean = values.iter().sum::() / values.len() as f64; + let variance = values + .iter() + .map(|&return_| (return_ - mean).powi(2)) + .sum::() + / values.len() as f64; + variance + } + fn compute_std_deviation(self) -> f64 { + let variance = self.compute_variance(); + variance.sqrt() + } + fn compute_realized_volatility(self) -> f64 { + let returns = self.compute_log_returns(); + let len = returns.len() + 1; + let rv = returns.compute_std_deviation() / (len as f64 / 365.0); + rv + } + fn compute_sharpe_ratio(self) -> f64 { + let returns = self.compute_log_returns(); + let mean = returns.iter().sum::() / returns.len() as f64; + let std_deviation = returns.compute_std_deviation(); + mean / std_deviation + } } /// Finds the volatility delta to match the target volatility, with respect to @@ -56,30 +80,52 @@ pub fn compute_target_volatility_delta( // todo: make sure these tests are correct, used copilot for them. mod tests { #[test] - fn test_compute_returns() { + fn test_compute_log_returns() { + use super::ComputeReturns; let values = vec![1.0, 2.0, 3.0]; - let returns = super::compute_returns(values); - assert_eq!(returns, vec![1.0, 0.5]); + let returns = values.compute_log_returns(); + //let returns = super::compute_returns(values); + //assert_eq!(returns, vec![1.0, 0.5]); + assert_eq!(returns, [0.6931471805599453, 0.4054651081081644]); } #[test] fn test_compute_variance() { + use super::ComputeReturns; let values = vec![1.0, 2.0, 3.0]; - let variance = super::compute_variance(values); + let variance = values.compute_variance(); assert_eq!(variance, 0.6666666666666666); } #[test] fn test_compute_std_deviation() { + use super::ComputeReturns; let values = vec![1.0, 2.0, 3.0]; - let std_deviation = super::compute_std_deviation(values); + let std_deviation = values.compute_std_deviation(); assert_eq!(std_deviation, 0.816496580927726); } + #[test] + fn test_std_dev_log_returns() { + use super::ComputeReturns; + let values = vec![1.0, 2.0, 3.0]; + let vol = values.compute_log_returns().compute_std_deviation() / (3.0 / 365.0); + assert_eq!(vol, 17.50065940748334); + } + + #[test] + fn test_realized_volatility() { + use super::ComputeReturns; + let values = vec![1.0, 2.0, 3.0]; + let vol = values.compute_realized_volatility(); + assert_eq!(vol, 17.50065940748334); + } + #[test] fn test_compute_sharpe_ratio() { + use super::ComputeReturns; let values = vec![1.0, 2.0, 3.0]; - let sharpe_ratio = super::compute_sharpe_ratio(values); - assert_eq!(sharpe_ratio, 3.0); + let sharpe_ratio = values.compute_sharpe_ratio(); + assert_eq!(sharpe_ratio, 3.8188416793064177); } } diff --git a/box-simulation/Cargo.toml b/box-simulation/Cargo.toml index 5eedd15f4..b9b169ae8 100644 --- a/box-simulation/Cargo.toml +++ b/box-simulation/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] bindings.workspace = true +box_core.workspace = true tokio = { version = "1.11.0", features = ["full"] } anyhow = "1.0.75" arbiter-core = { git = "https://github.com/primitivefinance/arbiter.git", branch = "arbiter/version" } diff --git a/box-simulation/src/agents/mod.rs b/box-simulation/src/agents/mod.rs index da0099d4a..894997f8c 100644 --- a/box-simulation/src/agents/mod.rs +++ b/box-simulation/src/agents/mod.rs @@ -1,4 +1,6 @@ use super::*; pub mod arbitrageur; +pub mod atomic_arbitrage; pub mod price_changer; +pub mod rebalancer; diff --git a/box-simulation/src/agents/rebalancer.rs b/box-simulation/src/agents/rebalancer.rs new file mode 100644 index 000000000..9f541a6c6 --- /dev/null +++ b/box-simulation/src/agents/rebalancer.rs @@ -0,0 +1,158 @@ +use box_core::math::ComputeReturns; +use ethers::utils::format_ether; +use std::collections::VecDeque; +use std::ops::Div; + +use super::*; + +pub struct Rebalancer { + pub lex: LiquidExchange, + pub g3m: G3M, + pub next_update_timestamp: u64, + pub target_volatility: f64, + pub asset_vol: U256, + pub portfolio_vol: U256, + pub last_asset_value: f64, + pub last_portfolio_value: f64, + pub portfolio_prices: Vec, + pub asset_prices: Vec, + pub portfolio_returns: VecDeque, + pub asset_returns: VecDeque, +} + +impl Rebalancer { + pub async fn new( + label: &str, + environment: &Environment, + liquid_exchange_address: Address, + exchange_address: Address, + target_volatility: f64, + ) -> Result { + let client = RevmMiddleware::new(environment, Some(label))?; + let lex = LiquidExchange::new(liquid_exchange_address, client.clone()); + let g3m = G3M::new(exchange_address, client.clone()); + + Ok(Self { + lex, + g3m, + target_volatility, + next_update_timestamp: 0, + asset_vol: U256::from(0), + portfolio_vol: U256::from(0), + last_asset_value: 0.0, + last_portfolio_value: 0.0, + portfolio_prices: Vec::new(), + asset_prices: Vec::new(), + portfolio_returns: VecDeque::new(), + asset_returns: VecDeque::new(), + }) + } + + pub async fn step(&mut self, timestamp: u64) -> Result<()> { + if timestamp > self.next_update_timestamp { + self.next_update_timestamp = timestamp + 86400; + let asset_price = self.lex.price().call().await?; + + let reserve_x = self.g3m.reserve_x().call().await?; + let reserve_y = self.g3m.reserve_y().call().await?; + let liquidity = self.g3m.total_liquidity().call().await?; + let x_per_liquidity = reserve_x.div(liquidity); + let y_per_liquidity = reserve_y.div(liquidity); + + let portfolio_price = x_per_liquidity + .checked_mul(asset_price) + .unwrap() + .checked_add(y_per_liquidity) + .unwrap(); + + let asset_price_float = format_ether(asset_price).parse::().unwrap(); + let portfolio_price_float = format_ether(portfolio_price).parse::().unwrap(); + + self.append_asset_return(asset_price_float)?; + self.append_portfolio_return(portfolio_price_float)?; + } + Ok(()) + } + + pub fn append_asset_return(&mut self, asset_price: f64) -> Result<()> { + if self.asset_returns.len() == 15 { + self.asset_returns.pop_front(); + } + if self.last_asset_value == 0.0 { + self.asset_returns.push_back(0.0); + } else { + } + self.asset_returns.push_back(new_asset_return); + + Ok(()) + } + + pub fn append_portfolio_return(&mut self, portfolio_price: f64) -> Result<()> { + if self.portfolio_returns.len() == 15 { + self.portfolio_returns.pop_front(); + } + self.portfolio_returns.push_back(new_portfolio_return); + + Ok(()) + } + + /// Detects if there is an arbitrage opportunity. + /// Returns the direction of the swap `XtoY` or `YtoX` if there is an + /// arbitrage opportunity. Returns `None` if there is no arbitrage + /// opportunity. + async fn detect_arbitrage(&mut self) -> Result { + // Update the prices the for the arbitrageur. + let liquid_exchange_price_wad = self.liquid_exchange.price().call().await?; + let g3m_price_wad = self.g3m.get_spot_price().call().await?; + + let gamma_wad = WAD - self.g3m.swap_fee().call().await?; + + // Compute the no-arbitrage bounds. + let upper_arb_bound = WAD * g3m_price_wad / gamma_wad; + let lower_arb_bound = g3m_price_wad * gamma_wad / WAD; + + // Check if we have an arbitrage opportunity by comparing against the bounds and + // current price. + // If these conditions are not satisfied, there cannot be a profitable + // arbitrage. See: [An Analysis of Uniswap Markets](https://arxiv.org/pdf/1911.03380.pdf) Eq. 3, for example. + if liquid_exchange_price_wad > upper_arb_bound && liquid_exchange_price_wad > g3m_price_wad + { + // Raise the portfolio price by selling asset for quote + Ok(Swap::RaiseExchangePrice(liquid_exchange_price_wad)) + } else if liquid_exchange_price_wad < lower_arb_bound + && liquid_exchange_price_wad < g3m_price_wad + { + // Lower the exchange price by selling asset for quote + Ok(Swap::LowerExchangePrice(liquid_exchange_price_wad)) + } else { + // Prices are within the no-arbitrage bounds, so we don't have an arbitrage. + Ok(Swap::None) + } + } + + async fn get_x_input(&mut self, target_price_wad: U256) -> Result { + let weight_x = self.g3m.weight_x().call().await?; + let weight_y = self.g3m.weight_y().call().await?; + let reserve_y = self.g3m.reserve_y().call().await?; + let invariant = self.g3m.get_invariant().call().await?; + + Ok(weight_y + * U256::from(1) + .div(target_price_wad * invariant.pow(U256::from(1).div(weight_x))) + .pow(U256::from(1) + weight_y.div(weight_x)) + - reserve_y) + } + + async fn get_y_input(&mut self, target_price_wad: U256) -> Result { + let weight_x = self.g3m.weight_x().call().await?; + let weight_y = self.g3m.weight_y().call().await?; + let reserve_x = self.g3m.reserve_x().call().await?; + let invariant = self.g3m.get_invariant().call().await?; + + Ok(weight_x + * target_price_wad + .div(invariant.pow(U256::from(1).div(weight_y))) + .pow(U256::from(1) + weight_x.div(weight_y)) + - reserve_x) + } +} diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index d1f4fea21..9abb09ee8 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -39,5 +39,7 @@ async fn main() -> Result<()> { .await?; setup::init::init(&contracts).await; + let init = setup::init::init(&contracts).await?; + Ok(()) } diff --git a/box-simulation/src/setup/deploy.rs b/box-simulation/src/setup/deploy.rs index 833a18a3b..65621747b 100644 --- a/box-simulation/src/setup/deploy.rs +++ b/box-simulation/src/setup/deploy.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use arbiter_core::bindings::arbiter_token::ArbiterToken; -use arbiter_core::bindings::liquid_exchange::LiquidExchange; +use arbiter_core::bindings::{arbiter_token::ArbiterToken, liquid_exchange::LiquidExchange}; +use arbiter_core::data_collection::EventLogger; use arbiter_core::environment::Environment; use arbiter_core::middleware::RevmMiddleware; use arbiter_derive::Deploy; From 83676baf988f2176e37708fec01bc93f3d92c11e Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 15:19:08 -0400 Subject: [PATCH 08/23] calculate 15 day trailing rv on portfolio and asset --- box-simulation/src/agents/rebalancer.rs | 139 ++++++++---------------- box-simulation/src/main.rs | 2 +- 2 files changed, 49 insertions(+), 92 deletions(-) diff --git a/box-simulation/src/agents/rebalancer.rs b/box-simulation/src/agents/rebalancer.rs index 9f541a6c6..1f882c768 100644 --- a/box-simulation/src/agents/rebalancer.rs +++ b/box-simulation/src/agents/rebalancer.rs @@ -1,6 +1,5 @@ use box_core::math::ComputeReturns; use ethers::utils::format_ether; -use std::collections::VecDeque; use std::ops::Div; use super::*; @@ -10,14 +9,10 @@ pub struct Rebalancer { pub g3m: G3M, pub next_update_timestamp: u64, pub target_volatility: f64, - pub asset_vol: U256, - pub portfolio_vol: U256, - pub last_asset_value: f64, - pub last_portfolio_value: f64, - pub portfolio_prices: Vec, - pub asset_prices: Vec, - pub portfolio_returns: VecDeque, - pub asset_returns: VecDeque, + pub portfolio_prices: Vec<(f64, u64)>, + pub asset_prices: Vec<(f64, u64)>, + pub portfolio_rv: Vec<(f64, u64)>, + pub asset_rv: Vec<(f64, u64)>, } impl Rebalancer { @@ -37,14 +32,10 @@ impl Rebalancer { g3m, target_volatility, next_update_timestamp: 0, - asset_vol: U256::from(0), - portfolio_vol: U256::from(0), - last_asset_value: 0.0, - last_portfolio_value: 0.0, portfolio_prices: Vec::new(), asset_prices: Vec::new(), - portfolio_returns: VecDeque::new(), - asset_returns: VecDeque::new(), + portfolio_rv: Vec::new(), + asset_rv: Vec::new(), }) } @@ -68,91 +59,57 @@ impl Rebalancer { let asset_price_float = format_ether(asset_price).parse::().unwrap(); let portfolio_price_float = format_ether(portfolio_price).parse::().unwrap(); - self.append_asset_return(asset_price_float)?; - self.append_portfolio_return(portfolio_price_float)?; + self.asset_prices.push((asset_price_float, timestamp)); + self.portfolio_prices + .push((portfolio_price_float, timestamp)); } Ok(()) } - pub fn append_asset_return(&mut self, asset_price: f64) -> Result<()> { - if self.asset_returns.len() == 15 { - self.asset_returns.pop_front(); - } - if self.last_asset_value == 0.0 { - self.asset_returns.push_back(0.0); + pub fn calculate_rv(&mut self) -> Result<()> { + // if self.asset_prices.len() > 15 then only calcualte for the last 15 elements + if self.asset_prices.len() > 15 { + let asset_prices = self + .asset_prices + .iter() + .skip(self.asset_prices.len() - 15) + .map(|(price, _)| price.clone()) + .collect::>(); + + let asset_rv = asset_prices.compute_realized_volatility(); + self.asset_rv.push((asset_rv, self.next_update_timestamp)); } else { + let asset_prices = self + .asset_prices + .iter() + .map(|(price, _)| price.clone()) + .collect::>(); + let asset_rv = asset_prices.compute_realized_volatility(); + self.asset_rv.push((asset_rv, self.next_update_timestamp)); } - self.asset_returns.push_back(new_asset_return); - - Ok(()) - } - - pub fn append_portfolio_return(&mut self, portfolio_price: f64) -> Result<()> { - if self.portfolio_returns.len() == 15 { - self.portfolio_returns.pop_front(); - } - self.portfolio_returns.push_back(new_portfolio_return); - - Ok(()) - } - /// Detects if there is an arbitrage opportunity. - /// Returns the direction of the swap `XtoY` or `YtoX` if there is an - /// arbitrage opportunity. Returns `None` if there is no arbitrage - /// opportunity. - async fn detect_arbitrage(&mut self) -> Result { - // Update the prices the for the arbitrageur. - let liquid_exchange_price_wad = self.liquid_exchange.price().call().await?; - let g3m_price_wad = self.g3m.get_spot_price().call().await?; - - let gamma_wad = WAD - self.g3m.swap_fee().call().await?; - - // Compute the no-arbitrage bounds. - let upper_arb_bound = WAD * g3m_price_wad / gamma_wad; - let lower_arb_bound = g3m_price_wad * gamma_wad / WAD; - - // Check if we have an arbitrage opportunity by comparing against the bounds and - // current price. - // If these conditions are not satisfied, there cannot be a profitable - // arbitrage. See: [An Analysis of Uniswap Markets](https://arxiv.org/pdf/1911.03380.pdf) Eq. 3, for example. - if liquid_exchange_price_wad > upper_arb_bound && liquid_exchange_price_wad > g3m_price_wad - { - // Raise the portfolio price by selling asset for quote - Ok(Swap::RaiseExchangePrice(liquid_exchange_price_wad)) - } else if liquid_exchange_price_wad < lower_arb_bound - && liquid_exchange_price_wad < g3m_price_wad - { - // Lower the exchange price by selling asset for quote - Ok(Swap::LowerExchangePrice(liquid_exchange_price_wad)) + if self.portfolio_prices.len() > 15 { + let portfolio_prices = self + .portfolio_prices + .iter() + .skip(self.portfolio_prices.len() - 15) + .map(|(price, _)| price.clone()) + .collect::>(); + + let portfolio_rv = portfolio_prices.compute_realized_volatility(); + self.portfolio_rv + .push((portfolio_rv, self.next_update_timestamp)); } else { - // Prices are within the no-arbitrage bounds, so we don't have an arbitrage. - Ok(Swap::None) + let portfolio_prices = self + .portfolio_prices + .iter() + .map(|(price, _)| price.clone()) + .collect::>(); + let portfolio_rv = portfolio_prices.compute_realized_volatility(); + self.portfolio_rv + .push((portfolio_rv, self.next_update_timestamp)); } - } - - async fn get_x_input(&mut self, target_price_wad: U256) -> Result { - let weight_x = self.g3m.weight_x().call().await?; - let weight_y = self.g3m.weight_y().call().await?; - let reserve_y = self.g3m.reserve_y().call().await?; - let invariant = self.g3m.get_invariant().call().await?; - Ok(weight_y - * U256::from(1) - .div(target_price_wad * invariant.pow(U256::from(1).div(weight_x))) - .pow(U256::from(1) + weight_y.div(weight_x)) - - reserve_y) - } - - async fn get_y_input(&mut self, target_price_wad: U256) -> Result { - let weight_x = self.g3m.weight_x().call().await?; - let weight_y = self.g3m.weight_y().call().await?; - let reserve_x = self.g3m.reserve_x().call().await?; - let invariant = self.g3m.get_invariant().call().await?; - - Ok(weight_x - * target_price_wad - .div(invariant.pow(U256::from(1).div(weight_y))) - .pow(U256::from(1) + weight_x.div(weight_y)) - - reserve_x) + Ok(()) } } diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index 9abb09ee8..fea3046fd 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -39,7 +39,7 @@ async fn main() -> Result<()> { .await?; setup::init::init(&contracts).await; - let init = setup::init::init(&contracts).await?; + let init = setup::init::init(&contracts, &config).await?; Ok(()) } From 64519d9fe6261ffa7283f3ac32e050904b718fbd Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 15:23:05 -0400 Subject: [PATCH 09/23] minor cleanup --- box-simulation/src/agents/rebalancer.rs | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/box-simulation/src/agents/rebalancer.rs b/box-simulation/src/agents/rebalancer.rs index 1f882c768..c764455b5 100644 --- a/box-simulation/src/agents/rebalancer.rs +++ b/box-simulation/src/agents/rebalancer.rs @@ -62,6 +62,7 @@ impl Rebalancer { self.asset_prices.push((asset_price_float, timestamp)); self.portfolio_prices .push((portfolio_price_float, timestamp)); + self.calculate_rv()?; } Ok(()) } @@ -69,43 +70,42 @@ impl Rebalancer { pub fn calculate_rv(&mut self) -> Result<()> { // if self.asset_prices.len() > 15 then only calcualte for the last 15 elements if self.asset_prices.len() > 15 { - let asset_prices = self + let asset_rv = self .asset_prices .iter() .skip(self.asset_prices.len() - 15) .map(|(price, _)| price.clone()) - .collect::>(); - - let asset_rv = asset_prices.compute_realized_volatility(); + .collect::>() + .compute_realized_volatility(); self.asset_rv.push((asset_rv, self.next_update_timestamp)); } else { - let asset_prices = self + let asset_rv = self .asset_prices .iter() .map(|(price, _)| price.clone()) - .collect::>(); - let asset_rv = asset_prices.compute_realized_volatility(); + .collect::>() + .compute_realized_volatility(); self.asset_rv.push((asset_rv, self.next_update_timestamp)); } if self.portfolio_prices.len() > 15 { - let portfolio_prices = self + let portfolio_rv = self .portfolio_prices .iter() .skip(self.portfolio_prices.len() - 15) .map(|(price, _)| price.clone()) - .collect::>(); + .collect::>() + .compute_realized_volatility(); - let portfolio_rv = portfolio_prices.compute_realized_volatility(); self.portfolio_rv .push((portfolio_rv, self.next_update_timestamp)); } else { - let portfolio_prices = self + let portfolio_rv = self .portfolio_prices .iter() .map(|(price, _)| price.clone()) - .collect::>(); - let portfolio_rv = portfolio_prices.compute_realized_volatility(); + .collect::>() + .compute_realized_volatility(); self.portfolio_rv .push((portfolio_rv, self.next_update_timestamp)); } From 60ee166b81846cc0974bb30e9dad9ac8e0212c19 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Oct 2023 12:42:10 -0700 Subject: [PATCH 10/23] feat(builder): engine builder for buildign the sim loop --- box-simulation/src/engine/builder.rs | 118 +++++++++++++++++++++ box-simulation/src/engine/mod.rs | 74 ++++++++++++++ box-simulation/src/engine/price_path.rs | 130 ++++++++++++++++++++++++ box-simulation/src/main.rs | 1 + 4 files changed, 323 insertions(+) create mode 100644 box-simulation/src/engine/builder.rs create mode 100644 box-simulation/src/engine/mod.rs create mode 100644 box-simulation/src/engine/price_path.rs diff --git a/box-simulation/src/engine/builder.rs b/box-simulation/src/engine/builder.rs new file mode 100644 index 000000000..9f14093f2 --- /dev/null +++ b/box-simulation/src/engine/builder.rs @@ -0,0 +1,118 @@ +//! Builds the core simulation logic into the engine so it's run in the main loop. +use arbiter_core::environment::Environment; + +use crate::engine::PriceChanger; +use crate::settings::{self, params::SimulationConfig}; +use crate::setup::{self, deploy::Contracts}; + +/// A specific type for the async action function that uses contracts and environment +/// to run the core simulation logic. +/// Implements the Sized trait so it can be used as a type. +pub type Action = Box Result<(), anyhow::Error> + Send + Sync>; + +/// A builder for creating the simulation loop by specifying the actions to run inside the main sim loop. +/// before_hooks - Async functions that that are run inside the sim loop before the core logic. +/// after_hooks - Async functions that that are run inside the sim loop after the core logic. +pub struct EngineBuilder { + pub before_hooks: Vec, + pub after_hooks: Vec, +} + +/// Initializes the simulation engine with two vectors of actions to run before and after the core simulation logic. +/// Calling the `run` method on the engine will run the before hooks, then the core simulation logic, then the after hooks within the sim loop. +impl EngineBuilder { + pub fn new() -> Self { + Self { + before_hooks: Vec::new(), + after_hooks: Vec::new(), + } + } + + /// Adds a before hook to the engine. + pub fn before(mut self, hook: Action) -> Self { + self.before_hooks.push(hook); + self + } + + pub async fn run( + &self, + contracts: &Contracts, + env: &Environment, + config: &SimulationConfig, + ) -> Result<(), anyhow::Error> { + // Run the main simulation loop + let mut price_changer = PriceChanger::new( + contracts.exchanges.lex.clone(), + config.price_process_parameters.clone(), + ); + + for _ in 0..(price_changer.trajectory.paths[0].len() - 1) { + // Run the before hooks + for hook in self.before_hooks.iter() { + hook(contracts)?; + } + + // Run the core simulation logic, which is updating the price. + price_changer.update_price().await?; + + // Run the after hooks + for hook in self.after_hooks.iter() { + hook(contracts)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float}; + + async fn setup_test_environment( + ) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> { + let config = settings::params::SimulationConfig::new()?; + + let env = EnvironmentBuilder::new().build(); + let contracts = setup::deploy::deploy_contracts(&env, &config).await?; + + Ok((contracts, env, config)) + } + + #[tokio::test] + async fn test_engine_builder_vanilla() -> Result<(), anyhow::Error> { + let (contracts, env, config) = setup_test_environment().await?; + + let engine_builder = EngineBuilder::new().run(&contracts, &env, &config).await?; + + let initial_price_float = config.price_process_parameters.initial_price; + let lex_price_float = wad_to_float(contracts.exchanges.lex.price().call().await?); + + // Assert the price of the liquid exchange != initial config price to ensure the sim's core logic was run. + assert_ne!( + initial_price_float, lex_price_float, + "The price of the liquid exchange should have been updated by the simulation's core logic." + ); + + println!("initial_price_float: {}", initial_price_float); + println!("lex_price_float: {}", lex_price_float); + + Ok(()) + } + + #[tokio::test] + async fn test_engine_builder_add_before_hook() -> Result<(), anyhow::Error> { + let (contracts, env, config) = setup_test_environment().await?; + + let engine_builder = EngineBuilder::new() + .before(Box::new(|contracts| { + println!("before hook"); + Ok(()) + })) + .run(&contracts, &env, &config) + .await?; + + Ok(()) + } +} diff --git a/box-simulation/src/engine/mod.rs b/box-simulation/src/engine/mod.rs new file mode 100644 index 000000000..51723cd76 --- /dev/null +++ b/box-simulation/src/engine/mod.rs @@ -0,0 +1,74 @@ +use crate::settings::{self, params::SimulationConfig}; +use crate::setup::{self, deploy::Contracts}; + +use self::price_path::PriceChanger; +use arbiter_core::environment::Environment; + +pub mod builder; +pub mod price_path; + +/// Runs the main simulation loop. +pub async fn run( + contracts: &Contracts, + env: &Environment, + config: &SimulationConfig, +) -> Result { + // Get the initial price path from the configuration. + let mut price_changer = price_path::PriceChanger::new( + contracts.exchanges.lex.clone(), + config.price_process_parameters.clone(), + ); + + for _ in 0..(price_changer.trajectory.paths[0].len() - 1) { + price_changer.update_price().await?; + } + + Ok(price_changer) +} + +#[cfg(test)] +mod tests { + use super::*; + use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float}; + + async fn setup_test_environment( + ) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> { + let config = settings::params::SimulationConfig::new()?; + + let env = EnvironmentBuilder::new().build(); + let contracts = setup::deploy::deploy_contracts(&env, &config).await?; + + Ok((contracts, env, config)) + } + + #[tokio::test] + async fn test_price_path_update_in_loop() -> Result<(), anyhow::Error> { + let (contracts, env, config) = setup_test_environment().await?; + + let mut price_changer = run(&contracts, &env, &config).await?; + + let price_index = price_changer.index; + + let new_price_lex = contracts.exchanges.lex.price().call().await?; + + // Assert the price of the lex is the same as the last price in the trajectory + assert_eq!( + price_changer.trajectory.paths[0][price_index - 1], + wad_to_float(new_price_lex) + ); + + // Assert we reached the end of the trajectory + assert_eq!(price_changer.index, price_changer.trajectory.paths[0].len()); + + // Print the last price in the trajectory + println!( + "last price in trajectory: {}", + price_changer.trajectory.paths[0][price_index - 1] + ); + + // Print price of lex + println!("price of lex: {}", new_price_lex); + + Ok(()) + } +} diff --git a/box-simulation/src/engine/price_path.rs b/box-simulation/src/engine/price_path.rs new file mode 100644 index 000000000..ef6adf62a --- /dev/null +++ b/box-simulation/src/engine/price_path.rs @@ -0,0 +1,130 @@ +//! ALl utilities for building and using a price path. + +use arbiter_core::{ + math::{ornstein_uhlenbeck::OrnsteinUhlenbeck, StochasticProcess, Trajectories}, + middleware::RevmMiddleware, +}; +use tracing::info; + +use crate::settings::params::PriceProcessParameters; +use arbiter_core::bindings::liquid_exchange::LiquidExchange; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// PriceChanger +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/// The `PriceChanger` holds the data and has methods that allow it to update +/// the price of the `LiquidExchange`. +pub struct PriceChanger { + /// The path the price process takes. + pub trajectory: Trajectories, + + /// The `LiquidExchange` contract with the admin `Client`. + pub liquid_exchange: LiquidExchange, + + /// The index of the current price in the trajectory. + pub index: usize, +} + +impl PriceChanger { + /// Create a new `PriceChanger` with the given `LiquidExchange` contract + /// bound to the admin `Client`. The `PriceChanger` will use the + /// `OrnsteinUhlenbeck` process to generate a price trajectory with the + /// constants defined in `config.rs`. + /// Ornstein-Uhlenbeck processes are useful for modeling the price of stable + /// tokens. + pub fn new( + liquid_exchange: LiquidExchange, + price_process_params: PriceProcessParameters, + ) -> Self { + let PriceProcessParameters { + initial_price, + mean, + std_dev, + theta, + t_0, + t_n, + num_steps, + seed, + } = price_process_params; + let process = OrnsteinUhlenbeck::new(mean, std_dev, theta); + + let trajectory = match seed { + Some(seed) => { + process.seedable_euler_maruyama(initial_price, t_0, t_n, num_steps, 1, false, seed) + } + None => process.euler_maruyama(initial_price, t_0, t_n, num_steps, 1, false), + }; + + Self { + trajectory, + liquid_exchange, + index: 1, /* start after the initial price since it is already set on contract + * deployment */ + } + } + + /// Update the price of the `LiquidExchange` contract to the next price in + /// the trajectory and increment the index. + /// !!IMPORTANT!! The update price function will have a real integer value that is smaller than the float value. + pub async fn update_price(&mut self) -> Result<(), anyhow::Error> { + let price = self.trajectory.paths[0][self.index]; + info!("Updating price of liquid_exchange to: {}", price); + self.liquid_exchange + .set_price(arbiter_core::math::float_to_wad(price)) + .send() + .await? + .await?; + self.index += 1; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::settings::{self, params::SimulationConfig}; + use crate::setup::{self, deploy::Contracts}; + use arbiter_core::environment::Environment; + use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float}; + + async fn setup_test_environment( + ) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> { + let config = settings::params::SimulationConfig::new()?; + + let env = EnvironmentBuilder::new().build(); + let contracts = setup::deploy::deploy_contracts(&env, &config).await?; + + Ok((contracts, env, config)) + } + + #[tokio::test] + async fn test_price_path_update() -> Result<(), anyhow::Error> { + let (contracts, env, config) = setup_test_environment().await?; + + let mut price_changer = PriceChanger::new( + contracts.exchanges.lex.clone(), + config.price_process_parameters.clone(), + ); + + let initial_price = price_changer.trajectory.paths[0][0]; + let initial_index = price_changer.index; + + let _ = price_changer.update_price().await?; + + let new_price = price_changer.trajectory.paths[0][1]; + let new_index = price_changer.index; + let new_price_lex = contracts.exchanges.lex.price().call().await?; + + assert_ne!(initial_price, new_price); + assert_eq!(initial_index + 1, new_index); + assert_eq!(new_price, wad_to_float(new_price_lex)); + + // print these prices in the assertions + println!("initial_price: {}", initial_price); + println!("new_price: {}", new_price); + println!("new_price_lex: {}", new_price_lex); + + Ok(()) + } +} diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index fea3046fd..742dcc865 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -9,6 +9,7 @@ use ethers::types::{Address, U256}; use bindings::{atomic_arbitrage::AtomicArbitrage, g3m::G3M}; mod agents; +mod engine; mod settings; mod setup; mod utils; From 9704f5d2c9f4a0b0b33a07a0779a9cb224aa29b2 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 15:43:00 -0400 Subject: [PATCH 11/23] add liquidity provider agent --- box-simulation/src/agents/liquidity_provider.rs | 0 box-simulation/src/agents/rebalancer.rs | 9 ++++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 box-simulation/src/agents/liquidity_provider.rs diff --git a/box-simulation/src/agents/liquidity_provider.rs b/box-simulation/src/agents/liquidity_provider.rs new file mode 100644 index 000000000..e69de29bb diff --git a/box-simulation/src/agents/rebalancer.rs b/box-simulation/src/agents/rebalancer.rs index c764455b5..0ce68be69 100644 --- a/box-simulation/src/agents/rebalancer.rs +++ b/box-simulation/src/agents/rebalancer.rs @@ -1,10 +1,12 @@ use box_core::math::ComputeReturns; use ethers::utils::format_ether; use std::ops::Div; +use std::sync::Arc; use super::*; pub struct Rebalancer { + pub client: Arc, pub lex: LiquidExchange, pub g3m: G3M, pub next_update_timestamp: u64, @@ -28,6 +30,7 @@ impl Rebalancer { let g3m = G3M::new(exchange_address, client.clone()); Ok(Self { + client, lex, g3m, target_volatility, @@ -67,7 +70,7 @@ impl Rebalancer { Ok(()) } - pub fn calculate_rv(&mut self) -> Result<()> { + fn calculate_rv(&mut self) -> Result<()> { // if self.asset_prices.len() > 15 then only calcualte for the last 15 elements if self.asset_prices.len() > 15 { let asset_rv = self @@ -112,4 +115,8 @@ impl Rebalancer { Ok(()) } + + fn execute_smooth_rebalance(&mut self) -> Result<()> { + Ok(()) + } } From 57b8732dc824c17445e31496ea6a7e3e7d0490dc Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 16:21:27 -0400 Subject: [PATCH 12/23] clean up init --- .../src/agents/liquidity_provider.rs | 77 ++++++ box-simulation/src/agents/mod.rs | 1 + box-simulation/src/main.rs | 26 +- box-simulation/src/setup/init.rs | 238 ++++++++---------- 4 files changed, 210 insertions(+), 132 deletions(-) diff --git a/box-simulation/src/agents/liquidity_provider.rs b/box-simulation/src/agents/liquidity_provider.rs index e69de29bb..e4a0e108a 100644 --- a/box-simulation/src/agents/liquidity_provider.rs +++ b/box-simulation/src/agents/liquidity_provider.rs @@ -0,0 +1,77 @@ +use crate::settings::params::SimulationConfig; +use arbiter_core::bindings::arbiter_token::ArbiterToken; +use ethers::utils::parse_ether; +use std::sync::Arc; + +use super::*; + +pub const INITIAL_PORTFOLIO_BALANCES: (u64, u64) = (100_000, 100_000); +pub struct LiquidityProvider { + pub client: Arc, + pub g3m: G3M, + pub arbx: ArbiterToken, + pub arby: ArbiterToken, +} + +impl LiquidityProvider { + pub async fn new(label: &str, environment: &Environment, g3m_address: Address) -> Result { + let client = RevmMiddleware::new(environment, Some(label))?; + let g3m = G3M::new(g3m_address, client.clone()); + + let arbx = ArbiterToken::new(g3m.token_x().call().await?, client.clone()); + let arby = ArbiterToken::new(g3m.token_y().call().await?, client.clone()); + + arbx.approve(g3m.address(), U256::MAX).send().await?; + arby.approve(g3m.address(), U256::MAX).send().await?; + + Ok(Self { + client, + g3m, + arbx, + arby, + }) + } + + pub async fn add_liquidity(self, config: &SimulationConfig) -> Result<()> { + // Initial weight is set in the simulation config, but it can be overridden with setWeightX() function. + let initial_weight_0 = + parse_ether(config.portfolio_pool_parameters.weight_token_0).unwrap(); + let initial_weight_1 = parse_ether(1) + .unwrap() + .checked_sub(initial_weight_0) + .unwrap(); + // Using the initial weight, initial price, and initial reserve x, we can compute reserve y. + let initial_price = config.portfolio_pool_parameters.initial_price; + let initial_reserve_x = parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap(); + + // p = (x / w_x) / (y / w_y) + // y / w_y = (x / w_x) / p + // y = (x / w_x) / p * w_y + let one_ether = parse_ether(1).unwrap(); + let initial_reserve_y = initial_reserve_x + .checked_mul(one_ether) + .unwrap() + .checked_div(initial_weight_0) + .unwrap() + .checked_mul(one_ether) + .unwrap() + .checked_div(parse_ether(initial_price).unwrap()) + .unwrap() + .checked_mul(initial_weight_1) + .unwrap() + .checked_div(one_ether) + .unwrap(); + + // Get the parsed amounts for the portfolio deposit. + let amounts = (initial_reserve_x, initial_reserve_y); + + // Call init pool to setup the portfolio + // Needs an amount of both tokens, the amounts can be anything but note that they affect the spot price. + self.g3m + .init_pool(amounts.0.into(), amounts.1.into()) + .send() + .await? + .await?; + Ok(()) + } +} diff --git a/box-simulation/src/agents/mod.rs b/box-simulation/src/agents/mod.rs index 894997f8c..9ffde5553 100644 --- a/box-simulation/src/agents/mod.rs +++ b/box-simulation/src/agents/mod.rs @@ -2,5 +2,6 @@ use super::*; pub mod arbitrageur; pub mod atomic_arbitrage; +pub mod liquidity_provider; pub mod price_changer; pub mod rebalancer; diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index 742dcc865..7c7746b80 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -1,10 +1,13 @@ use agents::arbitrageur::Arbitrageur; +use agents::liquidity_provider::LiquidityProvider; +use agents::rebalancer::Rebalancer; use anyhow::Result; use arbiter_core::environment::builder::EnvironmentBuilder; use arbiter_core::{ bindings::liquid_exchange::LiquidExchange, environment::Environment, middleware::RevmMiddleware, }; use ethers::types::{Address, U256}; +use tracing_subscriber; use bindings::{atomic_arbitrage::AtomicArbitrage, g3m::G3M}; @@ -17,6 +20,11 @@ mod utils; /// The number 10^18. pub const WAD: ethers::types::U256 = ethers::types::U256([10_u64.pow(18), 0, 0, 0]); +pub struct Agents { + pub liquidity_provider: LiquidityProvider, + pub rebalancer: Rebalancer, +} + #[tokio::main] async fn main() -> Result<()> { if std::env::var("RUST_LOG").is_err() { @@ -38,9 +46,23 @@ async fn main() -> Result<()> { contracts.exchanges.g3m.address(), ) .await?; - setup::init::init(&contracts).await; - let init = setup::init::init(&contracts, &config).await?; + let lp = LiquidityProvider::new("lp", &env, contracts.exchanges.g3m.address()).await?; + let rebalancer = Rebalancer::new( + "rebalancer", + &env, + contracts.exchanges.lex.address(), + contracts.exchanges.g3m.address(), + 0.15, + ) + .await?; + + let agents = Agents { + liquidity_provider: lp, + rebalancer, + }; + + let init = setup::init::init(&contracts, agents, &config).await?; Ok(()) } diff --git a/box-simulation/src/setup/init.rs b/box-simulation/src/setup/init.rs index 0b504f7a7..26dd78154 100644 --- a/box-simulation/src/setup/init.rs +++ b/box-simulation/src/setup/init.rs @@ -1,119 +1,48 @@ use crate::settings::params::SimulationConfig; +use crate::Agents; use super::deploy::Contracts; -use ethers::types::U256; use ethers::utils::parse_ether; // Initial balances of the deployer and portfolio where (arbx, arby) -pub const INITIAL_BALANCES: (u64, u64) = (250, 250); -pub const INITIAL_PORTFOLIO_BALANCES: (u64, u64) = (100, 100); +pub const INITIAL_PORTFOLIO_BALANCES: (u64, u64) = (100_000, 100_000); /// Initialize the pools /// In initialize we add liquidity and seed actors with tokens -pub async fn init(contracts: &Contracts, config: &SimulationConfig) -> Result<(), anyhow::Error> { - // Setup the initial token state by minting tokens to the deployer and approving the G3M contract to spend them. - setup_token_state(&contracts).await?; - - // Setup initial contract state of G3M by initializing the pool. - setup_pool_state(&contracts, config).await?; - - Ok(()) -} - /// All the stateful calls to the tokens used in the simulation. -pub async fn setup_token_state(contracts: &Contracts) -> Result<(), anyhow::Error> { - // Mint INITIAL_BALANCES of tokens to deployer. - contracts - .tokens - .arbx - .mint( - contracts.deployer.address(), - parse_ether(INITIAL_BALANCES.0).unwrap(), - ) - .send() - .await? - .await?; - contracts - .tokens - .arby - .mint( - contracts.deployer.address(), - parse_ether(INITIAL_BALANCES.1).unwrap(), - ) - .send() - .await? - .await?; - - // Get the parsed amounts for the portfolio deposit. +pub async fn init( + contracts: &Contracts, + agents: Agents, + config: &SimulationConfig, +) -> Result<(), anyhow::Error> { let amounts = ( parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap(), parse_ether(INITIAL_PORTFOLIO_BALANCES.1).unwrap(), ); - // Approve tokens to be spent by the G3M contract. - contracts - .tokens - .arbx - .approve(contracts.exchanges.g3m.address(), amounts.0) - .send() - .await? - .await?; - contracts - .tokens - .arby - .approve(contracts.exchanges.g3m.address(), amounts.1) - .send() - .await? - .await?; - - Ok(()) -} - -/// Initializes the pool with the desired amounts of tokens, making it have an initial spot price. -pub async fn setup_pool_state( - contracts: &Contracts, - config: &SimulationConfig, -) -> Result<(), anyhow::Error> { - // Initial weight is set in the simulation config, but it can be overridden with setWeightX() function. - let initial_weight_0 = parse_ether(config.portfolio_pool_parameters.weight_token_0).unwrap(); - let initial_weight_1 = parse_ether(1) - .unwrap() - .checked_sub(initial_weight_0) - .unwrap(); - // Using the initial weight, initial price, and initial reserve x, we can compute reserve y. - let initial_price = config.portfolio_pool_parameters.initial_price; - let initial_reserve_x = parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap(); - - // p = (x / w_x) / (y / w_y) - // y / w_y = (x / w_x) / p - // y = (x / w_x) / p * w_y - let one_ether = parse_ether(1).unwrap(); - let initial_reserve_y = initial_reserve_x - .checked_mul(one_ether) - .unwrap() - .checked_div(initial_weight_0) - .unwrap() - .checked_mul(one_ether) - .unwrap() - .checked_div(parse_ether(initial_price).unwrap()) - .unwrap() - .checked_mul(initial_weight_1) - .unwrap() - .checked_div(one_ether) - .unwrap(); + let agent_addrs = vec![ + agents.liquidity_provider.client.address(), + agents.rebalancer.client.address(), + ]; - // Get the parsed amounts for the portfolio deposit. - let amounts = (initial_reserve_x, initial_reserve_y); - - // Call init pool to setup the portfolio - // Needs an amount of both tokens, the amounts can be anything but note that they affect the spot price. - let init_pool = contracts - .exchanges - .g3m - .init_pool(amounts.0.into(), amounts.1.into()) - .send() - .await? - .await?; + for addr in agent_addrs { + // Mint INITIAL_PORTFOLIO_BALANCES of tokens to agents. + contracts + .tokens + .arbx + .mint(addr, amounts.0) + .send() + .await? + .await?; + contracts + .tokens + .arby + .mint(addr, amounts.1) + .send() + .await? + .await?; + } + agents.liquidity_provider.add_liquidity(config).await?; Ok(()) } @@ -142,25 +71,47 @@ mod tests { /// Should return the correct initial token balances of the contracts + agents. async fn test_initial_balances() -> Result<(), anyhow::Error> { let (contracts, env, config) = setup_test_environment().await?; + let agents = Agents { + liquidity_provider: crate::agents::liquidity_provider::LiquidityProvider::new( + "lp", + &env, + contracts.exchanges.g3m.address(), + ) + .await?, + rebalancer: crate::agents::rebalancer::Rebalancer::new( + "rebalancer", + &env, + contracts.exchanges.lex.address(), + contracts.exchanges.g3m.address(), + 0.15, + ) + .await?, + }; // Setup the token state to assert the balances are set and spent correctly. - setup_token_state(&contracts).await?; + init(&contracts, agents, &config).await?; let balance_0 = contracts .tokens .arbx - .balance_of(contracts.deployer.address()) + .balance_of(contracts.exchanges.g3m.address()) .call() .await?; let balance_1 = contracts .tokens .arby - .balance_of(contracts.deployer.address()) + .balance_of(contracts.exchanges.g3m.address()) .call() .await?; - assert_eq!(balance_0, parse_ether(INITIAL_BALANCES.0).unwrap()); - assert_eq!(balance_1, parse_ether(INITIAL_BALANCES.1).unwrap()); + assert_eq!( + balance_0, + parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap() + ); + assert_eq!( + balance_1, + parse_ether(INITIAL_PORTFOLIO_BALANCES.1).unwrap() + ); Ok(()) } @@ -170,8 +121,25 @@ mod tests { /// Since the constructor arguments are derived from the simulation config, /// this is a good test for making sure the config is integrated correctly. async fn test_constructor_args() -> Result<(), anyhow::Error> { + use ethers::types::U256; let (contracts, env, config) = setup_test_environment().await?; - init(&contracts, &config).await?; + let agents = Agents { + liquidity_provider: crate::agents::liquidity_provider::LiquidityProvider::new( + "lp", + &env, + contracts.exchanges.g3m.address(), + ) + .await?, + rebalancer: crate::agents::rebalancer::Rebalancer::new( + "rebalancer", + &env, + contracts.exchanges.lex.address(), + contracts.exchanges.g3m.address(), + 0.15, + ) + .await?, + }; + init(&contracts, agents, &config).await?; let initial_weight_x_wad = parse_ether(config.portfolio_pool_parameters.weight_token_0)?; let initial_swap_fee_bps = U256::from(config.portfolio_pool_parameters.fee_basis_points); @@ -200,7 +168,23 @@ mod tests { /// Should return the desired start price of the pool. async fn test_initial_pool_price() -> Result<(), anyhow::Error> { let (contracts, env, config) = setup_test_environment().await?; - init(&contracts, &config).await?; + let agents = Agents { + liquidity_provider: crate::agents::liquidity_provider::LiquidityProvider::new( + "lp", + &env, + contracts.exchanges.g3m.address(), + ) + .await?, + rebalancer: crate::agents::rebalancer::Rebalancer::new( + "rebalancer", + &env, + contracts.exchanges.lex.address(), + contracts.exchanges.g3m.address(), + 0.15, + ) + .await?, + }; + init(&contracts, agents, &config).await?; let price = contracts.exchanges.g3m.get_spot_price().call().await?; println!("Price: {}", price); @@ -216,24 +200,26 @@ mod tests { /// Should return the correct balances of the pool after initialization. async fn test_initial_pool_balances() -> Result<(), anyhow::Error> { let (contracts, env, config) = setup_test_environment().await?; + let agents = Agents { + liquidity_provider: crate::agents::liquidity_provider::LiquidityProvider::new( + "lp", + &env, + contracts.exchanges.g3m.address(), + ) + .await?, + rebalancer: crate::agents::rebalancer::Rebalancer::new( + "rebalancer", + &env, + contracts.exchanges.lex.address(), + contracts.exchanges.g3m.address(), + 0.15, + ) + .await?, + }; // Setup the token state to assert the balances are set and spent correctly. - setup_token_state(&contracts).await?; - // Setup the pool state to assert the tokens are sent into the pool on initialization. - setup_pool_state(&contracts, &config).await?; + init(&contracts, agents, &config).await?; - let balance_0 = contracts - .tokens - .arbx - .balance_of(contracts.deployer.address()) - .call() - .await?; - let balance_1 = contracts - .tokens - .arby - .balance_of(contracts.deployer.address()) - .call() - .await?; let balance_2 = contracts .tokens .arbx @@ -247,14 +233,6 @@ mod tests { .call() .await?; - assert_eq!( - balance_0, - parse_ether(INITIAL_BALANCES.0 - INITIAL_PORTFOLIO_BALANCES.0).unwrap() - ); - assert_eq!( - balance_1, - parse_ether(INITIAL_BALANCES.1 - INITIAL_PORTFOLIO_BALANCES.1).unwrap() - ); assert_eq!( balance_2, parse_ether(INITIAL_PORTFOLIO_BALANCES.0).unwrap() From e4ac386c29009b45c06586af6a733f509434329f Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 16:21:34 -0400 Subject: [PATCH 13/23] clean up init --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 716a90f36..039ee73d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,8 +171,8 @@ dependencies = [ [[package]] name = "arbiter-core" -version = "0.5.3" -source = "git+https://github.com/primitivefinance/arbiter.git?branch=arbiter/version#1ee29f9b868eee2ef4d6902da475a02780223d1a" +version = "0.6.0" +source = "git+https://github.com/primitivefinance/arbiter.git?branch=arbiter/version#3c908aed7b2d66d703e5101a41dbf63b23900846" dependencies = [ "RustQuant", "async-trait", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "arbiter-derive" version = "0.1.0" -source = "git+https://github.com/primitivefinance/arbiter.git?branch=arbiter/version#1ee29f9b868eee2ef4d6902da475a02780223d1a" +source = "git+https://github.com/primitivefinance/arbiter.git?branch=arbiter/version#3c908aed7b2d66d703e5101a41dbf63b23900846" dependencies = [ "proc-macro2", "quote", From 4adea083721bf1434f5227dcda4276f9b5e69e16 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 16:32:52 -0400 Subject: [PATCH 14/23] add dumb rebalancing strategy --- box-simulation/src/agents/rebalancer.rs | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/box-simulation/src/agents/rebalancer.rs b/box-simulation/src/agents/rebalancer.rs index 0ce68be69..17cbb240c 100644 --- a/box-simulation/src/agents/rebalancer.rs +++ b/box-simulation/src/agents/rebalancer.rs @@ -1,5 +1,5 @@ use box_core::math::ComputeReturns; -use ethers::utils::format_ether; +use ethers::utils::{format_ether, parse_ether}; use std::ops::Div; use std::sync::Arc; @@ -66,6 +66,7 @@ impl Rebalancer { self.portfolio_prices .push((portfolio_price_float, timestamp)); self.calculate_rv()?; + self.execute_smooth_rebalance().await?; } Ok(()) } @@ -116,7 +117,31 @@ impl Rebalancer { Ok(()) } - fn execute_smooth_rebalance(&mut self) -> Result<()> { + // dumb poc, this just checks if the portfolio rv is greater than the target rv + // then changes weight by 1% over the course of a day depending on if rv is greater or less than target + async fn execute_smooth_rebalance(&mut self) -> Result<()> { + let portfolio_rv = self.portfolio_rv.last().unwrap().0; + let current_weight_x = self.g3m.weight_x().call().await?; + let current_weight_float = format_ether(current_weight_x).parse::().unwrap(); + if portfolio_rv < self.target_volatility { + let new_weight = current_weight_float + 0.01; + self.g3m + .set_weight_x( + parse_ether(new_weight.to_string()).unwrap(), + U256::from(86400), + ) + .send() + .await?; + } else { + let new_weight = current_weight_float - 0.01; + self.g3m + .set_weight_x( + parse_ether(new_weight.to_string()).unwrap(), + U256::from(86400), + ) + .send() + .await?; + } Ok(()) } } From 2d31ec97fa147d36ca66cfb31a7c95abb3027187 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 10 Oct 2023 16:33:53 -0400 Subject: [PATCH 15/23] minor fmting --- box-simulation/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index 7c7746b80..ded15ddaa 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -10,6 +10,7 @@ use ethers::types::{Address, U256}; use tracing_subscriber; use bindings::{atomic_arbitrage::AtomicArbitrage, g3m::G3M}; +use setup::init::init; mod agents; mod engine; @@ -62,7 +63,7 @@ async fn main() -> Result<()> { rebalancer, }; - let init = setup::init::init(&contracts, agents, &config).await?; + init(&contracts, agents, &config).await?; Ok(()) } From 9c069040132f44d80c979e62e6494ec94d65e9f6 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Oct 2023 14:08:41 -0700 Subject: [PATCH 16/23] refactor(engine-builder): working action builder --- Cargo.lock | 1 + box-simulation/Cargo.toml | 3 +- box-simulation/src/engine/builder.rs | 99 ++++++++++++++++++++-------- box-simulation/src/engine/mod.rs | 72 -------------------- 4 files changed, 75 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 039ee73d7..411e0985b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "box_core", "config", "ethers", + "futures", "serde", "serde_json", "tokio", diff --git a/box-simulation/Cargo.toml b/box-simulation/Cargo.toml index b9b169ae8..6641e8e1b 100644 --- a/box-simulation/Cargo.toml +++ b/box-simulation/Cargo.toml @@ -19,4 +19,5 @@ tracing = "0.1.37" tracing-subscriber = "0.3.17" toml = "0.8.0" config = "0.13.1" -async-trait = "0.1.73" \ No newline at end of file +futures = "0.3.28" +async-trait = "0.1.73" diff --git a/box-simulation/src/engine/builder.rs b/box-simulation/src/engine/builder.rs index 9f14093f2..c98e4a0df 100644 --- a/box-simulation/src/engine/builder.rs +++ b/box-simulation/src/engine/builder.rs @@ -1,26 +1,44 @@ //! Builds the core simulation logic into the engine so it's run in the main loop. +//! The main part of the "simulation loop" is to update the reference price of the liquid exchange. +//! All simulation logic occurs around this main event, so this builder is used to easily +//! add before and after hooks to this main event. use arbiter_core::environment::Environment; -use crate::engine::PriceChanger; -use crate::settings::{self, params::SimulationConfig}; -use crate::setup::{self, deploy::Contracts}; +use super::price_path::PriceChanger; +use crate::settings::params::SimulationConfig; +use crate::setup::deploy::Contracts; -/// A specific type for the async action function that uses contracts and environment -/// to run the core simulation logic. -/// Implements the Sized trait so it can be used as a type. -pub type Action = Box Result<(), anyhow::Error> + Send + Sync>; +use std::future::Future; +use std::pin::Pin; + +// Step 1: Define our return type for async functions +type AsyncResult = Result<(), anyhow::Error>; + +// Step 2: Define the basic Future type that our async functions will return +type OurFuture<'a> = dyn Future + Send + 'a; + +// Step 3: Pin that Future because async functions often return Pin> +type PinnedFuture<'a> = Pin>>; + +// Step 4: Define the type for functions that take a &Contracts and return our PinnedFuture +type FunctionType<'a> = dyn Fn(&'a Contracts) -> PinnedFuture<'a> + Send; + +// Step 5: Box the function type so we can store it easily in a Vec +// The lifetime 'a is the lifetime of the Contracts reference, because we need to make sure its still alive! +pub type Action<'a> = Box>; /// A builder for creating the simulation loop by specifying the actions to run inside the main sim loop. /// before_hooks - Async functions that that are run inside the sim loop before the core logic. /// after_hooks - Async functions that that are run inside the sim loop after the core logic. -pub struct EngineBuilder { - pub before_hooks: Vec, - pub after_hooks: Vec, +pub struct EngineBuilder<'a> { + pub before_hooks: Vec>, + pub after_hooks: Vec>, } /// Initializes the simulation engine with two vectors of actions to run before and after the core simulation logic. /// Calling the `run` method on the engine will run the before hooks, then the core simulation logic, then the after hooks within the sim loop. -impl EngineBuilder { +impl<'a> EngineBuilder<'a> { + /// Creates a new engine builder with empty hook vectors. pub fn new() -> Self { Self { before_hooks: Vec::new(), @@ -29,14 +47,22 @@ impl EngineBuilder { } /// Adds a before hook to the engine. - pub fn before(mut self, hook: Action) -> Self { + pub fn before(mut self, hook: Action<'a>) -> Self { self.before_hooks.push(hook); self } + /// Adds an after hook to the engine. + pub fn after(mut self, hook: Action<'a>) -> Self { + self.after_hooks.push(hook); + self + } + + /// Creates a "PriceChanger", which has a vector of a price trajectory. + /// Loops over that trajectory and runs the before hooks, updates the price, then the after hooks. pub async fn run( &self, - contracts: &Contracts, + contracts: &'a Contracts, env: &Environment, config: &SimulationConfig, ) -> Result<(), anyhow::Error> { @@ -48,16 +74,16 @@ impl EngineBuilder { for _ in 0..(price_changer.trajectory.paths[0].len() - 1) { // Run the before hooks - for hook in self.before_hooks.iter() { - hook(contracts)?; + for func in &self.before_hooks { + func(contracts).await?; } // Run the core simulation logic, which is updating the price. price_changer.update_price().await?; // Run the after hooks - for hook in self.after_hooks.iter() { - hook(contracts)?; + for func in &self.after_hooks { + func(contracts).await?; } } @@ -68,15 +94,15 @@ impl EngineBuilder { #[cfg(test)] mod tests { use super::*; + use crate::settings::{self, params::SimulationConfig}; + use crate::setup::{self, deploy::Contracts}; use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float}; async fn setup_test_environment( ) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> { let config = settings::params::SimulationConfig::new()?; - let env = EnvironmentBuilder::new().build(); let contracts = setup::deploy::deploy_contracts(&env, &config).await?; - Ok((contracts, env, config)) } @@ -84,8 +110,7 @@ mod tests { async fn test_engine_builder_vanilla() -> Result<(), anyhow::Error> { let (contracts, env, config) = setup_test_environment().await?; - let engine_builder = EngineBuilder::new().run(&contracts, &env, &config).await?; - + let _ = EngineBuilder::new().run(&contracts, &env, &config).await?; let initial_price_float = config.price_process_parameters.initial_price; let lex_price_float = wad_to_float(contracts.exchanges.lex.price().call().await?); @@ -95,9 +120,6 @@ mod tests { "The price of the liquid exchange should have been updated by the simulation's core logic." ); - println!("initial_price_float: {}", initial_price_float); - println!("lex_price_float: {}", lex_price_float); - Ok(()) } @@ -105,14 +127,37 @@ mod tests { async fn test_engine_builder_add_before_hook() -> Result<(), anyhow::Error> { let (contracts, env, config) = setup_test_environment().await?; - let engine_builder = EngineBuilder::new() + // Run a before hook to increment the swap fee of the g3m exchange by 1. + let _ = EngineBuilder::new() .before(Box::new(|contracts| { - println!("before hook"); - Ok(()) + Box::pin(async move { + let current_swap_fee = contracts.exchanges.g3m.swap_fee().call().await?; + contracts + .exchanges + .g3m + .set_swap_fee((current_swap_fee + 1).into()) + .send() + .await? + .await?; + + Ok(()) + }) })) .run(&contracts, &env, &config) .await?; + // Assert the swap_fee is equal to the nonce of the price changer trajectory. + // We incremented the swap fee by 1 on each step, + // so the actual swap fee should be equal to initial swap fee + the number of steps in the trajectory. + let actual_swap_fee = contracts.exchanges.g3m.swap_fee().call().await?; + let expected_swap_fee = config.portfolio_pool_parameters.fee_basis_points as u128 + + config.price_process_parameters.num_steps as u128; + + assert_eq!( + actual_swap_fee.as_u128(), expected_swap_fee, + "The swap fee should have been incremented by the number of steps in the price changer trajectory." + ); + Ok(()) } } diff --git a/box-simulation/src/engine/mod.rs b/box-simulation/src/engine/mod.rs index 51723cd76..faba4a300 100644 --- a/box-simulation/src/engine/mod.rs +++ b/box-simulation/src/engine/mod.rs @@ -1,74 +1,2 @@ -use crate::settings::{self, params::SimulationConfig}; -use crate::setup::{self, deploy::Contracts}; - -use self::price_path::PriceChanger; -use arbiter_core::environment::Environment; - pub mod builder; pub mod price_path; - -/// Runs the main simulation loop. -pub async fn run( - contracts: &Contracts, - env: &Environment, - config: &SimulationConfig, -) -> Result { - // Get the initial price path from the configuration. - let mut price_changer = price_path::PriceChanger::new( - contracts.exchanges.lex.clone(), - config.price_process_parameters.clone(), - ); - - for _ in 0..(price_changer.trajectory.paths[0].len() - 1) { - price_changer.update_price().await?; - } - - Ok(price_changer) -} - -#[cfg(test)] -mod tests { - use super::*; - use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float}; - - async fn setup_test_environment( - ) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> { - let config = settings::params::SimulationConfig::new()?; - - let env = EnvironmentBuilder::new().build(); - let contracts = setup::deploy::deploy_contracts(&env, &config).await?; - - Ok((contracts, env, config)) - } - - #[tokio::test] - async fn test_price_path_update_in_loop() -> Result<(), anyhow::Error> { - let (contracts, env, config) = setup_test_environment().await?; - - let mut price_changer = run(&contracts, &env, &config).await?; - - let price_index = price_changer.index; - - let new_price_lex = contracts.exchanges.lex.price().call().await?; - - // Assert the price of the lex is the same as the last price in the trajectory - assert_eq!( - price_changer.trajectory.paths[0][price_index - 1], - wad_to_float(new_price_lex) - ); - - // Assert we reached the end of the trajectory - assert_eq!(price_changer.index, price_changer.trajectory.paths[0].len()); - - // Print the last price in the trajectory - println!( - "last price in trajectory: {}", - price_changer.trajectory.paths[0][price_index - 1] - ); - - // Print price of lex - println!("price of lex: {}", new_price_lex); - - Ok(()) - } -} From 046868522e11c5e1360d083b6eb18feeac5e6ff8 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Oct 2023 14:20:14 -0700 Subject: [PATCH 17/23] feat(builder-main): adds engine builder to main entrypoint on sim --- box-simulation/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index ded15ddaa..ad78310d8 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -65,5 +65,9 @@ async fn main() -> Result<()> { init(&contracts, agents, &config).await?; + engine::builder::EngineBuilder::new() + .run(&contracts, &env, &config) + .await?; + Ok(()) } From 94c4a8ae8038e1e6ef6e00499fa4a0083ca0570c Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 11 Oct 2023 11:42:17 -0400 Subject: [PATCH 18/23] add compute_simple_return and fix sharpe_ratio calculation --- box-core/src/math.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/box-core/src/math.rs b/box-core/src/math.rs index e16dbbbd7..67f835eec 100644 --- a/box-core/src/math.rs +++ b/box-core/src/math.rs @@ -13,6 +13,7 @@ pub fn compute_returns(values: Vec) -> Vec { pub trait ComputeReturns { fn compute_log_returns(self) -> Vec; + fn compute_simple_returns(self) -> Vec; fn compute_variance(self) -> f64; fn compute_std_deviation(self) -> f64; fn compute_realized_volatility(self) -> f64; @@ -34,6 +35,17 @@ where } returns } + fn compute_simple_returns(self) -> Vec { + let mut previous_value = 0.0_f64; + let mut returns = Vec::new(); + for value in self { + if previous_value != 0.0 { + returns.push(value / previous_value - 1.0); + } + previous_value = value; + } + returns + } fn compute_variance(self) -> f64 { let values = self.into_iter().collect::>(); let mean = values.iter().sum::() / values.len() as f64; @@ -54,8 +66,9 @@ where let rv = returns.compute_std_deviation() / (len as f64 / 365.0); rv } + // TODO: don't use log returns here, use simple returns fn compute_sharpe_ratio(self) -> f64 { - let returns = self.compute_log_returns(); + let returns = self.compute_simple_returns(); let mean = returns.iter().sum::() / returns.len() as f64; let std_deviation = returns.compute_std_deviation(); mean / std_deviation @@ -84,11 +97,17 @@ mod tests { use super::ComputeReturns; let values = vec![1.0, 2.0, 3.0]; let returns = values.compute_log_returns(); - //let returns = super::compute_returns(values); - //assert_eq!(returns, vec![1.0, 0.5]); assert_eq!(returns, [0.6931471805599453, 0.4054651081081644]); } + #[test] + fn test_compute_simple_returns() { + use super::ComputeReturns; + let values = vec![1.0, 2.0, 3.0]; + let returns = values.compute_simple_returns(); + assert_eq!(returns, [1.0, 0.5]); + } + #[test] fn test_compute_variance() { use super::ComputeReturns; @@ -126,6 +145,6 @@ mod tests { use super::ComputeReturns; let values = vec![1.0, 2.0, 3.0]; let sharpe_ratio = values.compute_sharpe_ratio(); - assert_eq!(sharpe_ratio, 3.8188416793064177); + assert_eq!(sharpe_ratio, 3.0); } } From cdb62cc47e6db8c7b6505a8a7a90bb36381deb6a Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 11 Oct 2023 11:52:45 -0400 Subject: [PATCH 19/23] fix conflicts --- box-simulation/Cargo.toml | 4 ++-- box-simulation/src/agents/mod.rs | 1 - box-simulation/src/main.rs | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/box-simulation/Cargo.toml b/box-simulation/Cargo.toml index 6641e8e1b..0c18a11e7 100644 --- a/box-simulation/Cargo.toml +++ b/box-simulation/Cargo.toml @@ -10,8 +10,8 @@ bindings.workspace = true box_core.workspace = true tokio = { version = "1.11.0", features = ["full"] } anyhow = "1.0.75" -arbiter-core = { git = "https://github.com/primitivefinance/arbiter.git", branch = "arbiter/version" } -arbiter-derive = { git = "https://github.com/primitivefinance/arbiter.git", branch = "arbiter/version" } +arbiter-core = { git = "https://github.com/primitivefinance/arbiter.git", branch = "main" } +arbiter-derive = { git = "https://github.com/primitivefinance/arbiter.git", branch = "main" } ethers = "2.0.10" serde = "1.0.188" serde_json = "1.0.107" diff --git a/box-simulation/src/agents/mod.rs b/box-simulation/src/agents/mod.rs index 9ffde5553..816561116 100644 --- a/box-simulation/src/agents/mod.rs +++ b/box-simulation/src/agents/mod.rs @@ -1,7 +1,6 @@ use super::*; pub mod arbitrageur; -pub mod atomic_arbitrage; pub mod liquidity_provider; pub mod price_changer; pub mod rebalancer; diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index ad78310d8..e1d660218 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -35,10 +35,10 @@ async fn main() -> Result<()> { .with_max_level(tracing::Level::TRACE) .init(); - let _config = settings::params::SimulationConfig::new()?; + let config = settings::params::SimulationConfig::new()?; let env = EnvironmentBuilder::new().build(); - let contracts = setup::deploy::deploy_contracts(&env).await?; + let contracts = setup::deploy::deploy_contracts(&env, &config).await?; let _arbitrageur = Arbitrageur::>::new( "arbitrageur", From 04e1d5c1e588d3ab1b766384c628a6b35859c7fa Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 11 Oct 2023 11:52:54 -0400 Subject: [PATCH 20/23] fix conflicts --- Cargo.lock | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 411e0985b..1ee6a4bf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,8 +171,8 @@ dependencies = [ [[package]] name = "arbiter-core" -version = "0.6.0" -source = "git+https://github.com/primitivefinance/arbiter.git?branch=arbiter/version#3c908aed7b2d66d703e5101a41dbf63b23900846" +version = "0.6.1" +source = "git+https://github.com/primitivefinance/arbiter.git?branch=main#9907eaaf81be03d36fb1447bea5692b8745fa916" dependencies = [ "RustQuant", "async-trait", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "arbiter-derive" version = "0.1.0" -source = "git+https://github.com/primitivefinance/arbiter.git?branch=arbiter/version#3c908aed7b2d66d703e5101a41dbf63b23900846" +source = "git+https://github.com/primitivefinance/arbiter.git?branch=main#9907eaaf81be03d36fb1447bea5692b8745fa916" dependencies = [ "proc-macro2", "quote", @@ -5025,11 +5025,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] From 00953891ecf20cc2f40225d637c205afc73e43c7 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 11 Oct 2023 11:54:17 -0400 Subject: [PATCH 21/23] add contracts feature flag to arbiter-core --- box-simulation/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/box-simulation/Cargo.toml b/box-simulation/Cargo.toml index 0c18a11e7..a30ac4fb6 100644 --- a/box-simulation/Cargo.toml +++ b/box-simulation/Cargo.toml @@ -10,7 +10,7 @@ bindings.workspace = true box_core.workspace = true tokio = { version = "1.11.0", features = ["full"] } anyhow = "1.0.75" -arbiter-core = { git = "https://github.com/primitivefinance/arbiter.git", branch = "main" } +arbiter-core = { git = "https://github.com/primitivefinance/arbiter.git", branch = "main", features = ["contracts"] } arbiter-derive = { git = "https://github.com/primitivefinance/arbiter.git", branch = "main" } ethers = "2.0.10" serde = "1.0.188" From 74e662c2786171ea10c95c6d55bde459e9678dcc Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 11 Oct 2023 12:49:40 -0400 Subject: [PATCH 22/23] rename rebalancer -> weight_changer, add constructor to atomic arbitrage contract --- box-simulation/src/agents/arbitrageur/mod.rs | 12 ++++++++---- box-simulation/src/agents/mod.rs | 2 +- .../src/agents/{rebalancer.rs => weight_changer.rs} | 4 ++-- box-simulation/src/main.rs | 9 ++++++--- box-simulation/src/setup/deploy.rs | 4 ++-- box-simulation/src/setup/init.rs | 9 +++++---- 6 files changed, 24 insertions(+), 16 deletions(-) rename box-simulation/src/agents/{rebalancer.rs => weight_changer.rs} (99%) diff --git a/box-simulation/src/agents/arbitrageur/mod.rs b/box-simulation/src/agents/arbitrageur/mod.rs index 861361b45..1d408e130 100644 --- a/box-simulation/src/agents/arbitrageur/mod.rs +++ b/box-simulation/src/agents/arbitrageur/mod.rs @@ -39,10 +39,14 @@ impl Arbitrageur { // Get the exchanges and arb contract connected to the arbitrageur client. let liquid_exchange = LiquidExchange::new(liquid_exchange_address, client.clone()); let strategy = Strategy::new(strategy_address, client.clone()); - let atomic_arbitrage = - AtomicArbitrage::deploy(client, (strategy_address, liquid_exchange_address))? - .send() - .await?; + let arbx = liquid_exchange.arbiter_token_x().call().await?; + let arby = liquid_exchange.arbiter_token_y().call().await?; + let atomic_arbitrage = AtomicArbitrage::deploy( + client, + (strategy_address, liquid_exchange_address, arbx, arby), + )? + .send() + .await?; Ok(Self { liquid_exchange, diff --git a/box-simulation/src/agents/mod.rs b/box-simulation/src/agents/mod.rs index 816561116..654331f45 100644 --- a/box-simulation/src/agents/mod.rs +++ b/box-simulation/src/agents/mod.rs @@ -3,4 +3,4 @@ use super::*; pub mod arbitrageur; pub mod liquidity_provider; pub mod price_changer; -pub mod rebalancer; +pub mod weight_changer; diff --git a/box-simulation/src/agents/rebalancer.rs b/box-simulation/src/agents/weight_changer.rs similarity index 99% rename from box-simulation/src/agents/rebalancer.rs rename to box-simulation/src/agents/weight_changer.rs index 17cbb240c..1e2ca0385 100644 --- a/box-simulation/src/agents/rebalancer.rs +++ b/box-simulation/src/agents/weight_changer.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use super::*; -pub struct Rebalancer { +pub struct WeightChanger { pub client: Arc, pub lex: LiquidExchange, pub g3m: G3M, @@ -17,7 +17,7 @@ pub struct Rebalancer { pub asset_rv: Vec<(f64, u64)>, } -impl Rebalancer { +impl WeightChanger { pub async fn new( label: &str, environment: &Environment, diff --git a/box-simulation/src/main.rs b/box-simulation/src/main.rs index e1d660218..f8b2fc58c 100644 --- a/box-simulation/src/main.rs +++ b/box-simulation/src/main.rs @@ -1,6 +1,6 @@ use agents::arbitrageur::Arbitrageur; use agents::liquidity_provider::LiquidityProvider; -use agents::rebalancer::Rebalancer; +use agents::weight_changer::WeightChanger; use anyhow::Result; use arbiter_core::environment::builder::EnvironmentBuilder; use arbiter_core::{ @@ -23,7 +23,7 @@ pub const WAD: ethers::types::U256 = ethers::types::U256([10_u64.pow(18), 0, 0, pub struct Agents { pub liquidity_provider: LiquidityProvider, - pub rebalancer: Rebalancer, + pub rebalancer: WeightChanger, } #[tokio::main] @@ -48,8 +48,11 @@ async fn main() -> Result<()> { ) .await?; + // have the loop iterate blcoks and block timestamps + // draw random # from poisson distribution which determines how long we wait for price to change + // loop that causes price change -> arbitrageur -> check if weightchanger needs to run let lp = LiquidityProvider::new("lp", &env, contracts.exchanges.g3m.address()).await?; - let rebalancer = Rebalancer::new( + let rebalancer = WeightChanger::new( "rebalancer", &env, contracts.exchanges.lex.address(), diff --git a/box-simulation/src/setup/deploy.rs b/box-simulation/src/setup/deploy.rs index 65621747b..f5eebfef0 100644 --- a/box-simulation/src/setup/deploy.rs +++ b/box-simulation/src/setup/deploy.rs @@ -91,11 +91,11 @@ pub async fn deploy_contracts( exchanges, }; - let listener = EventLogger::builder() + EventLogger::builder() .add(contracts.exchanges.lex.events(), "lex") .add(contracts.tokens.arbx.events(), "arbx") .add(contracts.tokens.arby.events(), "arby") - .run(); + .run()?; // agents // 1. arbitraguer :check: diff --git a/box-simulation/src/setup/init.rs b/box-simulation/src/setup/init.rs index 26dd78154..633f43e40 100644 --- a/box-simulation/src/setup/init.rs +++ b/box-simulation/src/setup/init.rs @@ -24,6 +24,7 @@ pub async fn init( agents.liquidity_provider.client.address(), agents.rebalancer.client.address(), ]; + println!("get here????"); for addr in agent_addrs { // Mint INITIAL_PORTFOLIO_BALANCES of tokens to agents. @@ -78,7 +79,7 @@ mod tests { contracts.exchanges.g3m.address(), ) .await?, - rebalancer: crate::agents::rebalancer::Rebalancer::new( + rebalancer: crate::agents::weight_changer::WeightChanger::new( "rebalancer", &env, contracts.exchanges.lex.address(), @@ -130,7 +131,7 @@ mod tests { contracts.exchanges.g3m.address(), ) .await?, - rebalancer: crate::agents::rebalancer::Rebalancer::new( + rebalancer: crate::agents::weight_changer::WeightChanger::new( "rebalancer", &env, contracts.exchanges.lex.address(), @@ -175,7 +176,7 @@ mod tests { contracts.exchanges.g3m.address(), ) .await?, - rebalancer: crate::agents::rebalancer::Rebalancer::new( + rebalancer: crate::agents::weight_changer::WeightChanger::new( "rebalancer", &env, contracts.exchanges.lex.address(), @@ -207,7 +208,7 @@ mod tests { contracts.exchanges.g3m.address(), ) .await?, - rebalancer: crate::agents::rebalancer::Rebalancer::new( + rebalancer: crate::agents::weight_changer::WeightChanger::new( "rebalancer", &env, contracts.exchanges.lex.address(), From 3946b590f7b9532b7e093f757a2ce8058f786f94 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 11 Oct 2023 12:50:54 -0400 Subject: [PATCH 23/23] rename rebalancer -> weight_changer, add constructor to atomic arbitrage contract --- Cargo.lock | 1 + box-contracts/src/AtomicArbitrage.sol | 8 ++++++++ box-simulation/src/setup/deploy.rs | 1 + 3 files changed, 10 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1ee6a4bf6..768a41d3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,6 +344,7 @@ name = "bindings" version = "0.1.0" dependencies = [ "ethers", + "serde", ] [[package]] diff --git a/box-contracts/src/AtomicArbitrage.sol b/box-contracts/src/AtomicArbitrage.sol index 23c890eb5..90e1a0627 100644 --- a/box-contracts/src/AtomicArbitrage.sol +++ b/box-contracts/src/AtomicArbitrage.sol @@ -17,6 +17,14 @@ contract AtomicArbitrage { address public asset; address public quote; + constructor(address exchangeAddress, address liquidExchangeAddress, address assetAddress, address quoteAddress) { + exchange = exchangeAddress; + liquidExchange = liquidExchangeAddress; + asset = assetAddress; + quote = quoteAddress; + } + + function lower_exchange_price( uint256 input ) external { diff --git a/box-simulation/src/setup/deploy.rs b/box-simulation/src/setup/deploy.rs index f5eebfef0..d91ad9fed 100644 --- a/box-simulation/src/setup/deploy.rs +++ b/box-simulation/src/setup/deploy.rs @@ -95,6 +95,7 @@ pub async fn deploy_contracts( .add(contracts.exchanges.lex.events(), "lex") .add(contracts.tokens.arbx.events(), "arbx") .add(contracts.tokens.arby.events(), "arby") + .add(contracts.exchanges.g3m.events(), "g3m") .run()?; // agents