-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(builder): engine builder for buildign the sim loop
- Loading branch information
1 parent
1da1c57
commit a57c317
Showing
4 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<dyn Fn(&Contracts) -> 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<Action>, | ||
pub after_hooks: Vec<Action>, | ||
} | ||
|
||
/// 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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PriceChanger, anyhow::Error> { | ||
// 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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RevmMiddleware>, | ||
|
||
/// 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<RevmMiddleware>, | ||
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters