From 3619cabee0de59b4feaeda177ae0624e333236e8 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 1 Nov 2023 13:36:24 -0600 Subject: [PATCH 1/3] documentation --- .gitignore | 1 + README.md | 45 +++--- src/agents/block_admin.rs | 26 ++- src/agents/counter_agent.rs | 26 ++- src/agents/mod.rs | 16 +- src/agents/price_changer.rs | 18 +-- src/agents/token_admin.rs | 18 ++- src/bindings/counter.rs | 197 +++++++++-------------- src/bindings/mod.rs | 2 +- src/main.rs | 24 ++- src/settings/mod.rs | 30 +++- src/settings/parameters.rs | 37 +++++ src/simulations/counter.rs | 29 ++-- src/simulations/mod.rs | 55 ++++--- src/simulations/price_path_simulation.rs | 28 +++- 15 files changed, 345 insertions(+), 207 deletions(-) diff --git a/.gitignore b/.gitignore index 10b6c04..0b17328 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ docs/ .env *.lock +analysis/counter/* \ No newline at end of file diff --git a/README.md b/README.md index b9c775b..3c026e0 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,34 @@ -# arbiter-template +# Arbiter Template -Minimal template for simulating contracts with arbiter. +Minimal template for simulating contracts with arbiter. This template is used by the `arbiter init` command when starting new simulations. This template provides a framework for performantly simulating Agent Based Models (ABM) with evm parity. In this model you can think of any things that happens as an action of an agent. Agents can own keys and externally owned accounts, they can interact with each other and they can interact with contracts. This repository has some example agents including a `TokenAdmin`, `BlockAdmin`, and `CounterAgent` which their own functionality and responsibilities. We also give an example of how to parametarize your simulations with a configuration file containing different price paths and price path parameters. These can be played with to see how the simulation changes. Furthermore we provide an api to batch simulations and run them in parallel. This is useful for running many simulations with different parameters. -## Usage - -1. Clone this repository +### Prerequisites -``` -git clone https://github.com/primitivefinance/arbiter-template.git -cd arbiter-template -``` +- Rust programming language and Cargo package manager (latest stable version recommended) +- [Foundry](https://book.getfoundry.sh/getting-started/installation) is used behind the scenes to generate rust contract bindings. Make sure you have forge installed and up to date. -2. Install foundry +## Usage +1. Install arbiter -``` -curl -L https://foundry.paradigm.xyz | bash -foundryup +``` bash +cargo install arbiter ``` -3. Install forge libraries +2. Create arbiter project from this template +``` bash +arbiter init ``` -forge install -``` - -4. Generate bindings -``` -forge bind --revert-strings debug -b src/bindings/ --module --overwrite +3. Run the project +```bash +cargo run simulate src/config/counter.toml ``` -5. Run the project +## Documentation -``` -cargo run -``` +The documentation for the repository is primarily inline with the code. Cargo automatically can compile these into a browsable format. To view the documentation run the following command: + +```bash +cargo doc --open +``` \ No newline at end of file diff --git a/src/agents/block_admin.rs b/src/agents/block_admin.rs index 29d0c43..58146a9 100644 --- a/src/agents/block_admin.rs +++ b/src/agents/block_admin.rs @@ -7,15 +7,33 @@ use settings::SimulationConfig; use super::*; +/// A structure representing a block admin agent. +/// This agent is responsible for updating the block number and timestamp. #[derive(Clone)] pub struct BlockAdmin { + /// A client to interface with arbiter's revm middleware. + /// You can think of this as the agents wallet or EOA. pub client: Arc, + + /// The size of each timestep in the simulation, representing block time passage. pub timestep_size: u64, + + /// The current simulated block timestamp. pub block_timestamp: u64, + + /// The current simulated block number. pub block_number: u64, } impl BlockAdmin { + /// Creates a new BlockAdmin using the provided environment and simulation configuration. + /// + /// # Arguments + /// * [`Environment`] - The environment containing blockchain node information. + /// * [`SimulationConfig`] - The simulation configuration providing block timestep size. + /// + /// # Returns + /// * [`Result`] - A result containing the new BlockAdmin or an error. pub async fn new(environment: &Environment, config: &SimulationConfig) -> Result { let client = RevmMiddleware::new(environment, "block_admin".into())?; let timestep_size = config.block.timestep_size; @@ -29,7 +47,12 @@ impl BlockAdmin { block_number, }) } - + /// Updates the simulated block information. + /// + /// Increments the block number and calculates the new block timestamp based on the timestep size. + /// + /// # Returns + /// * [`Result<()>`] - A result indicating the success or failure of the operation. pub fn update_block(&mut self) -> Result<()> { self.block_number += 1; self.block_timestamp = self.block_number * self.timestep_size; @@ -45,7 +68,6 @@ impl Agent for BlockAdmin { self.update_block()?; Ok(()) } - async fn startup(&mut self) -> Result<()> { self.update_block()?; Ok(()) diff --git a/src/agents/counter_agent.rs b/src/agents/counter_agent.rs index 0da035c..a1b23c8 100644 --- a/src/agents/counter_agent.rs +++ b/src/agents/counter_agent.rs @@ -1,26 +1,40 @@ -use std::sync::Arc; - -use arbiter_core::{middleware::RevmMiddleware, environment::Environment, bindings::arbiter_token::ArbiterToken}; -use ethers::types::{Address, U256}; +use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; use bindings::counter::Counter; +use std::sync::Arc; use super::*; +/// A structure representing a counter agent. +/// This agent is responsible for incrementing the count on a counter contract. #[derive(Clone)] pub struct CounterAgent { + // A client to interface with arbiter's revm middleware pub client: Arc, + + // An instance of a deployed counter contract pub counter: Counter, } impl CounterAgent { + /// Creates a new instance of a [`CounterAgent`]. + /// + /// # Arguments + /// * [`Environment`] - A reference to the environment that holds blockchain configuration. + /// + /// # Returns + /// * [`Result`] - Result of CounterAgent creation, containing the agent or an error. pub async fn new(environment: &Environment) -> Result { let client = RevmMiddleware::new(environment, "counter_agent".into())?; let counter = Counter::deploy(client.clone(), ())?.send().await?; - Ok(Self { client, counter}) + Ok(Self { client, counter }) } - pub async fn increment (&self) -> Result<()> { + /// Increments the counter in the smart contract. + /// + /// # Returns + /// * [`Result<()>`] - Result of the increment operation, indicating success or error. + pub async fn increment(&self) -> Result<()> { self.counter.increment().send().await?.await?; Ok(()) } diff --git a/src/agents/mod.rs b/src/agents/mod.rs index 4d3b7ae..e648eca 100644 --- a/src/agents/mod.rs +++ b/src/agents/mod.rs @@ -11,6 +11,7 @@ use crate::settings::parameters::Fixed; /// Universal agent methods for interacting with the simulation environment or /// loop. +/// Agents are expected to be both [`Send`] and [`Sync`]. #[async_trait::async_trait] pub trait Agent: Sync + Send { /// Executed outside the main simulation loop. @@ -29,33 +30,46 @@ pub trait Agent: Sync + Send { Ok(()) } } - +/// A collection of agents that can be operated on collectively. pub struct Agents(pub Vec>); impl Agents { + /// Returns a mutable iterator over the agents. + /// This can be used to invoke methods on each agent individually. pub fn iter_mut(&mut self) -> impl Iterator> { self.0.iter_mut() } } impl Agents { + /// Constructs a new [`Agents`] collection. + /// This static method provides a way to create a new collection of agents. #[allow(clippy::new_without_default)] pub fn new() -> Self { Self(vec![]) } + /// Adds a new agent to the collection. + /// This method takes ownership of the agent and adds it to the collection. + #[allow(clippy::should_implement_trait)] pub fn add(mut self, agent: impl Agent + 'static) -> Self { self.0.push(Box::new(agent)); self } } +/// [`Agent`] trait implementation for a collection of agents. +/// This allows collective operations on the group of agents. #[async_trait::async_trait] impl Agent for Agents { + /// Implementation of the `step` method for the collection. + /// This allows the collection to forward the step action to each agent. async fn step(&mut self) -> Result<()> { Ok(()) } + /// Implementation of the `priority_step` method for the collection. + /// This allows the collection to forward the priority step action to each agent. async fn priority_step(&mut self) -> Result<()> { Ok(()) } diff --git a/src/agents/price_changer.rs b/src/agents/price_changer.rs index 02ac7fd..c870e5f 100644 --- a/src/agents/price_changer.rs +++ b/src/agents/price_changer.rs @@ -1,18 +1,18 @@ -use arbiter_core::math::{float_to_wad, Trajectories, GeometricBrownianMotion, StochasticProcess}; +use crate::agents::*; +use crate::settings::{parameters::GBMParameters, SimulationConfig}; use arbiter_core::bindings::liquid_exchange::LiquidExchange; use arbiter_core::environment::Environment; +use arbiter_core::math::{float_to_wad, GeometricBrownianMotion, StochasticProcess, Trajectories}; use arbiter_core::middleware::RevmMiddleware; use ethers::utils::parse_ether; -use crate::settings::{SimulationConfig, parameters::GBMParameters}; -use crate::agents::*; -/// The `PriceChanger` holds the data and has methods that allow it to update -/// the price of the `LiquidExchange`. +/// The [`PriceChanger`] holds the data and has methods that allow it to update +/// the price of the [`LiquidExchange`]. pub struct PriceChanger { /// The path the price process takes. pub trajectory: Trajectories, - /// The `LiquidExchange` contract with the admin `Client`. + /// The [`LiquidExchange`] contract with the admin `Client`. pub liquid_exchange: LiquidExchange, /// The index of the current price in the trajectory. @@ -20,8 +20,8 @@ pub struct PriceChanger { } impl PriceChanger { - /// Create a new `PriceChanger` with the given `LiquidExchange` contract - /// bound to the admin `Client`. The `PriceChanger` will use the + /// Create a new [`PriceChanger`] with the given [`LiquidExchange`] contract + /// bound to the admin `Client`. The [`PriceChanger`] will use the /// `OrnsteinUhlenbeck` process to generate a price trajectory with the /// constants defined in `config.rs`. /// Ornstein-Uhlenbeck processes are useful for modeling the price of stable @@ -76,7 +76,7 @@ impl PriceChanger { }) } - /// Update the price of the `LiquidExchange` contract to the next price in + /// Update the price of the [`LiquidExchange`] contract to the next price in /// the trajectory and increment the index. pub async fn update_price(&mut self) -> Result<()> { let price = self.trajectory.paths[0][self.index]; diff --git a/src/agents/token_admin.rs b/src/agents/token_admin.rs index b35ff34..f45b6e5 100644 --- a/src/agents/token_admin.rs +++ b/src/agents/token_admin.rs @@ -1,18 +1,34 @@ use std::sync::Arc; -use arbiter_core::{middleware::RevmMiddleware, environment::Environment, bindings::arbiter_token::ArbiterToken}; +use arbiter_core::{ + bindings::arbiter_token::ArbiterToken, environment::Environment, middleware::RevmMiddleware, +}; use ethers::types::{Address, U256}; use super::*; +/// Manages the administrative operations for two types of tokens within the simulation environment. +/// The token admin is responsible for minting tokens to agents and other contracts. #[derive(Clone)] pub struct TokenAdmin { + /// The client interface for interacting with the [RevmMiddleware]. pub client: Arc, + + /// The arbiter token X contract. pub arbx: ArbiterToken, + + /// The arbiter token Y contract. pub arby: ArbiterToken, } impl TokenAdmin { + /// Creates a new [`TokenAdmin`] instance, deploying two ArbiterToken contracts. + /// + /// # Arguments + /// * [`Environment`] - The simulation environment containing blockchain network configurations. + /// + /// # Returns + /// * [`Result`] - The result of the operation, yielding a new [`TokenAdmin`] if successful. pub async fn new(environment: &Environment) -> Result { let client = RevmMiddleware::new(environment, "token_admin".into())?; let decimals = 18_u8; diff --git a/src/bindings/counter.rs b/src/bindings/counter.rs index 03ccfcb..936824b 100644 --- a/src/bindings/counter.rs +++ b/src/bindings/counter.rs @@ -7,7 +7,7 @@ pub use counter::*; clippy::upper_case_acronyms, clippy::type_complexity, dead_code, - non_camel_case_types, + non_camel_case_types )] pub mod counter { #[allow(deprecated)] @@ -17,102 +17,77 @@ pub mod counter { functions: ::core::convert::From::from([ ( ::std::borrow::ToOwned::to_owned("increment"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("increment"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::NonPayable, - }, - ], + ::std::vec![::ethers::core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("increment"), + inputs: ::std::vec![], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers::core::abi::ethabi::StateMutability::NonPayable, + },], ), ( ::std::borrow::ToOwned::to_owned("number"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("number"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], + ::std::vec![::ethers::core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("number"), + inputs: ::std::vec![], + outputs: ::std::vec![::ethers::core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers::core::abi::ethabi::ParamType::Uint(256usize,), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint256"), + ), + },], + constant: ::core::option::Option::None, + state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, + },], ), ( ::std::borrow::ToOwned::to_owned("setNumber"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("setNumber"), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("newNumber"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ]), - events: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("Incremented"), - ::std::vec![ - ::ethers::core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("Incremented"), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("number"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - indexed: false, - }, - ], - anonymous: false, - }, - ], + ::std::vec![::ethers::core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("setNumber"), + inputs: ::std::vec![::ethers::core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("newNumber"), + kind: ::ethers::core::abi::ethabi::ParamType::Uint(256usize,), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint256"), + ), + },], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers::core::abi::ethabi::StateMutability::NonPayable, + },], ), ]), + events: ::core::convert::From::from([( + ::std::borrow::ToOwned::to_owned("Incremented"), + ::std::vec![::ethers::core::abi::ethabi::Event { + name: ::std::borrow::ToOwned::to_owned("Incremented"), + inputs: ::std::vec![::ethers::core::abi::ethabi::EventParam { + name: ::std::borrow::ToOwned::to_owned("number"), + kind: ::ethers::core::abi::ethabi::ParamType::Uint(256usize,), + indexed: false, + },], + anonymous: false, + },], + )]), errors: ::std::collections::BTreeMap::new(), receive: false, fallback: false, } } ///The parsed JSON ABI of the contract. - pub static COUNTER_ABI: ::ethers::contract::Lazy<::ethers::core::abi::Abi> = ::ethers::contract::Lazy::new( - __abi, - ); + pub static COUNTER_ABI: ::ethers::contract::Lazy<::ethers::core::abi::Abi> = + ::ethers::contract::Lazy::new(__abi); #[rustfmt::skip] const __BYTECODE: &[u8] = b"`\x80`@R4\x80\x15a\0]W`@QbF\x1B\xCD`\xE5\x1B\x81R` `\x04\x82\x01R`\"`$\x82\x01R\x7FEther sent to non-payable functi`D\x82\x01\x90\x81Ra7\xB7`\xF1\x1B`d\x83\x01R`\x84\x82\xFD[Pa\x028\x80a\0m`\09`\0\xF3\xFE`\x80`@R4\x80\x15a\0]W`@QbF\x1B\xCD`\xE5\x1B\x81R` `\x04\x82\x01R`\"`$\x82\x01R\x7FEther sent to non-payable functi`D\x82\x01\x90\x81Ra7\xB7`\xF1\x1B`d\x83\x01R`\x84\x82\xFD[P`\x046\x10a\0\x8EW`\x005`\xE0\x1C\x80c?\xB5\xC1\xCB\x14a\0\xF3W\x80c\x83\x81\xF5\x8A\x14a\x01\x08W\x80c\xD0\x9D\xE0\x8A\x14a\x01#W[`@QbF\x1B\xCD`\xE5\x1B\x81R` `\x04\x82\x01R`5`$\x82\x01R\x7FContract does not have fallback `D\x82\x01\x90\x81Rtnor receive functions`X\x1B`d\x83\x01R`\x84\x82\xFD[a\x01\x06a\x01\x016`\x04a\x01wV[`\0UV[\0[a\x01\x11`\0T\x81V[`@Q\x90\x81R` \x01`@Q\x80\x91\x03\x90\xF3[a\x01\x06`\0\x80T\x90\x80a\x015\x83a\x01\xDBV[\x91\x90PUP\x7F \xD8\xA6\xF5\xA6\x93\xF9\xD1\xD6'\xA5\x98\xE8\x82\x0FzU\xEEt\xC1\x83\xAA\x8F\x1A0\xE8\xD4\xE8\xDD\x9A\x8D\x84`\0T`@Qa\x01m\x91\x81R` \x01\x90V[`@Q\x80\x91\x03\x90\xA1V[`\0` \x82\x84\x03\x12\x15a\x01\xD4W`@QbF\x1B\xCD`\xE5\x1B\x81R` `\x04\x82\x01R`\"`$\x82\x01R\x7FABI decoding: tuple data too sho`D\x82\x01Ra\x1C\x9D`\xF2\x1B`d\x82\x01R`\x84\x81\xFD[P5\x91\x90PV[`\0`\x01\x82\x01a\x01\xFBWcNH{q`\xE0\x1B`\0R`\x11`\x04R`$`\0\xFD[P`\x01\x01\x90V\xFE\xA2dipfsX\"\x12 \xDB\xAA\xB4\x86\xE5\x17I\xF1\x17\x15\x88\x9D\xFE\x8A\x18<\xD6\x1E\xB3\xEB\x1E\xC2\x1AB\xF5\x93\xF7\xCE\xC8\x8F}qdsolcC\0\x08\x13\x003"; /// The bytecode of the contract. - pub static COUNTER_BYTECODE: ::ethers::core::types::Bytes = ::ethers::core::types::Bytes::from_static( - __BYTECODE, - ); + pub static COUNTER_BYTECODE: ::ethers::core::types::Bytes = + ::ethers::core::types::Bytes::from_static(__BYTECODE); #[rustfmt::skip] const __DEPLOYED_BYTECODE: &[u8] = b"`\x80`@R4\x80\x15a\0]W`@QbF\x1B\xCD`\xE5\x1B\x81R` `\x04\x82\x01R`\"`$\x82\x01R\x7FEther sent to non-payable functi`D\x82\x01\x90\x81Ra7\xB7`\xF1\x1B`d\x83\x01R`\x84\x82\xFD[P`\x046\x10a\0\x8EW`\x005`\xE0\x1C\x80c?\xB5\xC1\xCB\x14a\0\xF3W\x80c\x83\x81\xF5\x8A\x14a\x01\x08W\x80c\xD0\x9D\xE0\x8A\x14a\x01#W[`@QbF\x1B\xCD`\xE5\x1B\x81R` `\x04\x82\x01R`5`$\x82\x01R\x7FContract does not have fallback `D\x82\x01\x90\x81Rtnor receive functions`X\x1B`d\x83\x01R`\x84\x82\xFD[a\x01\x06a\x01\x016`\x04a\x01wV[`\0UV[\0[a\x01\x11`\0T\x81V[`@Q\x90\x81R` \x01`@Q\x80\x91\x03\x90\xF3[a\x01\x06`\0\x80T\x90\x80a\x015\x83a\x01\xDBV[\x91\x90PUP\x7F \xD8\xA6\xF5\xA6\x93\xF9\xD1\xD6'\xA5\x98\xE8\x82\x0FzU\xEEt\xC1\x83\xAA\x8F\x1A0\xE8\xD4\xE8\xDD\x9A\x8D\x84`\0T`@Qa\x01m\x91\x81R` \x01\x90V[`@Q\x80\x91\x03\x90\xA1V[`\0` \x82\x84\x03\x12\x15a\x01\xD4W`@QbF\x1B\xCD`\xE5\x1B\x81R` `\x04\x82\x01R`\"`$\x82\x01R\x7FABI decoding: tuple data too sho`D\x82\x01Ra\x1C\x9D`\xF2\x1B`d\x82\x01R`\x84\x81\xFD[P5\x91\x90PV[`\0`\x01\x82\x01a\x01\xFBWcNH{q`\xE0\x1B`\0R`\x11`\x04R`$`\0\xFD[P`\x01\x01\x90V\xFE\xA2dipfsX\"\x12 \xDB\xAA\xB4\x86\xE5\x17I\xF1\x17\x15\x88\x9D\xFE\x8A\x18<\xD6\x1E\xB3\xEB\x1E\xC2\x1AB\xF5\x93\xF7\xCE\xC8\x8F}qdsolcC\0\x08\x13\x003"; /// The deployed bytecode of the contract. - pub static COUNTER_DEPLOYED_BYTECODE: ::ethers::core::types::Bytes = ::ethers::core::types::Bytes::from_static( - __DEPLOYED_BYTECODE, - ); + pub static COUNTER_DEPLOYED_BYTECODE: ::ethers::core::types::Bytes = + ::ethers::core::types::Bytes::from_static(__DEPLOYED_BYTECODE); pub struct Counter(::ethers::contract::Contract); impl ::core::clone::Clone for Counter { fn clone(&self) -> Self { @@ -132,7 +107,9 @@ pub mod counter { } impl ::core::fmt::Debug for Counter { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(::core::stringify!(Counter)).field(&self.address()).finish() + f.debug_tuple(::core::stringify!(Counter)) + .field(&self.address()) + .finish() } } impl Counter { @@ -142,13 +119,11 @@ pub mod counter { address: T, client: ::std::sync::Arc, ) -> Self { - Self( - ::ethers::contract::Contract::new( - address.into(), - COUNTER_ABI.clone(), - client, - ), - ) + Self(::ethers::contract::Contract::new( + address.into(), + COUNTER_ABI.clone(), + client, + )) } /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it. /// Returns a new instance of a deployer that returns an instance of this contract after sending the transaction @@ -215,26 +190,20 @@ pub mod counter { ///Gets the contract's `Incremented` event pub fn incremented_filter( &self, - ) -> ::ethers::contract::builders::Event< - ::std::sync::Arc, - M, - IncrementedFilter, - > { + ) -> ::ethers::contract::builders::Event<::std::sync::Arc, M, IncrementedFilter> + { self.0.event() } /// Returns an `Event` builder for all the events of this contract. pub fn events( &self, - ) -> ::ethers::contract::builders::Event< - ::std::sync::Arc, - M, - IncrementedFilter, - > { - self.0.event_with_filter(::core::default::Default::default()) + ) -> ::ethers::contract::builders::Event<::std::sync::Arc, M, IncrementedFilter> + { + self.0 + .event_with_filter(::core::default::Default::default()) } } - impl From<::ethers::contract::Contract> - for Counter { + impl From<::ethers::contract::Contract> for Counter { fn from(contract: ::ethers::contract::Contract) -> Self { Self::new(contract.address(), contract.client()) } @@ -249,7 +218,7 @@ pub mod counter { Debug, PartialEq, Eq, - Hash + Hash, )] #[ethevent(name = "Incremented", abi = "Incremented(uint256)")] pub struct IncrementedFilter { @@ -266,7 +235,7 @@ pub mod counter { Debug, PartialEq, Eq, - Hash + Hash, )] #[ethcall(name = "increment", abi = "increment()")] pub struct IncrementCall; @@ -281,7 +250,7 @@ pub mod counter { Debug, PartialEq, Eq, - Hash + Hash, )] #[ethcall(name = "number", abi = "number()")] pub struct NumberCall; @@ -296,7 +265,7 @@ pub mod counter { Debug, PartialEq, Eq, - Hash + Hash, )] #[ethcall(name = "setNumber", abi = "setNumber(uint256)")] pub struct SetNumberCall { @@ -311,7 +280,7 @@ pub mod counter { Debug, PartialEq, Eq, - Hash + Hash, )] pub enum CounterCalls { Increment(IncrementCall), @@ -323,19 +292,13 @@ pub mod counter { data: impl AsRef<[u8]>, ) -> ::core::result::Result { let data = data.as_ref(); - if let Ok(decoded) = ::decode( - data, - ) { + if let Ok(decoded) = ::decode(data) { return Ok(Self::Increment(decoded)); } - if let Ok(decoded) = ::decode( - data, - ) { + if let Ok(decoded) = ::decode(data) { return Ok(Self::Number(decoded)); } - if let Ok(decoded) = ::decode( - data, - ) { + if let Ok(decoded) = ::decode(data) { return Ok(Self::SetNumber(decoded)); } Err(::ethers::core::abi::Error::InvalidData.into()) @@ -344,13 +307,9 @@ pub mod counter { impl ::ethers::core::abi::AbiEncode for CounterCalls { fn encode(self) -> Vec { match self { - Self::Increment(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } + Self::Increment(element) => ::ethers::core::abi::AbiEncode::encode(element), Self::Number(element) => ::ethers::core::abi::AbiEncode::encode(element), - Self::SetNumber(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } + Self::SetNumber(element) => ::ethers::core::abi::AbiEncode::encode(element), } } } @@ -389,7 +348,7 @@ pub mod counter { Debug, PartialEq, Eq, - Hash + Hash, )] pub struct NumberReturn(pub ::ethers::core::types::U256); } diff --git a/src/bindings/mod.rs b/src/bindings/mod.rs index be18f2f..455a6a1 100644 --- a/src/bindings/mod.rs +++ b/src/bindings/mod.rs @@ -3,4 +3,4 @@ //! This is autogenerated code. //! Do not manually edit these files. //! These files may be overwritten by the codegen system at any time. -pub mod counter; \ No newline at end of file +pub mod counter; diff --git a/src/main.rs b/src/main.rs index bf6fffc..404f906 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,12 @@ use std::time::Instant; use anyhow::Result; use clap::{ArgAction, CommandFactory, Parser, Subcommand}; -pub mod simulations; -pub mod settings; pub mod agents; pub mod bindings; +pub mod settings; +pub mod simulations; -/// Represents command-line arguments passed to the `Arbiter` tool. +/// Represents command-line arguments passed to this binary. #[derive(Parser)] #[clap(name = "Excalibur")] #[clap(version = env!("CARGO_PKG_VERSION"))] @@ -34,6 +34,22 @@ enum Commands { }, } +/// The entry point for the simulation tool. +/// +/// This binary provides a command-line interface for the simulation-driven development. +/// It allows users to run simulations by specifying configuration paths, with detailed command-line +/// feedback provided through the `clap` crate. +/// +/// # Usage +/// Run the binary without arguments to see available commands and options. +/// Example usage for running simulations: +/// ``` +/// $ cargo run simulate [path_to_config] +/// ``` +/// +/// By default, if no configuration path is provided, it will read from "src/config/gbm.toml". +/// +/// These simulations are performed in Arbiter's in memory revm instance and with the exposed RevmMiddleware. fn main() -> Result<()> { let args = Args::parse(); @@ -41,6 +57,7 @@ fn main() -> Result<()> { Some(Commands::Simulate { config_path }) => { println!("Reading from config path: {}", config_path); let start = Instant::now(); + // This is the entry point for the simulation simulations::batch(config_path)?; let duration = start.elapsed(); println!("Total duration of simulations: {:?}", duration); @@ -49,4 +66,3 @@ fn main() -> Result<()> { } Ok(()) } - diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 2af4dc3..85d3cd7 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -1,24 +1,44 @@ pub mod parameters; -use std::{env, path::Path}; use parameters::*; use crate::simulations::SimulationType; -use serde::{Deserialize, Serialize}; use config::{Config, ConfigError}; +use serde::{Deserialize, Serialize}; +/// Defines the configuration for a simulation. +/// +/// This struct holds all the necessary parameters and configurations needed to run a simulation. +/// It encompasses several sub-configurations such as `TrajectoryParameters` and `GBMParameters`. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SimulationConfig> { + /// The type of simulation to run, defined by an enum `SimulationType`. pub simulation: SimulationType, + + /// Maximum number of parallel simulations to run. pub max_parallel: Option, + + /// Directory where the simulation output will be stored. pub output_directory: String, + + /// Name of the file where the simulation results will be written. pub output_file_name: Option, + + /// Parameters specific to the trajectory of the simulation. pub trajectory: TrajectoryParameters

, + + /// Parameters specific to the Geometric Brownian Motion (GBM) if applicable. pub gbm: Option>, + + /// Parameters related to block configurations. pub block: BlockParameters, } impl SimulationConfig { + /// Creates a new `SimulationConfig` instance from a configuration file. + /// + /// Reads the specified configuration file and deserializes it into a `SimulationConfig` object. + /// The `config_path` is the path to the configuration file in question. pub fn new(config_path: &str) -> Result { let s = Config::builder() .add_source(config::File::with_name(config_path)) @@ -28,6 +48,10 @@ impl SimulationConfig { } impl Parameterized> for SimulationConfig { + /// Generates a list of `SimulationConfig` instances with fixed parameters. + /// + /// This method is responsible for taking the meta parameters defined in the configuration, + /// generating the actual fixed parameters, and creating a list of complete `SimulationConfig` instances. fn generate(&self) -> Vec> { let mut result = vec![]; let trajectories = self.trajectory.generate(); @@ -65,4 +89,4 @@ impl Parameterized> for SimulationConfig { result } -} \ No newline at end of file +} diff --git a/src/settings/parameters.rs b/src/settings/parameters.rs index fcc7725..d5d3e41 100644 --- a/src/settings/parameters.rs +++ b/src/settings/parameters.rs @@ -1,10 +1,19 @@ use std::{collections::hash_map::DefaultHasher, hash::Hasher}; use super::*; + +/// A trait defining objects that can generate a set of parameters. +/// +/// This trait is implemented by various parameter structs that provide a method +/// to produce a vector of parameters based on their internal state. pub trait Parameterized { fn generate(&self) -> Vec; } +/// Represents a fixed parameter value. +/// +/// This struct holds a fixed value of type `f64` that can be generated +/// directly without any modification. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Fixed(pub f64); impl Parameterized for Fixed { @@ -13,6 +22,10 @@ impl Parameterized for Fixed { } } +/// Represents meta parameter configuration. +/// +/// This struct wraps around the `LinspaceParameters` to facilitate parameter generation +/// in a certain defined space. #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Meta(LinspaceParameters); impl Parameterized for Meta { @@ -21,6 +34,10 @@ impl Parameterized for Meta { } } +/// Contains the parameters for generating a linear space of values. +/// +/// This struct can be configured to generate a sequence of evenly spaced values +/// between a start and end point, or a single fixed value. #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct LinspaceParameters { pub start: Option, @@ -53,19 +70,34 @@ pub struct BlockParameters { pub timestep_size: u64, } +/// Defines parameters for a trajectory in the simulation. +/// +/// Contains information like initial price, start and end times, +/// and number of steps and paths in the simulation. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TrajectoryParameters> { + /// The name. pub process: String, + /// The initial price of the asset. pub initial_price: P, + /// The start time of the process. pub t_0: P, + /// The end time of the process. pub t_n: P, + /// The number of steps in the process. pub num_steps: usize, + + /// The number of paths in the process. pub num_paths: usize, + + /// The seed for the process. pub seed: u64, + + /// The tag for the output file. pub output_tag: Option, } @@ -101,10 +133,12 @@ impl Parameterized> for TrajectoryParameters { } } +/// Contains the parameters for the Geometric Brownian Motion (GBM) process. #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct GBMParameters> { // The drift of the process. pub drift: P, + // The volatility of the process. pub volatility: P, } @@ -126,12 +160,15 @@ impl Parameterized> for GBMParameters { } } +/// Contains the parameters for the Ornstein–Uhlenbeck (OU) process. #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct OUParameters> { /// The mean (price) of the process. pub mean: P, + /// The standard deviation of the process. pub std_dev: P, + /// The theta parameter of the process. /// This describes how strongly the process will revert to the mean. pub theta: P, diff --git a/src/simulations/counter.rs b/src/simulations/counter.rs index 1037a2d..3e11422 100644 --- a/src/simulations/counter.rs +++ b/src/simulations/counter.rs @@ -1,15 +1,26 @@ -use arbiter_core::{data_collection::EventLogger, environment::builder::{BlockSettings, EnvironmentBuilder}}; +use arbiter_core::{ + data_collection::EventLogger, + environment::builder::{BlockSettings, EnvironmentBuilder}, +}; +use super::*; use crate::{ - agents::{ - block_admin::BlockAdmin, - counter_agent::CounterAgent, - Agents, - }, + agents::{block_admin::BlockAdmin, counter_agent::CounterAgent, Agents}, settings::SimulationConfig, }; -use super::*; +/// Asynchronously sets up a `Counter` simulation using the provided configuration. +/// +/// This function prepares the environment, initializes the `BlockAdmin` and `CounterAgent`, +/// logs events, and returns a `Simulation` with the configured agents, steps, and environment. +/// +/// # Arguments +/// +/// * `config` - The configuration for the simulation based on `SimulationConfig`. +/// +/// # Returns +/// +/// * A `Result` containing the fully initialized `Simulation` or an error if any step fails. pub async fn setup(config: SimulationConfig) -> Result { let environment = EnvironmentBuilder::new() .block_settings(BlockSettings::UserControlled) @@ -25,9 +36,7 @@ pub async fn setup(config: SimulationConfig) -> Result { .run()?; Ok(Simulation { - agents: Agents::new() - .add(block_admin) - .add(counter_agent), + agents: Agents::new().add(block_admin).add(counter_agent), steps: config.trajectory.num_steps, environment, }) diff --git a/src/simulations/mod.rs b/src/simulations/mod.rs index faab844..34281dd 100644 --- a/src/simulations/mod.rs +++ b/src/simulations/mod.rs @@ -1,38 +1,54 @@ +/// Arbiter Simulation module for handling different types of simulations. +/// +/// This module provides structs and functions for executing and managing +/// various types of simulations, including counter simulations and price path simulations. use arbiter_core::environment::Environment; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::Semaphore; -use serde::{Serialize, Deserialize}; use super::*; -use crate::{ - agents::{Agent, Agents}, - settings::parameters::Fixed, -}; +use crate::{agents::Agents, settings::parameters::Fixed}; -pub mod price_path_simulation; pub mod counter; +pub mod price_path_simulation; -use settings::parameters::Parameterized; -use tokio::runtime::Builder; use crate::settings::SimulationConfig; use anyhow::Result; +use settings::parameters::Parameterized; +use tokio::runtime::Builder; +/// Represents the main Simulation structure. +/// +/// This struct encapsulates agents, steps, and the environment needed +/// for a simulation. pub struct Simulation { pub agents: Agents, pub steps: usize, environment: Environment, } +/// Defines the types of simulations available. +/// +/// The `SimulationType` enum provides an easy way to specify and differentiate +/// between different types of simulations, such as `SimulatedPricePath` and `Counter`. +/// If you wanted to add a simulation you would add it here to this enum #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum SimulationType { SimulatedPricePath, - Counter + Counter, } impl SimulationType { + /// Asynchronously runs the specified simulation type based on the provided configuration. + /// + /// This function matches on the `SimulationType` to determine which simulation setup to use, + /// then executes the chosen simulation. async fn run(config: SimulationConfig) -> Result<()> { let simulation = match config.simulation { - SimulationType::SimulatedPricePath => price_path_simulation::setup(config.clone()).await?, + SimulationType::SimulatedPricePath => { + price_path_simulation::setup(config.clone()).await? + } SimulationType::Counter => counter::setup(config.clone()).await?, }; match looper(simulation.agents, simulation.steps).await { @@ -41,12 +57,6 @@ impl SimulationType { Ok(()) } Err(e) => { - let metadata = format!( - "{}_{}", - config.output_directory, - config.output_file_name.unwrap() - ); - let error_string = format!("Error in simulation `{:?}`: {:?}", metadata, e); simulation.environment.stop()?; Err(e) } @@ -54,8 +64,12 @@ impl SimulationType { } } - +/// Executes a batch of simulations based on the provided configuration path. +/// +/// This function sets up multiple simulations to run in parallel, manages available resources using a semaphore, +/// and handles any errors that arise during execution. pub fn batch(config_path: &str) -> Result<()> { + // let config = SimulationConfig::new(config_path)?; let direct_configs: Vec> = config.generate(); @@ -108,8 +122,11 @@ pub fn batch(config_path: &str) -> Result<()> { }) } +/// Asynchronously loops through agents and performs the steps for each agent. +/// +/// This function starts each agent, then performs priority steps and regular steps +/// for a given number of iterations. pub async fn looper(mut agents: Agents, steps: usize) -> Result<()> { - for agent in agents.iter_mut() { agent.startup().await?; } @@ -125,4 +142,4 @@ pub async fn looper(mut agents: Agents, steps: usize) -> Result<()> { } Ok(()) -} \ No newline at end of file +} diff --git a/src/simulations/price_path_simulation.rs b/src/simulations/price_path_simulation.rs index 84546e8..56f51d0 100644 --- a/src/simulations/price_path_simulation.rs +++ b/src/simulations/price_path_simulation.rs @@ -1,15 +1,29 @@ -use arbiter_core::{data_collection::EventLogger, environment::builder::{BlockSettings, EnvironmentBuilder}}; +use arbiter_core::{ + data_collection::EventLogger, + environment::builder::{BlockSettings, EnvironmentBuilder}, +}; +use super::*; use crate::{ agents::{ - block_admin::BlockAdmin, - price_changer::PriceChanger, token_admin::TokenAdmin, - Agent, Agents, + block_admin::BlockAdmin, price_changer::PriceChanger, token_admin::TokenAdmin, Agents, }, settings::SimulationConfig, }; -use super::*; +/// Asynchronously sets up a `SimulatedPricePath` simulation using the provided configuration. +/// +/// This function prepares the environment, initializes various agents including +/// `BlockAdmin`, `TokenAdmin`, and `PriceChanger`, logs events, and then returns a `Simulation` +/// object which houses the configured agents, steps, and the environment. +/// +/// # Arguments +/// +/// * `config` - The configuration for the simulation based on `SimulationConfig`. +/// +/// # Returns +/// +/// * A `Result` containing the fully initialized `Simulation` or an error if any step of the setup fails. pub async fn setup(config: SimulationConfig) -> Result { let environment = EnvironmentBuilder::new() .block_settings(BlockSettings::UserControlled) @@ -26,9 +40,7 @@ pub async fn setup(config: SimulationConfig) -> Result { .run()?; Ok(Simulation { - agents: Agents::new() - .add(price_changer) - .add(block_admin), + agents: Agents::new().add(price_changer).add(block_admin), steps: config.trajectory.num_steps, environment, }) From b6c2ba50fdeda13c307d118024a0421976f6b9c4 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Wed, 1 Nov 2023 13:53:43 -0600 Subject: [PATCH 2/3] readme links --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c026e0..fd154bf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Arbiter Template -Minimal template for simulating contracts with arbiter. This template is used by the `arbiter init` command when starting new simulations. This template provides a framework for performantly simulating Agent Based Models (ABM) with evm parity. In this model you can think of any things that happens as an action of an agent. Agents can own keys and externally owned accounts, they can interact with each other and they can interact with contracts. This repository has some example agents including a `TokenAdmin`, `BlockAdmin`, and `CounterAgent` which their own functionality and responsibilities. We also give an example of how to parametarize your simulations with a configuration file containing different price paths and price path parameters. These can be played with to see how the simulation changes. Furthermore we provide an api to batch simulations and run them in parallel. This is useful for running many simulations with different parameters. +Minimal template for simulating contracts with arbiter. This template is used by the `arbiter init` command when starting new simulations. This template provides a framework for performantly simulating Agent Based Models (ABM) with evm parity. In this model you can think of any things that happens as an action of an agent. Agents can own keys and externally owned accounts, they can interact with each other and they can interact with smart contracts. + +This repository has some example agents including a [`TokenAdmin`](src/agents/token_admin.rs), [`BlockAdmin`](src/agents/block_admin.rs), and [`CounterAgent`](src/agents/counter_agent.rs) which their own functionality and responsibilities. We also give an example of [how to parametarize your simulations](src/settings/mod.rs) with a configuration file containing different price paths and price path parameters. These can be played with to see how the simulation changes. Furthermore we provide an [api to batch simulations](src/simulations/mod.rs) and run them in parallel. This is useful for running many simulations with different parameters. ### Prerequisites From bdc42e9c32dbdf57d4fc5dc5f18ebf5b83430cb5 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Thu, 2 Nov 2023 16:45:20 -0400 Subject: [PATCH 3/3] rm .vscode --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 77cc3a7..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.linkedProjects": ["./Cargo.toml", "./Cargo.toml"] -}