From 637acbf143b3789e64501788bd8a568c35a7f6b2 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Fri, 28 Jul 2023 08:33:20 -0600 Subject: [PATCH 01/19] poisson MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update block number fmt passing tests 🧪 Update stochastic_new.rs new stoacastics new stoacastics added --- Cargo.toml | 2 + bin/init.rs | 5 +- core/Cargo.toml | 2 + core/src/agent.rs | 3 +- core/src/environment.rs | 51 +++-- core/src/manager.rs | 3 +- core/src/math/mod.rs | 1 + core/src/math/stochastic_process.rs | 324 ++++++++++------------------ core/src/middleware.rs | 26 ++- core/src/tests.rs | 11 +- 10 files changed, 186 insertions(+), 242 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 25ee8dad..83c6884d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,8 @@ bytes = "1.4.0" # Simulation dependencies quote = "1.0.28" +statrs = "0.16.0" +RustQuant = "0.0.23" # Development dependencies [dev-dependencies] diff --git a/bin/init.rs b/bin/init.rs index 23ef0997..17eeff38 100644 --- a/bin/init.rs +++ b/bin/init.rs @@ -1,7 +1,6 @@ +use std::{fs, io::Write, path::Path}; + use quote::quote; -use std::fs; -use std::io::Write; -use std::path::Path; pub(crate) fn create_simulation(simulation_name: &str) -> std::io::Result<()> { let main = quote! { diff --git a/core/Cargo.toml b/core/Cargo.toml index 9e0d18a6..bdb72da7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" # For execution bytes = "1.4.0" revm = "3.3.0" +statrs = "0.16.0" # For handling events crossbeam-channel = "0.5.8" @@ -17,6 +18,7 @@ crossbeam-channel = "0.5.8" rand = "0.8.5" rand_chacha = "0.3.1" rand_distr = "0.4.3" +RustQuant = "*" # For void middleware async-trait = "0.1.68" diff --git a/core/src/agent.rs b/core/src/agent.rs index 926e2bca..fc6443d0 100644 --- a/core/src/agent.rs +++ b/core/src/agent.rs @@ -62,9 +62,10 @@ pub(crate) mod tests { pub(crate) const TEST_AGENT_NAME: &str = "test_agent"; pub(crate) const TEST_BEHAVIOR_DATA: &str = "test_behavior_data"; - use super::*; use ethers::providers::{MockProvider, ProviderError}; + use super::*; + #[derive(Debug)] pub(crate) struct TestMiddleware {} diff --git a/core/src/environment.rs b/core/src/environment.rs index 7cf8a02f..08fcb4f2 100644 --- a/core/src/environment.rs +++ b/core/src/environment.rs @@ -1,20 +1,26 @@ #![warn(missing_docs)] #![warn(unsafe_code)] +use std::{ + fmt::Debug, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, + thread, +}; + use crossbeam_channel::{unbounded, Receiver, Sender}; use revm::{ db::{CacheDB, EmptyDB}, primitives::{ExecutionResult, Log, TxEnv, U256}, EVM, }; -use std::{ - fmt::Debug, - sync::{Arc, Mutex}, - thread, -}; -use crate::utils::revm_logs_to_ethers_logs; -use crate::{agent::Agent, middleware::RevmMiddleware}; +use crate::{ + agent::Agent, math::stochastic_process::poisson_process, middleware::RevmMiddleware, + utils::revm_logs_to_ethers_logs, +}; /// Type Aliases for the event channel. pub(crate) type ToTransact = bool; @@ -22,6 +28,7 @@ pub(crate) type ExecutionSender = Sender; pub(crate) type TxEnvSender = Sender<(ToTransact, TxEnv, ExecutionSender)>; pub(crate) type TxEnvReceiver = Receiver<(ToTransact, TxEnv, ExecutionSender)>; +/// State enum for the [`Environment`]. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum State { /// The [`Environment`] is currently running. @@ -32,21 +39,28 @@ pub enum State { Stopped, } +/// The environment struct. pub struct Environment { + /// label for the environment pub label: String, pub(crate) state: State, pub(crate) evm: EVM>, + /// Connection to the environment pub connection: Connection, /// Clients (Agents) in the environment pub clients: Vec>>, - // pub deployed_contracts: HashMap>, + /// expected events per block + pub lambda: f64, } +/// Connection struct for the [`Environment`]. #[derive(Debug, Clone)] pub struct Connection { pub(crate) tx_sender: TxEnvSender, tx_receiver: TxEnvReceiver, pub(crate) event_broadcaster: Arc>, + /// expected events per block + pub tx_per_block: Arc, } impl Environment { @@ -61,6 +75,7 @@ impl Environment { tx_sender: transaction_channel.0, tx_receiver: transaction_channel.1, event_broadcaster: Arc::new(Mutex::new(EventBroadcaster::new())), + tx_per_block: Arc::new(AtomicUsize::new(0)), }; Self { label, @@ -68,12 +83,17 @@ impl Environment { evm, connection, clients: vec![], + lambda: 0.0, } } - // TODO: We need to make this the way to add agents to the environment. - // in `agent.rs` we have `new_simulation_agent` which should probably just be called from this function instead. - // OR agents can be created (without a connection?) and then added to the environment where they will gain a connection? - pub fn add_agent(&mut self, agent: Agent) { + /// Set the expected events per block + pub fn configure_lambda(&mut self, lambda: f64) { + self.lambda = lambda; + } + + /// Creates a new [`Agent= expected_occurance[0] as usize { + evm.env.block.number += U256::from(1); + counter.store(0, Ordering::Relaxed); + } evm.env.tx = tx; + counter.fetch_add(1, Ordering::Relaxed); if to_transact { let execution_result = match evm.transact_commit() { Ok(val) => val, diff --git a/core/src/manager.rs b/core/src/manager.rs index 75182979..82373e0d 100644 --- a/core/src/manager.rs +++ b/core/src/manager.rs @@ -5,9 +5,10 @@ use std::collections::HashMap; -use crate::environment::{Environment, State}; use anyhow::{anyhow, Result}; +use crate::environment::{Environment, State}; + /// Manages simulations. pub struct SimulationManager { /// The list of [`SimulationEnvironment`] that the simulation manager controls. diff --git a/core/src/math/mod.rs b/core/src/math/mod.rs index 30106b3f..62d0ecf2 100644 --- a/core/src/math/mod.rs +++ b/core/src/math/mod.rs @@ -2,6 +2,7 @@ use ethers::types::U256; +/// Stochastic process module. pub mod stochastic_process; /// Converts a float to a WAD fixed point prepared U256 number. diff --git a/core/src/math/stochastic_process.rs b/core/src/math/stochastic_process.rs index ef16f0ac..4cb1ee89 100644 --- a/core/src/math/stochastic_process.rs +++ b/core/src/math/stochastic_process.rs @@ -1,173 +1,65 @@ -//! Module for price process generation and plotting. -use anyhow::Result; -use rand::SeedableRng; -use rand_distr::{Distribution, Normal}; -use serde::{Deserialize, Serialize}; - -// TODO: I think that this could probably be made a trait so that people can readily implement their own processes too. -// Errors also need to be better handled here. - -/// Enum for type of price process being used. -#[derive(Clone, Serialize, Deserialize, Debug)] -#[serde(tag = "price_process_type", content = "price_process")] -pub enum PriceProcessType { - /// Geometric Brownian Motion (GBM) process. - GBM(GBM), - /// Ornstein-Uhlenbeck (OU) process. - OU(OU), -} - -/// Struct for all price processes init parameters. -/// A price process is a stochastic process that describes the evolution of a price_process. -/// # Fields -/// * `process_type` - Type of price process. (PriceProcessType) -/// * `timestep` - Time step of the simulation. (f64) -/// * `timescale` - Time in string interpretation. (String) -/// * `num_steps` - Number of steps in the simulation. (usize) -/// * `initial_price` - Initial price of the simulation. (f64) -/// * `seed` - Seed for testing. (u64) -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct PriceProcess { - /// Type of price process. - pub process_type: PriceProcessType, - /// Time step of the simulation. - pub timestep: f64, - /// Timescale in string interpretation. - pub timescale: String, - /// Number of steps in the simulation. - pub num_steps: usize, - /// Initial price of the simulation. - pub initial_price: f64, - /// Seed for testing. - pub seed: u64, +use anyhow::{Ok, Result}; +use RustQuant::{ + statistics::distributions::{Distribution, Poisson}, + stochastics::{BrownianMotion, OrnsteinUhlenbeck, StochasticProcess, Trajectories}, +}; + +/// Type enum for process +pub enum StochasticProcessType { + /// Brownian motion + BrownianMotion(BrownianMotion), + /// Ornstein-Uhlenbeck + OrnsteinUhlenbeck(OrnsteinUhlenbeck), } - -impl PriceProcess { - /// Public builder function that instantiates a [`Price`]. - pub fn new( - process_type: PriceProcessType, - timestep: f64, - timescale: String, - num_steps: usize, - initial_price: f64, - seed: u64, - ) -> Self { - PriceProcess { - process_type, - timestep, - timescale, - num_steps, - initial_price, - seed, - } - } - - /// Generates a price path. - pub fn generate_price_path(&self) -> Result<(Vec, Vec)> { - match &self.process_type { - PriceProcessType::GBM(gbm) => gbm.generate(self), - PriceProcessType::OU(ou) => ou.generate(self), - } - } +/// Struct for all processes init parameters. +pub struct EulerMaruyamaInput { + /// initial value at t_0 + pub x_0: f64, + /// initial time point + pub t_0: f64, + /// terminal time point + pub t_n: f64, + /// number of time steps between t_0 and t_n + pub n_steps: usize, + /// how many process trajectories to simulate + pub m_paths: usize, + /// run in parallel or not (recommended for > 1000 paths) + pub parallel: bool, } -/// Geometric Brownian Motion process parameters struct. -/// # Fields -/// * `drift` - Price drift of the underlying asset. (f64) -/// * `volatility` - Volatility of the underlying asset. (f64) -#[derive(Copy, Clone, Serialize, Deserialize, Debug)] -pub struct GBM { - /// Price drift of the underlying asset. - pub drift: f64, - /// Volatility of the underlying asset. - pub volatility: f64, +/// Create new process and run euler maruyama. +pub fn new_procces( + proccess_type: StochasticProcessType, + config: EulerMaruyamaInput, +) -> Result { + let trajectories: Trajectories = match proccess_type { + StochasticProcessType::BrownianMotion(process) => process.euler_maruyama( + config.x_0, + config.t_0, + config.t_n, + config.n_steps, + config.m_paths, + config.parallel, + ), + StochasticProcessType::OrnsteinUhlenbeck(process) => process.euler_maruyama( + config.x_0, + config.t_0, + config.t_n, + config.n_steps, + config.m_paths, + config.parallel, + ), + }; + + Ok(trajectories) } -impl GBM { - /// Public builder function that instantiates a [`GBM`]. - pub fn new(drift: f64, volatility: f64) -> Self { - GBM { drift, volatility } - } - /// Generates a GBM price path. - /// # Arguments - /// * `timestep` - Time step of the simulation. (f64) - /// * `num_steps` - Number of steps in the simulation. (usize) - /// * `initial_price` - Initial price of the simulation. (f64) - /// * `seed` - Seed for testing. (u64) - /// # Returns - /// * `time` - Vector of time steps. (Vec) - /// * `prices` - Vector of prices. (Vec) - fn generate(&self, price_process: &PriceProcess) -> Result<(Vec, Vec)> { - let mut rng = rand::rngs::StdRng::seed_from_u64(price_process.seed); - let normal = Normal::new(0.0, 1.0)?; - let mut prices = vec![price_process.initial_price]; - let mut new_price = price_process.initial_price; - - for _ in 0..price_process.num_steps { - let noise = normal.sample(&mut rng); - new_price *= 1.0 - + self.drift * price_process.timestep - + self.volatility * noise * price_process.timestep.sqrt(); - prices.push(new_price); - } - let time = (0..price_process.num_steps) - .map(|i| i as f64 * price_process.timestep) - .collect::>(); - Ok((time, prices)) - } -} - -/// Ornstein-Uhlenbeck process parameters struct. -/// # Fields -/// * `volatility` - Volatility of the underlying asset. (f64) -/// * `mean_reversion_speed` - Mean reversion speed of the underlying asset. (f64) -/// * `mean_price` - Mean price of the underlying asset. (f64) -#[derive(Copy, Clone, Serialize, Deserialize, Debug)] -pub struct OU { - /// Volatility of the underlying asset. - pub volatility: f64, - /// Mean reversion speed of the underlying asset. - pub mean_reversion_speed: f64, - /// Mean price of the underlying asset. - pub mean_price: f64, -} - -impl OU { - /// Public builder function that instantiates a [`OU`]. - pub fn new(volatility: f64, mean_reversion_speed: f64, mean_price: f64) -> Self { - OU { - volatility, - mean_reversion_speed, - mean_price, - } - } - /// Generates an OU price path. - /// # Arguments - /// * `timestep` - Time step of the simulation. (f64) - /// * `num_steps` - Number of steps in the simulation. (usize) - /// * `initial_price` - Initial price of the simulation. (f64) - /// * `seed` - Seed for testing. (u64) - /// # Returns - /// * `time` - Vector of time steps. (Vec) - /// * `prices` - Vector of prices. (Vec) - fn generate(&self, price_process: &PriceProcess) -> Result<(Vec, Vec)> { - let mut rng = rand::rngs::StdRng::seed_from_u64(price_process.seed); - let normal = Normal::new(0.0, 1.0)?; - let mut prices = vec![price_process.initial_price]; - let mut new_price = price_process.initial_price; - - for _ in 0..price_process.num_steps { - let noise = normal.sample(&mut rng); - new_price += - self.mean_reversion_speed * (self.mean_price - new_price) * price_process.timestep - + self.volatility * noise * price_process.timestep.sqrt(); - prices.push(new_price); - } - let time = (0..price_process.num_steps) - .map(|i| i as f64 * price_process.timestep) - .collect::>(); - Ok((time, prices)) - } +/// Sample Poisson process. +pub fn poisson_process(lambda: f64, length: usize) -> Result> { + let poisson = Poisson::new(lambda); + let float_samples = poisson.sample(length); + let int_samples: Vec = float_samples.iter().map(|&x| x.round() as i32).collect(); + Ok(int_samples) } #[cfg(test)] @@ -176,56 +68,64 @@ mod tests { use super::*; #[test] - fn seeded_randomness_test() -> Result<()> { - let gbm = GBM::new(0.05, 0.3); - let price_process = PriceProcess::new( - PriceProcessType::GBM(gbm), - 0.1, - "Days".to_string(), - 100, - 100.0, - 1, - ); - // Test to see if same seed yields same result - let (time, prices) = price_process.generate_price_path()?; - let (time2, prices2) = price_process.generate_price_path()?; - assert_eq!(time, time2); - assert_eq!(prices, prices2); - // Test to see if different seed yields different result - let price_process_diff_seed = PriceProcess::new( - PriceProcessType::GBM(GBM::new(0.05, 0.3)), - 0.1, - "Days".to_string(), - 100, - 100.0, - 2, - ); - let (time3, prices3) = price_process_diff_seed.generate_price_path()?; - assert_eq!(time, time3); - assert_ne!(prices, prices3); - Ok(()) + fn new_process_brownian_motion() { + let bm = BrownianMotion::new(); + let config = EulerMaruyamaInput { + x_0: 0.0, + t_0: 0.0, + t_n: 1.0, + n_steps: 100, + m_paths: 10, + parallel: false, + }; + let process = StochasticProcessType::BrownianMotion(bm); + let result = new_procces(process, config); + + assert!(result.is_ok()); + let trajectories = result.unwrap(); + assert_eq!(trajectories.times.len(), 101); + assert_eq!(trajectories.paths.len(), 10); } #[test] - fn gbm_step_test() -> Result<()> { - let gbm = GBM::new(0.05, 0.2); - let price_process = PriceProcess::new( - PriceProcessType::GBM(gbm), - 0.01, - "1D".to_string(), - 1, - 100.0, - 42, - ); - let (_, prices) = price_process.generate_price_path()?; - let initial_price = prices[0]; - let final_price = prices[1]; + fn new_process_ornstein_uhlenbeck() { + let ou = OrnsteinUhlenbeck::new(1.0, 1.0, 1.0); + let config = EulerMaruyamaInput { + x_0: 0.0, + t_0: 0.0, + t_n: 1.0, + n_steps: 100, + m_paths: 10, + parallel: false, + }; + let process = StochasticProcessType::OrnsteinUhlenbeck(ou); + let result = new_procces(process, config); + + assert!(result.is_ok()); + let trajectories = result.unwrap(); + assert_eq!(trajectories.times.len(), 101); + assert_eq!(trajectories.paths.len(), 10); + } - let mut rng = rand::rngs::StdRng::seed_from_u64(price_process.seed); - let expected_final_price = initial_price - // Check if the GBM is evolving as it should - * (1.0 + 0.05 * 0.01 + 0.2 * Normal::new(0.0, 1.0)?.sample(&mut rng) * (0.01_f64).sqrt()); - assert_eq!(final_price, expected_final_price); - Ok(()) + #[test] + fn poisson_process_test() { + let lambda = 1.0; + let length = 100; + let result = poisson_process(lambda, length); + + assert!(result.is_ok()); + let samples = result.unwrap(); + assert_eq!(samples.len(), length); + // Because Poisson distribution is a random process, + // we cannot predict exact values, but we can check if mean is close to lambda. + let mean: f64 = samples.iter().map(|&x| x as f64).sum::() / length as f64; + assert!((mean - lambda).abs() < 0.2 * lambda); // tolerance of 20% + } + + #[test] + pub fn brownian_motion() { + let bm = BrownianMotion::new(); + let trajectory = bm.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); + assert_eq!(trajectory.times.len(), 1001); } } diff --git a/core/src/middleware.rs b/core/src/middleware.rs index c360f9fa..98a22e22 100644 --- a/core/src/middleware.rs +++ b/core/src/middleware.rs @@ -3,22 +3,28 @@ //! This module contains the middleware for the Revm simulation environment. //! Most of the middleware is essentially a placeholder, but it is necessary to have a middleware to work with bindings more efficiently. -use ethers::prelude::pending_transaction::PendingTxState; -use ethers::providers::{PendingTransaction, Provider}; +use std::fmt::Debug; + use ethers::{ - prelude::k256::{ecdsa::SigningKey,sha2::{Digest, Sha256}}, - prelude::ProviderError, - providers::{FilterWatcher, Middleware, MockProvider}, + prelude::{ + k256::{ + ecdsa::SigningKey, + sha2::{Digest, Sha256}, + }, + pending_transaction::PendingTxState, + ProviderError, + }, + providers::{FilterWatcher, Middleware, MockProvider, PendingTransaction, Provider}, signers::{Signer, Wallet}, types::{transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Filter, Log}, }; -use rand::rngs::StdRng; -use rand::SeedableRng; +use rand::{rngs::StdRng, SeedableRng}; use revm::primitives::{CreateScheme, ExecutionResult, Output, TransactTo, TxEnv, B160, U256}; -use std::fmt::Debug; -use crate::environment::Connection; -use crate::utils::{recast_address, revm_logs_to_ethers_logs}; +use crate::{ + environment::Connection, + utils::{recast_address, revm_logs_to_ethers_logs}, +}; // TODO: Refactor the connection and channels slightly to be more intuitive. For instance, the middleware may not really need to own a connection, but input one to set up everything else? #[derive(Debug)] diff --git a/core/src/tests.rs b/core/src/tests.rs index 90543806..651d4ed2 100644 --- a/core/src/tests.rs +++ b/core/src/tests.rs @@ -41,9 +41,14 @@ fn simulation_agent_wallet() { #[test] fn multiple_agent_addresses() { let environment = Environment::new(TEST_ENV_LABEL.to_string()); - let agent = Agent::new_simulation_agent(TEST_AGENT_NAME.to_string(), environment.connection.clone()); - let agent2 = Agent::new_simulation_agent(format!("new_{}", TEST_AGENT_NAME), environment.connection); - assert_ne!(agent.client.default_sender(), agent2.client.default_sender()); + let agent = + Agent::new_simulation_agent(TEST_AGENT_NAME.to_string(), environment.connection.clone()); + let agent2 = + Agent::new_simulation_agent(format!("new_{}", TEST_AGENT_NAME), environment.connection); + assert_ne!( + agent.client.default_sender(), + agent2.client.default_sender() + ); } #[test] From 071c7598ba428f0aa916bb00a91a8ba3fe8bc72e Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Sat, 29 Jul 2023 08:00:08 -0600 Subject: [PATCH 02/19] more processes --- core/src/math/stochastic_process.rs | 122 +++++++++++++++++++++++++++- core/src/middleware.rs | 17 ++-- core/src/tests.rs | 5 +- 3 files changed, 133 insertions(+), 11 deletions(-) diff --git a/core/src/math/stochastic_process.rs b/core/src/math/stochastic_process.rs index 4cb1ee89..2043c422 100644 --- a/core/src/math/stochastic_process.rs +++ b/core/src/math/stochastic_process.rs @@ -1,7 +1,11 @@ use anyhow::{Ok, Result}; use RustQuant::{ statistics::distributions::{Distribution, Poisson}, - stochastics::{BrownianMotion, OrnsteinUhlenbeck, StochasticProcess, Trajectories}, + stochastics::{ + cox_ingersoll_ross::CoxIngersollRoss, extended_vasicek::ExtendedVasicek, ho_lee::HoLee, + hull_white::HullWhite, BlackDermanToy, BrownianMotion, OrnsteinUhlenbeck, + StochasticProcess, Trajectories, + }, }; /// Type enum for process @@ -10,6 +14,16 @@ pub enum StochasticProcessType { BrownianMotion(BrownianMotion), /// Ornstein-Uhlenbeck OrnsteinUhlenbeck(OrnsteinUhlenbeck), + /// Black-Derman-Toy + BlackDermanToy(BlackDermanToy), + /// Cox-Ingersoll-Ross + CoxIngersollRoss(CoxIngersollRoss), + /// Extended Vasicek + ExtendedVasicek(ExtendedVasicek), + /// Ho-Lee + HoLee(HoLee), + /// Hull-White + HullWhite(HullWhite), } /// Struct for all processes init parameters. pub struct EulerMaruyamaInput { @@ -49,6 +63,46 @@ pub fn new_procces( config.m_paths, config.parallel, ), + StochasticProcessType::BlackDermanToy(process) => process.euler_maruyama( + config.x_0, + config.t_0, + config.t_n, + config.n_steps, + config.m_paths, + config.parallel, + ), + StochasticProcessType::CoxIngersollRoss(process) => process.euler_maruyama( + config.x_0, + config.t_0, + config.t_n, + config.n_steps, + config.m_paths, + config.parallel, + ), + StochasticProcessType::ExtendedVasicek(process) => process.euler_maruyama( + config.x_0, + config.t_0, + config.t_n, + config.n_steps, + config.m_paths, + config.parallel, + ), + StochasticProcessType::HoLee(process) => process.euler_maruyama( + config.x_0, + config.t_0, + config.t_n, + config.n_steps, + config.m_paths, + config.parallel, + ), + StochasticProcessType::HullWhite(process) => process.euler_maruyama( + config.x_0, + config.t_0, + config.t_n, + config.n_steps, + config.m_paths, + config.parallel, + ), }; Ok(trajectories) @@ -65,6 +119,8 @@ pub fn poisson_process(lambda: f64, length: usize) -> Result> { #[cfg(test)] mod tests { + use RustQuant::stochastics::Sigma; + use super::*; #[test] @@ -128,4 +184,68 @@ mod tests { let trajectory = bm.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); assert_eq!(trajectory.times.len(), 1001); } + + #[test] + pub fn ornstein_uhlenbeck() { + let ou = OrnsteinUhlenbeck::new(1.0, 1.0, 1.0); + let trajectory = ou.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); + assert_eq!(trajectory.times.len(), 1001); + } + + #[test] + pub fn black_derman_toy() { + fn theta_t(_t: f64) -> f64 { + 1.5 + } + + let sig = Sigma::Const(0.13); + let bdt = BlackDermanToy::new(sig, theta_t); + let trajectory = bdt.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); + assert_eq!(trajectory.times.len(), 1001); + } + + #[test] + pub fn cox_ingersoll_ross() { + let cir = CoxIngersollRoss::new(0.15, 0.45, 0.01); + let trajectory = cir.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); + assert_eq!(trajectory.times.len(), 1001); + } + + #[test] + pub fn extended_vasicek() { + fn theta_t(_t: f64) -> f64 { + 0.5 + } + fn alpha_t(_t: f64) -> f64 { + 2.0 + } + let sig = 2.0; + + let ev = ExtendedVasicek::new(alpha_t, sig, theta_t); + let trajectory = ev.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); + assert_eq!(trajectory.times.len(), 1001); + } + + #[test] + pub fn ho_lee() { + fn theta_t(_t: f64) -> f64 { + 2.0 + } + let hl = HoLee::new(1.6, theta_t); + let trajectory = hl.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); + assert_eq!(trajectory.times.len(), 1001); + } + + #[test] + pub fn hull_white() { + fn theta_t(_t: f64) -> f64 { + 0.5 + } + let alpha = 2.0; + let sig = 2.0; + + let hw = HullWhite::new(alpha, sig, theta_t); + let trajectory = hw.euler_maruyama(10.0, 0.0, 0.5, 1000, 1000, false); + assert_eq!(trajectory.times.len(), 1001); + } } diff --git a/core/src/middleware.rs b/core/src/middleware.rs index 0598de66..db64ada3 100644 --- a/core/src/middleware.rs +++ b/core/src/middleware.rs @@ -3,9 +3,7 @@ //! This module contains the middleware for the Revm simulation environment. //! Most of the middleware is essentially a placeholder, but it is necessary to have a middleware to work with bindings more efficiently. -use std::fmt::Debug; -use ethers::prelude::pending_transaction::PendingTxState; -use ethers::providers::{PendingTransaction, Provider, FilterKind}; +use std::{fmt::Debug, time::Duration}; use ethers::{ prelude::{ @@ -16,16 +14,15 @@ use ethers::{ pending_transaction::PendingTxState, ProviderError, }, - providers::{FilterWatcher, Middleware, MockProvider, PendingTransaction, Provider}, + providers::{ + FilterKind, FilterWatcher, Middleware, MockProvider, PendingTransaction, Provider, + }, signers::{Signer, Wallet}, types::{transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Filter, Log}, }; use rand::{rngs::StdRng, SeedableRng}; use revm::primitives::{CreateScheme, ExecutionResult, Output, TransactTo, TxEnv, B160, U256}; -use std::fmt::Debug; -use std::time::Duration; - use crate::{ environment::Connection, utils::{recast_address, revm_logs_to_ethers_logs}, @@ -199,11 +196,13 @@ impl Middleware for RevmMiddleware { todo!("we should be able to get logs.") } - // NOTES: It might be good to have individual channels for the EVM to send events to so that an agent can install a filter and the logs can be filtered by the EVM itself. // This could be handled similarly to how broadcasts are done now and maybe nothing there needs to change except for attaching a filter to the event channels. // It would be good to also pass to a separate thread to do broadcasting if we aren't already doing that so that the EVM can process while events are being sent out. - async fn new_filter(&self, filter: FilterKind<'_>) -> Result { + async fn new_filter( + &self, + filter: FilterKind<'_>, + ) -> Result { todo!() // let (method, args) = match filter { // FilterKind::NewBlocks => unimplemented!("We will need to implement this."), diff --git a/core/src/tests.rs b/core/src/tests.rs index e090fe85..918952a7 100644 --- a/core/src/tests.rs +++ b/core/src/tests.rs @@ -2,7 +2,10 @@ use std::str::FromStr; use anyhow::Result; -use ethers::{prelude::{FilterWatcher, Middleware, StreamExt}, types::{Address, Filter}}; +use ethers::{ + prelude::{FilterWatcher, Middleware, StreamExt}, + types::{Address, Filter}, +}; use crate::{ agent::{tests::*, *}, From 792932bfee1ed5cef9d081680c29bc13d3320c51 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 10:17:05 -0600 Subject: [PATCH 03/19] block number added to the result --- core/src/environment.rs | 21 ++++++++++++++++++--- core/src/middleware.rs | 14 +++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/core/src/environment.rs b/core/src/environment.rs index 08fcb4f2..a0305233 100644 --- a/core/src/environment.rs +++ b/core/src/environment.rs @@ -24,10 +24,17 @@ use crate::{ /// Type Aliases for the event channel. pub(crate) type ToTransact = bool; -pub(crate) type ExecutionSender = Sender; +pub(crate) type ExecutionSender = Sender; pub(crate) type TxEnvSender = Sender<(ToTransact, TxEnv, ExecutionSender)>; pub(crate) type TxEnvReceiver = Receiver<(ToTransact, TxEnv, ExecutionSender)>; +/// Result struct for the [`Environment`]. that wraps the [`ExecutionResult`] and the block number. +#[derive(Debug, Clone)] +pub struct RevmResult { + pub(crate) result: ExecutionResult, + pub(crate) block_number: U256, +} + /// State enum for the [`Environment`]. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum State { @@ -70,7 +77,7 @@ impl Environment { evm.database(db); evm.env.cfg.limit_contract_code_size = Some(0x100000); // This is a large contract size limit, beware! evm.env.block.gas_limit = U256::MAX; - let transaction_channel = unbounded::<(ToTransact, TxEnv, Sender)>(); + let transaction_channel = unbounded::<(ToTransact, TxEnv, Sender)>(); let connection = Connection { tx_sender: transaction_channel.0, tx_receiver: transaction_channel.1, @@ -126,6 +133,10 @@ impl Environment { .lock() .unwrap() .broadcast(execution_result.logs()); + let execution_result = RevmResult { + result: execution_result, + block_number: evm.env.block.number + }; sender.send(execution_result).unwrap(); } else { let execution_result = match evm.transact() { @@ -133,7 +144,11 @@ impl Environment { // URGENT: change this to a custom error Err(_) => panic!("failed"), }; - sender.send(execution_result.result).unwrap(); + let result_and_block = RevmResult { + result: execution_result.result, + block_number: evm.env.block.number + }; + sender.send(result_and_block).unwrap(); } } }); diff --git a/core/src/middleware.rs b/core/src/middleware.rs index db64ada3..e81ed19b 100644 --- a/core/src/middleware.rs +++ b/core/src/middleware.rs @@ -24,7 +24,7 @@ use rand::{rngs::StdRng, SeedableRng}; use revm::primitives::{CreateScheme, ExecutionResult, Output, TransactTo, TxEnv, B160, U256}; use crate::{ - environment::Connection, + environment::{Connection, RevmResult}, utils::{recast_address, revm_logs_to_ethers_logs}, }; @@ -34,8 +34,8 @@ pub struct RevmMiddleware { provider: Provider, connection: Connection, wallet: Wallet, - result_sender: crossbeam_channel::Sender, - result_receiver: crossbeam_channel::Receiver, + result_sender: crossbeam_channel::Sender, + result_receiver: crossbeam_channel::Receiver, event_receiver: crossbeam_channel::Receiver>, } @@ -117,8 +117,8 @@ impl Middleware for RevmMiddleware { .tx_sender .send((true, tx_env.clone(), self.result_sender.clone())) .unwrap(); - let result = self.result_receiver.recv().unwrap(); - let (output, revm_logs) = match result.clone() { + let revm_result = self.result_receiver.recv().unwrap(); + let (output, revm_logs) = match revm_result.result { ExecutionResult::Success { output, logs, .. } => (output, logs), ExecutionResult::Revert { output, .. } => panic!("Failed due to revert: {:?}", output), ExecutionResult::Halt { reason, .. } => panic!("Failed due to halt: {:?}", reason), @@ -176,8 +176,8 @@ impl Middleware for RevmMiddleware { .tx_sender .send((false, tx_env.clone(), self.result_sender.clone())) .unwrap(); - let result = self.result_receiver.recv().unwrap(); - let output = match result.clone() { + let revm_result = self.result_receiver.recv().unwrap(); + let output = match revm_result.result.clone() { ExecutionResult::Success { output, .. } => output, ExecutionResult::Revert { output, .. } => panic!("Failed due to revert: {:?}", output), ExecutionResult::Halt { reason, .. } => panic!("Failed due to halt: {:?}", reason), From 5e2a1eb0245d14d8f30531c5c77c189fbe0f0afb Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 11:18:42 -0600 Subject: [PATCH 04/19] reciepts --- bin/main.rs | 2 +- core/Cargo.toml | 3 ++- core/src/environment.rs | 21 ++++++++++++--------- core/src/lib.rs | 1 + core/src/manager.rs | 1 + core/src/middleware.rs | 15 +++++++++------ core/src/utils.rs | 37 +++++++++++++++++++++++++++++++++++-- 7 files changed, 61 insertions(+), 19 deletions(-) diff --git a/bin/main.rs b/bin/main.rs index a8f8b075..8a827fa2 100644 --- a/bin/main.rs +++ b/bin/main.rs @@ -4,7 +4,7 @@ use std::error::Error; -use clap::{arg, command, CommandFactory, Parser, Subcommand}; +use clap::{command, CommandFactory, Parser, Subcommand}; use eyre::Result; use thiserror::Error; diff --git a/core/Cargo.toml b/core/Cargo.toml index bdb72da7..3f81e4b5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -32,7 +32,8 @@ thiserror = "1.0.30" # url = "2.2.2" # Workspace dependencies -ethers = { git = "https://github.com/Autoparallel/ethers-rs.git"} +# TODO: Move to main after PR is merged +ethers = { git = "https://github.com/primitivefinance/ethers-rs.git", branch = "add_block_number" } # ethers-middleware = { version = "2.0.4" } serde = { version = "1.0.163", features= ["derive"]} serde_json = { version = "1.0.96" } diff --git a/core/src/environment.rs b/core/src/environment.rs index a0305233..4bc6a1e1 100644 --- a/core/src/environment.rs +++ b/core/src/environment.rs @@ -11,6 +11,7 @@ use std::{ }; use crossbeam_channel::{unbounded, Receiver, Sender}; +use ethers::core::types::U64; use revm::{ db::{CacheDB, EmptyDB}, primitives::{ExecutionResult, Log, TxEnv, U256}, @@ -18,8 +19,10 @@ use revm::{ }; use crate::{ - agent::Agent, math::stochastic_process::poisson_process, middleware::RevmMiddleware, - utils::revm_logs_to_ethers_logs, + agent::Agent, + math::stochastic_process::poisson_process, + middleware::RevmMiddleware, + utils::{convert_uint_to_u64, revm_logs_to_ethers_logs}, }; /// Type Aliases for the event channel. @@ -32,7 +35,7 @@ pub(crate) type TxEnvReceiver = Receiver<(ToTransact, TxEnv, ExecutionSender)>; #[derive(Debug, Clone)] pub struct RevmResult { pub(crate) result: ExecutionResult, - pub(crate) block_number: U256, + pub(crate) block_number: U64, } /// State enum for the [`Environment`]. @@ -133,9 +136,9 @@ impl Environment { .lock() .unwrap() .broadcast(execution_result.logs()); - let execution_result = RevmResult { - result: execution_result, - block_number: evm.env.block.number + let execution_result = RevmResult { + result: execution_result, + block_number: convert_uint_to_u64(evm.env.block.number).unwrap(), }; sender.send(execution_result).unwrap(); } else { @@ -144,9 +147,9 @@ impl Environment { // URGENT: change this to a custom error Err(_) => panic!("failed"), }; - let result_and_block = RevmResult { - result: execution_result.result, - block_number: evm.env.block.number + let result_and_block = RevmResult { + result: execution_result.result, + block_number: convert_uint_to_u64(evm.env.block.number).unwrap(), }; sender.send(result_and_block).unwrap(); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 49d89ed3..c4dd358a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -4,6 +4,7 @@ pub mod agent; pub mod bindings; +/// Module for managing the environment. pub mod environment; pub mod manager; pub mod math; diff --git a/core/src/manager.rs b/core/src/manager.rs index 82373e0d..38434cf5 100644 --- a/core/src/manager.rs +++ b/core/src/manager.rs @@ -10,6 +10,7 @@ use anyhow::{anyhow, Result}; use crate::environment::{Environment, State}; /// Manages simulations. +#[derive(Default)] pub struct SimulationManager { /// The list of [`SimulationEnvironment`] that the simulation manager controls. pub environments: HashMap, diff --git a/core/src/middleware.rs b/core/src/middleware.rs index e81ed19b..a08b6a7e 100644 --- a/core/src/middleware.rs +++ b/core/src/middleware.rs @@ -29,6 +29,8 @@ use crate::{ }; // TODO: Refactor the connection and channels slightly to be more intuitive. For instance, the middleware may not really need to own a connection, but input one to set up everything else? +/// The Revm middleware struct. +/// This struct is modular with ther ethers.rs middleware, and is used to connect the Revm environment in memory rather than over the network. #[derive(Debug)] pub struct RevmMiddleware { provider: Provider, @@ -40,6 +42,7 @@ pub struct RevmMiddleware { } impl RevmMiddleware { + /// Creates a new Revm middleware struct. pub fn new(connection: Connection, name: String) -> Self { let provider = Provider::new(MockProvider::new()); let mut hasher = Sha256::new(); @@ -90,7 +93,7 @@ impl Middleware for RevmMiddleware { async fn send_transaction + Send + Sync>( &self, tx: T, - block: Option, + _block: Option, ) -> Result, Self::Error> { let mut tx: TypedTransaction = tx.into(); tx.set_from(self.wallet.address()); @@ -118,8 +121,10 @@ impl Middleware for RevmMiddleware { .send((true, tx_env.clone(), self.result_sender.clone())) .unwrap(); let revm_result = self.result_receiver.recv().unwrap(); - let (output, revm_logs) = match revm_result.result { - ExecutionResult::Success { output, logs, .. } => (output, logs), + let (output, revm_logs, block) = match revm_result.result { + ExecutionResult::Success { output, logs, .. } => { + (output, logs, revm_result.block_number) + } ExecutionResult::Revert { output, .. } => panic!("Failed due to revert: {:?}", output), ExecutionResult::Halt { reason, .. } => panic!("Failed due to halt: {:?}", reason), }; @@ -136,12 +141,10 @@ impl Middleware for RevmMiddleware { PendingTransaction::new(ethers::types::H256::zero(), self.provider()); let logs = revm_logs_to_ethers_logs(revm_logs); - pending_tx.state = PendingTxState::RevmTransactOutput(logs); + pending_tx.state = PendingTxState::RevmTransactOutput(logs, block); return Ok(pending_tx); } } - - // TODO: RECEIPTS OF TRANSACTIONS SHOULD STORE THE BLOCKNUMBERS THEY OCCURED IN } /// Makes a call to revm that will not commit a state change to the DB. Can be used for a mock transaction diff --git a/core/src/utils.rs b/core/src/utils.rs index 4fa1a38e..df20b010 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -6,8 +6,11 @@ use std::{ }; use bytes::Bytes; -use ethers::{prelude::Address, types::H256}; -use revm::primitives::{ExecutionResult, Output, B160, B256}; +use ethers::{ + prelude::Address, + types::{H256, U64}, +}; +use revm::primitives::{ExecutionResult, Output, B160, B256, U256}; #[derive(Debug)] // We should use anyhow / thisError instead @@ -31,6 +34,11 @@ impl Display for UnpackError { } } +/// Recast a logs from Revm into the ethers.rs Log type. +/// # Arguments +/// * `revm_logs` - Logs from Revm. (Vec) +/// # Returns +/// * `Vec` - Logs recasted into ethers.rs Log type. pub fn revm_logs_to_ethers_logs( revm_logs: Vec, ) -> Vec { @@ -66,6 +74,11 @@ pub fn recast_address(address: B160) -> Address { Address::from(temp) } +/// Recast a B256 into an H256 type +/// # Arguments +/// * `input` - B256 to recast. (B256) +/// # Returns +/// * `H256` - Recasted H256. pub fn recast_b256(input: B256) -> H256 { let temp: [u8; 32] = input.as_bytes().try_into().unwrap(); H256::from(temp) @@ -98,3 +111,23 @@ pub fn unpack_execution(execution_result: ExecutionResult) -> Result Result { + // Convert to base 2^64 digits + let digits: Vec<_> = input.to_base_le(2u64.pow(64)).collect(); + + // If there are no digits, the value was 0 + if digits.is_empty() { + return Ok(U64::from(0)); + } + + // Otherwise, return the least significant 64 bits + // If the number was larger than 2^64, this will silently discard the higher bits + Ok(U64::from(digits[0])) +} From b18658ae7f0e98f961919d4df153d517707690af Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 11:52:02 -0600 Subject: [PATCH 05/19] unsused argument --- core/src/agent.rs | 4 ++-- core/src/environment.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/agent.rs b/core/src/agent.rs index fc6443d0..c081ef83 100644 --- a/core/src/agent.rs +++ b/core/src/agent.rs @@ -23,10 +23,10 @@ impl std::fmt::Debug for Agent { } impl Agent { - pub(crate) fn new_simulation_agent(name: String, connection: Connection) -> Self { + pub(crate) fn new_simulation_agent(name: String, connection: &Connection) -> Self { Self { name: name.clone(), - client: Arc::new(RevmMiddleware::new(connection, name)), + client: Arc::new(RevmMiddleware::new(connection.clone(), name)), behaviors: vec![], } } diff --git a/core/src/environment.rs b/core/src/environment.rs index 4bc6a1e1..b701a43f 100644 --- a/core/src/environment.rs +++ b/core/src/environment.rs @@ -74,7 +74,7 @@ pub struct Connection { } impl Environment { - pub(crate) fn new(label: String) -> Self { + pub fn new(label: String) -> Self { let mut evm = EVM::new(); let db = CacheDB::new(EmptyDB {}); evm.database(db); @@ -102,8 +102,8 @@ impl Environment { } /// Creates a new [`Agent Date: Mon, 31 Jul 2023 12:19:03 -0600 Subject: [PATCH 06/19] Working with agents --- core/Cargo.toml | 4 ++-- core/src/environment.rs | 9 +++++---- core/src/middleware.rs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 3f81e4b5..27381832 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" # Dependency configuration [dependencies] # For execution +dashmap = "5.5.0" bytes = "1.4.0" revm = "3.3.0" statrs = "0.16.0" @@ -32,8 +33,7 @@ thiserror = "1.0.30" # url = "2.2.2" # Workspace dependencies -# TODO: Move to main after PR is merged -ethers = { git = "https://github.com/primitivefinance/ethers-rs.git", branch = "add_block_number" } +ethers = { git = "https://github.com/primitivefinance/ethers-rs.git"} # ethers-middleware = { version = "2.0.4" } serde = { version = "1.0.163", features= ["derive"]} serde_json = { version = "1.0.96" } diff --git a/core/src/environment.rs b/core/src/environment.rs index b701a43f..ac474cf9 100644 --- a/core/src/environment.rs +++ b/core/src/environment.rs @@ -8,6 +8,7 @@ use std::{ Arc, Mutex, }, thread, + collections::HashMap, }; use crossbeam_channel::{unbounded, Receiver, Sender}; @@ -58,7 +59,7 @@ pub struct Environment { /// Connection to the environment pub connection: Connection, /// Clients (Agents) in the environment - pub clients: Vec>>, + pub clients: HashMap>, /// expected events per block pub lambda: f64, } @@ -92,7 +93,7 @@ impl Environment { state: State::Stopped, evm, connection, - clients: vec![], + clients: HashMap::new(), lambda: 0.0, } } @@ -103,8 +104,8 @@ impl Environment { /// Creates a new [`Agent, connection: Connection, From 774a9c0271a7e7a481c57883a8a589ad4f466255 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 12:28:23 -0600 Subject: [PATCH 07/19] Update environment.rs --- core/src/environment.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/environment.rs b/core/src/environment.rs index ac474cf9..1429e18e 100644 --- a/core/src/environment.rs +++ b/core/src/environment.rs @@ -75,6 +75,7 @@ pub struct Connection { } impl Environment { + /// Create a new [`Environment`]. pub fn new(label: String) -> Self { let mut evm = EVM::new(); let db = CacheDB::new(EmptyDB {}); From dc3a4b074e4fdd009ed70fb99447dac45c558e65 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 12:37:47 -0600 Subject: [PATCH 08/19] rename --- Cargo.toml | 4 ++-- {core => arbiter-core}/Cargo.toml | 2 +- {core => arbiter-core}/LICENSE | 0 {core => arbiter-core}/README.md | 0 {core => arbiter-core}/src/agent.rs | 0 {core => arbiter-core}/src/bindings/arbiter_math.rs | 0 {core => arbiter-core}/src/bindings/arbiter_token.rs | 0 {core => arbiter-core}/src/bindings/liquid_exchange.rs | 0 {core => arbiter-core}/src/bindings/mod.rs | 0 {core => arbiter-core}/src/environment.rs | 0 {core => arbiter-core}/src/lib.rs | 0 {core => arbiter-core}/src/manager.rs | 0 {core => arbiter-core}/src/math/mod.rs | 0 {core => arbiter-core}/src/math/stochastic_process.rs | 0 {core => arbiter-core}/src/middleware.rs | 0 {core => arbiter-core}/src/tests.rs | 0 {core => arbiter-core}/src/utils.rs | 0 17 files changed, 3 insertions(+), 3 deletions(-) rename {core => arbiter-core}/Cargo.toml (97%) rename {core => arbiter-core}/LICENSE (100%) rename {core => arbiter-core}/README.md (100%) rename {core => arbiter-core}/src/agent.rs (100%) rename {core => arbiter-core}/src/bindings/arbiter_math.rs (100%) rename {core => arbiter-core}/src/bindings/arbiter_token.rs (100%) rename {core => arbiter-core}/src/bindings/liquid_exchange.rs (100%) rename {core => arbiter-core}/src/bindings/mod.rs (100%) rename {core => arbiter-core}/src/environment.rs (100%) rename {core => arbiter-core}/src/lib.rs (100%) rename {core => arbiter-core}/src/manager.rs (100%) rename {core => arbiter-core}/src/math/mod.rs (100%) rename {core => arbiter-core}/src/math/stochastic_process.rs (100%) rename {core => arbiter-core}/src/middleware.rs (100%) rename {core => arbiter-core}/src/tests.rs (100%) rename {core => arbiter-core}/src/utils.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 83c6884d..0182bdb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ [workspace] # List of crates included in this workspace members = [ - "core", + "arbiter-core", ] # Package configuration @@ -21,7 +21,7 @@ path = "bin/main.rs" # Dependency configuration [dependencies] # Local dependencies -core = { path = "core" } +arbiter-core = { path = "arbiter-core" } # External dependencies clap = { version = "4.3.0", features = ["derive"] } diff --git a/core/Cargo.toml b/arbiter-core/Cargo.toml similarity index 97% rename from core/Cargo.toml rename to arbiter-core/Cargo.toml index 27381832..41b2a7fa 100644 --- a/core/Cargo.toml +++ b/arbiter-core/Cargo.toml @@ -1,6 +1,6 @@ # Package configuration [package] -name = "core" +name = "arbiter-core" version = "0.3.0" edition = "2021" diff --git a/core/LICENSE b/arbiter-core/LICENSE similarity index 100% rename from core/LICENSE rename to arbiter-core/LICENSE diff --git a/core/README.md b/arbiter-core/README.md similarity index 100% rename from core/README.md rename to arbiter-core/README.md diff --git a/core/src/agent.rs b/arbiter-core/src/agent.rs similarity index 100% rename from core/src/agent.rs rename to arbiter-core/src/agent.rs diff --git a/core/src/bindings/arbiter_math.rs b/arbiter-core/src/bindings/arbiter_math.rs similarity index 100% rename from core/src/bindings/arbiter_math.rs rename to arbiter-core/src/bindings/arbiter_math.rs diff --git a/core/src/bindings/arbiter_token.rs b/arbiter-core/src/bindings/arbiter_token.rs similarity index 100% rename from core/src/bindings/arbiter_token.rs rename to arbiter-core/src/bindings/arbiter_token.rs diff --git a/core/src/bindings/liquid_exchange.rs b/arbiter-core/src/bindings/liquid_exchange.rs similarity index 100% rename from core/src/bindings/liquid_exchange.rs rename to arbiter-core/src/bindings/liquid_exchange.rs diff --git a/core/src/bindings/mod.rs b/arbiter-core/src/bindings/mod.rs similarity index 100% rename from core/src/bindings/mod.rs rename to arbiter-core/src/bindings/mod.rs diff --git a/core/src/environment.rs b/arbiter-core/src/environment.rs similarity index 100% rename from core/src/environment.rs rename to arbiter-core/src/environment.rs diff --git a/core/src/lib.rs b/arbiter-core/src/lib.rs similarity index 100% rename from core/src/lib.rs rename to arbiter-core/src/lib.rs diff --git a/core/src/manager.rs b/arbiter-core/src/manager.rs similarity index 100% rename from core/src/manager.rs rename to arbiter-core/src/manager.rs diff --git a/core/src/math/mod.rs b/arbiter-core/src/math/mod.rs similarity index 100% rename from core/src/math/mod.rs rename to arbiter-core/src/math/mod.rs diff --git a/core/src/math/stochastic_process.rs b/arbiter-core/src/math/stochastic_process.rs similarity index 100% rename from core/src/math/stochastic_process.rs rename to arbiter-core/src/math/stochastic_process.rs diff --git a/core/src/middleware.rs b/arbiter-core/src/middleware.rs similarity index 100% rename from core/src/middleware.rs rename to arbiter-core/src/middleware.rs diff --git a/core/src/tests.rs b/arbiter-core/src/tests.rs similarity index 100% rename from core/src/tests.rs rename to arbiter-core/src/tests.rs diff --git a/core/src/utils.rs b/arbiter-core/src/utils.rs similarity index 100% rename from core/src/utils.rs rename to arbiter-core/src/utils.rs From 73c91205334c1e1037ba156d4185cb2f1fd1c384 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 14:59:11 -0600 Subject: [PATCH 09/19] Public interface through manager --- arbiter-core/src/environment.rs | 6 +++--- arbiter-core/src/manager.rs | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/arbiter-core/src/environment.rs b/arbiter-core/src/environment.rs index 1429e18e..cf3a7f85 100644 --- a/arbiter-core/src/environment.rs +++ b/arbiter-core/src/environment.rs @@ -76,7 +76,7 @@ pub struct Connection { impl Environment { /// Create a new [`Environment`]. - pub fn new(label: String) -> Self { + pub(crate) fn new(label: String) -> Self { let mut evm = EVM::new(); let db = CacheDB::new(EmptyDB {}); evm.database(db); @@ -99,12 +99,12 @@ impl Environment { } } /// Set the expected events per block - pub fn configure_lambda(&mut self, lambda: f64) { + pub(crate) fn configure_lambda(&mut self, lambda: f64) { self.lambda = lambda; } /// Creates a new [`Agent Result<()> { + match self.environments.get_mut(&environment_label) { + Some(environment) => { + environment.configure_lambda(lambda); + Ok(()) + } + None => Err(anyhow!("Environment does not exist.")), + } + } + + /// adds an agent to an environment + pub fn add_agent(&mut self, agent: Agent, environment_label: String) -> Result<()> { + match self.environments.get_mut(&environment_label) { + Some(environment) => { + environment.add_agent(agent); + Ok(()) + } + None => Err(anyhow!("Environment does not exist.")), + } + } + /// Runs an environment that is in the [`SimulationManager`]'s list. pub fn run_environment(&mut self, environment_label: String) -> Result<()> { match self.environments.get_mut(&environment_label) { From aa1a1aec7bd363305a50aef60b8c5ea059b78ee7 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 15:01:32 -0600 Subject: [PATCH 10/19] Update manager.rs --- arbiter-core/src/manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbiter-core/src/manager.rs b/arbiter-core/src/manager.rs index 28be9eb1..fba21834 100644 --- a/arbiter-core/src/manager.rs +++ b/arbiter-core/src/manager.rs @@ -37,7 +37,7 @@ impl SimulationManager { } /// Configure the lambda for an environment. - pub fn configure_lambda(&mut self, lambda: f64, label: String) -> Result<()> { + pub fn configure_lambda(&mut self, lambda: f64, environment_label: String) -> Result<()> { match self.environments.get_mut(&environment_label) { Some(environment) => { environment.configure_lambda(lambda); From 12b8deed9bede9954f0ec31b9767d7055de0effc Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 31 Jul 2023 15:02:36 -0600 Subject: [PATCH 11/19] Update manager.rs --- arbiter-core/src/manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arbiter-core/src/manager.rs b/arbiter-core/src/manager.rs index fba21834..acbbaae3 100644 --- a/arbiter-core/src/manager.rs +++ b/arbiter-core/src/manager.rs @@ -48,10 +48,10 @@ impl SimulationManager { } /// adds an agent to an environment - pub fn add_agent(&mut self, agent: Agent, environment_label: String) -> Result<()> { + pub fn add_agent(&mut self, agent_name: String, environment_label: String) -> Result<()> { match self.environments.get_mut(&environment_label) { Some(environment) => { - environment.add_agent(agent); + environment.add_agent(agent_name); Ok(()) } None => Err(anyhow!("Environment does not exist.")), From 1ab19dd02b1aa68d5f00cc8e58b786cfaf585af6 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Tue, 1 Aug 2023 11:09:12 -0600 Subject: [PATCH 12/19] some fixes --- arbiter-core/src/environment.rs | 15 ++++++++------- arbiter-core/src/lib.rs | 1 - arbiter-core/src/math/stochastic_process.rs | 9 ++++----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/arbiter-core/src/environment.rs b/arbiter-core/src/environment.rs index cf3a7f85..f3642854 100644 --- a/arbiter-core/src/environment.rs +++ b/arbiter-core/src/environment.rs @@ -61,7 +61,7 @@ pub struct Environment { /// Clients (Agents) in the environment pub clients: HashMap>, /// expected events per block - pub lambda: f64, + pub lambda: Some(f64), } /// Connection struct for the [`Environment`]. @@ -95,12 +95,13 @@ impl Environment { evm, connection, clients: HashMap::new(), - lambda: 0.0, + // Default is none + lambda: None, } } - /// Set the expected events per block - pub(crate) fn configure_lambda(&mut self, lambda: f64) { - self.lambda = lambda; + + pub(crate) fn configure_lambda(&mut self, lamda: f64) { + self.lambda = Some(lamda); } /// Creates a new [`Agent val, @@ -143,6 +143,7 @@ impl Environment { block_number: convert_uint_to_u64(evm.env.block.number).unwrap(), }; sender.send(execution_result).unwrap(); + counter.fetch_add(1, Ordering::Relaxed); } else { let execution_result = match evm.transact() { Ok(val) => val, diff --git a/arbiter-core/src/lib.rs b/arbiter-core/src/lib.rs index c4dd358a..49d89ed3 100644 --- a/arbiter-core/src/lib.rs +++ b/arbiter-core/src/lib.rs @@ -4,7 +4,6 @@ pub mod agent; pub mod bindings; -/// Module for managing the environment. pub mod environment; pub mod manager; pub mod math; diff --git a/arbiter-core/src/math/stochastic_process.rs b/arbiter-core/src/math/stochastic_process.rs index 2043c422..17254c28 100644 --- a/arbiter-core/src/math/stochastic_process.rs +++ b/arbiter-core/src/math/stochastic_process.rs @@ -109,11 +109,11 @@ pub fn new_procces( } /// Sample Poisson process. -pub fn poisson_process(lambda: f64, length: usize) -> Result> { +pub fn poisson_process(lambda: f64) -> Result> { let poisson = Poisson::new(lambda); - let float_samples = poisson.sample(length); + let float_samples = poisson.sample(1); let int_samples: Vec = float_samples.iter().map(|&x| x.round() as i32).collect(); - Ok(int_samples) + Ok(int_sample) } #[cfg(test)] @@ -166,8 +166,7 @@ mod tests { #[test] fn poisson_process_test() { let lambda = 1.0; - let length = 100; - let result = poisson_process(lambda, length); + let result = poisson_process(lambda); assert!(result.is_ok()); let samples = result.unwrap(); From 55d49bb52bddd066ca2dfc5a216c99108a61458d Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 2 Aug 2023 08:29:42 -0700 Subject: [PATCH 13/19] resolving conflicts --- arbiter-core/src/agent.rs | 3 +- arbiter-core/src/environment.rs | 95 ++++++++------------- arbiter-core/src/manager.rs | 6 +- arbiter-core/src/math/stochastic_process.rs | 6 +- 4 files changed, 43 insertions(+), 67 deletions(-) diff --git a/arbiter-core/src/agent.rs b/arbiter-core/src/agent.rs index 9181fae7..d238b3f5 100644 --- a/arbiter-core/src/agent.rs +++ b/arbiter-core/src/agent.rs @@ -54,12 +54,11 @@ impl Agent { pub fn attach_to_environment(self, environment: &mut crate::environment::Environment) { let middleware = RevmMiddleware::new(&self, environment); - let agent = Agent::> { + let agent_attached = Agent::> { name: self.name, client: Arc::new(middleware), behaviors: self.behaviors, }; - environment.add_agent(agent); } } diff --git a/arbiter-core/src/environment.rs b/arbiter-core/src/environment.rs index 210454f0..45a1d8c7 100644 --- a/arbiter-core/src/environment.rs +++ b/arbiter-core/src/environment.rs @@ -8,7 +8,6 @@ use std::{ Arc, Mutex, }, thread, - collections::HashMap, }; use crossbeam_channel::{unbounded, Receiver, Sender}; @@ -21,20 +20,12 @@ use revm::{ }; use crate::{ - agent::Agent, + agent::{Agent, IsAttached, NotAttached}, math::stochastic_process::poisson_process, middleware::RevmMiddleware, - utils::{convert_uint_to_u64, revm_logs_to_ethers_logs}, + utils::{convert_uint_to_u64, revm_logs_to_ethers_logs, }, }; use serde::{de::DeserializeOwned, Serialize}; -use std::{ - fmt::Debug, - sync::{Arc, Mutex}, - thread, -}; - -use crate::{utils::revm_logs_to_ethers_logs, agent::IsAttached}; -use crate::{agent::Agent, middleware::RevmMiddleware}; /// Type Aliases for the event channel. pub(crate) type ToTransact = bool; @@ -66,40 +57,23 @@ pub struct Environment { pub label: String, pub(crate) state: State, pub(crate) evm: EVM>, - /// Connection to the environment - pub connection: Connection, - /// Clients (Agents) in the environment - pub clients: HashMap>, - /// expected events per block - pub lambda: Some(f64), -} - -/// Connection struct for the [`Environment`]. -#[derive(Debug, Clone)] -pub struct Connection { - pub(crate) tx_sender: TxEnvSender, - tx_receiver: TxEnvReceiver, + pub(crate) tx_sender: Sender<(bool, TxEnv, Sender)>, + pub tx_receiver: Receiver<(bool, TxEnv, Sender)>, pub(crate) event_broadcaster: Arc>, + /// Clients (Agents) in the environment + pub agents: Vec>>, /// expected events per block + pub lambda: Option, pub tx_per_block: Arc, -} -impl Environment { - /// Create a new [`Environment`]. - pub(crate) fn new(label: String) -> Self { - pub(crate) tx_sender: TxEnvSender, - tx_receiver: TxEnvReceiver, - pub(crate) event_broadcaster: Arc>, - /// Clients (Agents) in the environment - pub agents: Vec>>, - // pub deployed_contracts: HashMap>, } + // TODO: If the provider holds the connection then this can work better. pub struct RevmProvider { pub(crate) tx_sender: TxEnvSender, - pub(crate) result_sender: crossbeam_channel::Sender, - pub(crate) result_receiver: crossbeam_channel::Receiver, + pub(crate) result_sender: crossbeam_channel::Sender, + pub(crate) result_receiver: crossbeam_channel::Receiver, pub(crate) event_receiver: crossbeam_channel::Receiver>, } @@ -134,7 +108,9 @@ impl JsonRpcClient for RevmProvider { } } + impl Environment { + /// Creates a new [`Environment`] with the given label. pub(crate) fn new>(label: S) -> Self { let mut evm = EVM::new(); let db = CacheDB::new(EmptyDB {}); @@ -142,20 +118,16 @@ impl Environment { evm.env.cfg.limit_contract_code_size = Some(0x100000); // This is a large contract size limit, beware! evm.env.block.gas_limit = U256::MAX; let (tx_sender, tx_receiver) = unbounded::<(ToTransact, TxEnv, Sender)>(); - let connection = Connection { - tx_sender: tx_sender, - tx_receiver: tx_receiver, - event_broadcaster: Arc::new(Mutex::new(EventBroadcaster::new())), - tx_per_block: Arc::new(AtomicUsize::new(0)), - }; Self { label: label.into(), state: State::Stopped, evm, - connection, - clients: HashMap::new(), - // Default is none + tx_sender, + tx_receiver, + event_broadcaster: Arc::new(Mutex::new(EventBroadcaster::new())), + agents: vec![], lambda: None, + tx_per_block: Arc::new(AtomicUsize::new(0)), } } @@ -164,19 +136,18 @@ impl Environment { } /// Creates a new [`Agent>) { + // Waylon: I like them being created without a connection and then added to the environment where they will gain a connection. + pub fn add_agent(&mut self, agent: Agent) { + let client = RevmProvider::new( + self.tx_sender.clone(), + self.event_broadcaster.clone(), + self.tx_per_block.clone(), + ); + let attached = agent.attach_to_client(client); + agent.attach_to_client(self.tx_sender.clone()); self.agents.push(agent); } @@ -184,10 +155,13 @@ impl Environment { pub(crate) fn run(&mut self) { let tx_receiver = self.tx_receiver.clone(); let mut evm = self.evm.clone(); - let event_broadcaster = self.connection.event_broadcaster.clone(); - let counter = Arc::clone(&self.connection.tx_per_block); - let expected_occurance = poisson_process(self.lambda).unwrap(); + let event_broadcaster = self.event_broadcaster.clone(); + let counter = Arc::clone(&self.tx_per_block); self.state = State::Running; + let mut expected_occurance: Vec; + if let Some(lambda) = self.lambda { + let expected_occurance = poisson_process(lambda).unwrap(); + } //give all agents their own thread and let them start watching for their evnts thread::spawn(move || { @@ -213,7 +187,10 @@ impl Environment { block_number: convert_uint_to_u64(evm.env.block.number).unwrap(), }; sender.send(execution_result).unwrap(); - counter.fetch_add(1, Ordering::Relaxed); + if let Some(lambda) = self.lambda { + counter.fetch_add(1, Ordering::Relaxed); + } + } else { let execution_result = match evm.transact() { Ok(val) => val, diff --git a/arbiter-core/src/manager.rs b/arbiter-core/src/manager.rs index acbbaae3..64e6f84f 100644 --- a/arbiter-core/src/manager.rs +++ b/arbiter-core/src/manager.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use anyhow::{anyhow, Result}; -use crate::environment::{Environment, State}; +use crate::{environment::{Environment, State}, agent::{Agent, NotAttached}}; /// Manages simulations. #[derive(Default)] @@ -48,10 +48,10 @@ impl SimulationManager { } /// adds an agent to an environment - pub fn add_agent(&mut self, agent_name: String, environment_label: String) -> Result<()> { + pub fn add_agent(&mut self, agent: Agent, environment_label: String) -> Result<()> { match self.environments.get_mut(&environment_label) { Some(environment) => { - environment.add_agent(agent_name); + environment.add_agent(agent); Ok(()) } None => Err(anyhow!("Environment does not exist.")), diff --git a/arbiter-core/src/math/stochastic_process.rs b/arbiter-core/src/math/stochastic_process.rs index 17254c28..6c31f245 100644 --- a/arbiter-core/src/math/stochastic_process.rs +++ b/arbiter-core/src/math/stochastic_process.rs @@ -112,7 +112,7 @@ pub fn new_procces( pub fn poisson_process(lambda: f64) -> Result> { let poisson = Poisson::new(lambda); let float_samples = poisson.sample(1); - let int_samples: Vec = float_samples.iter().map(|&x| x.round() as i32).collect(); + let int_sample: Vec = float_samples.iter().map(|&x| x.round() as i32).collect(); Ok(int_sample) } @@ -170,10 +170,10 @@ mod tests { assert!(result.is_ok()); let samples = result.unwrap(); - assert_eq!(samples.len(), length); + assert_eq!(samples.len(), 1); // Because Poisson distribution is a random process, // we cannot predict exact values, but we can check if mean is close to lambda. - let mean: f64 = samples.iter().map(|&x| x as f64).sum::() / length as f64; + let mean: f64 = samples.iter().map(|&x| x as f64).sum::() / 1 as f64; assert!((mean - lambda).abs() < 0.2 * lambda); // tolerance of 20% } From 4a1059be96a26894436feb88d2a4763c2bbcd9c0 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 2 Aug 2023 09:15:17 -0700 Subject: [PATCH 14/19] changes working --- arbiter-core/src/agent.rs | 7 ++-- arbiter-core/src/environment.rs | 37 ++++++++++----------- arbiter-core/src/math/stochastic_process.rs | 2 +- arbiter-core/src/middleware.rs | 34 ++++--------------- arbiter-core/src/tests.rs | 4 ++- 5 files changed, 29 insertions(+), 55 deletions(-) diff --git a/arbiter-core/src/agent.rs b/arbiter-core/src/agent.rs index d238b3f5..994b1701 100644 --- a/arbiter-core/src/agent.rs +++ b/arbiter-core/src/agent.rs @@ -54,11 +54,8 @@ impl Agent { pub fn attach_to_environment(self, environment: &mut crate::environment::Environment) { let middleware = RevmMiddleware::new(&self, environment); - let agent_attached = Agent::> { - name: self.name, - client: Arc::new(middleware), - behaviors: self.behaviors, - }; + let agent_attached = self.attach_to_client(middleware.into()); + environment.agents.push(agent_attached); } } diff --git a/arbiter-core/src/environment.rs b/arbiter-core/src/environment.rs index 45a1d8c7..f618d2d8 100644 --- a/arbiter-core/src/environment.rs +++ b/arbiter-core/src/environment.rs @@ -57,8 +57,8 @@ pub struct Environment { pub label: String, pub(crate) state: State, pub(crate) evm: EVM>, - pub(crate) tx_sender: Sender<(bool, TxEnv, Sender)>, - pub tx_receiver: Receiver<(bool, TxEnv, Sender)>, + pub(crate) tx_sender: TxEnvSender, + pub tx_receiver: TxEnvReceiver, pub(crate) event_broadcaster: Arc>, /// Clients (Agents) in the environment pub agents: Vec>>, @@ -70,6 +70,7 @@ pub struct Environment { // TODO: If the provider holds the connection then this can work better. +#[derive(Clone)] pub struct RevmProvider { pub(crate) tx_sender: TxEnvSender, pub(crate) result_sender: crossbeam_channel::Sender, @@ -90,7 +91,7 @@ impl JsonRpcClient for RevmProvider { async fn request( &self, method: &str, - params: T, + _params: T, ) -> Result { match method { "eth_getFilterChanges" => { @@ -141,14 +142,7 @@ impl Environment { // OR agents can be created (without a connection?) and then added to the environment where they will gain a connection? // Waylon: I like them being created without a connection and then added to the environment where they will gain a connection. pub fn add_agent(&mut self, agent: Agent) { - let client = RevmProvider::new( - self.tx_sender.clone(), - self.event_broadcaster.clone(), - self.tx_per_block.clone(), - ); - let attached = agent.attach_to_client(client); - agent.attach_to_client(self.tx_sender.clone()); - self.agents.push(agent); + agent.attach_to_environment(self); } // TODO: Run should now run the agents as well as the evm. @@ -158,19 +152,21 @@ impl Environment { let event_broadcaster = self.event_broadcaster.clone(); let counter = Arc::clone(&self.tx_per_block); self.state = State::Running; - let mut expected_occurance: Vec; + let mut expected_occurance: Option> = None; if let Some(lambda) = self.lambda { - let expected_occurance = poisson_process(lambda).unwrap(); + expected_occurance = Some(poisson_process(lambda).unwrap()); } - //give all agents their own thread and let them start watching for their evnts thread::spawn(move || { while let Ok((to_transact, tx, sender)) = tx_receiver.recv() { // Execute the transaction, echo the logs to all agents, and report the execution result to the agent who made the transaction. - if counter.load(Ordering::Relaxed) >= expected_occurance[0] as usize { - evm.env.block.number += U256::from(1); - counter.store(0, Ordering::Relaxed); + if let Some(occurance) = &expected_occurance { + if counter.load(Ordering::Relaxed) >= occurance[0] as usize { + evm.env.block.number += U256::from(1); + counter.store(0, Ordering::Relaxed); + } } + evm.env.tx = tx; if to_transact { let execution_result = match evm.transact_commit() { @@ -178,16 +174,17 @@ impl Environment { // URGENT: change this to a custom error Err(_) => panic!("failed"), }; + event_broadcaster .lock() .unwrap() .broadcast(execution_result.logs()); - let execution_result = RevmResult { + let revm_result = RevmResult { result: execution_result, block_number: convert_uint_to_u64(evm.env.block.number).unwrap(), }; - sender.send(execution_result).unwrap(); - if let Some(lambda) = self.lambda { + sender.send(revm_result).unwrap(); + if let Some(_occurance) = &expected_occurance { counter.fetch_add(1, Ordering::Relaxed); } diff --git a/arbiter-core/src/math/stochastic_process.rs b/arbiter-core/src/math/stochastic_process.rs index 6c31f245..5c1ee6d5 100644 --- a/arbiter-core/src/math/stochastic_process.rs +++ b/arbiter-core/src/math/stochastic_process.rs @@ -173,7 +173,7 @@ mod tests { assert_eq!(samples.len(), 1); // Because Poisson distribution is a random process, // we cannot predict exact values, but we can check if mean is close to lambda. - let mean: f64 = samples.iter().map(|&x| x as f64).sum::() / 1 as f64; + let mean: f64 = samples.iter().map(|&x| x as f64).sum::(); assert!((mean - lambda).abs() < 0.2 * lambda); // tolerance of 20% } diff --git a/arbiter-core/src/middleware.rs b/arbiter-core/src/middleware.rs index 7d350ff2..b634538e 100644 --- a/arbiter-core/src/middleware.rs +++ b/arbiter-core/src/middleware.rs @@ -15,7 +15,7 @@ use ethers::{ ProviderError, }, providers::{ - FilterKind, FilterWatcher, Middleware, MockProvider, PendingTransaction, Provider, + FilterKind, FilterWatcher, Middleware, PendingTransaction, Provider, }, signers::{Signer, Wallet}, types::{transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Filter, Log}, @@ -24,34 +24,11 @@ use rand::{rngs::StdRng, SeedableRng}; use revm::primitives::{CreateScheme, ExecutionResult, Output, TransactTo, TxEnv, B160, U256}; use crate::{ - environment::{Connection, RevmResult}, utils::{recast_address, revm_logs_to_ethers_logs}, + environment::{Environment, RevmProvider}, + agent::{Agent, NotAttached}, }; -use ethers::prelude::pending_transaction::PendingTxState; -use ethers::providers::{FilterKind, JsonRpcClient, JsonRpcError, PendingTransaction, Provider}; -use ethers::{ - prelude::k256::{ - ecdsa::SigningKey, - sha2::{Digest, Sha256}, - }, - prelude::ProviderError, - providers::{FilterWatcher, Middleware, MockProvider}, - signers::{Signer, Wallet}, - types::{transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Filter, Log}, -}; -use rand::rngs::StdRng; -use rand::SeedableRng; -use revm::primitives::{ - result, CreateScheme, ExecutionResult, Output, TransactTo, TxEnv, B160, U256, -}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::fmt::Debug; -use std::time::Duration; -use crate::agent::{Agent, NotAttached}; -use crate::environment::{Environment, RevmProvider}; -use crate::utils::{recast_address, revm_logs_to_ethers_logs}; // TODO: Refactor the connection and channels slightly to be more intuitive. For instance, the middleware may not really need to own a connection, but input one to set up everything else? /// The Revm middleware struct. @@ -147,7 +124,8 @@ impl Middleware for RevmMiddleware { .unwrap(); let revm_result = self.provider.as_ref().result_receiver.recv().unwrap(); - let (output, revm_logs, block) = match result.clone() { + + let (output, revm_logs, block) = match revm_result.result.clone() { ExecutionResult::Success { output, logs, .. } => (output, logs, revm_result.block_number), ExecutionResult::Revert { output, .. } => panic!("Failed due to revert: {:?}", output), ExecutionResult::Halt { reason, .. } => panic!("Failed due to halt: {:?}", reason), @@ -210,7 +188,7 @@ impl Middleware for RevmMiddleware { .unwrap(); let revm_result = self.provider.as_ref().result_receiver.recv().unwrap(); - let output = match result.clone() { + let output = match revm_result.result.clone() { ExecutionResult::Success { output, .. } => output, ExecutionResult::Revert { output, .. } => panic!("Failed due to revert: {:?}", output), ExecutionResult::Halt { reason, .. } => panic!("Failed due to halt: {:?}", reason), diff --git a/arbiter-core/src/tests.rs b/arbiter-core/src/tests.rs index 04619962..6f2e5b0e 100644 --- a/arbiter-core/src/tests.rs +++ b/arbiter-core/src/tests.rs @@ -1,4 +1,4 @@ -#[allow(missing_docs)] +#![allow(missing_docs)] use std::str::FromStr; use std::sync::Arc; @@ -21,6 +21,8 @@ const TEST_ARG_SYMBOL: &str = "ARBT"; const TEST_ARG_DECIMALS: u8 = 18; const TEST_MINT_AMOUNT: u128 = 1; const TEST_MINT_TO: &str = "0xf7e93cc543d97af6632c9b8864417379dba4bf15"; +const TEST_ENV_LABEL: &str = "test_env"; +const TEST_AGENT_NAME: &str = "test_agent"; #[test] fn token_mint() -> Result<()> { From 282da874784989e6a498dd03ea86735bbef8421b Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 2 Aug 2023 09:24:47 -0700 Subject: [PATCH 15/19] Requested Changes from Colin --- arbiter-core/src/environment.rs | 4 +- arbiter-core/src/math/mod.rs | 2 - arbiter-core/src/math/stochastic_process.rs | 113 +------------------- 3 files changed, 5 insertions(+), 114 deletions(-) diff --git a/arbiter-core/src/environment.rs b/arbiter-core/src/environment.rs index f618d2d8..716c4632 100644 --- a/arbiter-core/src/environment.rs +++ b/arbiter-core/src/environment.rs @@ -21,7 +21,7 @@ use revm::{ use crate::{ agent::{Agent, IsAttached, NotAttached}, - math::stochastic_process::poisson_process, + math::stochastic_process::sample_poisson, middleware::RevmMiddleware, utils::{convert_uint_to_u64, revm_logs_to_ethers_logs, }, }; @@ -154,7 +154,7 @@ impl Environment { self.state = State::Running; let mut expected_occurance: Option> = None; if let Some(lambda) = self.lambda { - expected_occurance = Some(poisson_process(lambda).unwrap()); + expected_occurance = Some(sample_poisson(lambda).unwrap()); } //give all agents their own thread and let them start watching for their evnts thread::spawn(move || { diff --git a/arbiter-core/src/math/mod.rs b/arbiter-core/src/math/mod.rs index 62d0ecf2..a2ce6a25 100644 --- a/arbiter-core/src/math/mod.rs +++ b/arbiter-core/src/math/mod.rs @@ -1,8 +1,6 @@ //! Math module. use ethers::types::U256; - -/// Stochastic process module. pub mod stochastic_process; /// Converts a float to a WAD fixed point prepared U256 number. diff --git a/arbiter-core/src/math/stochastic_process.rs b/arbiter-core/src/math/stochastic_process.rs index 5c1ee6d5..502d8bfd 100644 --- a/arbiter-core/src/math/stochastic_process.rs +++ b/arbiter-core/src/math/stochastic_process.rs @@ -1,5 +1,5 @@ use anyhow::{Ok, Result}; -use RustQuant::{ +pub use RustQuant::{ statistics::distributions::{Distribution, Poisson}, stochastics::{ cox_ingersoll_ross::CoxIngersollRoss, extended_vasicek::ExtendedVasicek, ho_lee::HoLee, @@ -41,75 +41,8 @@ pub struct EulerMaruyamaInput { pub parallel: bool, } -/// Create new process and run euler maruyama. -pub fn new_procces( - proccess_type: StochasticProcessType, - config: EulerMaruyamaInput, -) -> Result { - let trajectories: Trajectories = match proccess_type { - StochasticProcessType::BrownianMotion(process) => process.euler_maruyama( - config.x_0, - config.t_0, - config.t_n, - config.n_steps, - config.m_paths, - config.parallel, - ), - StochasticProcessType::OrnsteinUhlenbeck(process) => process.euler_maruyama( - config.x_0, - config.t_0, - config.t_n, - config.n_steps, - config.m_paths, - config.parallel, - ), - StochasticProcessType::BlackDermanToy(process) => process.euler_maruyama( - config.x_0, - config.t_0, - config.t_n, - config.n_steps, - config.m_paths, - config.parallel, - ), - StochasticProcessType::CoxIngersollRoss(process) => process.euler_maruyama( - config.x_0, - config.t_0, - config.t_n, - config.n_steps, - config.m_paths, - config.parallel, - ), - StochasticProcessType::ExtendedVasicek(process) => process.euler_maruyama( - config.x_0, - config.t_0, - config.t_n, - config.n_steps, - config.m_paths, - config.parallel, - ), - StochasticProcessType::HoLee(process) => process.euler_maruyama( - config.x_0, - config.t_0, - config.t_n, - config.n_steps, - config.m_paths, - config.parallel, - ), - StochasticProcessType::HullWhite(process) => process.euler_maruyama( - config.x_0, - config.t_0, - config.t_n, - config.n_steps, - config.m_paths, - config.parallel, - ), - }; - - Ok(trajectories) -} - /// Sample Poisson process. -pub fn poisson_process(lambda: f64) -> Result> { +pub fn sample_poisson(lambda: f64) -> Result> { let poisson = Poisson::new(lambda); let float_samples = poisson.sample(1); let int_sample: Vec = float_samples.iter().map(|&x| x.round() as i32).collect(); @@ -123,50 +56,10 @@ mod tests { use super::*; - #[test] - fn new_process_brownian_motion() { - let bm = BrownianMotion::new(); - let config = EulerMaruyamaInput { - x_0: 0.0, - t_0: 0.0, - t_n: 1.0, - n_steps: 100, - m_paths: 10, - parallel: false, - }; - let process = StochasticProcessType::BrownianMotion(bm); - let result = new_procces(process, config); - - assert!(result.is_ok()); - let trajectories = result.unwrap(); - assert_eq!(trajectories.times.len(), 101); - assert_eq!(trajectories.paths.len(), 10); - } - - #[test] - fn new_process_ornstein_uhlenbeck() { - let ou = OrnsteinUhlenbeck::new(1.0, 1.0, 1.0); - let config = EulerMaruyamaInput { - x_0: 0.0, - t_0: 0.0, - t_n: 1.0, - n_steps: 100, - m_paths: 10, - parallel: false, - }; - let process = StochasticProcessType::OrnsteinUhlenbeck(ou); - let result = new_procces(process, config); - - assert!(result.is_ok()); - let trajectories = result.unwrap(); - assert_eq!(trajectories.times.len(), 101); - assert_eq!(trajectories.paths.len(), 10); - } - #[test] fn poisson_process_test() { let lambda = 1.0; - let result = poisson_process(lambda); + let result = sample_poisson(lambda); assert!(result.is_ok()); let samples = result.unwrap(); From de4b7d5aa6d5cb07541533bf36af3e196c49cd8d Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 2 Aug 2023 09:53:44 -0700 Subject: [PATCH 16/19] make agen constructor public --- arbiter-core/src/agent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbiter-core/src/agent.rs b/arbiter-core/src/agent.rs index 994b1701..b6691a1c 100644 --- a/arbiter-core/src/agent.rs +++ b/arbiter-core/src/agent.rs @@ -29,7 +29,7 @@ pub struct Agent { impl Agent { - pub(crate) fn new>(name: S) -> Self { + pub fn new>(name: S) -> Self { Self { name: name.into(), client: (), From 80ef518e46918f862ef789af77bbf27e6573994c Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 2 Aug 2023 09:54:37 -0700 Subject: [PATCH 17/19] remove unsused imports in tests to compile --- arbiter-core/src/tests.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/arbiter-core/src/tests.rs b/arbiter-core/src/tests.rs index 6f2e5b0e..564babe7 100644 --- a/arbiter-core/src/tests.rs +++ b/arbiter-core/src/tests.rs @@ -9,11 +9,10 @@ use ethers::{ }; use crate::{ - agent::{tests::*, *}, + agent::{*}, bindings::arbiter_token::*, - environment::{tests::*, *}, - manager::{tests::*, *}, - middleware::{tests::*, *}, + environment::{*}, + middleware::{*}, }; const TEST_ARG_NAME: &str = "ArbiterToken"; From cf78613d76ddd0320e54d3b17b8e1a8a9245a391 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 2 Aug 2023 13:53:37 -0700 Subject: [PATCH 18/19] feedback --- arbiter-core/Cargo.toml | 1 - arbiter-core/src/lib.rs | 2 +- arbiter-core/src/math/stochastic_process.rs | 33 --------------------- arbiter-core/src/tests.rs | 6 ++-- 4 files changed, 3 insertions(+), 39 deletions(-) diff --git a/arbiter-core/Cargo.toml b/arbiter-core/Cargo.toml index 41b2a7fa..08369594 100644 --- a/arbiter-core/Cargo.toml +++ b/arbiter-core/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" # Dependency configuration [dependencies] # For execution -dashmap = "5.5.0" bytes = "1.4.0" revm = "3.3.0" statrs = "0.16.0" diff --git a/arbiter-core/src/lib.rs b/arbiter-core/src/lib.rs index fa3827b2..49d89ed3 100644 --- a/arbiter-core/src/lib.rs +++ b/arbiter-core/src/lib.rs @@ -8,6 +8,6 @@ pub mod environment; pub mod manager; pub mod math; pub mod middleware; -// #[cfg(test)] //TODO: UNCOMMENT THIS LATER +#[cfg(test)] //TODO: UNCOMMENT THIS LATER pub mod tests; pub mod utils; diff --git a/arbiter-core/src/math/stochastic_process.rs b/arbiter-core/src/math/stochastic_process.rs index 502d8bfd..d834de2f 100644 --- a/arbiter-core/src/math/stochastic_process.rs +++ b/arbiter-core/src/math/stochastic_process.rs @@ -8,39 +8,6 @@ pub use RustQuant::{ }, }; -/// Type enum for process -pub enum StochasticProcessType { - /// Brownian motion - BrownianMotion(BrownianMotion), - /// Ornstein-Uhlenbeck - OrnsteinUhlenbeck(OrnsteinUhlenbeck), - /// Black-Derman-Toy - BlackDermanToy(BlackDermanToy), - /// Cox-Ingersoll-Ross - CoxIngersollRoss(CoxIngersollRoss), - /// Extended Vasicek - ExtendedVasicek(ExtendedVasicek), - /// Ho-Lee - HoLee(HoLee), - /// Hull-White - HullWhite(HullWhite), -} -/// Struct for all processes init parameters. -pub struct EulerMaruyamaInput { - /// initial value at t_0 - pub x_0: f64, - /// initial time point - pub t_0: f64, - /// terminal time point - pub t_n: f64, - /// number of time steps between t_0 and t_n - pub n_steps: usize, - /// how many process trajectories to simulate - pub m_paths: usize, - /// run in parallel or not (recommended for > 1000 paths) - pub parallel: bool, -} - /// Sample Poisson process. pub fn sample_poisson(lambda: f64) -> Result> { let poisson = Poisson::new(lambda); diff --git a/arbiter-core/src/tests.rs b/arbiter-core/src/tests.rs index 564babe7..5a0dc79d 100644 --- a/arbiter-core/src/tests.rs +++ b/arbiter-core/src/tests.rs @@ -9,9 +9,9 @@ use ethers::{ }; use crate::{ - agent::{*}, + agent::{*, tests::TEST_AGENT_NAME}, bindings::arbiter_token::*, - environment::{*}, + environment::{*, tests::TEST_ENV_LABEL}, middleware::{*}, }; @@ -20,8 +20,6 @@ const TEST_ARG_SYMBOL: &str = "ARBT"; const TEST_ARG_DECIMALS: u8 = 18; const TEST_MINT_AMOUNT: u128 = 1; const TEST_MINT_TO: &str = "0xf7e93cc543d97af6632c9b8864417379dba4bf15"; -const TEST_ENV_LABEL: &str = "test_env"; -const TEST_AGENT_NAME: &str = "test_agent"; #[test] fn token_mint() -> Result<()> { From 93930479e7fe0e9cc70ba73b0aaf8e2d36a5bb62 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 2 Aug 2023 14:53:39 -0700 Subject: [PATCH 19/19] testing multi threaded stuff is hard fmt Tests seeds sample with seed --- arbiter-core/src/agent.rs | 1 - arbiter-core/src/environment.rs | 67 ++++++++-------- arbiter-core/src/manager.rs | 35 +++++---- arbiter-core/src/math/stochastic_process.rs | 56 +++++++++++--- arbiter-core/src/tests.rs | 84 ++++++++++++++++----- arbiter-core/src/utils.rs | 32 +++++--- 6 files changed, 186 insertions(+), 89 deletions(-) diff --git a/arbiter-core/src/agent.rs b/arbiter-core/src/agent.rs index b6691a1c..bf05924a 100644 --- a/arbiter-core/src/agent.rs +++ b/arbiter-core/src/agent.rs @@ -27,7 +27,6 @@ pub struct Agent { pub behaviors: Vec>, } - impl Agent { pub fn new>(name: S) -> Self { Self { diff --git a/arbiter-core/src/environment.rs b/arbiter-core/src/environment.rs index 716c4632..6f7f494b 100644 --- a/arbiter-core/src/environment.rs +++ b/arbiter-core/src/environment.rs @@ -4,7 +4,7 @@ use std::{ fmt::Debug, sync::{ - atomic::{AtomicUsize, Ordering}, + atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, Mutex, }, thread, @@ -18,12 +18,13 @@ use revm::{ primitives::{ExecutionResult, Log, TxEnv, U256}, EVM, }; +use RustQuant::statistics::distributions::i; use crate::{ agent::{Agent, IsAttached, NotAttached}, - math::stochastic_process::sample_poisson, + math::stochastic_process::{sample_poisson, SeededPoisson}, middleware::RevmMiddleware, - utils::{convert_uint_to_u64, revm_logs_to_ethers_logs, }, + utils::{convert_uint_to_u64, revm_logs_to_ethers_logs}, }; use serde::{de::DeserializeOwned, Serialize}; @@ -63,12 +64,9 @@ pub struct Environment { /// Clients (Agents) in the environment pub agents: Vec>>, /// expected events per block - pub lambda: Option, - pub tx_per_block: Arc, - + pub seeded_poisson: SeededPoisson, } - // TODO: If the provider holds the connection then this can work better. #[derive(Clone)] pub struct RevmProvider { @@ -101,7 +99,7 @@ impl JsonRpcClient for RevmProvider { let logs_deserializeowned: R = serde_json::from_str(&logs_str)?; return Ok(logs_deserializeowned); // return Ok(serde::to_value(self.event_receiver.recv().ok()).unwrap()) - }, + } _ => { unimplemented!("We don't cover this case yet.") } @@ -109,13 +107,14 @@ impl JsonRpcClient for RevmProvider { } } - impl Environment { /// Creates a new [`Environment`] with the given label. - pub(crate) fn new>(label: S) -> Self { + pub(crate) fn new>(label: S, block_rate: f64, seed: u64) -> Self { let mut evm = EVM::new(); let db = CacheDB::new(EmptyDB {}); evm.database(db); + + let seeded_poisson = SeededPoisson::new(block_rate, seed); evm.env.cfg.limit_contract_code_size = Some(0x100000); // This is a large contract size limit, beware! evm.env.block.gas_limit = U256::MAX; let (tx_sender, tx_receiver) = unbounded::<(ToTransact, TxEnv, Sender)>(); @@ -127,15 +126,10 @@ impl Environment { tx_receiver, event_broadcaster: Arc::new(Mutex::new(EventBroadcaster::new())), agents: vec![], - lambda: None, - tx_per_block: Arc::new(AtomicUsize::new(0)), + seeded_poisson, } } - pub(crate) fn configure_lambda(&mut self, lamda: f64) { - self.lambda = Some(lamda); - } - /// Creates a new [`Agent> = None; - if let Some(lambda) = self.lambda { - expected_occurance = Some(sample_poisson(lambda).unwrap()); - } - //give all agents their own thread and let them start watching for their evnts + thread::spawn(move || { + let mut expected_events_per_block = seeded_poisson.sample(); + while let Ok((to_transact, tx, sender)) = tx_receiver.recv() { // Execute the transaction, echo the logs to all agents, and report the execution result to the agent who made the transaction. - if let Some(occurance) = &expected_occurance { - if counter.load(Ordering::Relaxed) >= occurance[0] as usize { - evm.env.block.number += U256::from(1); - counter.store(0, Ordering::Relaxed); - } + if counter == expected_events_per_block { + counter = 0; + println!("EVM expected number of transactions reached. Moving to next block."); + println!("old block number: {:?}", evm.env.block.number); + evm.env.block.number += U256::from(1); + println!("new block number: {:?}", evm.env.block.number); + expected_events_per_block = seeded_poisson.sample(); } evm.env.tx = tx; @@ -184,10 +181,7 @@ impl Environment { block_number: convert_uint_to_u64(evm.env.block.number).unwrap(), }; sender.send(revm_result).unwrap(); - if let Some(_occurance) = &expected_occurance { - counter.fetch_add(1, Ordering::Relaxed); - } - + counter += 1; } else { let execution_result = match evm.transact() { Ok(val) => val, @@ -230,20 +224,27 @@ impl EventBroadcaster { #[cfg(test)] pub(crate) mod tests { + use std::time::Duration; + + use anyhow::{Ok, Result}; + use ethers::types::Address; + + use crate::bindings::arbiter_token::ArbiterToken; + use super::*; pub(crate) const TEST_ENV_LABEL: &str = "test"; #[test] fn new() { - let env = Environment::new(TEST_ENV_LABEL.to_string()); + let env = Environment::new(TEST_ENV_LABEL.to_string(), 1.0, 1); assert_eq!(env.label, TEST_ENV_LABEL); assert_eq!(env.state, State::Stopped); } #[test] fn run() { - let mut environment = Environment::new(TEST_ENV_LABEL.to_string()); + let mut environment = Environment::new(TEST_ENV_LABEL.to_string(), 1.0, 1); environment.run(); assert_eq!(environment.state, State::Running); } diff --git a/arbiter-core/src/manager.rs b/arbiter-core/src/manager.rs index 64e6f84f..ed0012d9 100644 --- a/arbiter-core/src/manager.rs +++ b/arbiter-core/src/manager.rs @@ -7,7 +7,10 @@ use std::collections::HashMap; use anyhow::{anyhow, Result}; -use crate::{environment::{Environment, State}, agent::{Agent, NotAttached}}; +use crate::{ + agent::{Agent, NotAttached}, + environment::{Environment, State}, +}; /// Manages simulations. #[derive(Default)] @@ -25,30 +28,32 @@ impl SimulationManager { } /// Adds an environment to the [`SimulationManager`]'s list. - pub fn add_environment(&mut self, environment_label: String) -> Result<()> { + pub fn add_environment( + &mut self, + environment_label: String, + block_rate: f64, + seed: u64, + ) -> Result<()> { if self.environments.get(&environment_label).is_some() { return Err(anyhow!("Environment already exists.")); } self.environments.insert( environment_label.clone(), - Environment::new(environment_label), + Environment::new(environment_label, block_rate, seed), ); Ok(()) } - /// Configure the lambda for an environment. - pub fn configure_lambda(&mut self, lambda: f64, environment_label: String) -> Result<()> { - match self.environments.get_mut(&environment_label) { - Some(environment) => { - environment.configure_lambda(lambda); - Ok(()) - } - None => Err(anyhow!("Environment does not exist.")), - } + pub fn _stop_environemt(self, _environment_label: String) -> Result<()> { + todo!() } /// adds an agent to an environment - pub fn add_agent(&mut self, agent: Agent, environment_label: String) -> Result<()> { + pub fn add_agent( + &mut self, + agent: Agent, + environment_label: String, + ) -> Result<()> { match self.environments.get_mut(&environment_label) { Some(environment) => { environment.add_agent(agent); @@ -88,7 +93,7 @@ pub(crate) mod tests { fn add_environment() { let mut manager = SimulationManager::new(); let label = "test".to_string(); - manager.add_environment(label.clone()).unwrap(); + manager.add_environment(label.clone(), 1.0, 1).unwrap(); assert!(manager.environments.contains_key(&label)); } @@ -96,7 +101,7 @@ pub(crate) mod tests { fn run_environment() { let mut manager = SimulationManager::new(); let label = "test".to_string(); - manager.add_environment(label.clone()).unwrap(); + manager.add_environment(label.clone(), 1.0, 1).unwrap(); manager.run_environment(label.clone()).unwrap(); assert_eq!( manager.environments.get(&label).unwrap().state, diff --git a/arbiter-core/src/math/stochastic_process.rs b/arbiter-core/src/math/stochastic_process.rs index d834de2f..e8f1e246 100644 --- a/arbiter-core/src/math/stochastic_process.rs +++ b/arbiter-core/src/math/stochastic_process.rs @@ -1,4 +1,8 @@ use anyhow::{Ok, Result}; +use rand::{rngs::StdRng, SeedableRng}; +use rand_distr::Distribution as statrs_distribution; +use statrs::distribution::Poisson as statrs_poisson; + pub use RustQuant::{ statistics::distributions::{Distribution, Poisson}, stochastics::{ @@ -16,6 +20,29 @@ pub fn sample_poisson(lambda: f64) -> Result> { Ok(int_sample) } +/// Poisson process with seed. +#[derive(Debug, Clone)] +pub struct SeededPoisson { + /// Poisson distribution. + pub distribution: statrs_poisson, + /// Random number generator. + pub rng: StdRng, +} + +/// Sample Poisson process with seed. +impl SeededPoisson { + /// Create new Poisson process with seed. + pub fn new(lambda: f64, seed: u64) -> Self { + let distribution = statrs_poisson::new(lambda).unwrap(); + let rng = StdRng::seed_from_u64(seed); + Self { distribution, rng } + } + /// Sample Poisson process. + pub fn sample(&mut self) -> usize { + self.distribution.sample(&mut self.rng) as usize + } +} + #[cfg(test)] mod tests { @@ -24,17 +51,24 @@ mod tests { use super::*; #[test] - fn poisson_process_test() { - let lambda = 1.0; - let result = sample_poisson(lambda); - - assert!(result.is_ok()); - let samples = result.unwrap(); - assert_eq!(samples.len(), 1); - // Because Poisson distribution is a random process, - // we cannot predict exact values, but we can check if mean is close to lambda. - let mean: f64 = samples.iter().map(|&x| x as f64).sum::(); - assert!((mean - lambda).abs() < 0.2 * lambda); // tolerance of 20% + fn seed_test() { + let mut test_dist_1 = SeededPoisson::new(10.0, 321); + let mut test_dist_2 = SeededPoisson::new(10000.0, 123); + let mut test_dist_3 = SeededPoisson::new(10000.0, 123); + + let result_1 = test_dist_1.sample(); + let result_2 = test_dist_1.sample(); + let result_3 = test_dist_2.sample(); + let result_4 = test_dist_2.sample(); + let result_5 = test_dist_3.sample(); + let result_6 = test_dist_3.sample(); + + assert_eq!(result_1, 15); + assert_eq!(result_2, 12); + assert_eq!(result_3, 9914); + assert_eq!(result_4, 10143); + assert_eq!(result_5, result_3); + assert_eq!(result_6, result_4); } #[test] diff --git a/arbiter-core/src/tests.rs b/arbiter-core/src/tests.rs index 5a0dc79d..1c31ba09 100644 --- a/arbiter-core/src/tests.rs +++ b/arbiter-core/src/tests.rs @@ -1,25 +1,29 @@ #![allow(missing_docs)] -use std::str::FromStr; use std::sync::Arc; +use std::{ + str::FromStr, + sync::atomic::{AtomicUsize, Ordering}, +}; -use anyhow::Result; +use anyhow::{Ok, Result}; use ethers::{ prelude::{FilterWatcher, Middleware, StreamExt}, - types::{Address, Filter}, + types::{Address, Filter, U64}, }; use crate::{ - agent::{*, tests::TEST_AGENT_NAME}, + agent::{tests::TEST_AGENT_NAME, *}, bindings::arbiter_token::*, - environment::{*, tests::TEST_ENV_LABEL}, - middleware::{*}, + environment::{tests::TEST_ENV_LABEL, *}, + math::*, + middleware::*, }; -const TEST_ARG_NAME: &str = "ArbiterToken"; -const TEST_ARG_SYMBOL: &str = "ARBT"; -const TEST_ARG_DECIMALS: u8 = 18; -const TEST_MINT_AMOUNT: u128 = 1; -const TEST_MINT_TO: &str = "0xf7e93cc543d97af6632c9b8864417379dba4bf15"; +pub const TEST_ARG_NAME: &str = "ArbiterToken"; +pub const TEST_ARG_SYMBOL: &str = "ARBT"; +pub const TEST_ARG_DECIMALS: u8 = 18; +pub const TEST_MINT_AMOUNT: u128 = 1; +pub const TEST_MINT_TO: &str = "0xf7e93cc543d97af6632c9b8864417379dba4bf15"; #[test] fn token_mint() -> Result<()> { @@ -34,16 +38,15 @@ fn arbiter_math() -> Result<()> { // TODO: Finish off a test like this. #[test] fn attach_agent() { - let environment = &mut Environment::new(TEST_ENV_LABEL); + let environment = &mut Environment::new(TEST_ENV_LABEL, 1.0, 1); let agent = Agent::new(TEST_AGENT_NAME); agent.attach_to_environment(environment); assert_eq!(environment.agents[0].name, TEST_AGENT_NAME); } - #[test] fn simulation_agent_wallet() { - let environment = &mut Environment::new(TEST_ENV_LABEL); + let environment = &mut Environment::new(TEST_ENV_LABEL, 1.0, 1); let agent = Agent::new(TEST_AGENT_NAME); agent.attach_to_environment(environment); assert_eq!( @@ -54,12 +57,10 @@ fn simulation_agent_wallet() { #[test] fn multiple_agent_addresses() { - let environment = &mut Environment::new(TEST_ENV_LABEL); - let agent = - Agent::new(TEST_AGENT_NAME); + let environment = &mut Environment::new(TEST_ENV_LABEL, 1.0, 1); + let agent = Agent::new(TEST_AGENT_NAME); agent.attach_to_environment(environment); - let agent2 = - Agent::new(format!("new_{}", TEST_AGENT_NAME)); + let agent2 = Agent::new(format!("new_{}", TEST_AGENT_NAME)); agent2.attach_to_environment(environment); assert_ne!( environment.agents[0].client.default_sender(), @@ -73,7 +74,7 @@ fn agent_name_collision() { } async fn deploy() -> Result> { - let environment = &mut Environment::new(TEST_ENV_LABEL); + let environment = &mut Environment::new(TEST_ENV_LABEL, 1.0, 1); let agent = Agent::new(TEST_AGENT_NAME); agent.attach_to_environment(environment); environment.run(); @@ -170,3 +171,46 @@ async fn filter_watcher() -> Result<()> { // TODO: Test that we can filter out approvals and NOT transfers (or something like this) todo!() } + +// This test has two parts +// 1 check that the expected number of transactions per block is the actual number of transactions per block. +// 2 check the block number is incremented after the expected number of transactions is reached. +#[tokio::test] +async fn transaction_loop() -> Result<()> { + let mut env = Environment::new(TEST_ENV_LABEL, 2.0, 1); + + let mut dist = env.seeded_poisson.clone(); + let expected_tx_per_block = dist.sample(); + + println!("expected_tx_per_block: {}", expected_tx_per_block); + + let agent = Agent::new(TEST_AGENT_NAME); + env.add_agent(agent); + let agent = &env.agents[0]; + // tx_0 is the transaction that creates the token contract + let arbiter_token = deploy().await?; + + for index in 1..expected_tx_per_block { + println!("index: {}", index); + let tx = arbiter_token + .mint(agent.client.default_sender().unwrap(), 1000u64.into()) + .send() + .await + .unwrap() + .await + .unwrap() + .unwrap(); + + // minus 1 from deploy tx + if index < expected_tx_per_block - 1 { + let block_number = tx.block_number.unwrap(); + println!("block_number: {}", block_number); + assert_eq!(block_number, U64::from(0)); + } else { + let block_number = tx.block_number.unwrap(); + println!("block_number: {}", block_number); + assert_eq!(block_number, U64::from(1)); + } + } + Ok(()) +} diff --git a/arbiter-core/src/utils.rs b/arbiter-core/src/utils.rs index df20b010..6a1c8630 100644 --- a/arbiter-core/src/utils.rs +++ b/arbiter-core/src/utils.rs @@ -119,15 +119,29 @@ pub fn unpack_execution(execution_result: ExecutionResult) -> Result Result { - // Convert to base 2^64 digits - let digits: Vec<_> = input.to_base_le(2u64.pow(64)).collect(); - - // If there are no digits, the value was 0 - if digits.is_empty() { - return Ok(U64::from(0)); + let as_str = input.to_string(); + match as_str.parse::() { + Ok(val) => Ok(val.into()), + Err(_) => Err("U256 value is too large to fit into u64"), } +} + +#[cfg(test)] +mod tests { + use super::*; - // Otherwise, return the least significant 64 bits - // If the number was larger than 2^64, this will silently discard the higher bits - Ok(U64::from(digits[0])) + #[test] + fn test_conversion() { + // Test with a value that fits in u64. + let input = U256::from(10000); + assert_eq!(convert_uint_to_u64(input).unwrap(), U64::from(10000)); + + // Test with a value that is exactly at the limit of u64. + let input = U256::from(u64::MAX); + assert_eq!(convert_uint_to_u64(input).unwrap(), U64::from(u64::MAX)); + + // Test with a value that exceeds the limit of u64. + let input = U256::from(u64::MAX) + U256::from(1); + assert!(convert_uint_to_u64(input).is_err()); + } }