diff --git a/Cargo.toml b/Cargo.toml index 7f7ba525..e17646d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,6 @@ path = "bin/main.rs" # Dependencies for the release build [dependencies] - -# Local dependencies -arbiter-core = { path = "arbiter-core" } - # Command line and config clap = { version = "4.3.0", features = ["derive"] } serde = { version = "1.0.163", features =["derive"] } diff --git a/README.md b/README.md index 95107704..fd9a1e6f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ [![](https://dcbadge.vercel.app/api/server/primitive?style=flat)](https://discord.gg/primitive) [![Twitter Badge](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/primitivefi) + +This library enables user to communicate with a sandboxed revm instance via the implementation of the [ethers-rs](ethers.rs) middleware. + 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 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. @@ -20,32 +23,6 @@ Arbiter can be used for: - 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: - -For our next beta release, we will be focusing on the following features: - - -## Build From Source - -First, clone the repository to your local environment so - -```bash -git clone https://github.com/primitivefinance/arbiter.git -cd arbiter -``` - -Install arbiter on your system: - -```bash -cargo install --path . --force -``` - -With the `arbiter` binary generated, you can run commands such as: - -```bash -arbiter simulate uniswap -``` - ## Generating Docs To see the documentation for Arbiter, after cloning the repo, you can run: @@ -56,17 +33,6 @@ 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. -## Including More Contracts - -In the `contracts/` directory you can add additional smart contracts or regenerate Rust bindings. Once that is done, you will want to make sure the bindings are generated in the script: - -```bash -./bind.sh -``` -You will need to add the relevant directory for your new contracts to the script above and make sure they are also handled by `forge install`. We look forward to improving upon this UX in the future. - -At the moment, this only builds the bindings for the contracts in the `lib/arbmod/contracts/` and `lib/portfolio/contracts`. You can of course add an additional directory of contracts in `lib/`. Just be sure to include it when you generate bindings! - ## Contributing See our [Contributing Guidelines](https://github.com/primitivefinance/arbiter/blob/main/.github/CONTRIBUTING.md) diff --git a/arbiter-core/README.md b/arbiter-core/README.md deleted file mode 100644 index 101b59bf..00000000 --- a/arbiter-core/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Simulate Crate - -This crate contains agents, price paths, and middleware to interface with revm. There are two primary sub modules in this crate: `agents` and `stochastic`. - -- The `agents` module contains the agents that are used in the simulations, this module is where we define the behavior of different agents in the simulations. We have some pre-built agents, but we anticipate that users will want to build their own agents and for this module to grow with different use cases. - -- The `stochastic` module contains the price paths and other stochastic processes that are used in the simulations. The existing price paths we support (GBM and OU) are defined here. - -- The rest of the files in this crate are concerned with middleware to interface with revm with utils and tools for backtesting with historical data. diff --git a/arbiter-core/src/agent.rs b/arbiter-core/src/agent.rs deleted file mode 100644 index fe57d1de..00000000 --- a/arbiter-core/src/agent.rs +++ /dev/null @@ -1,118 +0,0 @@ -#![allow(missing_docs)] -#![warn(unsafe_code)] - -use std::sync::Arc; - -use ethers::providers::Middleware; - -use crate::{environment::Environment, middleware::RevmMiddleware}; - -pub trait Attached { - type Client; -} -pub struct IsAttached { - marker: std::marker::PhantomData, -} -pub struct NotAttached {} -impl Attached for IsAttached { - type Client = Arc; -} -impl Attached for NotAttached { - type Client = (); -} - -pub struct Agent { - pub name: String, - pub client: A::Client, - pub behaviors: Vec>, -} - -impl Agent { - pub fn new>(name: S) -> Self { - Self { - name: name.into(), - client: (), - behaviors: vec![], - } - } - - pub fn add_behavior(&mut self, behavior: B) - where - B: Behavior + 'static, - { - self.behaviors.push(Box::new(behavior)); - } - - pub fn attach_to_client(self, client: Arc) -> Agent> { - Agent::> { - name: self.name, - client, - behaviors: self.behaviors, - } - } - - pub fn attach_to_environment(self, environment: &mut Environment) { - let middleware = Arc::new(RevmMiddleware::new(&self, environment)); - let agent_attached = self.attach_to_client(middleware); - environment.agents.push(agent_attached); - } -} - -// TODO: Note -- Artemis uses a `process_event` function that returns an `Option` for something to happen. -// https://github.com/paradigmxyz/artemis/blob/c8ab223a363a875f685ab177839eacfffc9d8de0/crates/artemis-core/src/types.rs#L25 -#[async_trait::async_trait] -pub trait Behavior: Send + Sync { - async fn process_event(&mut self) -> bool; - fn sync_state(&mut self); -} - -#[cfg(test)] -pub(crate) mod tests { - pub(crate) const TEST_AGENT_NAME: &str = "test_agent"; - pub(crate) const TEST_BEHAVIOR_DATA: &str = "test_behavior_data"; - - use ethers::providers::{MockProvider, ProviderError}; - - use super::*; - - #[derive(Debug)] - pub(crate) struct TestMiddleware {} - - impl Middleware for TestMiddleware { - type Inner = Self; - type Provider = MockProvider; - type Error = ProviderError; - - fn inner(&self) -> &Self::Inner { - self - } - } - - pub(crate) struct TestBehavior { - data: String, - } - - #[async_trait::async_trait] - impl Behavior for TestBehavior { - async fn process_event(&mut self) -> bool { - true - } - fn sync_state(&mut self) { - assert_eq!(self.data, TEST_BEHAVIOR_DATA.to_string()); - } - } - - #[tokio::test] - async fn agent_behavior() { - let name = TEST_AGENT_NAME.to_string(); - let mut agent = Agent::new(name); - - // Add a behavior of the first type. - let data = TEST_BEHAVIOR_DATA.to_string(); - let behavior = TestBehavior { data }; - agent.add_behavior(behavior); - assert!(agent.behaviors.len() == 1); - assert!(agent.behaviors[0].process_event().await); - agent.behaviors[0].sync_state(); - } -} diff --git a/arbiter-core/src/environment.rs b/arbiter-core/src/environment.rs index 51a89b65..7c068ccd 100644 --- a/arbiter-core/src/environment.rs +++ b/arbiter-core/src/environment.rs @@ -21,11 +21,7 @@ use revm::{ }; use thiserror::Error; -use crate::{ - agent::{Agent, IsAttached, NotAttached}, - math::*, - middleware::RevmMiddleware, -}; +use crate::{math::*, middleware::RevmMiddleware}; pub(crate) type ToTransact = bool; pub(crate) type ResultSender = Sender; @@ -39,7 +35,6 @@ pub struct Environment { pub(crate) state: Arc, pub(crate) evm: EVM>, pub(crate) socket: Socket, - pub agents: Vec>>, pub seeded_poisson: SeededPoisson, pub(crate) handle: Option>>, pub(crate) pausevar: Arc<(Mutex<()>, Condvar)>, @@ -82,17 +77,12 @@ impl Environment { state: Arc::new(AtomicState::new(State::Initialization)), evm, socket, - agents: vec![], seeded_poisson, handle: None, pausevar: Arc::new((Mutex::new(()), Condvar::new())), } } - pub fn add_agent(&mut self, agent: Agent) { - agent.attach_to_environment(self); - } - pub(crate) fn run(&mut self) { let mut evm = self.evm.clone(); let tx_receiver = self.socket.tx_receiver.clone(); diff --git a/arbiter-core/src/lib.rs b/arbiter-core/src/lib.rs index 5149e842..49525f88 100644 --- a/arbiter-core/src/lib.rs +++ b/arbiter-core/src/lib.rs @@ -1,6 +1,5 @@ #![warn(missing_docs, unsafe_code)] -pub mod agent; pub mod bindings; // TODO: Add better documentation here and some kind of overwrite protection. pub mod environment; pub mod manager; diff --git a/arbiter-core/src/manager.rs b/arbiter-core/src/manager.rs index 807b666e..d96b4b34 100644 --- a/arbiter-core/src/manager.rs +++ b/arbiter-core/src/manager.rs @@ -7,10 +7,7 @@ use std::collections::HashMap; use log::{info, warn}; use thiserror::Error; -use crate::{ - agent::{Agent, NotAttached}, - environment::{Environment, State}, -}; +use crate::environment::{Environment, State}; #[derive(Default)] pub struct Manager { @@ -174,22 +171,6 @@ impl Manager { }), } } - - pub fn add_agent( - &mut self, - agent: Agent, - environment_label: String, - ) -> Result<(), ManagerError> { - match self.environments.get_mut(&environment_label) { - Some(environment) => { - environment.add_agent(agent); - Ok(()) - } - None => Err(ManagerError::EnvironmentDoesNotExist { - label: environment_label, - }), - } - } } #[cfg(test)] diff --git a/arbiter-core/src/middleware.rs b/arbiter-core/src/middleware.rs index ce16c551..7b305ca0 100644 --- a/arbiter-core/src/middleware.rs +++ b/arbiter-core/src/middleware.rs @@ -10,6 +10,7 @@ use std::{ }; use ethers::{ + core::rand::{thread_rng, SeedableRng}, prelude::{ k256::{ ecdsa::SigningKey, @@ -28,15 +29,12 @@ use ethers::{ Log, }, }; -use rand::{rngs::StdRng, SeedableRng}; +use rand::rngs; use revm::primitives::{CreateScheme, ExecutionResult, Output, TransactTo, TxEnv, B160, U256}; use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; -use crate::{ - agent::{Agent, NotAttached}, - environment::{Environment, EventBroadcaster, ResultReceiver, ResultSender, TxSender}, -}; +use crate::environment::{Environment, EventBroadcaster, ResultReceiver, ResultSender, TxSender}; #[derive(Debug)] pub struct RevmMiddleware { @@ -84,7 +82,7 @@ impl MiddlewareError for RevmMiddlewareError { } impl RevmMiddleware { - pub fn new(agent: &Agent, environment: &Environment) -> Self { + pub fn new(environment: &Environment, seed_and_label: Option) -> Self { let tx_sender = environment.socket.tx_sender.clone(); let (result_sender, result_receiver) = crossbeam_channel::unbounded(); let connection = Connection { @@ -95,12 +93,18 @@ impl RevmMiddleware { filter_receivers: Arc::new(tokio::sync::Mutex::new(HashMap::new())), }; let provider = Provider::new(connection); - let mut hasher = Sha256::new(); - hasher.update(agent.name.as_bytes()); - let seed = hasher.finalize(); - let mut rng = StdRng::from_seed(seed.into()); - let wallet = Wallet::new(&mut rng); - Self { provider, wallet } + if let Some(seed) = seed_and_label { + let mut hasher = Sha256::new(); + hasher.update(seed.clone()); + let hashed = hasher.finalize(); + let mut rng: rngs::StdRng = SeedableRng::from_seed(hashed.into()); + let wallet = Wallet::new(&mut rng); + Self { provider, wallet } + } else { + let mut rng = thread_rng(); + let wallet = Wallet::new(&mut rng); + Self { provider, wallet } + } } } diff --git a/arbiter-core/src/tests/interaction.rs b/arbiter-core/src/tests/interaction.rs index e8d3941f..3c92a277 100644 --- a/arbiter-core/src/tests/interaction.rs +++ b/arbiter-core/src/tests/interaction.rs @@ -2,30 +2,30 @@ use super::*; #[tokio::test] async fn deploy() -> Result<()> { - let (arbiter_token, _environment) = deploy_and_start().await?; + let (arbiter_token, _environment, _) = deploy_and_start().await?; println!("{:?}", arbiter_token); assert_eq!( arbiter_token.address(), - Address::from_str("0x1a9bb958b1ea4d24475aaa545b25fc2e7eb0871c").unwrap() + Address::from_str("0x067ea9e44c76a2620f10b39a1b51d5124a299192").unwrap() ); Ok(()) } #[tokio::test] async fn call() -> Result<()> { - let (arbiter_token, _) = deploy_and_start().await?; + let (arbiter_token, _, client) = deploy_and_start().await?; let admin = arbiter_token.admin(); let output = admin.call().await?; assert_eq!( output, - Address::from_str("0x09e12ce98726acd515b68f87f49dc2e5558f6a72")? + Address::from_str("0x2efdc9eecfee3a776209fcb8e9a83a6b221d74f5")? ); Ok(()) } #[tokio::test] async fn transact() -> Result<()> { - let (arbiter_token, _) = deploy_and_start().await?; + let (arbiter_token, _, _) = deploy_and_start().await?; let mint = arbiter_token.mint( Address::from_str(TEST_MINT_TO).unwrap(), ethers::types::U256::from(TEST_MINT_AMOUNT), @@ -47,7 +47,7 @@ async fn transact() -> Result<()> { .unwrap(), ]; assert_eq!(receipt.logs[0].topics, topics); - let bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000001")?; + let bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000045")?; assert_eq!( receipt.logs[0].data, ethers::core::types::Bytes::from(bytes) @@ -58,8 +58,7 @@ async fn transact() -> Result<()> { #[tokio::test] async fn filter_watcher() -> Result<()> { - let (arbiter_token, environment) = deploy_and_start().await.unwrap(); - let client = environment.agents[0].client.clone(); + let (arbiter_token, environment, client) = deploy_and_start().await.unwrap(); let mut filter_watcher = client.watch(&Filter::default()).await?; let approval = arbiter_token.approve( client.default_sender().unwrap(), @@ -103,8 +102,7 @@ async fn filter_watcher() -> Result<()> { #[tokio::test] async fn filter_address() -> Result<()> { - let (arbiter_token, environment) = deploy_and_start().await.unwrap(); - let client = environment.agents[0].client.clone(); + let (arbiter_token, environment, client) = deploy_and_start().await.unwrap(); let mut default_watcher = client.watch(&Filter::default()).await?; let mut address_watcher = client .watch(&Filter::new().address(arbiter_token.address())) @@ -166,8 +164,7 @@ async fn filter_address() -> Result<()> { #[tokio::test] async fn filter_topics() -> Result<()> { - let (arbiter_token, environment) = deploy_and_start().await.unwrap(); - let client = environment.agents[0].client.clone(); + let (arbiter_token, environment, client) = deploy_and_start().await.unwrap(); let mut default_watcher = client.watch(&Filter::default()).await?; let mut approval_watcher = client .watch(&arbiter_token.approval_filter().filter) @@ -221,23 +218,17 @@ async fn filter_topics() -> Result<()> { // 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); + // tx_0 is the transaction that creates the token contract + let (arbiter_token, env, client) = deploy_and_start().await?; 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_and_start().await?; - - for index in 1..expected_tx_per_block { + for index in 1..expected_tx_per_block + 1 { println!("index: {}", index); let tx = arbiter_token - .mint(agent.client.default_sender().unwrap(), 1000u64.into()) + .mint(client.default_sender().unwrap(), 1000u64.into()) .send() .await .unwrap() @@ -245,12 +236,12 @@ async fn transaction_loop() -> Result<()> { .unwrap() .unwrap(); - // minus 1 from deploy tx - if index < expected_tx_per_block - 1 { + if index < expected_tx_per_block { let block_number = tx.block_number.unwrap(); println!("block_number: {}", block_number); assert_eq!(block_number, U64::from(0)); } else { + println!("in else"); let block_number = tx.block_number.unwrap(); println!("block_number: {}", block_number); assert_eq!(block_number, U64::from(1)); diff --git a/arbiter-core/src/tests/mod.rs b/arbiter-core/src/tests/mod.rs index 02c0b506..a17e92ec 100644 --- a/arbiter-core/src/tests/mod.rs +++ b/arbiter-core/src/tests/mod.rs @@ -1,9 +1,11 @@ #![allow(missing_docs)] +// mod interaction; mod interaction; mod management; +mod signer; -use std::str::FromStr; +use std::{str::FromStr, sync::Arc}; use anyhow::{Ok, Result}; use ethers::{ @@ -12,7 +14,6 @@ use ethers::{ }; use crate::{ - agent::{tests::TEST_AGENT_NAME, *}, bindings::arbiter_token::*, environment::{tests::TEST_ENV_LABEL, *}, manager::*, @@ -26,17 +27,24 @@ pub const TEST_ARG_DECIMALS: u8 = 18; pub const TEST_MINT_AMOUNT: u128 = 69; pub const TEST_MINT_TO: &str = "0xf7e93cc543d97af6632c9b8864417379dba4bf15"; pub const TEST_APPROVAL_AMOUNT: u128 = 420; +pub const TEST_SIGNER_SEED_AND_LABEL: &str = "test_seed_and_label"; //TODO: Send a tx before and after pausing the environment. -async fn deploy_and_start() -> Result<(ArbiterToken, Environment)> { - let mut environment = Environment::new(TEST_ENV_LABEL, 1.0, 1); - let agent = Agent::new(TEST_AGENT_NAME); - agent.attach_to_environment(&mut environment); +async fn deploy_and_start() -> Result<( + ArbiterToken, + Environment, + Arc, +)> { + let mut environment = Environment::new(TEST_ENV_LABEL, 2.0, 1); + let client = Arc::new(RevmMiddleware::new( + &environment, + Some(TEST_SIGNER_SEED_AND_LABEL.to_string()), + )); environment.run(); Ok(( ArbiterToken::deploy( - environment.agents[0].client.clone(), + client.clone(), ( TEST_ARG_NAME.to_string(), TEST_ARG_SYMBOL.to_string(), @@ -47,5 +55,6 @@ async fn deploy_and_start() -> Result<(ArbiterToken, Environment .await .unwrap(), environment, + client, )) } diff --git a/arbiter-core/src/tests/signer.rs b/arbiter-core/src/tests/signer.rs new file mode 100644 index 00000000..0ab4a4b7 --- /dev/null +++ b/arbiter-core/src/tests/signer.rs @@ -0,0 +1,31 @@ +use std::sync::Arc; + +use super::*; + +#[test] +fn simulation_signer() { + let environment = &mut Environment::new(TEST_ENV_LABEL, 1.0, 1); + let client = Arc::new(RevmMiddleware::new( + environment, + Some(TEST_SIGNER_SEED_AND_LABEL.to_string()), + )); + assert_eq!( + client.default_sender().unwrap(), + Address::from_str("0x2efdc9eecfee3a776209fcb8e9a83a6b221d74f5").unwrap() + ); +} + +#[test] +fn multiple_signer_addresses() { + let environment = &mut Environment::new(TEST_ENV_LABEL, 1.0, 1); + let client_1 = Arc::new(RevmMiddleware::new(environment, Some("0".to_string()))); + let client_2 = Arc::new(RevmMiddleware::new(environment, Some("1".to_string()))); + assert_ne!(client_1.default_sender(), client_2.default_sender()); +} + +// TODO: Test to see that we prvent agents with the same name from being added. + +#[test] +fn signer_collision() { + todo!(); +} diff --git a/arbiter-core/src/tests/strategies.rs b/arbiter-core/src/tests/strategies.rs deleted file mode 100644 index 59ca694d..00000000 --- a/arbiter-core/src/tests/strategies.rs +++ /dev/null @@ -1,39 +0,0 @@ -use super::*; - -#[test] -fn attach_agent() { - 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, 1.0, 1); - let agent = Agent::new(TEST_AGENT_NAME); - agent.attach_to_environment(environment); - assert_eq!( - environment.agents[0].client.default_sender().unwrap(), - Address::from_str("0x09e12ce98726acd515b68f87f49dc2e5558f6a72").unwrap() - ); -} - -#[test] -fn multiple_agent_addresses() { - 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)); - agent2.attach_to_environment(environment); - assert_ne!( - environment.agents[0].client.default_sender(), - environment.agents[1].client.default_sender() - ); -} - -// TODO: Test to see that we prvent agents with the same name from being added. -#[test] -fn agent_name_collision() { - todo!(); -} \ No newline at end of file