From 33b50696c88c036d2c927e97ea60832976efc483 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 23 Mar 2023 12:12:14 -0600 Subject: [PATCH 1/4] added documentation for environment --- crates/cli/src/main.rs | 2 +- crates/simulate/src/agent.rs | 7 +-- crates/simulate/src/environment.rs | 70 +++++++++++++++++++++++------- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 5fbd5d74..f7ecc728 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -9,7 +9,7 @@ mod config; #[derive(Parser)] #[command(name = "Arbiter")] -#[command(version = "1.0")] +#[command(version = "0.1.0")] #[command(about = "Data analysis tool for decentralized exchanges.", long_about = None)] #[command(author)] struct Args { diff --git a/crates/simulate/src/agent.rs b/crates/simulate/src/agent.rs index 62e6e474..a994bdfa 100644 --- a/crates/simulate/src/agent.rs +++ b/crates/simulate/src/agent.rs @@ -3,8 +3,6 @@ use revm::primitives::{ExecutionResult, Log, TxEnv, B160, U256}; use crate::environment::{IsDeployed, SimulationContract}; -// use crate::environment::SimulationEnvironment; - pub struct TransactSettings { pub gas_limit: u64, pub gas_price: U256, @@ -24,7 +22,6 @@ pub trait Agent { call_data: Bytes, value: U256, ) -> TxEnv; - fn read_logs(&mut self) -> Vec; // TODO: Not sure this needs to be mutable self - - // TODO: Should agents be labeled as `active` or `inactive` similarly to `IsDeployed` and `NotDeployed`? + // TODO: Not sure this needs to be mutable self + fn read_logs(&mut self) -> Vec; } diff --git a/crates/simulate/src/environment.rs b/crates/simulate/src/environment.rs index e5904fe5..a6ff9226 100644 --- a/crates/simulate/src/environment.rs +++ b/crates/simulate/src/environment.rs @@ -1,7 +1,13 @@ +#![warn(missing_docs)] +//! This module contains the structs necessary to serve as an environment that a simulation that can be run inside of. +//! The `SimulationManager` inherits the `Agent` trait and is given control over the `SimulationEnvironment` struct. +//! The job of the manager is to deploy contracts and call upon special functionality that guides the simulation forward. + use std::{ collections::HashMap, str::FromStr, sync::{Arc, RwLock}, + thread, }; use bytes::Bytes; @@ -19,15 +25,20 @@ use revm::{ use crate::agent::{Agent, TransactSettings}; +/// The `SimulationEnvironment` struct controls the execution environment (EVM), has an associated set of agents, and provides an event (ETH log) buffer. struct SimulationEnvironment { evm: EVM>, _agents: HashMap>, event_buffer: Arc>>, // TODO: This should probably just store head - writer_thread: Option>, + writer_thread: Option>, } -impl Default for SimulationEnvironment { - /// Public default constructor function to instantiate an `ExecutionManager`. +impl SimulationEnvironment { + /** + Public default constructor function to instantiate an `ExecutionManager`. + Private and can only be called by the `SimulationManager` struct. + This prevents a user from creating an `ExecutionManager` without a `SimulationManager` which will not function well. + */ fn default() -> Self { let mut evm = EVM::new(); let db = CacheDB::new(EmptyDB {}); @@ -38,12 +49,14 @@ impl Default for SimulationEnvironment { evm, _agents: HashMap::new(), // This will only store agents that aren't the manager. event_buffer: Arc::new(RwLock::new(Vec::::new())), - writer_thread: Some(std::thread::spawn(|| {})), + writer_thread: Some(thread::spawn(|| {})), } } } +/// `SimulationEnvironment` serves as a mechanism for getting EVM computation results and distributing logs to agents. impl SimulationEnvironment { + /// `execute` is a public function that takes a transaction and executes it on the EVM. fn execute(&mut self, tx: TxEnv) -> ExecutionResult { self.evm.env.tx = tx; let execution_result = match self.evm.transact_commit() { @@ -56,7 +69,7 @@ impl SimulationEnvironment { self.echo_logs(logs); execution_result } - // TODO: Echo logs to a buffer that all agents can read and await. + /// `echo_logs` is a private function that takes a vector of logs and writes them to the event buffer that all agents can read. fn echo_logs(&mut self, logs: Vec) { // let writer_thread = self.writer_thread.take(); if let Some(handle) = self.writer_thread.take() { @@ -69,19 +82,28 @@ impl SimulationEnvironment { } // TODO: Implementing the following functions could be useful. // fn decode_event; - // fn decode_output; - // fn handle_execution_result; + // fn decode_output; // These two could be combined. + // fn unpack_execution_result; } - +/** +`SimulationManager` is the main struct that is used to control the simulation environment. +It acts in some ways as an agent, but it also has special control over the environment such as deploying contracts. +It is the only agent that contains an execution environment. +*/ pub struct SimulationManager { - pub address: B160, //TODO: Consider using Address=H160 instead of B160 + /// `address` is the public address of the simulation manager. + pub address: B160, //TODO: Consider using Address=H160 instead of B160, or wait for ruints. + /// `account` is the revm-primitive account of the simulation manager. pub account: Account, + /// `environment` is the `SimulationEnvironment` that the simulation manager controls. environment: SimulationEnvironment, + /// `transact_settings` is a struct that contains the default transaction options for revm such as gas limit and gas price. pub transact_settings: TransactSettings, } impl Agent for SimulationManager { // TODO: Can calling a contract ever need for raw eth "value" to be sent along with? + /// `call_contract` is a public function that takes a contract call and passes it to the execution environment. fn call_contract( &mut self, contract: &SimulationContract, @@ -92,11 +114,11 @@ impl Agent for SimulationManager { self.environment.execute(tx) } // TODO: Handle the output of the execution result and decode? - + /// `read_logs` gets the most current event (which is all that is stored in the event buffer). fn read_logs(&mut self) -> Vec { self.environment.event_buffer.read().unwrap().to_vec() } - + /// `build_call_transaction` is a constructor function that takes an address and an account and returns a `TxEnv` which the EVM uses natively. fn build_call_transaction( &self, receiver_address: B160, @@ -118,6 +140,8 @@ impl Agent for SimulationManager { } } impl Default for SimulationManager { + /// `default` is a public constructor function that returns a `SimulationManager` with default values. + /// Unless more simulations are being run in parallel, this is the only constructor function that should be used since a manager comes equipped with an environment. fn default() -> Self { Self { address: B160::from_str("0x0000000000000000000000000000000000000001").unwrap(), @@ -134,6 +158,7 @@ impl Default for SimulationManager { impl SimulationManager { // TODO: Unpacking should probably be defined on ExecutionResult type. But that will be in the crate. + /// `unpack_execution` is a public function that takes an `ExecutionResult` and returns the raw bytes of the output that can then be decoded. pub fn unpack_execution(&self, execution_result: ExecutionResult) -> Bytes { // unpack output call enum into raw bytes match execution_result { @@ -147,7 +172,8 @@ impl SimulationManager { _ => panic!("This call generated no execution result. This should not happen."), } } - /// Used in the deploy function to create a transaction environment for deploying a contract. + /// `build_deploy_transaction` is a constructor function for `Create` types of transactions. + /// This is special to the `SimulationManager` since it is the only agent that can deploy contracts. fn build_deploy_transaction(&self, bytecode: Bytes) -> TxEnv { TxEnv { caller: self.address, @@ -163,8 +189,8 @@ impl SimulationManager { } } + // TODO: This should call `recast_address` when a B160 is passed as an arg. Not sure how to handle this yet. /// Deploy a contract. We will assume the sender is always the admin. - /// TODO: This should call `recast_address` when a B160 is passed as an arg. Not sure how to handle this yet. pub fn deploy( &mut self, contract: SimulationContract, @@ -222,7 +248,7 @@ impl SimulationManager { account.info.balance += amount; } - + /// `mint` can be used to mint certain tokens to specific addresses. pub fn mint(&mut self, _address: B160, _amount: U256) { todo!() } @@ -235,19 +261,30 @@ pub fn recast_address(address: B160) -> Address { } #[derive(Debug)] +/// A struct use for `PhantomData` to indicate a lock on contracts that are not deployed. pub struct NotDeployed; #[derive(Debug)] +// TODO: It would be good to also allow the `IsDeployed` to also mention which `SimulationManager` has deployed it (when we have multiple managers). +/// A struct use for `PhantomData` to indicate an unlocked contract that is deployed. pub struct IsDeployed; #[derive(Debug)] +/// A struct that wraps around the ethers `BaseContract` and adds some additional information relevant for revm and the simulation. pub struct SimulationContract { + /// The ethers `BaseContract` that holds the ABI. pub base_contract: BaseContract, + // TODO: Bytecode is really only used for deployment. Maybe we don't need to store it like this. + /// The contract's deployed bytecode. pub bytecode: Vec, - pub address: Option, /* TODO: Options may not be the best thing here. Also, B160 might not and Address=H160 might be. */ + //TODO: Options are not great here. We want an address for the deployment to some `SimulationEnvironment`. + /// The address of the contract within the relevant `SimulationEnvironment`. + pub address: Option, + /// A `PhantomData` marker to indicate whether the contract is deployed or not. pub deployed: std::marker::PhantomData, } impl SimulationContract { + /// A constructor function for `SimulationContract` that takes a `BaseContract` and the deployment bytecode. pub fn new(base_contract: BaseContract, bytecode: Vec) -> Self { Self { base_contract, @@ -256,7 +293,8 @@ impl SimulationContract { deployed: std::marker::PhantomData, } } - + /// Creates a new `SimulationContract` that is marked as deployed. + /// This is only called by implicitly by the `SimulationManager` inside of the `deploy` function. fn to_deployed(&self, address: B160) -> SimulationContract { SimulationContract { base_contract: self.base_contract.clone(), From 4ead37c134892258d0ed4123056ebf16a93d3f54 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 23 Mar 2023 13:21:42 -0600 Subject: [PATCH 2/4] removed from --- crates/simulate/src/agent.rs | 10 ++++++++-- crates/simulate/src/environment.rs | 1 - 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/simulate/src/agent.rs b/crates/simulate/src/agent.rs index a994bdfa..0dfccbae 100644 --- a/crates/simulate/src/agent.rs +++ b/crates/simulate/src/agent.rs @@ -1,14 +1,20 @@ +#![warn(missing_docs)] +//! The data that describes agents that live in a `SimulationEnvironment`. +//! All agents must implement the `Agent` trait. + use bytes::Bytes; use revm::primitives::{ExecutionResult, Log, TxEnv, B160, U256}; use crate::environment::{IsDeployed, SimulationContract}; +/// Describes the gas settings for a transaction. pub struct TransactSettings { + /// Gas limit for the transaction for a simulation. pub gas_limit: u64, + /// Gas limit for the transaction for a simulation. pub gas_price: U256, - pub value: U256, } - +/// Basic traits that every `Agent` must implement in order to properly interact with an EVM. pub trait Agent { fn call_contract( &mut self, diff --git a/crates/simulate/src/environment.rs b/crates/simulate/src/environment.rs index a6ff9226..ec95f490 100644 --- a/crates/simulate/src/environment.rs +++ b/crates/simulate/src/environment.rs @@ -150,7 +150,6 @@ impl Default for SimulationManager { transact_settings: TransactSettings { gas_limit: u64::MAX, gas_price: U256::ZERO, - value: U256::ZERO, }, } } From 04228b5ec0a6ee83c09861845e1d8018d09ae3a9 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 23 Mar 2023 13:51:43 -0600 Subject: [PATCH 3/4] edited README --- README.md | 40 +++++++------------------ crates/cli/src/main.rs | 4 +++ crates/simulate/src/agent.rs | 7 +++-- crates/simulate/src/lib.rs | 3 ++ crates/simulate/src/price_simulation.rs | 30 +++++++++++-------- 5 files changed, 41 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 0b127e61..4340f0b8 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,16 @@ > Perform high speed modeling and economic fuzzing with EVM parity. -Ethereum's execution environment, the Ethereum virtual machine (EVM), has given fruit to a rich collection of decentralized applications. The EVM is stack machine that sequentially executes opcodes as decentralized applications are used, deployed, or exploited. Arbiter is a highly configurable rust interface over [revm](https://github.com/bluealloy/revm). +The Ethereum blockchain's execution environment, the Ethereum Virtual machine (EVM), contains a rich collection of decentralized applications. The EVM is stack machine that sequentially executes opcodes sent to it by users and smart contracts. Arbiter is a highly configurable rust interface over [revm](https://github.com/bluealloy/revm) which is a Rust implementation of the EVM stack machine logic. The purpose of Arbiter is to interface with arbitrary agents and contracts and run this all directly on a blazing-fast simulated EVM. -Financial engineers need to study complex portfolio management strategies against many market conditions, contract parameters, and agents. To configure such a rich simulation environment on a test network would take months to get a sufficient quantity of data points to draw conclusions with confidence and isolate key variables. Even with a local network with no block time, the networking latency on port to port communication will be significant. +Financial engineers need to study a wide array of complex portfolio management strategies against thousands of market conditions, contract parameters, and agents. To configure such a rich simulation environment on a test network could be possible, but a more efficient choice for getting the most robust, yet quick, simulations would bypass any local networking and use a low level language's implementation of the EVM. -In financial engineering, this is a critical tool in evaluating capital efficiency, loss vs. rebalancing, and game theoretic security. Arbiter can be used for: +Arbiter is being primarily developed to be a tool in evaluating economic and game theoretic security of DeFi applications. -- Evaluating the game theoretic and composable security of smart contracts in production environments (Security Firms and Academics) -- Engineering and testing new financial products built on top of more primitive financial products (DeFi Firms and Academics) -- Evaluating financial risk and mitigation strategies (Funds, prop-shops, searchers) +Arbiter can be used for: +- Evaluating the game theoretic and composable security of smart contracts in production environments (security firms and academics) +- investigating risk, capital efficiency, rebalancing strategies, and portfolio replication (or performance). (LPs, funds, quants, traders) +- Engineering and testing new financial products built on top of more primitive financial products (DeFi firms and academics) ## Features: @@ -42,31 +43,12 @@ cargo install --path ./crates/cli Set the PROVIDER environment variable to use a custom provider. -## Setting Custom RPC - -If you would like to use your own RPC endpoint, then you can set the environment variable `PROVIDER`. By default, the provider we have set is via Alchemy. To set your own environment variable on a UNIX OS just perform: - -``` -export PROVIDER=https://url-to-your-RPC-endpoint.xyz -``` - -and replace your own URL as needed. Double check the environment variable is set by: - -``` -echo $PROVIDER -``` - -or just list all environment variables with: - -``` -env -``` - -If you need to unset the `PROVIDER` variable, do: - +## Generating Docs +To see the documentation for Arbiter, after cloning the repo, you can run: ``` -unset PROVIDER +cargo doc --workspace --no-deps --open ``` +This will generate and open the docs in your browser. From there, you can look at the documentation for each crate in the Arbiter workspace. ## Contributing diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index f7ecc728..398facb1 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,3 +1,6 @@ +#![warn(missing_docs)] +//! Main lives in the `cli` crate so that we can do our input parsing. + use clap::{CommandFactory, Parser, Subcommand}; use ethers::prelude::BaseContract; use eyre::Result; @@ -18,6 +21,7 @@ struct Args { command: Option, } +/// Subcommands for the Arbiter CLI. #[derive(Subcommand)] enum Commands { Sim { diff --git a/crates/simulate/src/agent.rs b/crates/simulate/src/agent.rs index 0dfccbae..291742d0 100644 --- a/crates/simulate/src/agent.rs +++ b/crates/simulate/src/agent.rs @@ -16,18 +16,21 @@ pub struct TransactSettings { } /// Basic traits that every `Agent` must implement in order to properly interact with an EVM. pub trait Agent { + /// Used to allow agentws to make a generic call a specific smart contract. fn call_contract( &mut self, contract: &SimulationContract, call_data: Bytes, value: U256, ) -> ExecutionResult; + /// A constructor to build a `TxEnv` for an agent (uses agent data like `address` and `TransactSettings`). fn build_call_transaction( &self, receiver_address: B160, call_data: Bytes, value: U256, ) -> TxEnv; - // TODO: Not sure this needs to be mutable self - fn read_logs(&mut self) -> Vec; + // TODO: Not sure `read_logs` needs to be mutable self. + /// Provides the ability to read event logs from the simulation's EVM. + fn read_logs(&mut self) -> Vec; } diff --git a/crates/simulate/src/lib.rs b/crates/simulate/src/lib.rs index 4f7c1631..ad076e06 100644 --- a/crates/simulate/src/lib.rs +++ b/crates/simulate/src/lib.rs @@ -1,3 +1,6 @@ +#![warn(missing_docs)] +//! Lib crate for describing simulations. + pub mod agent; pub mod environment; pub mod price_simulation; diff --git a/crates/simulate/src/price_simulation.rs b/crates/simulate/src/price_simulation.rs index 513bbad7..96fe2bd3 100644 --- a/crates/simulate/src/price_simulation.rs +++ b/crates/simulate/src/price_simulation.rs @@ -1,34 +1,39 @@ +#![warn(missing_docs)] +//! Used to generate price paths for a simulation. +//! Managers will be able to read from this data to change prices of for infinitely liquid pools. + use plotly::{Plot, Scatter}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use rand_distr::{Distribution, StandardNormal}; +/// Data needed for a Geometric Brownian Motion (GBM) price path generator information. #[derive(Debug)] pub struct PriceSimulation { - // Name/identifier for the simulation (will set filenames) + /// Name/identifier for the simulation (will set filenames) pub identifier: String, // E.g., "test" - // Numerical timestep for the simulation (typically just 1). + /// Numerical timestep for the simulation (typically just 1). pub timestep: f64, - // Time in string interpretation. + /// Time in string interpretation. pub timescale: String, // E.g., "day" - // Number of steps. + /// Number of steps. pub num_steps: usize, - // Initial price of the simulation. + /// Initial price of the simulation. pub initial_price: f64, - // Price drift of the underlying asset. + /// Price drift of the underlying asset. pub drift: f64, - // Volatility of the underlying asset.c + /// Volatility of the underlying asset.c pub volatility: f64, - // Time data for the simulation. + /// Time data for the simulation. pub time_data: Vec, - // Price data for the simulation. + /// Price data for the simulation. pub price_data: Vec, - // Seed for testing. + /// Seed for testing. pub seed: u64, } impl PriceSimulation { - // Public builder function that instantiates a `Simulation`. + /// Public builder function that instantiates a `Simulation`. pub fn new( timestep: f64, timescale: String, @@ -73,7 +78,7 @@ impl PriceSimulation { seed, } } - + /// Displays a plot of the GBM price path. pub fn plot(&self) { let mut filename = self.identifier.to_owned(); filename.push_str(".html"); @@ -86,6 +91,7 @@ impl PriceSimulation { } } +/// Produces a GBM price path. fn generate_gbm( initial_price: f64, timestep: f64, From 3cf9436cef5e5ff72f087f9ddea15225654bdddb Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 23 Mar 2023 13:56:37 -0600 Subject: [PATCH 4/4] edited README a bit more --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4340f0b8..5cad0a7e 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,13 @@ First, clone the repository to your local environment so ``` git clone https://github.com/primitivefinance/arbiter.git cd arbiter -cargo install --path ./crates/cli +cargo build ``` -Set the PROVIDER environment variable to use a custom provider. +With the `arbiter` binary generated, you can run commands such as: +``` +arbiter sim +``` ## Generating Docs To see the documentation for Arbiter, after cloning the repo, you can run: